diff --git a/build/nuget/Uno.WinUI.nuspec b/build/nuget/Uno.WinUI.nuspec index c3e5bb87e603..66c1e278d755 100644 --- a/build/nuget/Uno.WinUI.nuspec +++ b/build/nuget/Uno.WinUI.nuspec @@ -608,6 +608,7 @@ + diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/BaseStorageService.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/BaseStorageService.cs deleted file mode 100644 index 5eb6a55592de..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/BaseStorageService.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.ApplicationInsights.Channel; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - internal abstract class BaseStorageService - { - /// - /// Peeked transmissions dictionary (maps file name to its full path). Holds all the transmissions that were peeked. - /// - /// - /// Note: The value (=file's full path) is not required in the Storage implementation. - /// If there was a concurrent Abstract Data Type Set it would have been used instead. - /// However, since there is no concurrent Set, dictionary is used and the second value is ignored. - /// - protected IDictionary PeekedTransmissions; - - /// - /// Gets or sets the maximum size of the storage in bytes. When limit is reached, the Enqueue method will drop new - /// transmissions. - /// - internal ulong CapacityInBytes { get; set; } - - /// - /// Gets or sets the maximum number of files. When limit is reached, the Enqueue method will drop new transmissions. - /// - internal uint MaxFiles { get; set; } - - internal abstract string StorageDirectoryPath { get; } - - /// - /// Initializes the - /// - /// A folder name. Under this folder all the transmissions will be saved. - internal abstract void Init(string desireStorageDirectoryPath); - - internal abstract StorageTransmission Peek(); - - internal abstract void Delete(StorageTransmission transmission); - - internal abstract Task EnqueueAsync(Transmission transmission); - - protected void OnPeekedItemDisposed(string fileName) - { - try - { - if (PeekedTransmissions.ContainsKey(fileName)) - { - PeekedTransmissions.Remove(fileName); - } - } - catch (Exception e) - { - PersistenceChannelDebugLog.WriteException(e, "Failed to remove the item from storage items."); - } - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/FixedSizeQueue.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/FixedSizeQueue.cs deleted file mode 100644 index e3663ec5c373..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/FixedSizeQueue.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System.Collections.Generic; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - /// - /// A light fixed size queue. If Enqueue is called and queue's limit has reached the last item will be removed. - /// This data structure is thread safe. - /// - internal class FixedSizeQueue - { - private readonly int _maxSize; - private readonly Queue _queue = new Queue(); - private readonly object _queueLockObj = new object(); - - internal FixedSizeQueue(int maxSize) - { - _maxSize = maxSize; - } - - internal void Enqueue(T item) - { - lock (_queueLockObj) - { - if (_queue.Count == _maxSize) - { - _queue.Dequeue(); - } - - _queue.Enqueue(item); - } - } - - internal bool Contains(T item) - { - lock (_queueLockObj) - { - return _queue.Contains(item); - } - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/FlushManager.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/FlushManager.cs deleted file mode 100644 index 850bcf769121..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/FlushManager.cs +++ /dev/null @@ -1,62 +0,0 @@ -// // Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using Microsoft.ApplicationInsights.Channel; -using Microsoft.ApplicationInsights.Extensibility.Implementation; -using IChannelTelemetry = Microsoft.ApplicationInsights.Channel.ITelemetry; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - /// - /// This class handles all the logic for flushing the In Memory buffer to the persistent storage. - /// - internal class FlushManager - { - /// - /// The storage that is used to persist all the transmissions. - /// - private readonly BaseStorageService _storage; - - /// - /// Initializes a new instance of the class. - /// - /// The storage that persists the telemetries. - internal FlushManager(BaseStorageService storage) - { - _storage = storage; - } - - /// - /// Gets or sets the service endpoint. - /// - /// - /// Q: Why flushManager knows about the endpoint? - /// A: Storage stores Transmission objects and Transmission objects contain the endpoint address. - /// - internal Uri EndpointAddress { get; set; } - - - /// - /// Persist the in-memory telemetry items. - /// - internal void Flush(IChannelTelemetry telemetryItem) - { - if (telemetryItem != null) - { - byte[] data = JsonSerializer.Serialize(new[] { telemetryItem }); - Transmission transmission = new Transmission( - EndpointAddress, - data, - "application/x-json-stream", - JsonSerializer.CompressionType); - - _storage.EnqueueAsync(transmission).ConfigureAwait(false).GetAwaiter().GetResult(); - } - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceChannel.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceChannel.cs deleted file mode 100644 index c470535cfba0..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceChannel.cs +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Threading; -using Microsoft.ApplicationInsights.Channel; -using IChannelTelemetry = Microsoft.ApplicationInsights.Channel.ITelemetry; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - /// - /// Represents a communication channel for sending telemetry to Application Insights via HTTPS. - /// - internal sealed class PersistenceChannel : ITelemetryChannel - { - internal const string TelemetryServiceEndpoint = "https://dc.services.visualstudio.com/v2/track"; - - private readonly FlushManager _flushManager; - - private int _disposeCount; - private readonly BaseStorageService _storage; - private readonly PersistenceTransmitter _transmitter; - - /// - /// Initializes a new instance of the class. - /// - /// - /// Full path of a directory name. Under this folder all the transmissions will be saved. - /// Setting this value groups channels, even from different processes. - /// If 2 (or more) channels has the same storageFolderName only one channel will perform the sending even if the - /// channel is in a different process/AppDomain/Thread. - /// - /// - /// Defines the number of senders. A sender is a long-running thread that sends telemetry batches in intervals defined - /// by . - /// So the amount of senders also defined the maximum amount of http channels opened at the same time. - /// - public PersistenceChannel(string storageDirectoryPath = null, int sendersCount = 1) - { - _storage = new StorageService(); - _storage.Init(storageDirectoryPath); - _transmitter = new PersistenceTransmitter(_storage, sendersCount); - _flushManager = new FlushManager(_storage); - EndpointAddress = TelemetryServiceEndpoint; - } - - /// - /// Gets or sets an interval between each successful sending. - /// - /// - /// On error scenario this value is ignored and the interval will be defined using an exponential back-off - /// algorithm. - /// - public TimeSpan? SendingInterval - { - get => _transmitter.SendingInterval; - set => _transmitter.SendingInterval = value; - } - - - /// - /// Gets or sets the maximum amount of files allowed in storage. When the limit is reached telemetries will be dropped. - /// - public uint MaxTransmissionStorageFilesCapacity - { - get => _storage.MaxFiles; - set => _storage.MaxFiles = value; - } - - /// - /// This flag has no effect. But it is required by base class - /// - public bool? DeveloperMode { get; set; } - - /// - /// Gets or sets the HTTP address where the telemetry is sent. - /// - public string EndpointAddress - { - get => _flushManager.EndpointAddress.ToString(); - - set - { - string address = value ?? TelemetryServiceEndpoint; - _flushManager.EndpointAddress = new Uri(address); - } - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - public void Dispose() - { - if (Interlocked.Increment(ref _disposeCount) == 1) - { - _transmitter?.Dispose(); - } - } - - /// - /// Sends an instance of ITelemetry through the channel. - /// - public void Send(IChannelTelemetry item) - { - _flushManager.Flush(item); - } - - /// - /// No operation, send will always flush. So nothing will be in memory - /// - public void Flush() - { - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceChannelDebugLog.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceChannelDebugLog.cs deleted file mode 100644 index 18b9541a2d4d..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceChannelDebugLog.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Globalization; -using System.IO; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - internal static class PersistenceChannelDebugLog - { - private static readonly bool _isEnabled = IsEnabledByEnvironment(); - - private static bool IsEnabledByEnvironment() - { - if (bool.TryParse(Environment.GetEnvironmentVariable("UNO_SOURCEGEN_ENABLE_PERSISTENCE_CHANNEL_DEBUG_OUTPUT"), out var enabled)) - { - return enabled; - } - - return false; - } - - public static void WriteLine(string message) - { - if (_isEnabled) - { - Console.WriteLine(message); - } - } - - internal static void WriteException(Exception exception, string format, params string[] args) - { - var message = string.Format(CultureInfo.InvariantCulture, format, args); - WriteLine(string.Format(CultureInfo.InvariantCulture, "{0} Exception: {1}", message, exception.ToString())); - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceTransmitter.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceTransmitter.cs deleted file mode 100644 index 67aeb23b8328..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/PersistenceTransmitter.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - /// - /// Implements throttled and persisted transmission of telemetry to Application Insights. - /// - internal class PersistenceTransmitter : IDisposable - { - /// - /// The number of times this object was disposed. - /// - private int _disposeCount; - - /// - /// A list of senders that sends transmissions. - /// - private readonly List _senders = new List(); - - /// - /// The storage that is used to persist all the transmissions. - /// - private readonly BaseStorageService _storage; - - /// - /// Initializes a new instance of the class. - /// - /// The transmissions storage. - /// The number of senders to create. - /// - /// A boolean value that indicates if this class should try and create senders. This is a - /// workaround for unit tests purposes only. - /// - internal PersistenceTransmitter(BaseStorageService storage, int sendersCount, bool createSenders = true) - { - _storage = storage; - if (createSenders) - { - for (int i = 0; i < sendersCount; i++) - { - _senders.Add(new Sender(_storage, this)); - } - } - } - - /// - /// Gets or sets the interval between each successful sending. - /// - internal TimeSpan? SendingInterval { get; set; } - - /// - /// Disposes the object. - /// - public void Dispose() - { - if (Interlocked.Increment(ref _disposeCount) == 1) - { - StopSenders(); - } - } - - /// - /// Stops the senders. - /// - /// As long as there is no Start implementation, this method should only be called from Dispose. - private void StopSenders() - { - if (_senders == null) - { - return; - } - - List stoppedTasks = new List(); - foreach (Sender sender in _senders) - { - stoppedTasks.Add(sender.StopAsync()); - } - - Task.WaitAll(stoppedTasks.ToArray()); - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/Sender.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/Sender.cs deleted file mode 100644 index 49f24a39f016..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/Sender.cs +++ /dev/null @@ -1,343 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Globalization; -using System.Net; -using System.Net.NetworkInformation; -using System.Threading; -using System.Threading.Tasks; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - /// - /// Fetch transmissions from the storage and sends it. - /// - internal class Sender : IDisposable - { - /// - /// The default sending interval. - /// - private readonly TimeSpan _defaultSendingInterval; - - /// - /// A wait handle that flags the sender when to start sending again. The type is protected for unit test. - /// - protected readonly AutoResetEvent DelayHandler; - - /// - /// Holds the maximum time for the exponential back-off algorithm. The sending interval will grow on every HTTP - /// Exception until this max value. - /// - private readonly TimeSpan _maxIntervalBetweenRetries = TimeSpan.FromHours(1); - - /// - /// When storage is empty it will be queried again after this interval. - /// Decreasing to 5 sec to send first data (users and sessions). - /// - private readonly TimeSpan _sendingIntervalOnNoData = TimeSpan.FromSeconds(5); - - /// - /// A wait handle that is being set when Sender is no longer sending. - /// - private readonly AutoResetEvent _stoppedHandler; - - /// - /// The number of times this object was disposed. - /// - private int _disposeCount; - - /// - /// The amount of time to wait, in the stop method, until the last transmission is sent. - /// If time expires, the stop method will return even if the transmission hasn't been sent. - /// - private readonly TimeSpan _drainingTimeout; - - /// - /// A boolean value that indicates if the sender should be stopped. The sender's while loop is checking this boolean - /// value. - /// - private bool _stopped; - - /// - /// The transmissions storage. - /// - private readonly BaseStorageService _storage; - - /// - /// Holds the transmitter. - /// - private readonly PersistenceTransmitter _transmitter; - - /// - /// Initializes a new instance of the class. - /// - /// The storage that holds the transmissions to send. - /// - /// The persistence transmitter that manages this Sender. - /// The transmitter will be used as a configuration class, it exposes properties like SendingInterval that will be read - /// by Sender. - /// - /// - /// A boolean value that determines if Sender should start sending immediately. This is only - /// used for unit tests. - /// - internal Sender(BaseStorageService storage, PersistenceTransmitter transmitter, bool startSending = true) - { - _stopped = false; - DelayHandler = new AutoResetEvent(false); - _stoppedHandler = new AutoResetEvent(false); - _drainingTimeout = TimeSpan.FromSeconds(100); - _defaultSendingInterval = TimeSpan.FromSeconds(5); - - _transmitter = transmitter; - _storage = storage; - - if (startSending) - { - // It is currently possible for the long - running task to be executed(and thereby block during WaitOne) on the UI thread when - // called by a task scheduled on the UI thread. Explicitly specifying TaskScheduler.Default - // when calling StartNew guarantees that Sender never blocks the main thread. - Task.Factory.StartNew(SendLoop, CancellationToken.None, TaskCreationOptions.LongRunning, - TaskScheduler.Default) - .ContinueWith( - t => PersistenceChannelDebugLog.WriteException(t.Exception, "Sender: Failure in SendLoop"), - TaskContinuationOptions.OnlyOnFaulted); - } - } - - /// - /// Gets the interval between each successful sending. - /// - private TimeSpan SendingInterval - { - get - { - if (_transmitter.SendingInterval != null) - { - return _transmitter.SendingInterval.Value; - } - - return _defaultSendingInterval; - } - } - - /// - /// Disposes the managed objects. - /// - public void Dispose() - { - if (Interlocked.Increment(ref _disposeCount) == 1) - { - StopAsync().ConfigureAwait(false).GetAwaiter().GetResult(); - - DelayHandler.Dispose(); - _stoppedHandler.Dispose(); - } - } - - /// - /// Stops the sender. - /// - internal Task StopAsync() - { - // After delayHandler is set, a sending iteration will immediately start. - // Setting stopped to true, will cause the iteration to skip the actual sending and stop immediately. - _stopped = true; - DelayHandler.Set(); - - // if delayHandler was set while a transmission was being sent, the return task will wait for it to finish, for an additional second, - // before it will mark the task as completed. - return Task.Run(() => - { - try - { - _stoppedHandler.WaitOne(_drainingTimeout); - } - catch (ObjectDisposedException) - { - } - }); - } - - /// - /// Send transmissions in a loop. - /// - protected void SendLoop() - { - TimeSpan prevSendingInterval = TimeSpan.Zero; - TimeSpan sendingInterval = _sendingIntervalOnNoData; - try - { - while (!_stopped) - { - using (StorageTransmission transmission = _storage.Peek()) - { - if (_stopped) - { - // This second verification is required for cases where 'stopped' was set while peek was happening. - // Once the actual sending starts the design is to wait until it finishes and deletes the transmission. - // So no extra validation is required. - break; - } - - // If there is a transmission to send - send it. - if (transmission != null) - { - bool shouldRetry = Send(transmission, ref sendingInterval); - if (!shouldRetry) - { - // If retry is not required - delete the transmission. - _storage.Delete(transmission); - } - } - else - { - sendingInterval = _sendingIntervalOnNoData; - } - } - - LogInterval(prevSendingInterval, sendingInterval); - DelayHandler.WaitOne(sendingInterval); - prevSendingInterval = sendingInterval; - } - - _stoppedHandler.Set(); - } - catch (ObjectDisposedException) - { - } - } - - /// - /// Sends a transmission and handle errors. - /// - /// The transmission to send. - /// - /// When this value returns it will hold a recommendation for when to start the next sending - /// iteration. - /// - /// True, if there was sent error and we need to retry sending, otherwise false. - protected virtual bool Send(StorageTransmission transmission, ref TimeSpan nextSendInterval) - { - try - { - if (transmission != null) - { - bool isConnected = NetworkInterface.GetIsNetworkAvailable(); - - // there is no internet connection available, return than. - if (!isConnected) - { - PersistenceChannelDebugLog.WriteLine( - "Cannot send data to the server. Internet connection is not available"); - return true; - } - - transmission.SendAsync().ConfigureAwait(false).GetAwaiter().GetResult(); - - // After a successful sending, try immediately to send another transmission. - nextSendInterval = SendingInterval; - } - } - catch (WebException e) - { - int? statusCode = GetStatusCode(e); - nextSendInterval = CalculateNextInterval(statusCode, nextSendInterval, _maxIntervalBetweenRetries); - return IsRetryable(statusCode, e.Status); - } - catch (Exception e) - { - nextSendInterval = CalculateNextInterval(null, nextSendInterval, _maxIntervalBetweenRetries); - PersistenceChannelDebugLog.WriteException(e, "Unknown exception during sending"); - } - - return false; - } - - /// - /// Log next interval. Only log the interval when it changes by more then a minute. So if interval grow by 1 minute or - /// decreased by 1 minute it will be logged. - /// Logging every interval will just make the log noisy. - /// - private static void LogInterval(TimeSpan prevSendInterval, TimeSpan nextSendInterval) - { - if (Math.Abs(nextSendInterval.TotalSeconds - prevSendInterval.TotalSeconds) > 60) - { - PersistenceChannelDebugLog.WriteLine("next sending interval: " + nextSendInterval); - } - } - - /// - /// Return the status code from the web exception or null if no such code exists. - /// - private static int? GetStatusCode(WebException e) - { - if (e.Response is HttpWebResponse httpWebResponse) - { - return (int)httpWebResponse.StatusCode; - } - - return null; - } - - /// - /// Returns true if or are retryable. - /// - private static bool IsRetryable(int? httpStatusCode, WebExceptionStatus webExceptionStatus) - { - switch (webExceptionStatus) - { - case WebExceptionStatus.ProxyNameResolutionFailure: - case WebExceptionStatus.NameResolutionFailure: - case WebExceptionStatus.Timeout: - case WebExceptionStatus.ConnectFailure: - return true; - } - - if (httpStatusCode == null) - { - return false; - } - - switch (httpStatusCode.Value) - { - case 503: // Server in maintenance. - case 408: // invalid request - case 500: // Internal Server Error - case 502: // Bad Gateway, can be common when there is no network. - case 511: // Network Authentication Required - return true; - } - - return false; - } - - /// - /// Calculates the next interval using exponential back-off algorithm (with the exceptions of few error codes that - /// reset the interval to . - /// - private TimeSpan CalculateNextInterval(int? httpStatusCode, TimeSpan currentSendInterval, TimeSpan maxInterval) - { - // if item is expired, no need for exponential back-off - if (httpStatusCode != null && httpStatusCode.Value == 400 /* expired */) - { - return SendingInterval; - } - - // exponential back-off. - if (Math.Abs(currentSendInterval.TotalSeconds) < 1) - { - return TimeSpan.FromSeconds(1); - } - - double nextIntervalInSeconds = Math.Min(currentSendInterval.TotalSeconds * 2, maxInterval.TotalSeconds); - - return TimeSpan.FromSeconds(nextIntervalInSeconds); - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/SnapshottingCollection.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/SnapshottingCollection.cs deleted file mode 100644 index 051adda5cea0..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/SnapshottingCollection.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - internal abstract class SnapshottingCollection : ICollection - where TCollection : class, ICollection - { - protected readonly TCollection Collection; - protected TCollection snapshot; - - protected SnapshottingCollection(TCollection collection) - { - Debug.Assert(collection != null, "collection"); - Collection = collection; - } - - public int Count => GetSnapshot().Count; - - public bool IsReadOnly => false; - - public void Add(TItem item) - { - lock (Collection) - { - Collection.Add(item); - snapshot = default; - } - } - - public void Clear() - { - lock (Collection) - { - Collection.Clear(); - snapshot = default; - } - } - - public bool Contains(TItem item) - { - return GetSnapshot().Contains(item); - } - - public void CopyTo(TItem[] array, int arrayIndex) - { - GetSnapshot().CopyTo(array, arrayIndex); - } - - public bool Remove(TItem item) - { - lock (Collection) - { - bool removed = Collection.Remove(item); - if (removed) - { - snapshot = default; - } - - return removed; - } - } - - public IEnumerator GetEnumerator() - { - return GetSnapshot().GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - protected abstract TCollection CreateSnapshot(TCollection collection); - - protected TCollection GetSnapshot() - { - TCollection localSnapshot = snapshot; - if (localSnapshot == null) - { - lock (Collection) - { - snapshot = CreateSnapshot(Collection); - localSnapshot = snapshot; - } - } - - return localSnapshot; - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/SnapshottingDictionary.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/SnapshottingDictionary.cs deleted file mode 100644 index 83897f5d0069..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/SnapshottingDictionary.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System.Collections.Generic; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - internal class SnapshottingDictionary : - SnapshottingCollection, IDictionary>, IDictionary - { - public SnapshottingDictionary() - : base(new Dictionary()) - { - } - - public ICollection Keys => GetSnapshot().Keys; - - public ICollection Values => GetSnapshot().Values; - - public TValue this[TKey key] - { - get => GetSnapshot()[key]; - - set - { - lock (Collection) - { - Collection[key] = value; - snapshot = null; - } - } - } - - public void Add(TKey key, TValue value) - { - lock (Collection) - { - Collection.Add(key, value); - snapshot = null; - } - } - - public bool ContainsKey(TKey key) - { - return GetSnapshot().ContainsKey(key); - } - - public bool Remove(TKey key) - { - lock (Collection) - { - bool removed = Collection.Remove(key); - if (removed) - { - snapshot = null; - } - - return removed; - } - } - - public bool TryGetValue(TKey key, out TValue value) - { - return GetSnapshot().TryGetValue(key, out value); - } - - protected sealed override IDictionary CreateSnapshot(IDictionary collection) - { - return new Dictionary(collection); - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/StorageService.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/StorageService.cs deleted file mode 100644 index b43163c0d495..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/StorageService.cs +++ /dev/null @@ -1,364 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.ApplicationInsights.Channel; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - internal sealed class StorageService : BaseStorageService - { - private const string DefaultStorageFolderName = "TelemetryStorageService"; - private readonly FixedSizeQueue _deletedFilesQueue = new FixedSizeQueue(10); - - private readonly object _peekLockObj = new object(); - private readonly object _storageFolderLock = new object(); - private string _storageDirectoryPath; - private string _storageDirectoryPathUsed; - private long _storageCountFiles; - private bool _storageFolderInitialized; - private long _storageSize; - private uint _transmissionsDropped; - - /// - /// Gets the storage's folder name. - /// - internal override string StorageDirectoryPath => _storageDirectoryPath; - - /// - /// Gets the storage folder. If storage folder couldn't be created, null will be returned. - /// - private string StorageFolder - { - get - { - if (!_storageFolderInitialized) - { - lock (_storageFolderLock) - { - if (!_storageFolderInitialized) - { - try - { - _storageDirectoryPathUsed = _storageDirectoryPath; - - if (!Directory.Exists(_storageDirectoryPathUsed)) - { - Directory.CreateDirectory(_storageDirectoryPathUsed); - } - } - catch (Exception e) - { - _storageDirectoryPathUsed = null; - PersistenceChannelDebugLog.WriteException(e, "Failed to create storage folder"); - } - - _storageFolderInitialized = true; - } - } - } - - return _storageDirectoryPathUsed; - } - } - - internal override void Init(string storageDirectoryPath) - { - PeekedTransmissions = new SnapshottingDictionary(); - - VerifyOrSetDefaultStorageDirectoryPath(storageDirectoryPath); - - CapacityInBytes = 10 * 1024 * 1024; // 10 MB - MaxFiles = 100; - - Task.Run(DeleteObsoleteFiles) - .ContinueWith( - task => - { - PersistenceChannelDebugLog.WriteException( - task.Exception, - "Storage: Unhandled exception in DeleteObsoleteFiles"); - }, - TaskContinuationOptions.OnlyOnFaulted); - } - - private void VerifyOrSetDefaultStorageDirectoryPath(string desireStorageDirectoryPath) - { - if (string.IsNullOrEmpty(desireStorageDirectoryPath)) - { - _storageDirectoryPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - DefaultStorageFolderName); - } - else - { - if (!Path.IsPathRooted(desireStorageDirectoryPath)) - { - throw new ArgumentException($"{nameof(desireStorageDirectoryPath)} need to be rooted (full path)"); - } - - _storageDirectoryPath = desireStorageDirectoryPath; - } - } - - /// - /// Reads an item from the storage. Order is Last-In-First-Out. - /// When the Transmission is no longer needed (it was either sent or failed with a non-retryable error) it should be - /// disposed. - /// - internal override StorageTransmission Peek() - { - IEnumerable files = GetFiles("*.trn", 50); - - lock (_peekLockObj) - { - foreach (string file in files) - { - try - { - // if a file was peeked before, skip it (wait until it is disposed). - if (PeekedTransmissions.ContainsKey(file) == false && - _deletedFilesQueue.Contains(file) == false) - { - // Load the transmission from disk. - StorageTransmission storageTransmissionItem = LoadTransmissionFromFileAsync(file) - .ConfigureAwait(false).GetAwaiter().GetResult(); - - // when item is disposed it should be removed from the peeked list. - storageTransmissionItem.Disposing = item => OnPeekedItemDisposed(file); - - // add the transmission to the list. - PeekedTransmissions.Add(file, storageTransmissionItem.FullFilePath); - return storageTransmissionItem; - } - } - catch (Exception e) - { - PersistenceChannelDebugLog.WriteException( - e, - "Failed to load an item from the storage. file: {0}", - file); - } - } - } - - return null; - } - - internal override void Delete(StorageTransmission item) - { - try - { - if (StorageFolder == null) - { - return; - } - - // Initial storage size calculation. - CalculateSize(); - - long fileSize = GetSize(item.FileName); - File.Delete(Path.Combine(StorageFolder, item.FileName)); - - _deletedFilesQueue.Enqueue(item.FileName); - - // calculate size - Interlocked.Add(ref _storageSize, -fileSize); - Interlocked.Decrement(ref _storageCountFiles); - } - catch (IOException e) - { - PersistenceChannelDebugLog.WriteException(e, "Failed to delete a file. file: {0}", item == null ? "null" : item.FullFilePath); - } - } - - internal override async Task EnqueueAsync(Transmission transmission) - { - try - { - if (transmission == null || StorageFolder == null) - { - return; - } - - // Initial storage size calculation. - CalculateSize(); - - if ((ulong)_storageSize >= CapacityInBytes || _storageCountFiles >= MaxFiles) - { - // if max storage capacity has reached, drop the transmission (but log every 100 lost transmissions). - if (_transmissionsDropped++ % 100 == 0) - { - PersistenceChannelDebugLog.WriteLine("Total transmissions dropped: " + _transmissionsDropped); - } - - return; - } - - // Writes content to a temporary file and only then rename to avoid the Peek from reading the file before it is being written. - // Creates the temp file name - string tempFileName = Guid.NewGuid().ToString("N"); - - // Now that the file got created we can increase the files count - Interlocked.Increment(ref _storageCountFiles); - - // Saves transmission to the temp file - await SaveTransmissionToFileAsync(transmission, tempFileName).ConfigureAwait(false); - - // Now that the file is written increase storage size. - long temporaryFileSize = GetSize(tempFileName); - Interlocked.Add(ref _storageSize, temporaryFileSize); - - // Creates a new file name - string now = DateTime.UtcNow.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture); - string newFileName = string.Format(CultureInfo.InvariantCulture, "{0}_{1}.trn", now, tempFileName); - - // Renames the file - File.Move(Path.Combine(StorageFolder, tempFileName), Path.Combine(StorageFolder, newFileName)); - } - catch (Exception e) - { - PersistenceChannelDebugLog.WriteException(e, "EnqueueAsync"); - } - } - - private async Task SaveTransmissionToFileAsync(Transmission transmission, string file) - { - try - { - using (Stream stream = File.OpenWrite(Path.Combine(StorageFolder, file))) - { - await StorageTransmission.SaveAsync(transmission, stream).ConfigureAwait(false); - } - } - catch (UnauthorizedAccessException) - { - string message = - string.Format( - CultureInfo.InvariantCulture, - "Failed to save transmission to file. UnauthorizedAccessException. File path: {0}, FileName: {1}", - StorageFolder, file); - PersistenceChannelDebugLog.WriteLine(message); - throw; - } - } - - private async Task LoadTransmissionFromFileAsync(string file) - { - try - { - using (Stream stream = File.OpenRead(Path.Combine(StorageFolder, file))) - { - StorageTransmission storageTransmissionItem = - await StorageTransmission.CreateFromStreamAsync(stream, file).ConfigureAwait(false); - return storageTransmissionItem; - } - } - catch (Exception e) - { - string message = - string.Format( - CultureInfo.InvariantCulture, - "Failed to load transmission from file. File path: {0}, FileName: {1}, Exception: {2}", - "storageFolderName", file, e); - PersistenceChannelDebugLog.WriteLine(message); - throw; - } - } - - /// - /// Get files from . - /// - /// Define the logic for sorting the files. - /// Defines a file extension. This method will return only files with this extension. - /// - /// Define how many files to return. This can be useful when the directory has a lot of files, in that case - /// GetFilesAsync will have a performance hit. - /// - private IEnumerable GetFiles(string filterByExtension, int top) - { - try - { - if (StorageFolder != null) - { - return Directory.GetFiles(StorageFolder, filterByExtension).Take(top); - } - } - catch (Exception e) - { - PersistenceChannelDebugLog.WriteException(e, "Peek failed while get files from storage."); - } - - return Enumerable.Empty(); - } - - /// - /// Gets a file's size. - /// - private long GetSize(string file) - { - using (FileStream stream = File.OpenRead(Path.Combine(StorageFolder, file))) - { - return stream.Length; - } - } - - /// - /// Check the storage limits and return true if they reached. - /// Storage limits are defined by the number of files and the total size on disk. - /// - private void CalculateSize() - { - string[] storageFiles = Directory.GetFiles(StorageFolder, "*.*"); - - _storageCountFiles = storageFiles.Length; - - long storageSizeInBytes = 0; - foreach (string file in storageFiles) - { - storageSizeInBytes += GetSize(file); - } - - _storageSize = storageSizeInBytes; - } - - /// - /// Enqueue is saving a transmission to a tmp file and after a successful write operation it renames it to a - /// trn file. - /// A file without a trn extension is ignored by Storage.Peek(), so if a process is taken down before rename - /// happens - /// it will stay on the disk forever. - /// This thread deletes files with the tmp extension that exists on disk for more than 5 minutes. - /// - private void DeleteObsoleteFiles() - { - try - { - IEnumerable files = GetFiles("*.tmp", 50); - foreach (string file in files) - { - DateTime creationTime = File.GetCreationTimeUtc(Path.Combine(StorageFolder, file)); - // if the file is older then 5 minutes - delete it. - if (DateTime.UtcNow - creationTime >= TimeSpan.FromMinutes(5)) - { - File.Delete(Path.Combine(StorageFolder, file)); - } - } - } - catch (Exception e) - { - PersistenceChannelDebugLog.WriteException(e, "Failed to delete tmp files."); - } - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/StorageTransmission.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/StorageTransmission.cs deleted file mode 100644 index 0070b3ede03b..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/PersistenceChannel/StorageTransmission.cs +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Globalization; -using System.IO; -using System.Threading.Tasks; -using Microsoft.ApplicationInsights.Channel; - -namespace Uno.UI.SourceGenerators.Telemetry.PersistenceChannel -{ - internal class StorageTransmission : Transmission, IDisposable - { - internal Action Disposing; - - protected StorageTransmission(string fullPath, Uri address, byte[] content, string contentType, - string contentEncoding) - : base(address, content, contentType, contentEncoding) - { - FullFilePath = fullPath; - FileName = Path.GetFileName(fullPath); - } - - internal string FileName { get; } - - internal string FullFilePath { get; } - - /// - /// Disposing the storage transmission. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Creates a new transmission from the specified . - /// - /// Return transmission loaded from file; return null if the file is corrupted. - internal static async Task CreateFromStreamAsync(Stream stream, string fileName) - { - StreamReader reader = new StreamReader(stream); - Uri address = await ReadAddressAsync(reader).ConfigureAwait(false); - string contentType = await ReadHeaderAsync(reader, "Content-Type").ConfigureAwait(false); - string contentEncoding = await ReadHeaderAsync(reader, "Content-Encoding").ConfigureAwait(false); - byte[] content = await ReadContentAsync(reader).ConfigureAwait(false); - return new StorageTransmission(fileName, address, content, contentType, contentEncoding); - } - - /// - /// Saves the transmission to the specified . - /// - internal static async Task SaveAsync(Transmission transmission, Stream stream) - { - StreamWriter writer = new StreamWriter(stream); - try - { - await writer.WriteLineAsync(transmission.EndpointAddress.ToString()).ConfigureAwait(false); - await writer.WriteLineAsync("Content-Type" + ":" + transmission.ContentType).ConfigureAwait(false); - await writer.WriteLineAsync("Content-Encoding" + ":" + transmission.ContentEncoding) - .ConfigureAwait(false); - await writer.WriteLineAsync(string.Empty).ConfigureAwait(false); - await writer.WriteAsync(Convert.ToBase64String(transmission.Content)).ConfigureAwait(false); - } - finally - { - writer.Flush(); - } - } - - private static async Task ReadHeaderAsync(TextReader reader, string headerName) - { - string line = await reader.ReadLineAsync().ConfigureAwait(false); - if (string.IsNullOrEmpty(line)) - { - throw new FormatException(string.Format(CultureInfo.InvariantCulture, "{0} header is expected.", - headerName)); - } - - string[] parts = line.Split(':'); - if (parts.Length != 2) - { - throw new FormatException(string.Format(CultureInfo.InvariantCulture, - "Unexpected header format. {0} header is expected. Actual header: {1}", headerName, line)); - } - - if (parts[0] != headerName) - { - throw new FormatException(string.Format(CultureInfo.InvariantCulture, - "{0} header is expected. Actual header: {1}", headerName, line)); - } - - return parts[1].Trim(); - } - - private static async Task ReadAddressAsync(TextReader reader) - { - string addressLine = await reader.ReadLineAsync().ConfigureAwait(false); - if (string.IsNullOrEmpty(addressLine)) - { - throw new FormatException("Transmission address is expected."); - } - - Uri address = new Uri(addressLine); - return address; - } - - private static async Task ReadContentAsync(TextReader reader) - { - string content = await reader.ReadToEndAsync().ConfigureAwait(false); - if (string.IsNullOrEmpty(content) || content == Environment.NewLine) - { - throw new FormatException("Content is expected."); - } - - return Convert.FromBase64String(content); - } - - private void Dispose(bool disposing) - { - if (disposing) - { - Action disposingDelegate = Disposing; - disposingDelegate?.Invoke(this); - } - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/TelemetryClient.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/TelemetryClient.cs deleted file mode 100644 index e38c207ae50b..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/TelemetryClient.cs +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.ApplicationInsights; -using Microsoft.ApplicationInsights.Extensibility; -using Microsoft.DotNet.PlatformAbstractions; - -namespace Uno.UI.SourceGenerators.Telemetry -{ - public class Telemetry - { - internal static string CurrentSessionId; - private TelemetryClient _client; - private Dictionary _commonProperties; - private Dictionary _commonMeasurements; - private TelemetryConfiguration _telemetryConfig; - private Task _trackEventTask; - private string _storageDirectoryPath; - private string _settingsStorageDirectoryPath; - private PersistenceChannel.PersistenceChannel _persistenceChannel; - private const string InstrumentationKey = "9a44058e-1913-4721-a979-9582ab8bedce"; - private const string TelemetryOptout = "UNO_PLATFORM_TELEMETRY_OPTOUT"; - - public bool Enabled { get; } - - public Telemetry() : this(null) { } - - public Telemetry(Func enabledProvider) : this(null, enabledProvider: enabledProvider) { } - - public Telemetry( - string sessionId, - bool blockThreadInitialization = false, - Func enabledProvider = null) - { - if (bool.TryParse(Environment.GetEnvironmentVariable(TelemetryOptout), out var telemetryOptOut)) - { - Enabled = !telemetryOptOut; - } - else - { - Enabled = !enabledProvider?.Invoke() ?? true; - } - - if (!Enabled) - { - return; - } - - // Store the session ID in a static field so that it can be reused - CurrentSessionId = sessionId ?? Guid.NewGuid().ToString(); - if (blockThreadInitialization) - { - InitializeTelemetry(); - } - else - { - //initialize in task to offload to parallel thread - _trackEventTask = Task.Run(() => InitializeTelemetry()); - } - } - - public void TrackEvent( - string eventName, - (string key, string value)[] properties, - (string key, double value)[] measurements) - => TrackEvent(eventName, properties?.ToDictionary(p => p.key, p => p.value), measurements?.ToDictionary(p => p.key, p => p.value)); - - public void TrackEvent(string eventName, IDictionary properties, - IDictionary measurements) - { - if (!Enabled) - { - return; - } - - //continue the task in different threads - _trackEventTask = _trackEventTask.ContinueWith( - x => TrackEventTask(eventName, properties, measurements) - ); - } - - public void Flush() - { - if (!Enabled || _trackEventTask == null) - { - return; - } - - // Skip the wait if the task has not yet been activated - if (_trackEventTask.Status != TaskStatus.WaitingForActivation) - { - _trackEventTask.Wait(TimeSpan.FromSeconds(1)); - } - } - - public void Dispose() - { - _persistenceChannel?.Dispose(); - _telemetryConfig?.Dispose(); - } - - public void ThreadBlockingTrackEvent(string eventName, IDictionary properties, IDictionary measurements) - { - if (!Enabled) - { - return; - } - TrackEventTask(eventName, properties, measurements); - } - - private void InitializeTelemetry() - { - try - { - _storageDirectoryPath = Path.Combine(Path.GetTempPath(), ".uno", "telemetry"); - - // Store the settings on in the user profile for linux - if (RuntimeEnvironment.OperatingSystemPlatform == Platform.Linux) - { - _settingsStorageDirectoryPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".uno", "telemetry"); - } - else - { - _settingsStorageDirectoryPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Uno Platform", "telemetry"); - } - - _persistenceChannel = new PersistenceChannel.PersistenceChannel( - storageDirectoryPath: _storageDirectoryPath); - - _persistenceChannel.SendingInterval = TimeSpan.FromMilliseconds(1); - - _commonProperties = new TelemetryCommonProperties(_settingsStorageDirectoryPath).GetTelemetryCommonProperties(); - _commonMeasurements = new Dictionary(); - - _telemetryConfig = new TelemetryConfiguration { InstrumentationKey = InstrumentationKey }; - _client = new TelemetryClient(_telemetryConfig); - _client.InstrumentationKey = InstrumentationKey; - _client.Context.User.Id = _commonProperties[TelemetryCommonProperties.MachineId]; - _client.Context.Session.Id = CurrentSessionId; - _client.Context.Device.OperatingSystem = RuntimeEnvironment.OperatingSystem; - } - catch (Exception e) - { - _client = null; - // we don't want to fail the tool if telemetry fails. - Debug.Fail(e.ToString()); - } - } - - private void TrackEventTask( - string eventName, - IDictionary properties, - IDictionary measurements) - { - if (_client == null) - { - return; - } - - try - { - var eventProperties = GetEventProperties(properties); - var eventMeasurements = GetEventMeasures(measurements); - - _client.TrackEvent(PrependProducerNamespace(eventName), eventProperties, eventMeasurements); - } - catch (Exception e) - { - Debug.Fail(e.ToString()); - } - } - - private static string PrependProducerNamespace(string eventName) - { - return "uno/generation/" + eventName; - } - - private Dictionary GetEventMeasures(IDictionary measurements) - { - var eventMeasurements = new Dictionary(_commonMeasurements); - if (measurements != null) - { - foreach (var measurement in measurements) - { - eventMeasurements[measurement.Key] = measurement.Value; - } - } - return eventMeasurements; - } - - private Dictionary GetEventProperties(IDictionary properties) - { - if (properties != null) - { - var eventProperties = new Dictionary(_commonProperties); - foreach (var property in properties) - { - eventProperties[property.Key] = property.Value; - } - return eventProperties; - } - else - { - return _commonProperties; - } - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/TelemetryCommonProperties.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/TelemetryCommonProperties.cs deleted file mode 100644 index 977160813fc2..000000000000 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Telemetry/TelemetryCommonProperties.cs +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// -// 2019/04/12 (Jerome Laban ): -// - Extracted from dotnet.exe -// - -using System; -using System.Collections.Generic; -using Microsoft.DotNet.PlatformAbstractions; -using System.IO; -using System.Security; -using Microsoft.Win32; -using System.Linq; -using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment; -using RuntimeInformation = System.Runtime.InteropServices.RuntimeInformation; -using System.Runtime.InteropServices; -using System.Diagnostics; -using System.Reflection; -using Uno.UI.SourceGenerators.Helpers; -using System.Security.Cryptography; -using System.Net.NetworkInformation; - -namespace Uno.UI.SourceGenerators.Telemetry -{ - internal class TelemetryCommonProperties - { - public TelemetryCommonProperties( - string storageDirectoryPath, - Func getCurrentDirectory = null) - { - _getCurrentDirectory = getCurrentDirectory ?? Directory.GetCurrentDirectory; - _storageDirectoryPath = storageDirectoryPath; - } - - private Func _getCurrentDirectory; - private string _storageDirectoryPath; - public const string OSVersion = "OS Version"; - public const string OSPlatform = "OS Platform"; - public const string OutputRedirected = "Output Redirected"; - public const string RuntimeId = "Runtime Id"; - public const string MachineId = "Machine ID"; - public const string ProductVersion = "Product Version"; - public const string TelemetryProfile = "Telemetry Profile"; - public const string CurrentPathHash = "Current Path Hash"; - public const string KernelVersion = "Kernel Version"; - - private const string TelemetryProfileEnvironmentVariable = "DOTNET_CLI_TELEMETRY_PROFILE"; - - public Dictionary GetTelemetryCommonProperties() => new Dictionary - { - {OSVersion, RuntimeEnvironment.OperatingSystemVersion}, - {OSPlatform, RuntimeEnvironment.OperatingSystemPlatform.ToString()}, - {OutputRedirected, Console.IsOutputRedirected.ToString()}, - {RuntimeId, RuntimeEnvironment.GetRuntimeIdentifier()}, - {ProductVersion, GetProductVersion()}, - {TelemetryProfile, Environment.GetEnvironmentVariable(TelemetryProfileEnvironmentVariable)}, - {MachineId, GetMachineId()}, - {CurrentPathHash, HashBuilder.Build(_getCurrentDirectory())}, - {KernelVersion, GetKernelVersion()}, - }; - - private string GetMachineId() - { - var machineHashPath = Path.Combine(_storageDirectoryPath, ".machinehash"); - - if (File.Exists(machineHashPath)) - { - if (File.ReadAllText(machineHashPath) is { Length: 32 /* hash */ or 36 /* guid */ } readHash) - { - return readHash; - } - } - - string hash = null; - try - { - var macAddr = - ( - from nic in NetworkInterface.GetAllNetworkInterfaces() - where nic.OperationalStatus == OperationalStatus.Up - select nic.GetPhysicalAddress().ToString() - ).FirstOrDefault(); - - hash = HashBuilder.Build(macAddr); - - if (!Directory.Exists(Path.GetDirectoryName(machineHashPath))) - { - Directory.CreateDirectory(Path.GetDirectoryName(machineHashPath)); - } - - File.WriteAllText(machineHashPath, hash); - } - catch (Exception e) - { - Debug.Fail($"Failed to get Mac address: {e}"); - - // if the hash was set, but the write failed, let's continue. - hash ??= Guid.NewGuid().ToString(); - } - - return hash; - } - - private string GetProductVersion() - { - if (this.GetType().Assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute)).FirstOrDefault() is AssemblyInformationalVersionAttribute attribute) - { - return attribute.InformationalVersion; - } - - return "Unknown"; - } - - - /// - /// Returns a string identifying the OS kernel. - /// For Unix this currently comes from "uname -srv". - /// For Windows this currently comes from RtlGetVersion(). - /// - /// Here are some example values: - /// - /// Alpine.36 Linux 4.9.60-linuxkit-aufs #1 SMP Mon Nov 6 16:00:12 UTC 2017 - /// Centos.73 Linux 3.10.0-514.26.2.el7.x86_64 #1 SMP Tue Jul 4 15:04:05 UTC 2017 - /// Debian.87 Linux 3.16.0-4-amd64 #1 SMP Debian 3.16.39-1+deb8u2 (2017-03-07) - /// Debian.90 Linux 4.9.0-2-amd64 #1 SMP Debian 4.9.18-1 (2017-03-30) - /// fedora.25 Linux 4.11.3-202.fc25.x86_64 #1 SMP Mon Jun 5 16:38:21 UTC 2017 - /// Fedora.26 Linux 4.14.15-200.fc26.x86_64 #1 SMP Wed Jan 24 04:26:15 UTC 2018 - /// Fedora.27 Linux 4.14.14-300.fc27.x86_64 #1 SMP Fri Jan 19 13:19:54 UTC 2018 - /// OpenSuse.423 Linux 4.4.104-39-default #1 SMP Thu Jan 4 08:11:03 UTC 2018 (7db1912) - /// RedHat.69 Linux 2.6.32-696.20.1.el6.x86_64 #1 SMP Fri Jan 12 15:07:59 EST 2018 - /// RedHat.72 Linux 3.10.0-514.21.1.el7.x86_64 #1 SMP Sat Apr 22 02:41:35 EDT 2017 - /// RedHat.73 Linux 3.10.0-514.21.1.el7.x86_64 #1 SMP Sat Apr 22 02:41:35 EDT 2017 - /// SLES.12 Linux 4.4.103-6.38-default #1 SMP Mon Dec 25 20:44:33 UTC 2017 (e4b9067) - /// suse.422 Linux 4.4.49-16-default #1 SMP Sun Feb 19 17:40:35 UTC 2017 (70e9954) - /// Ubuntu.1404 Linux 3.19.0-65-generic #73~14.04.1-Ubuntu SMP Wed Jun 29 21:05:22 UTC 2016 - /// Ubuntu.1604 Linux 4.13.0-1005-azure #7-Ubuntu SMP Mon Jan 8 21:37:36 UTC 2018 - /// Ubuntu.1604.WSL Linux 4.4.0-43-Microsoft #1-Microsoft Wed Dec 31 14:42:53 PST 2014 - /// Ubuntu.1610 Linux 4.8.0-45-generic #48-Ubuntu SMP Fri Mar 24 11:46:39 UTC 2017 - /// Ubuntu.1704 Linux 4.10.0-19-generic #21-Ubuntu SMP Thu Apr 6 17:04:57 UTC 2017 - /// Ubuntu.1710 Linux 4.13.0-25-generic #29-Ubuntu SMP Mon Jan 8 21:14:41 UTC 2018 - /// OSX1012 Darwin 16.7.0 Darwin Kernel Version 16.7.0: Thu Jan 11 22:59:40 PST 2018; root:xnu-3789.73.8~1/RELEASE_X86_64 - /// OSX1013 Darwin 17.4.0 Darwin Kernel Version 17.4.0: Sun Dec 17 09:19:54 PST 2017; root:xnu-4570.41.2~1/RELEASE_X86_64 - /// Windows.10 Microsoft Windows 10.0.14393 - /// Windows.10.Core Microsoft Windows 10.0.14393 - /// Windows.10.Nano Microsoft Windows 10.0.14393 - /// Windows.7 Microsoft Windows 6.1.7601 S - /// Windows.81 Microsoft Windows 6.3.9600 - /// - private static string GetKernelVersion() - { - return RuntimeInformation.OSDescription; - } - } -} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Uno.UI.SourceGenerators.csproj b/src/SourceGenerators/Uno.UI.SourceGenerators/Uno.UI.SourceGenerators.csproj index 483b92d29ba2..2675e89377fa 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Uno.UI.SourceGenerators.csproj +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/Uno.UI.SourceGenerators.csproj @@ -12,8 +12,7 @@ - - + diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.Telemetry.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.Telemetry.cs index f04a169f51b3..ae4596b71890 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.Telemetry.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.Telemetry.cs @@ -9,18 +9,21 @@ using Uno.Roslyn; using Microsoft.CodeAnalysis; using Uno.Extensions; -using Uno.UI.SourceGenerators.Telemetry; +using Uno.DevTools.Telemetry; namespace Uno.UI.SourceGenerators.XamlGenerator { internal partial class XamlCodeGeneration { - private Telemetry.Telemetry _telemetry; + private const string InstrumentationKey = "9a44058e-1913-4721-a979-9582ab8bedce"; + + private Telemetry _telemetry; public bool IsRunningCI => !Environment.GetEnvironmentVariable("TF_BUILD").IsNullOrEmpty() // https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?tabs=yaml&view=azure-devops#system-variables || !Environment.GetEnvironmentVariable("TRAVIS").IsNullOrEmpty() // https://docs.travis-ci.com/user/environment-variables/#default-environment-variables || !Environment.GetEnvironmentVariable("JENKINS_URL").IsNullOrEmpty() // https://wiki.jenkins.io/display/JENKINS/Building+a+software+project#Buildingasoftwareproject-belowJenkinsSetEnvironmentVariables + || !Environment.GetEnvironmentVariable("GITHUB_REPOSITORY").IsNullOrEmpty() // https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#default-environment-variables || !Environment.GetEnvironmentVariable("APPVEYOR").IsNullOrEmpty(); // https://www.appveyor.com/docs/environment-variables/ private void InitTelemetry(GeneratorExecutionContext context) @@ -32,7 +35,24 @@ private void InitTelemetry(GeneratorExecutionContext context) || telemetryOptOut.Equals("1", StringComparison.OrdinalIgnoreCase) || _isDesignTimeBuild; - _telemetry = new Telemetry.Telemetry(isTelemetryOptout); + string getCurrentDirectory() + { + var solutionDir = context.GetMSBuildPropertyValue("SolutionDir"); + if (!string.IsNullOrEmpty(solutionDir)) + { + return solutionDir; + } + + var projectDir = context.GetMSBuildPropertyValue("MSBuildProjectFullPath"); + if (!string.IsNullOrEmpty(projectDir)) + { + return projectDir; + } + + return Environment.CurrentDirectory; + } + + _telemetry = new Telemetry(InstrumentationKey, enabledProvider: isTelemetryOptout, currentDirectoryProvider: getCurrentDirectory); } private bool IsTelemetryEnabled => _telemetry?.Enabled ?? false; diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.cs index ad2efa1db815..9c46c9a78a15 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGeneration.cs @@ -12,7 +12,7 @@ using Uno.Roslyn; using Microsoft.CodeAnalysis; using Uno.Extensions; -using Uno.UI.SourceGenerators.Telemetry; +using Uno.DevTools.Telemetry; using Uno.UI.Xaml; using System.Drawing; using __uno::Uno.Xaml; diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGenerator.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGenerator.cs index 0db416f50d22..e0de4c87b13f 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGenerator.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlCodeGenerator.cs @@ -8,7 +8,7 @@ using Microsoft.CodeAnalysis; using Uno.Roslyn; using Uno.UI.SourceGenerators.Helpers; -using Uno.UI.SourceGenerators.Telemetry; +using Uno.DevTools.Telemetry; namespace Uno.UI.SourceGenerators.XamlGenerator {