initial commit

This commit is contained in:
Jo 2025-01-07 02:06:59 +01:00
parent 6715289efe
commit 788c3389af
37645 changed files with 2526849 additions and 80 deletions

View file

@ -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

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 560dda45fb9293949aa636b3e2a92807
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1b8d6e21512034f46b221109c045d40c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a04eba4a14d22c544a65ccec355aaabf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bb4fdb08c578f464ebaf468026b9d4b6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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();
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6cd651aefba97204c9f4b012f6ac8f54
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3330907631d21584281522898e9028cd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d82f57a40dd480c4992d1d23dd773765
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fa076e64208b8334a8a147efeebf66fc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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.
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 30648dc766559254d8ef3209219478ab
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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();
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 30e535d05ac7fad4db50b8dfe6751c14
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e0315c35a4ce1734a8197a6da9694713
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 34f5fe0d07586744aa92a02821c71e8d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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();
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 19ff1201e729ddc4d8128534ec9c435c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cafed3ef1baf3db47aac0e065c7edee4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f35a5b2933eb87b44869fb793b853ab5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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.
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3acf2a208165fee48a9a8604d2ab412a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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();
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2bc7ece372a03404394756573e450dd0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: be5e351f9a40dd54d8fb1ec23b94a1e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d0d7f6572d6b64d4f802e930a1670ab5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 004201c331a12b840b8ad34d6d987e83
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fc7e0d4e3660a3f45b604bde5c573b9f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b8945d067bb7acc4c9f86ddea96250c0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 91622ad6ca234524d8b947037f9de9db
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2f5bfa82bdb60974380529c55084671a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6b2d91503ff1f2c4e8eff2862ece88d0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,4 @@
namespace Unity.Services.Core.Telemetry.Internal
{
interface ITelemetryEvent {}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 81d320e0845023f44a8dfd79a7df2cfd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3c9dbeab193d6ad45bd9270d4100d511
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: abd0b6db8e602174d9e7f217e0571399
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a272eaf5ec4f30441854ab3a7b0a1bb6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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,
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 743e8642b43bf094d8dd50e2b9ddfb6c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d80ee8c7b400fc04797b6c579538d686
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 84b8d6187a577344d9f17e4d54fc2866
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b0fd45576b552084cbe828d41f7f045d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,10 @@
using System;
using UnityEngine.Networking;
namespace Unity.Services.Core.Telemetry.Internal
{
interface IUnityWebRequestSender
{
void SendRequest(UnityWebRequest request, Action<WebRequest> callback);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0a159a6fc91bd044097fa1497cf30399
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 489390cf0d252a44dab034d68a28ef09
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b683e2d9ea3ae904ba66b41fe103bdfc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ed4da48cc54847c4199d65126c9adaf2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c901d066a53aebc44874de9d15a7e12b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 47b040834d0cf144b80407277a1bcadd
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: d8e56012c572e204692b3fe5183b7da1
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d9bf7294c3b93424eb34f4ac137b7c35
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 03bfba0c7dddf814d81dea93d3248e62
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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,
};
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 97fbf25a3dc582348a4d5fff07736c6e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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";
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 65d7ef18358fe33498a7d554418dfbe7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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";
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1e808fd14ebfe6c4994ae957d6ca2b4d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a3d8d4bd36f9820498053c2dac2e959c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: