#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 { /// /// 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. /// abstract class TelemetryHandler { internal static string FormatOperatingSystemInfo(string rawOsInfo) { #if UNITY_ANDROID && !UNITY_EDITOR //Android's os data is formatted as follow: //" / API- (/)" //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 : TelemetryHandler where TPayload : ITelemetryPayload where TEvent : ITelemetryEvent { readonly IActionScheduler m_Scheduler; protected readonly ICachePersister m_CachePersister; protected readonly TelemetrySender m_Sender; internal long SendingLoopScheduleId; internal long PersistenceLoopScheduleId; public TelemetryConfig Config { get; } public CachedPayload Cache { get; } /// /// Members requiring thread safety: /// * . /// * . /// protected object Lock { get; } = new object(); protected TelemetryHandler( TelemetryConfig config, CachedPayload cache, IActionScheduler scheduler, ICachePersister 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() { lock (Lock) { 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 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(); lock (Lock) { SendCachedPayload(); } } } internal abstract void SendCachedPayload(); internal void SchedulePersistenceLoop() { PersistenceLoopScheduleId = m_Scheduler.ScheduleAction( PersistenceLoop, Config.SafetyPersistenceIntervalSeconds); void PersistenceLoop() { SchedulePersistenceLoop(); PersistCache(); } } internal void PersistCache() { lock (Lock) { if (!m_CachePersister.CanPersist || Cache.TimeOfOccurenceTicks <= 0 || Cache.Payload.Count <= 0) return; m_CachePersister.Persist(Cache); } } public void Register(TEvent telemetryEvent) { lock (Lock) { 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; } } } }