initial commit
This commit is contained in:
parent
6715289efe
commit
788c3389af
37645 changed files with 2526849 additions and 80 deletions
|
@ -0,0 +1,12 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.Services.Core.Editor")]
|
||||
[assembly: InternalsVisibleTo("Unity.Services.Core.Registration")]
|
||||
|
||||
#if UNITY_INCLUDE_TESTS
|
||||
[assembly: InternalsVisibleTo("Unity.Services.Core.Tests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Services.Core.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Services.Core.TestUtils.Tests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Services.Core.TestUtils.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 560dda45fb9293949aa636b3e2a92807
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1b8d6e21512034f46b221109c045d40c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
class DisabledCachePersister<TPayload> : ICachePersister<TPayload>
|
||||
where TPayload : ITelemetryPayload
|
||||
{
|
||||
const string k_ErrorMessage = "Cache persistence isn't supported on the current platform.";
|
||||
|
||||
public bool CanPersist => false;
|
||||
|
||||
public void Persist(CachedPayload<TPayload> cache) => throw new NotSupportedException(k_ErrorMessage);
|
||||
|
||||
public bool TryFetch(out CachedPayload<TPayload> persistedCache)
|
||||
=> throw new NotSupportedException(k_ErrorMessage);
|
||||
|
||||
public void Delete() => throw new NotSupportedException(k_ErrorMessage);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a04eba4a14d22c544a65ccec355aaabf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,122 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using Unity.Services.Core.Internal;
|
||||
using UnityEngine;
|
||||
using NotNull = JetBrains.Annotations.NotNullAttribute;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
abstract class FileCachePersister
|
||||
{
|
||||
internal static bool IsAvailableFor(RuntimePlatform platform)
|
||||
{
|
||||
return !string.IsNullOrEmpty(GetPersistentDataPathFor(platform));
|
||||
}
|
||||
|
||||
internal static string GetPersistentDataPathFor(RuntimePlatform platform)
|
||||
{
|
||||
// Application.persistentDataPath has side effects on Switch so it shouldn't be called.
|
||||
if (platform == RuntimePlatform.Switch)
|
||||
return string.Empty;
|
||||
|
||||
return Application.persistentDataPath;
|
||||
}
|
||||
}
|
||||
|
||||
class FileCachePersister<TPayload> : FileCachePersister, ICachePersister<TPayload>
|
||||
where TPayload : ITelemetryPayload
|
||||
{
|
||||
public FileCachePersister(string fileName)
|
||||
{
|
||||
FilePath = Path.Combine(GetPersistentDataPathFor(Application.platform), fileName);
|
||||
}
|
||||
|
||||
public string FilePath { get; }
|
||||
|
||||
public bool CanPersist { get; } = IsAvailableFor(Application.platform);
|
||||
|
||||
readonly string k_MultipleInstanceDiagnosticsName = "telemetry_cache_file_multiple_instances_exception";
|
||||
readonly string k_CacheFileException = "telemetry_cache_file_exception";
|
||||
readonly string k_MultipleInstanceError =
|
||||
"This exception is most likely caused by a multiple instance file sharing violation.";
|
||||
|
||||
public void Persist(CachedPayload<TPayload> cache)
|
||||
{
|
||||
if (cache.IsEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var serializedEvents = JsonConvert.SerializeObject(cache);
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllText(FilePath, serializedEvents);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
var exception = new IOException(k_MultipleInstanceError, e);
|
||||
CoreLogger.LogTelemetry(exception);
|
||||
CoreDiagnostics.Instance.SendCoreDiagnosticsAsync(k_MultipleInstanceDiagnosticsName, exception);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CoreLogger.LogTelemetry(e);
|
||||
CoreDiagnostics.Instance.SendCoreDiagnosticsAsync(k_CacheFileException, e);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryFetch(out CachedPayload<TPayload> persistedCache)
|
||||
{
|
||||
persistedCache = default;
|
||||
|
||||
if (!File.Exists(FilePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var rawPersistedCache = File.ReadAllText(FilePath);
|
||||
persistedCache = JsonConvert.DeserializeObject<CachedPayload<TPayload>>(rawPersistedCache);
|
||||
return persistedCache != null;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
var exception = new IOException(k_MultipleInstanceError, e);
|
||||
CoreLogger.LogTelemetry(exception);
|
||||
CoreDiagnostics.Instance.SendCoreDiagnosticsAsync(k_MultipleInstanceDiagnosticsName, exception);
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CoreLogger.LogTelemetry(e);
|
||||
CoreDiagnostics.Instance.SendCoreDiagnosticsAsync(k_CacheFileException, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
if (File.Exists(FilePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(FilePath);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
var exception = new IOException(k_MultipleInstanceError, e);
|
||||
CoreLogger.LogTelemetry(exception);
|
||||
CoreDiagnostics.Instance.SendCoreDiagnosticsAsync(k_MultipleInstanceDiagnosticsName, exception);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CoreLogger.LogTelemetry(e);
|
||||
CoreDiagnostics.Instance.SendCoreDiagnosticsAsync(k_CacheFileException, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bb4fdb08c578f464ebaf468026b9d4b6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,14 @@
|
|||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
interface ICachePersister<TPayload>
|
||||
where TPayload : ITelemetryPayload
|
||||
{
|
||||
bool CanPersist { get; }
|
||||
|
||||
void Persist(CachedPayload<TPayload> cache);
|
||||
|
||||
bool TryFetch(out CachedPayload<TPayload> persistedCache);
|
||||
|
||||
void Delete();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6cd651aefba97204c9f4b012f6ac8f54
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3330907631d21584281522898e9028cd
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Services.Core.Internal;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
class Diagnostics : IDiagnostics
|
||||
{
|
||||
internal const int MaxDiagnosticMessageLength = 10000;
|
||||
internal const string DiagnosticMessageTruncateSuffix = "[truncated]";
|
||||
internal DiagnosticsHandler Handler { get; }
|
||||
|
||||
internal IDictionary<string, string> PackageTags { get; }
|
||||
|
||||
public Diagnostics(DiagnosticsHandler handler, IDictionary<string, string> packageTags)
|
||||
{
|
||||
Handler = handler;
|
||||
PackageTags = packageTags;
|
||||
}
|
||||
|
||||
public void SendDiagnostic(string name, string message, IDictionary<string, string> tags = null)
|
||||
{
|
||||
var diagnostic = new Diagnostic
|
||||
{
|
||||
Content = tags is null
|
||||
? new Dictionary<string, string>(PackageTags)
|
||||
: new Dictionary<string, string>(tags)
|
||||
.MergeAllowOverride(PackageTags),
|
||||
};
|
||||
|
||||
diagnostic.Content.Add(TagKeys.DiagnosticName, name);
|
||||
if (message != null && message.Length > MaxDiagnosticMessageLength)
|
||||
{
|
||||
message = $"{message.Substring(0, MaxDiagnosticMessageLength)}{Environment.NewLine}{DiagnosticMessageTruncateSuffix}";
|
||||
}
|
||||
diagnostic.Content.Add(TagKeys.DiagnosticMessage, message);
|
||||
|
||||
|
||||
Handler.Register(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d82f57a40dd480c4992d1d23dd773765
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Services.Core.Configuration.Internal;
|
||||
using Unity.Services.Core.Internal;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
class DiagnosticsFactory : IDiagnosticsFactory
|
||||
{
|
||||
readonly IProjectConfiguration m_ProjectConfig;
|
||||
|
||||
public IReadOnlyDictionary<string, string> CommonTags { get; }
|
||||
|
||||
internal DiagnosticsHandler Handler { get; }
|
||||
|
||||
public DiagnosticsFactory(DiagnosticsHandler handler, IProjectConfiguration projectConfig)
|
||||
{
|
||||
Handler = handler;
|
||||
m_ProjectConfig = projectConfig;
|
||||
|
||||
CommonTags = new Dictionary<string, string>(handler.Cache.Payload.CommonTags)
|
||||
.MergeAllowOverride(handler.Cache.Payload.DiagnosticsCommonTags);
|
||||
}
|
||||
|
||||
public IDiagnostics Create(string packageName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(packageName))
|
||||
throw new ArgumentNullException(nameof(packageName));
|
||||
|
||||
var packageTags = FactoryUtils.CreatePackageTags(m_ProjectConfig, packageName);
|
||||
var diagnostics = new Diagnostics(Handler, packageTags);
|
||||
|
||||
return diagnostics;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fa076e64208b8334a8a147efeebf66fc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
class DisabledDiagnostics : IDiagnostics
|
||||
{
|
||||
void IDiagnostics.SendDiagnostic(string name, string message, IDictionary<string, string> tags)
|
||||
{
|
||||
// Do nothing since it's disabled.
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 30648dc766559254d8ef3209219478ab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
class DisabledDiagnosticsFactory : IDiagnosticsFactory
|
||||
{
|
||||
IReadOnlyDictionary<string, string> IDiagnosticsFactory.CommonTags { get; }
|
||||
= new Dictionary<string, string>();
|
||||
|
||||
IDiagnostics IDiagnosticsFactory.Create(string packageName) => new DisabledDiagnostics();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 30e535d05ac7fad4db50b8dfe6751c14
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e0315c35a4ce1734a8197a6da9694713
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,120 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Unity.Services.Core.Configuration.Internal;
|
||||
using Unity.Services.Core.Environments.Internal;
|
||||
using Unity.Services.Core.Scheduler.Internal;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles common logic between all <see cref="Diagnostics"/> instances.
|
||||
/// </summary>
|
||||
class DiagnosticsHandler : TelemetryHandler<DiagnosticsPayload, Diagnostic>
|
||||
{
|
||||
class SendState
|
||||
{
|
||||
public DiagnosticsHandler Self;
|
||||
|
||||
public CachedPayload<DiagnosticsPayload> Payload;
|
||||
}
|
||||
|
||||
public DiagnosticsHandler(
|
||||
TelemetryConfig config, CachedPayload<DiagnosticsPayload> cache, IActionScheduler scheduler,
|
||||
ICachePersister<DiagnosticsPayload> cachePersister, TelemetrySender sender)
|
||||
: base(config, cache, scheduler, cachePersister, sender) {}
|
||||
|
||||
internal override void SendPersistedCache(CachedPayload<DiagnosticsPayload> persistedCache)
|
||||
{
|
||||
var sendAsync = m_Sender.SendAsync(persistedCache.Payload);
|
||||
|
||||
m_CachePersister.Delete();
|
||||
|
||||
var localState = new SendState
|
||||
{
|
||||
Self = this,
|
||||
Payload = new CachedPayload<DiagnosticsPayload>
|
||||
{
|
||||
TimeOfOccurenceTicks = persistedCache.TimeOfOccurenceTicks,
|
||||
Payload = persistedCache.Payload,
|
||||
},
|
||||
};
|
||||
sendAsync.ContinueWith(OnSendAsyncCompleted, localState, TaskContinuationOptions.ExecuteSynchronously);
|
||||
}
|
||||
|
||||
static void OnSendAsyncCompleted(Task sendOperation, object state)
|
||||
{
|
||||
if (!(state is SendState castState))
|
||||
{
|
||||
throw new ArgumentException("The given state is invalid.");
|
||||
}
|
||||
|
||||
switch (sendOperation.Status)
|
||||
{
|
||||
case TaskStatus.Canceled:
|
||||
case TaskStatus.Faulted:
|
||||
{
|
||||
castState.Self.Cache.AddRangeFrom(castState.Payload);
|
||||
break;
|
||||
}
|
||||
case TaskStatus.RanToCompletion:
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(sendOperation.Status), "Can't continue without the send operation being completed.");
|
||||
}
|
||||
}
|
||||
|
||||
internal override void FetchSpecificCommonTags(ICloudProjectId cloudProjectId, IEnvironments environments)
|
||||
{
|
||||
var commonTags = Cache.Payload.DiagnosticsCommonTags;
|
||||
commonTags.Clear();
|
||||
|
||||
commonTags[TagKeys.ApplicationVersion] = Application.version;
|
||||
commonTags[TagKeys.ProductName] = Application.productName;
|
||||
commonTags[TagKeys.CloudProjectId] = cloudProjectId.GetCloudProjectId();
|
||||
commonTags[TagKeys.EnvironmentName] = environments.Current;
|
||||
commonTags[TagKeys.ApplicationGenuine] = Application.genuineCheckAvailable
|
||||
? Application.genuine.ToString(CultureInfo.InvariantCulture)
|
||||
: "unavailable";
|
||||
commonTags[TagKeys.InternetReachability] = Application.internetReachability.ToString();
|
||||
}
|
||||
|
||||
internal override void SendCachedPayload()
|
||||
{
|
||||
if (Cache.IsEmpty())
|
||||
return;
|
||||
|
||||
var sendAsync = m_Sender.SendAsync(Cache.Payload);
|
||||
|
||||
var localState = new SendState
|
||||
{
|
||||
Self = this,
|
||||
Payload = new CachedPayload<DiagnosticsPayload>
|
||||
{
|
||||
TimeOfOccurenceTicks = Cache.TimeOfOccurenceTicks,
|
||||
Payload = new DiagnosticsPayload
|
||||
{
|
||||
Diagnostics = new List<Diagnostic>(Cache.Payload.Diagnostics),
|
||||
CommonTags = new Dictionary<string, string>(Cache.Payload.CommonTags),
|
||||
DiagnosticsCommonTags = new Dictionary<string, string>(Cache.Payload.DiagnosticsCommonTags),
|
||||
},
|
||||
},
|
||||
};
|
||||
Cache.TimeOfOccurenceTicks = 0;
|
||||
Cache.Payload.Diagnostics.Clear();
|
||||
|
||||
if (m_CachePersister.CanPersist)
|
||||
{
|
||||
m_CachePersister.Delete();
|
||||
}
|
||||
|
||||
sendAsync.ContinueWith(OnSendAsyncCompleted, localState, TaskContinuationOptions.ExecuteSynchronously);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 34f5fe0d07586744aa92a02821c71e8d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Utilities;
|
||||
using Unity.Services.Core.Configuration.Internal;
|
||||
using Unity.Services.Core.Environments.Internal;
|
||||
using Unity.Services.Core.Scheduler.Internal;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles common logic between all <see cref="Metrics"/> instances.
|
||||
/// </summary>
|
||||
class MetricsHandler : TelemetryHandler<MetricsPayload, Metric>
|
||||
{
|
||||
public MetricsHandler(
|
||||
TelemetryConfig config, CachedPayload<MetricsPayload> cache, IActionScheduler scheduler,
|
||||
ICachePersister<MetricsPayload> cachePersister, TelemetrySender sender)
|
||||
: base(config, cache, scheduler, cachePersister, sender)
|
||||
{
|
||||
// prevent .ctor of StringEnumConverter from being stripped
|
||||
AotHelper.EnsureType<StringEnumConverter>();
|
||||
}
|
||||
|
||||
internal override void SendPersistedCache(CachedPayload<MetricsPayload> persistedCache)
|
||||
{
|
||||
if (!AreMetricsOutdated())
|
||||
{
|
||||
m_Sender.SendAsync(persistedCache.Payload);
|
||||
}
|
||||
|
||||
m_CachePersister.Delete();
|
||||
|
||||
bool AreMetricsOutdated()
|
||||
{
|
||||
var differenceFromUtcNow = DateTime.UtcNow - new DateTime(persistedCache.TimeOfOccurenceTicks);
|
||||
return differenceFromUtcNow.TotalSeconds > Config.PayloadExpirationSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
internal override void FetchSpecificCommonTags(ICloudProjectId cloudProjectId, IEnvironments environments)
|
||||
{
|
||||
Cache.Payload.MetricsCommonTags.Clear();
|
||||
}
|
||||
|
||||
internal override void SendCachedPayload()
|
||||
{
|
||||
if (Cache.Payload.Metrics.Count <= 0)
|
||||
return;
|
||||
|
||||
m_Sender.SendAsync(Cache.Payload);
|
||||
|
||||
Cache.Payload.Metrics.Clear();
|
||||
Cache.TimeOfOccurenceTicks = 0;
|
||||
|
||||
if (m_CachePersister.CanPersist)
|
||||
{
|
||||
m_CachePersister.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 19ff1201e729ddc4d8128534ec9c435c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,168 @@
|
|||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
using System;
|
||||
#endif
|
||||
using Newtonsoft.Json;
|
||||
using Unity.Services.Core.Configuration.Internal;
|
||||
using Unity.Services.Core.Environments.Internal;
|
||||
using Unity.Services.Core.Internal;
|
||||
using Unity.Services.Core.Scheduler.Internal;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
/// <remarks>
|
||||
/// A non-generic version of the class to hold non-generic static code in order to
|
||||
/// avoid unnecessary duplication that happens with static members in generic classes.
|
||||
/// </remarks>
|
||||
abstract class TelemetryHandler
|
||||
{
|
||||
internal static string FormatOperatingSystemInfo(string rawOsInfo)
|
||||
{
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
//Android's os data is formatted as follow:
|
||||
//"<Device system name> <Device system version> / API-<API level> (<ID>/<Version incremental>)"
|
||||
//eg. "Android OS 10 / API-29 (HONORHRY-LX1T/10.0.0.200C636)"
|
||||
var trimmedOsInfoSize = rawOsInfo.LastIndexOf(" (", StringComparison.Ordinal);
|
||||
if (trimmedOsInfoSize < 0)
|
||||
return rawOsInfo;
|
||||
|
||||
var osTag = rawOsInfo.Substring(0, trimmedOsInfoSize);
|
||||
return osTag;
|
||||
#else
|
||||
return rawOsInfo;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TelemetryHandler<TPayload, TEvent> : TelemetryHandler
|
||||
where TPayload : ITelemetryPayload
|
||||
where TEvent : ITelemetryEvent
|
||||
{
|
||||
readonly IActionScheduler m_Scheduler;
|
||||
|
||||
protected readonly ICachePersister<TPayload> m_CachePersister;
|
||||
|
||||
protected readonly TelemetrySender m_Sender;
|
||||
|
||||
internal long SendingLoopScheduleId;
|
||||
|
||||
internal long PersistenceLoopScheduleId;
|
||||
|
||||
public TelemetryConfig Config { get; }
|
||||
|
||||
public CachedPayload<TPayload> Cache { get; }
|
||||
|
||||
protected TelemetryHandler(
|
||||
TelemetryConfig config, CachedPayload<TPayload> cache, IActionScheduler scheduler,
|
||||
ICachePersister<TPayload> cachePersister, TelemetrySender sender)
|
||||
{
|
||||
Config = config;
|
||||
Cache = cache;
|
||||
m_Scheduler = scheduler;
|
||||
m_CachePersister = cachePersister;
|
||||
m_Sender = sender;
|
||||
}
|
||||
|
||||
public void Initialize(ICloudProjectId cloudProjectId, IEnvironments environments)
|
||||
{
|
||||
HandlePersistedCache();
|
||||
FetchAllCommonTags(cloudProjectId, environments);
|
||||
ScheduleSendingLoop();
|
||||
|
||||
if (m_CachePersister.CanPersist)
|
||||
{
|
||||
SchedulePersistenceLoop();
|
||||
}
|
||||
}
|
||||
|
||||
internal void HandlePersistedCache()
|
||||
{
|
||||
if (!m_CachePersister.CanPersist
|
||||
|| !m_CachePersister.TryFetch(out var persistedCache))
|
||||
return;
|
||||
|
||||
if (persistedCache.IsEmpty())
|
||||
{
|
||||
m_CachePersister.Delete();
|
||||
return;
|
||||
}
|
||||
|
||||
SendPersistedCache(persistedCache);
|
||||
}
|
||||
|
||||
internal abstract void SendPersistedCache(CachedPayload<TPayload> persistedCache);
|
||||
|
||||
void FetchAllCommonTags(ICloudProjectId cloudProjectId, IEnvironments environments)
|
||||
{
|
||||
FetchTelemetryCommonTags();
|
||||
FetchSpecificCommonTags(cloudProjectId, environments);
|
||||
}
|
||||
|
||||
internal abstract void FetchSpecificCommonTags(ICloudProjectId cloudProjectId, IEnvironments environments);
|
||||
|
||||
internal void FetchTelemetryCommonTags()
|
||||
{
|
||||
var commonTags = Cache.Payload.CommonTags;
|
||||
commonTags.Clear();
|
||||
commonTags[TagKeys.ApplicationInstallMode] = Application.installMode.ToString();
|
||||
commonTags[TagKeys.OperatingSystem] = FormatOperatingSystemInfo(SystemInfo.operatingSystem);
|
||||
commonTags[TagKeys.Platform] = Application.platform.ToString();
|
||||
commonTags[TagKeys.Engine] = "Unity";
|
||||
commonTags[TagKeys.UnityVersion] = Application.unityVersion;
|
||||
}
|
||||
|
||||
internal void ScheduleSendingLoop()
|
||||
{
|
||||
SendingLoopScheduleId = m_Scheduler.ScheduleAction(SendingLoop, Config.PayloadSendingMaxIntervalSeconds);
|
||||
|
||||
void SendingLoop()
|
||||
{
|
||||
ScheduleSendingLoop();
|
||||
SendCachedPayload();
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract void SendCachedPayload();
|
||||
|
||||
internal void SchedulePersistenceLoop()
|
||||
{
|
||||
PersistenceLoopScheduleId = m_Scheduler.ScheduleAction(
|
||||
PersistenceLoop, Config.SafetyPersistenceIntervalSeconds);
|
||||
|
||||
void PersistenceLoop()
|
||||
{
|
||||
SchedulePersistenceLoop();
|
||||
PersistCache();
|
||||
}
|
||||
}
|
||||
|
||||
internal void PersistCache()
|
||||
{
|
||||
if (!m_CachePersister.CanPersist
|
||||
|| Cache.TimeOfOccurenceTicks <= 0
|
||||
|| Cache.Payload.Count <= 0)
|
||||
return;
|
||||
|
||||
m_CachePersister.Persist(Cache);
|
||||
}
|
||||
|
||||
public void Register(TEvent telemetryEvent)
|
||||
{
|
||||
CoreLogger.LogTelemetry(
|
||||
$"Cached the {typeof(TEvent).Name} event: {JsonConvert.SerializeObject(telemetryEvent)}");
|
||||
Cache.Add(telemetryEvent);
|
||||
|
||||
if (!IsCacheFull())
|
||||
return;
|
||||
|
||||
SendCachedPayload();
|
||||
m_Scheduler.CancelAction(SendingLoopScheduleId);
|
||||
ScheduleSendingLoop();
|
||||
|
||||
bool IsCacheFull()
|
||||
{
|
||||
return Cache.Payload.Count >= Config.MaxMetricCountPerPayload;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cafed3ef1baf3db47aac0e065c7edee4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f35a5b2933eb87b44869fb793b853ab5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,22 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
class DisabledMetrics : IMetrics
|
||||
{
|
||||
void IMetrics.SendGaugeMetric(string name, double value, IDictionary<string, string> tags)
|
||||
{
|
||||
// Do nothing since it's disabled.
|
||||
}
|
||||
|
||||
void IMetrics.SendHistogramMetric(string name, double time, IDictionary<string, string> tags)
|
||||
{
|
||||
// Do nothing since it's disabled.
|
||||
}
|
||||
|
||||
void IMetrics.SendSumMetric(string name, double value, IDictionary<string, string> tags)
|
||||
{
|
||||
// Do nothing since it's disabled.
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3acf2a208165fee48a9a8604d2ab412a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
class DisabledMetricsFactory : IMetricsFactory
|
||||
{
|
||||
IReadOnlyDictionary<string, string> IMetricsFactory.CommonTags { get; }
|
||||
= new Dictionary<string, string>();
|
||||
|
||||
IMetrics IMetricsFactory.Create(string packageName) => new DisabledMetrics();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2bc7ece372a03404394756573e450dd0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,49 @@
|
|||
using System.Collections.Generic;
|
||||
using Unity.Services.Core.Internal;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
class Metrics : IMetrics
|
||||
{
|
||||
internal MetricsHandler Handler { get; }
|
||||
|
||||
internal IDictionary<string, string> PackageTags { get; }
|
||||
|
||||
public Metrics(MetricsHandler handler, IDictionary<string, string> packageTags)
|
||||
{
|
||||
Handler = handler;
|
||||
PackageTags = packageTags;
|
||||
}
|
||||
|
||||
internal Metric CreateMetric(string name, double value, MetricType type, IDictionary<string, string> tags)
|
||||
{
|
||||
var metric = new Metric
|
||||
{
|
||||
Name = name,
|
||||
Value = value,
|
||||
Type = type,
|
||||
Tags = tags is null ? PackageTags : tags.MergeAllowOverride(PackageTags),
|
||||
};
|
||||
|
||||
return metric;
|
||||
}
|
||||
|
||||
void IMetrics.SendGaugeMetric(string name, double value, IDictionary<string, string> tags)
|
||||
{
|
||||
var metric = CreateMetric(name, value, MetricType.Gauge, tags);
|
||||
Handler.Register(metric);
|
||||
}
|
||||
|
||||
void IMetrics.SendHistogramMetric(string name, double time, IDictionary<string, string> tags)
|
||||
{
|
||||
var metric = CreateMetric(name, time, MetricType.Histogram, tags);
|
||||
Handler.Register(metric);
|
||||
}
|
||||
|
||||
void IMetrics.SendSumMetric(string name, double value, IDictionary<string, string> tags)
|
||||
{
|
||||
var metric = CreateMetric(name, value, MetricType.Sum, tags);
|
||||
Handler.Register(metric);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: be5e351f9a40dd54d8fb1ec23b94a1e9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Services.Core.Configuration.Internal;
|
||||
using Unity.Services.Core.Internal;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
class MetricsFactory : IMetricsFactory
|
||||
{
|
||||
readonly IProjectConfiguration m_ProjectConfig;
|
||||
|
||||
public IReadOnlyDictionary<string, string> CommonTags { get; }
|
||||
|
||||
internal MetricsHandler Handler { get; }
|
||||
|
||||
public MetricsFactory(MetricsHandler handler, IProjectConfiguration projectConfig)
|
||||
{
|
||||
Handler = handler;
|
||||
m_ProjectConfig = projectConfig;
|
||||
|
||||
CommonTags = new Dictionary<string, string>(handler.Cache.Payload.CommonTags)
|
||||
.MergeAllowOverride(handler.Cache.Payload.MetricsCommonTags);
|
||||
}
|
||||
|
||||
public IMetrics Create(string packageName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(packageName))
|
||||
throw new ArgumentNullException(nameof(packageName));
|
||||
|
||||
var packageTags = FactoryUtils.CreatePackageTags(m_ProjectConfig, packageName);
|
||||
var metrics = new Metrics(Handler, packageTags);
|
||||
|
||||
return metrics;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d0d7f6572d6b64d4f802e930a1670ab5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 004201c331a12b840b8ad34d6d987e83
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
[Serializable]
|
||||
class CachedPayload<TPayload>
|
||||
where TPayload : ITelemetryPayload
|
||||
{
|
||||
/// <summary>
|
||||
/// Time, in ticks, the first event of this payload was recorded.
|
||||
/// It uses <see cref="DateTime.UtcNow"/>.
|
||||
/// </summary>
|
||||
public long TimeOfOccurenceTicks;
|
||||
|
||||
public TPayload Payload;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fc7e0d4e3660a3f45b604bde5c573b9f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b8945d067bb7acc4c9f86ddea96250c0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
[Serializable]
|
||||
struct Diagnostic : ITelemetryEvent
|
||||
{
|
||||
[JsonProperty("content")]
|
||||
public IDictionary<string, string> Content;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 91622ad6ca234524d8b947037f9de9db
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
[Serializable]
|
||||
struct DiagnosticsPayload : ITelemetryPayload
|
||||
{
|
||||
[JsonProperty("diagnostics")]
|
||||
public List<Diagnostic> Diagnostics;
|
||||
|
||||
[JsonProperty("commonTags")]
|
||||
public Dictionary<string, string> CommonTags;
|
||||
|
||||
[JsonProperty("diagnosticsCommonTags")]
|
||||
public Dictionary<string, string> DiagnosticsCommonTags;
|
||||
|
||||
Dictionary<string, string> ITelemetryPayload.CommonTags => CommonTags;
|
||||
|
||||
int ITelemetryPayload.Count => Diagnostics?.Count ?? 0;
|
||||
|
||||
void ITelemetryPayload.Add(ITelemetryEvent telemetryEvent)
|
||||
{
|
||||
if (!(telemetryEvent is Diagnostic diagnostic))
|
||||
throw new ArgumentException("This payload accepts only Diagnostic events.");
|
||||
|
||||
if (Diagnostics is null)
|
||||
{
|
||||
Diagnostics = new List<Diagnostic>(1);
|
||||
}
|
||||
|
||||
Diagnostics.Add(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2f5bfa82bdb60974380529c55084671a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6b2d91503ff1f2c4e8eff2862ece88d0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,4 @@
|
|||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
interface ITelemetryEvent {}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 81d320e0845023f44a8dfd79a7df2cfd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,13 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
interface ITelemetryPayload
|
||||
{
|
||||
Dictionary<string, string> CommonTags { get; }
|
||||
|
||||
int Count { get; }
|
||||
|
||||
void Add(ITelemetryEvent telemetryEvent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3c9dbeab193d6ad45bd9270d4100d511
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: abd0b6db8e602174d9e7f217e0571399
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
[Serializable]
|
||||
struct Metric : ITelemetryEvent
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name;
|
||||
|
||||
[JsonProperty("value")]
|
||||
public double Value;
|
||||
|
||||
[JsonProperty("type")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public MetricType Type;
|
||||
|
||||
[JsonProperty("tags")]
|
||||
public IDictionary<string, string> Tags;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a272eaf5ec4f30441854ab3a7b0a1bb6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,14 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
enum MetricType
|
||||
{
|
||||
[EnumMember(Value = "GAUGE")]
|
||||
Gauge = 0,
|
||||
[EnumMember(Value = "SUM")]
|
||||
Sum = 1,
|
||||
[EnumMember(Value = "HISTOGRAM")]
|
||||
Histogram = 2,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 743e8642b43bf094d8dd50e2b9ddfb6c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
[Serializable]
|
||||
struct MetricsPayload : ITelemetryPayload
|
||||
{
|
||||
[JsonProperty("metrics")]
|
||||
public List<Metric> Metrics;
|
||||
|
||||
[JsonProperty("commonTags")]
|
||||
public Dictionary<string, string> CommonTags;
|
||||
|
||||
[JsonProperty("metricsCommonTags")]
|
||||
public Dictionary<string, string> MetricsCommonTags;
|
||||
|
||||
Dictionary<string, string> ITelemetryPayload.CommonTags => CommonTags;
|
||||
|
||||
int ITelemetryPayload.Count => Metrics?.Count ?? 0;
|
||||
|
||||
void ITelemetryPayload.Add(ITelemetryEvent telemetryEvent)
|
||||
{
|
||||
if (!(telemetryEvent is Metric metric))
|
||||
throw new ArgumentException("This payload accepts only Metric events.");
|
||||
|
||||
if (Metrics is null)
|
||||
{
|
||||
Metrics = new List<Metric>(1);
|
||||
}
|
||||
|
||||
Metrics.Add(metric);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d80ee8c7b400fc04797b6c579538d686
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 84b8d6187a577344d9f17e4d54fc2866
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
using NotNull = JetBrains.Annotations.NotNullAttribute;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
class ExponentialBackOffRetryPolicy
|
||||
{
|
||||
int m_MaxTryCount = 10;
|
||||
|
||||
public int MaxTryCount
|
||||
{
|
||||
get => m_MaxTryCount;
|
||||
set => m_MaxTryCount = Math.Max(value, 0);
|
||||
}
|
||||
|
||||
float m_BaseDelaySeconds = 2;
|
||||
|
||||
public float BaseDelaySeconds
|
||||
{
|
||||
get => m_BaseDelaySeconds;
|
||||
set => m_BaseDelaySeconds = Math.Max(value, 0);
|
||||
}
|
||||
|
||||
public bool CanRetry(WebRequest webRequest, int sendCount)
|
||||
{
|
||||
return sendCount < MaxTryCount
|
||||
&& IsTransientError(webRequest);
|
||||
}
|
||||
|
||||
public static bool IsTransientError(WebRequest webRequest)
|
||||
{
|
||||
return webRequest.Result == WebRequestResult.ConnectionError
|
||||
|| webRequest.Result == WebRequestResult.ProtocolError && IsServerErrorCode(webRequest.ResponseCode);
|
||||
|
||||
bool IsServerErrorCode(long responseCode)
|
||||
{
|
||||
return responseCode >= 500
|
||||
&& responseCode < 600;
|
||||
}
|
||||
}
|
||||
|
||||
public float GetDelayBeforeSendingSeconds(int sendCount)
|
||||
{
|
||||
if (sendCount <= 0)
|
||||
{
|
||||
return BaseDelaySeconds;
|
||||
}
|
||||
|
||||
return Mathf.Pow(BaseDelaySeconds, sendCount);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b0fd45576b552084cbe828d41f7f045d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
interface IUnityWebRequestSender
|
||||
{
|
||||
void SendRequest(UnityWebRequest request, Action<WebRequest> callback);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0a159a6fc91bd044097fa1497cf30399
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,107 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine.Networking;
|
||||
using Unity.Services.Core.Internal;
|
||||
using Unity.Services.Core.Scheduler.Internal;
|
||||
using NotNull = JetBrains.Annotations.NotNullAttribute;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
class TelemetrySender
|
||||
{
|
||||
public string TargetUrl { get; }
|
||||
|
||||
readonly ExponentialBackOffRetryPolicy m_RetryPolicy;
|
||||
|
||||
readonly IActionScheduler m_Scheduler;
|
||||
|
||||
readonly IUnityWebRequestSender m_RequestSender;
|
||||
|
||||
public TelemetrySender(
|
||||
[NotNull] string targetUrl, [NotNull] string servicePath,
|
||||
[NotNull] IActionScheduler scheduler, [NotNull] ExponentialBackOffRetryPolicy retryPolicy,
|
||||
[NotNull] IUnityWebRequestSender requestSender)
|
||||
{
|
||||
TargetUrl = $"{targetUrl}/{servicePath}";
|
||||
m_RetryPolicy = retryPolicy;
|
||||
m_Scheduler = scheduler;
|
||||
m_RequestSender = requestSender;
|
||||
}
|
||||
|
||||
public Task SendAsync<TPayload>(TPayload payload)
|
||||
where TPayload : ITelemetryPayload
|
||||
{
|
||||
var completionSource = new TaskCompletionSource<object>();
|
||||
var sendCount = 0;
|
||||
byte[] serializedPayload;
|
||||
|
||||
try
|
||||
{
|
||||
serializedPayload = SerializePayload(payload);
|
||||
SendWebRequest();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
completionSource.TrySetException(e);
|
||||
}
|
||||
|
||||
return completionSource.Task;
|
||||
|
||||
void SendWebRequest()
|
||||
{
|
||||
var request = CreateRequest(serializedPayload);
|
||||
|
||||
sendCount++;
|
||||
CoreLogger.LogTelemetry($"Attempt #{sendCount.ToString()} to send {typeof(TPayload).Name}.");
|
||||
|
||||
m_RequestSender.SendRequest(request, OnRequestCompleted);
|
||||
}
|
||||
|
||||
void OnRequestCompleted(WebRequest webRequest)
|
||||
{
|
||||
if (webRequest.IsSuccess)
|
||||
{
|
||||
CoreLogger.LogTelemetry($"{typeof(TPayload).Name} sent successfully");
|
||||
completionSource.SetResult(null);
|
||||
}
|
||||
else if (m_RetryPolicy.CanRetry(webRequest, sendCount))
|
||||
{
|
||||
var delayBeforeSendingSeconds = m_RetryPolicy.GetDelayBeforeSendingSeconds(sendCount);
|
||||
m_Scheduler.ScheduleAction(SendWebRequest, delayBeforeSendingSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMessage = $"Error: {webRequest.ErrorMessage}\nBody: {webRequest.ErrorBody}";
|
||||
completionSource.TrySetException(new Exception(errorMessage));
|
||||
CoreLogger.LogTelemetry(
|
||||
$"{typeof(TPayload).Name} couldn't be sent after {sendCount.ToString()} tries."
|
||||
+ $"\n{errorMessage}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static byte[] SerializePayload<TPayload>(TPayload payload)
|
||||
where TPayload : ITelemetryPayload
|
||||
{
|
||||
var jsonPayload = JsonConvert.SerializeObject(payload);
|
||||
var serializedPayload = Encoding.UTF8.GetBytes(jsonPayload);
|
||||
return serializedPayload;
|
||||
}
|
||||
|
||||
internal UnityWebRequest CreateRequest(byte[] serializedPayload)
|
||||
{
|
||||
var request = new UnityWebRequest(TargetUrl, UnityWebRequest.kHttpVerbPOST)
|
||||
{
|
||||
uploadHandler = new UploadHandlerRaw(serializedPayload)
|
||||
{
|
||||
contentType = UnityWebRequestUtils.JsonContentType,
|
||||
},
|
||||
downloadHandler = new DownloadHandlerBuffer()
|
||||
};
|
||||
request.SetRequestHeader("Content-Type", UnityWebRequestUtils.JsonContentType);
|
||||
return request;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 489390cf0d252a44dab034d68a28ef09
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,81 @@
|
|||
using System;
|
||||
using Unity.Services.Core.Internal;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
class UnityWebRequestSender : IUnityWebRequestSender
|
||||
{
|
||||
public void SendRequest(UnityWebRequest request, Action<WebRequest> callback)
|
||||
{
|
||||
var sendingOperation = request.SendWebRequest();
|
||||
sendingOperation.completed += OnSendingRequestCompleted;
|
||||
|
||||
void OnSendingRequestCompleted(UnityEngine.AsyncOperation operation)
|
||||
{
|
||||
using (var webRequest = ((UnityWebRequestAsyncOperation)operation).webRequest)
|
||||
{
|
||||
if (callback is null)
|
||||
return;
|
||||
|
||||
var simplifiedRequest = Simplify(webRequest);
|
||||
callback(simplifiedRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static WebRequest Simplify(UnityWebRequest webRequest)
|
||||
{
|
||||
var simplifiedRequest = new WebRequest
|
||||
{
|
||||
ResponseCode = webRequest.responseCode,
|
||||
};
|
||||
|
||||
if (webRequest.HasSucceeded())
|
||||
{
|
||||
simplifiedRequest.Result = WebRequestResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
switch (webRequest.result)
|
||||
{
|
||||
case UnityWebRequest.Result.ConnectionError:
|
||||
{
|
||||
simplifiedRequest.Result = WebRequestResult.ConnectionError;
|
||||
break;
|
||||
}
|
||||
case UnityWebRequest.Result.ProtocolError:
|
||||
{
|
||||
simplifiedRequest.Result = WebRequestResult.ProtocolError;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
simplifiedRequest.Result = WebRequestResult.UnknownError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (webRequest.isHttpError)
|
||||
{
|
||||
simplifiedRequest.Result = WebRequestResult.ProtocolError;
|
||||
}
|
||||
else if (webRequest.isNetworkError)
|
||||
{
|
||||
simplifiedRequest.Result = WebRequestResult.ConnectionError;
|
||||
}
|
||||
else
|
||||
{
|
||||
simplifiedRequest.Result = WebRequestResult.UnknownError;
|
||||
}
|
||||
#endif
|
||||
|
||||
simplifiedRequest.ErrorMessage = webRequest.error;
|
||||
simplifiedRequest.ErrorBody = webRequest.downloadHandler.text;
|
||||
}
|
||||
|
||||
return simplifiedRequest;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b683e2d9ea3ae904ba66b41fe103bdfc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,23 @@
|
|||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
enum WebRequestResult
|
||||
{
|
||||
Success,
|
||||
ConnectionError,
|
||||
ProtocolError,
|
||||
UnknownError,
|
||||
}
|
||||
|
||||
struct WebRequest
|
||||
{
|
||||
public WebRequestResult Result;
|
||||
|
||||
public string ErrorMessage;
|
||||
|
||||
public string ErrorBody;
|
||||
|
||||
public long ResponseCode;
|
||||
|
||||
public bool IsSuccess => Result == WebRequestResult.Success;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ed4da48cc54847c4199d65126c9adaf2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
[Serializable]
|
||||
class TelemetryConfig
|
||||
{
|
||||
public const int MaxMetricCountPerPayloadLimit = 295;
|
||||
|
||||
public string TargetUrl;
|
||||
|
||||
public string ServicePath;
|
||||
|
||||
public double PayloadExpirationSeconds;
|
||||
|
||||
public double PayloadSendingMaxIntervalSeconds;
|
||||
|
||||
public double SafetyPersistenceIntervalSeconds;
|
||||
|
||||
public int MaxMetricCountPerPayload;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c901d066a53aebc44874de9d15a7e12b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,3 @@
|
|||
// This file is generated. Do not modify by hand.
|
||||
// XML documentation file not found. To check if public methods have XML comments,
|
||||
// make sure the XML doc file is present and located next to the scraped dll
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 47b040834d0cf144b80407277a1bcadd
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "Unity.Services.Core.Telemetry",
|
||||
"references": [
|
||||
"Unity.Services.Core",
|
||||
"Unity.Services.Core.Internal"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d8e56012c572e204692b3fe5183b7da1
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d9bf7294c3b93424eb34f4ac137b7c35
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
static class CacheExtensions
|
||||
{
|
||||
public static bool IsEmpty<TPayload>(this CachedPayload<TPayload> self)
|
||||
where TPayload : ITelemetryPayload
|
||||
{
|
||||
return (self.Payload?.Count ?? 0) <= 0;
|
||||
}
|
||||
|
||||
public static void AddRangeFrom(
|
||||
this CachedPayload<DiagnosticsPayload> self, CachedPayload<DiagnosticsPayload> payload)
|
||||
{
|
||||
var hasDiagnosticsToAdd = payload.Payload.Diagnostics.Count > 0;
|
||||
if (hasDiagnosticsToAdd)
|
||||
{
|
||||
self.Payload.Diagnostics.AddRange(payload.Payload.Diagnostics);
|
||||
}
|
||||
|
||||
if (hasDiagnosticsToAdd
|
||||
&& self.TimeOfOccurenceTicks <= 0)
|
||||
{
|
||||
self.TimeOfOccurenceTicks = payload.TimeOfOccurenceTicks;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Add<TPayload>(this CachedPayload<TPayload> self, ITelemetryEvent telemetryEvent)
|
||||
where TPayload : ITelemetryPayload
|
||||
{
|
||||
if (self.TimeOfOccurenceTicks == 0)
|
||||
{
|
||||
self.TimeOfOccurenceTicks = DateTime.UtcNow.Ticks;
|
||||
}
|
||||
|
||||
self.Payload.Add(telemetryEvent);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 03bfba0c7dddf814d81dea93d3248e62
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,28 @@
|
|||
using System.Collections.Generic;
|
||||
using Unity.Services.Core.Configuration.Internal;
|
||||
using Unity.Services.Core.Internal;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
static class FactoryUtils
|
||||
{
|
||||
internal const string PackageVersionKeyFormat = "{0}.version";
|
||||
|
||||
public static IDictionary<string, string> CreatePackageTags(
|
||||
IProjectConfiguration projectConfig, string packageName)
|
||||
{
|
||||
var packageVersion = projectConfig.GetString(
|
||||
string.Format(PackageVersionKeyFormat, packageName), string.Empty);
|
||||
if (string.IsNullOrEmpty(packageVersion))
|
||||
{
|
||||
CoreLogger.LogTelemetry($"No package version found for the package \"{packageName}\"");
|
||||
}
|
||||
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
[TagKeys.PackageName] = packageName,
|
||||
[TagKeys.PackageVersion] = packageVersion,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 97fbf25a3dc582348a4d5fff07736c6e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,35 @@
|
|||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
static class TagKeys
|
||||
{
|
||||
public const string ApplicationInstallMode = "application_install_mode";
|
||||
|
||||
public const string OperatingSystem = "operating_system";
|
||||
|
||||
public const string Platform = "platform";
|
||||
|
||||
public const string Engine = "engine";
|
||||
|
||||
public const string UnityVersion = "unity_version";
|
||||
|
||||
public const string PackageName = "package_name";
|
||||
|
||||
public const string PackageVersion = "package_version";
|
||||
|
||||
public const string DiagnosticName = "name";
|
||||
|
||||
public const string DiagnosticMessage = "message";
|
||||
|
||||
public const string ApplicationVersion = "application_version";
|
||||
|
||||
public const string ProductName = "product_name";
|
||||
|
||||
public const string CloudProjectId = "cloud_project_id";
|
||||
|
||||
public const string EnvironmentName = "environment_name";
|
||||
|
||||
public const string ApplicationGenuine = "application_genuine";
|
||||
|
||||
public const string InternetReachability = "internet_reachability";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 65d7ef18358fe33498a7d554418dfbe7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,19 @@
|
|||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
static class TelemetryConfigKeys
|
||||
{
|
||||
const string k_BaseKey = "com.unity.services.core.telemetry-";
|
||||
|
||||
public const string TargetUrl = k_BaseKey + "target-url";
|
||||
|
||||
public const string ServicePath = k_BaseKey + "service-path";
|
||||
|
||||
public const string PayloadExpirationSeconds = k_BaseKey + "payload-expiration-seconds";
|
||||
|
||||
public const string PayloadSendingMaxIntervalSeconds = k_BaseKey + "payload-sending-max-interval-seconds";
|
||||
|
||||
public const string SafetyPersistenceIntervalSeconds = k_BaseKey + "safety-persistence-interval-seconds";
|
||||
|
||||
public const string MaxMetricCountPerPayload = k_BaseKey + "max-metric-count-per-payload";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1e808fd14ebfe6c4994ae957d6ca2b4d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,109 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Services.Core.Configuration.Internal;
|
||||
using Unity.Services.Core.Environments.Internal;
|
||||
using Unity.Services.Core.Scheduler.Internal;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Services.Core.Telemetry.Internal
|
||||
{
|
||||
static class TelemetryUtils
|
||||
{
|
||||
internal const string TelemetryDisabledKey = "com.unity.services.core.telemetry-disabled";
|
||||
|
||||
public static IMetricsFactory CreateMetricsFactory(
|
||||
IActionScheduler scheduler, IProjectConfiguration projectConfiguration, ICloudProjectId cloudProjectId,
|
||||
IEnvironments environments)
|
||||
{
|
||||
if (IsTelemetryDisabled(projectConfiguration))
|
||||
{
|
||||
return new DisabledMetricsFactory();
|
||||
}
|
||||
|
||||
var config = CreateTelemetryConfig(projectConfiguration);
|
||||
var cache = new CachedPayload<MetricsPayload>
|
||||
{
|
||||
Payload = new MetricsPayload
|
||||
{
|
||||
Metrics = new List<Metric>(),
|
||||
CommonTags = new Dictionary<string, string>(),
|
||||
MetricsCommonTags = new Dictionary<string, string>(),
|
||||
},
|
||||
};
|
||||
var cachePersister = CreateCachePersister<MetricsPayload>("UnityServicesCachedMetrics", Application.platform);
|
||||
var retryPolicy = new ExponentialBackOffRetryPolicy();
|
||||
var requestSender = new UnityWebRequestSender();
|
||||
var metricsSender = new TelemetrySender(
|
||||
config.TargetUrl, config.ServicePath, scheduler, retryPolicy, requestSender);
|
||||
var handler = new MetricsHandler(config, cache, scheduler, cachePersister, metricsSender);
|
||||
handler.Initialize(cloudProjectId, environments);
|
||||
|
||||
return new MetricsFactory(handler, projectConfiguration);
|
||||
}
|
||||
|
||||
//TODO: Reuse components from MetricsFactory (or vice versa)
|
||||
public static IDiagnosticsFactory CreateDiagnosticsFactory(
|
||||
IActionScheduler scheduler, IProjectConfiguration projectConfiguration, ICloudProjectId cloudProjectId,
|
||||
IEnvironments environments)
|
||||
{
|
||||
if (IsTelemetryDisabled(projectConfiguration))
|
||||
{
|
||||
return new DisabledDiagnosticsFactory();
|
||||
}
|
||||
|
||||
var config = CreateTelemetryConfig(projectConfiguration);
|
||||
var cache = new CachedPayload<DiagnosticsPayload>
|
||||
{
|
||||
Payload = new DiagnosticsPayload
|
||||
{
|
||||
Diagnostics = new List<Diagnostic>(),
|
||||
CommonTags = new Dictionary<string, string>(),
|
||||
DiagnosticsCommonTags = new Dictionary<string, string>(),
|
||||
},
|
||||
};
|
||||
var cachePersister = CreateCachePersister<DiagnosticsPayload>("UnityServicesCachedDiagnostics", Application.platform);
|
||||
var retryPolicy = new ExponentialBackOffRetryPolicy();
|
||||
var requestSender = new UnityWebRequestSender();
|
||||
var metricsSender = new TelemetrySender(
|
||||
config.TargetUrl, config.ServicePath, scheduler, retryPolicy, requestSender);
|
||||
var handler = new DiagnosticsHandler(
|
||||
config, cache, scheduler, cachePersister, metricsSender);
|
||||
handler.Initialize(cloudProjectId, environments);
|
||||
|
||||
return new DiagnosticsFactory(handler, projectConfiguration);
|
||||
}
|
||||
|
||||
static bool IsTelemetryDisabled(IProjectConfiguration projectConfiguration)
|
||||
=> projectConfiguration.GetBool(TelemetryDisabledKey);
|
||||
|
||||
internal static ICachePersister<TPayload> CreateCachePersister<TPayload>(
|
||||
string fileName, RuntimePlatform platform)
|
||||
where TPayload : ITelemetryPayload
|
||||
{
|
||||
if (platform == RuntimePlatform.Switch)
|
||||
return new DisabledCachePersister<TPayload>();
|
||||
|
||||
return new FileCachePersister<TPayload>(fileName);
|
||||
}
|
||||
|
||||
internal static TelemetryConfig CreateTelemetryConfig(IProjectConfiguration projectConfiguration)
|
||||
{
|
||||
const string defaultTargetUrl = "https://operate-sdk-telemetry.unity3d.com";
|
||||
const string defaultServicePath = "v1/record";
|
||||
const int defaultPayloadExpirationSeconds = 3600;
|
||||
const int defaultPayloadSendingMaxIntervalSeconds = 600;
|
||||
const int defaultSafetyPersistenceIntervalSeconds = 300;
|
||||
|
||||
var config = new TelemetryConfig
|
||||
{
|
||||
TargetUrl = projectConfiguration.GetString(TelemetryConfigKeys.TargetUrl, defaultTargetUrl),
|
||||
ServicePath = projectConfiguration.GetString(TelemetryConfigKeys.ServicePath, defaultServicePath),
|
||||
PayloadExpirationSeconds = projectConfiguration.GetInt(TelemetryConfigKeys.PayloadExpirationSeconds, defaultPayloadExpirationSeconds),
|
||||
PayloadSendingMaxIntervalSeconds = projectConfiguration.GetInt(TelemetryConfigKeys.PayloadSendingMaxIntervalSeconds, defaultPayloadSendingMaxIntervalSeconds),
|
||||
SafetyPersistenceIntervalSeconds = projectConfiguration.GetInt(TelemetryConfigKeys.SafetyPersistenceIntervalSeconds, defaultSafetyPersistenceIntervalSeconds),
|
||||
MaxMetricCountPerPayload = Math.Min(TelemetryConfig.MaxMetricCountPerPayloadLimit, projectConfiguration.GetInt(TelemetryConfigKeys.MaxMetricCountPerPayload, TelemetryConfig.MaxMetricCountPerPayloadLimit)),
|
||||
};
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a3d8d4bd36f9820498053c2dac2e959c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Add table
Add a link
Reference in a new issue