Skip to content

Commit f0d6f8a

Browse files
committed
Minimal work to make System.Diagnostics.Activity usable by the dotnet CLI application and codebase
1 parent 460bff1 commit f0d6f8a

File tree

9 files changed

+114
-38
lines changed

9 files changed

+114
-38
lines changed

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
<PackageVersion Include="System.Composition.Runtime" Version="$(SystemCompositionRuntimePackageVersion)" />
109109
<PackageVersion Include="System.Composition.TypedParts" Version="$(SystemCompositionTypedPartsPackageVersion)" />
110110
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="$(SystemConfigurationConfigurationManagerPackageVersion)" />
111+
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="$(SystemDiagnosticsDiagnosticSourcePackageVersion)" />
111112
<PackageVersion Include="System.Formats.Asn1" Version="$(SystemFormatsAsn1Version)" />
112113
<PackageVersion Include="System.IO.Hashing" Version="$(SystemIOHashingPackageVersion)" />
113114
<!-- System.Reflection.Metadata and System.Collections.Immutable cannot be pinned here because of hard dependencies within Roslyn on specific versions that have to work both here and in VS -->

eng/Versions.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
<SystemCompositionHostingPackageVersion>10.0.0-preview.7.25359.101</SystemCompositionHostingPackageVersion>
120120
<SystemCompositionRuntimePackageVersion>10.0.0-preview.7.25359.101</SystemCompositionRuntimePackageVersion>
121121
<SystemCompositionTypedPartsPackageVersion>10.0.0-preview.7.25359.101</SystemCompositionTypedPartsPackageVersion>
122+
<SystemDiagnosticsDiagnosticSourcePackageVersion>10.0.0-preview.7.25359.101</SystemDiagnosticsDiagnosticSourcePackageVersion>
122123
<SystemConfigurationConfigurationManagerPackageVersion>10.0.0-preview.7.25359.101</SystemConfigurationConfigurationManagerPackageVersion>
123124
<SystemReflectionMetadataLoadContextVersion>10.0.0-preview.7.25359.101</SystemReflectionMetadataLoadContextVersion>
124125
<SystemResourcesExtensionsPackageVersion>10.0.0-preview.7.25359.101</SystemResourcesExtensionsPackageVersion>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
6+
namespace Microsoft.DotNet.Cli.Utils;
7+
8+
/// <summary>
9+
/// Contains helpers for working with <see cref="System.Diagnostics.Activity">Activities</see> in the .NET CLI.
10+
/// </summary>
11+
public static class Activities
12+
{
13+
14+
/// <summary>
15+
/// The main entrypoint for creating <see cref="Activity">Activities</see> in the .NET CLI.
16+
/// All activities created in the CLI should use this <see cref="ActivitySource"/>, to allow
17+
/// consumers to easily filter and trace CLI activities.
18+
/// </summary>
19+
public static ActivitySource Source { get; } = new("dotnet-cli", Product.Version);
20+
}

src/Cli/Microsoft.DotNet.Cli.Utils/ForwardingAppImplementation.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ internal class ForwardingAppImplementation
1919
private readonly string? _depsFile;
2020
private readonly string? _runtimeConfig;
2121
private readonly string? _additionalProbingPath;
22-
private Dictionary<string, string> _environmentVariables;
22+
private Dictionary<string, string?> _environmentVariables;
2323

2424
private readonly string[] _allArgs;
2525

@@ -29,7 +29,7 @@ public ForwardingAppImplementation(
2929
string? depsFile = null,
3030
string? runtimeConfig = null,
3131
string? additionalProbingPath = null,
32-
Dictionary<string, string>? environmentVariables = null)
32+
Dictionary<string, string?>? environmentVariables = null)
3333
{
3434
_forwardApplicationPath = forwardApplicationPath;
3535
_argsToForward = argsToForward;
@@ -86,7 +86,7 @@ public ProcessStartInfo GetProcessStartInfo()
8686
return processInfo;
8787
}
8888

89-
public ForwardingAppImplementation WithEnvironmentVariable(string name, string value)
89+
public ForwardingAppImplementation WithEnvironmentVariable(string name, string? value)
9090
{
9191
_environmentVariables.Add(name, value);
9292

src/Cli/Microsoft.DotNet.Cli.Utils/MSBuildForwardingAppWithoutLogging.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public static string MSBuildVersion
3939
// True if, given current state of the class, MSBuild would be executed in its own process.
4040
public bool ExecuteMSBuildOutOfProc => _forwardingApp != null;
4141

42-
private readonly Dictionary<string, string> _msbuildRequiredEnvironmentVariables = GetMSBuildRequiredEnvironmentVariables();
42+
private readonly Dictionary<string, string?> _msbuildRequiredEnvironmentVariables = GetMSBuildRequiredEnvironmentVariables();
4343

4444
private readonly List<string> _msbuildRequiredParameters = [ "-maxcpucount", "-verbosity:m" ];
4545

@@ -111,7 +111,7 @@ private static string EmitProperty(KeyValuePair<string, string> property, string
111111
: $"--{label}:{property.Key}={property.Value}";
112112
}
113113

114-
public void EnvironmentVariable(string name, string value)
114+
public void EnvironmentVariable(string name, string? value)
115115
{
116116
if (_forwardingApp != null)
117117
{
@@ -152,7 +152,7 @@ public int ExecuteInProc(string[] arguments)
152152
Dictionary<string, string?> savedEnvironmentVariables = [];
153153
try
154154
{
155-
foreach (KeyValuePair<string, string> kvp in _msbuildRequiredEnvironmentVariables)
155+
foreach (KeyValuePair<string, string?> kvp in _msbuildRequiredEnvironmentVariables)
156156
{
157157
savedEnvironmentVariables[kvp.Key] = Environment.GetEnvironmentVariable(kvp.Key);
158158
Environment.SetEnvironmentVariable(kvp.Key, kvp.Value);
@@ -217,7 +217,7 @@ private static string GetDotnetPath()
217217
return new Muxer().MuxerPath;
218218
}
219219

220-
internal static Dictionary<string, string> GetMSBuildRequiredEnvironmentVariables()
220+
internal static Dictionary<string, string?> GetMSBuildRequiredEnvironmentVariables()
221221
{
222222
return new()
223223
{

src/Cli/Microsoft.DotNet.Cli.Utils/Microsoft.DotNet.Cli.Utils.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
<PackageReference Include="Microsoft.Build" ExcludeAssets="runtime" PrivateAssets="all" />
5555
<PackageReference Include="Microsoft.Build.Utilities.Core" ExcludeAssets="runtime" PrivateAssets="all" />
5656
<PackageReference Include="System.CommandLine" />
57+
<PackageReference Include="System.Diagnostics.DiagnosticSource" />
5758
<PackageReference Include="System.IO.Hashing" />
5859
</ItemGroup>
5960

src/Cli/dotnet/Commands/MSBuild/MSBuildForwardingApp.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public MSBuildForwardingApp(MSBuildArgs msBuildArgs, string? msbuildPath = null,
5959

6060
public IEnumerable<string> MSBuildArguments { get { return _forwardingAppWithoutLogging.GetAllArguments(); } }
6161

62-
public void EnvironmentVariable(string name, string value)
62+
public void EnvironmentVariable(string name, string? value)
6363
{
6464
_forwardingAppWithoutLogging.EnvironmentVariable(name, value);
6565
}

src/Cli/dotnet/Telemetry/Telemetry.cs

Lines changed: 80 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
#nullable disable
5-
4+
using System.Collections.Frozen;
65
using System.Diagnostics;
76
using Microsoft.ApplicationInsights;
87
using Microsoft.ApplicationInsights.Extensibility;
@@ -14,27 +13,27 @@ namespace Microsoft.DotNet.Cli.Telemetry;
1413

1514
public class Telemetry : ITelemetry
1615
{
17-
internal static string CurrentSessionId = null;
16+
internal static string? CurrentSessionId = null;
1817
internal static bool DisabledForTests = false;
1918
private readonly int _senderCount;
20-
private TelemetryClient _client = null;
21-
private Dictionary<string, string> _commonProperties = null;
22-
private Dictionary<string, double> _commonMeasurements = null;
23-
private Task _trackEventTask = null;
19+
private TelemetryClient? _client = null;
20+
private FrozenDictionary<string, string>? _commonProperties = null;
21+
private FrozenDictionary<string, double>? _commonMeasurements = null;
22+
private Task? _trackEventTask = null;
2423

2524
private const string ConnectionString = "InstrumentationKey=74cc1c9e-3e6e-4d05-b3fc-dde9101d0254";
2625

2726
public bool Enabled { get; }
2827

2928
public Telemetry() : this(null) { }
3029

31-
public Telemetry(IFirstTimeUseNoticeSentinel sentinel) : this(sentinel, null) { }
30+
public Telemetry(IFirstTimeUseNoticeSentinel? sentinel) : this(sentinel, null) { }
3231

3332
public Telemetry(
34-
IFirstTimeUseNoticeSentinel sentinel,
35-
string sessionId,
33+
IFirstTimeUseNoticeSentinel? sentinel,
34+
string? sessionId,
3635
bool blockThreadInitialization = false,
37-
IEnvironmentProvider environmentProvider = null,
36+
IEnvironmentProvider? environmentProvider = null,
3837
int senderCount = 3)
3938
{
4039

@@ -78,7 +77,7 @@ internal static void EnableForTests()
7877
DisabledForTests = false;
7978
}
8079

81-
private static bool PermissionExists(IFirstTimeUseNoticeSentinel sentinel)
80+
private static bool PermissionExists(IFirstTimeUseNoticeSentinel? sentinel)
8281
{
8382
if (sentinel == null)
8483
{
@@ -97,9 +96,17 @@ public void TrackEvent(string eventName, IDictionary<string, string> properties,
9796
}
9897

9998
//continue the task in different threads
100-
_trackEventTask = _trackEventTask.ContinueWith(
101-
x => TrackEventTask(eventName, properties, measurements)
102-
);
99+
if (_trackEventTask == null)
100+
{
101+
_trackEventTask = Task.Run(() => TrackEventTask(eventName, properties, measurements));
102+
return;
103+
}
104+
else
105+
{
106+
_trackEventTask = _trackEventTask.ContinueWith(
107+
x => TrackEventTask(eventName, properties, measurements)
108+
);
109+
}
103110
}
104111

105112
public void Flush()
@@ -148,7 +155,7 @@ private void InitializeTelemetry()
148155
_client.Context.Device.OperatingSystem = CLIRuntimeEnvironment.OperatingSystem;
149156

150157
_commonProperties = new TelemetryCommonProperties().GetTelemetryCommonProperties();
151-
_commonMeasurements = [];
158+
_commonMeasurements = FrozenDictionary<string, double>.Empty;
152159
}
153160
catch (Exception e)
154161
{
@@ -170,39 +177,84 @@ private void TrackEventTask(
170177

171178
try
172179
{
173-
Dictionary<string, string> eventProperties = GetEventProperties(properties);
174-
Dictionary<string, double> eventMeasurements = GetEventMeasures(measurements);
180+
var eventProperties = GetEventProperties(properties);
181+
var eventMeasurements = GetEventMeasures(measurements);
175182

183+
eventProperties ??= new Dictionary<string, string>();
176184
eventProperties.Add("event id", Guid.NewGuid().ToString());
177185

178186
_client.TrackEvent(PrependProducerNamespace(eventName), eventProperties, eventMeasurements);
187+
Activity.Current?.AddEvent(CreateActivityEvent(eventName, eventProperties, eventMeasurements));
179188
}
180189
catch (Exception e)
181190
{
182191
Debug.Fail(e.ToString());
183192
}
184193
}
185194

186-
private static string PrependProducerNamespace(string eventName)
195+
private static ActivityEvent CreateActivityEvent(
196+
string eventName,
197+
IDictionary<string, string>? properties,
198+
IDictionary<string, double>? measurements)
187199
{
188-
return "dotnet/cli/" + eventName;
200+
var tags = MakeTags(properties, measurements);
201+
return new ActivityEvent(
202+
PrependProducerNamespace(eventName),
203+
tags: tags);
189204
}
190205

191-
private Dictionary<string, double> GetEventMeasures(IDictionary<string, double> measurements)
206+
private static ActivityTagsCollection? MakeTags(
207+
IDictionary<string, string>? properties,
208+
IDictionary<string, double>? measurements)
192209
{
193-
Dictionary<string, double> eventMeasurements = new(_commonMeasurements);
194-
if (measurements != null)
210+
if (properties == null && measurements == null)
195211
{
196-
foreach (KeyValuePair<string, double> measurement in measurements)
197-
{
198-
eventMeasurements[measurement.Key] = measurement.Value;
199-
}
212+
return null;
213+
}
214+
else if (properties != null && measurements == null)
215+
{
216+
return [.. properties.Select(p => new KeyValuePair<string, object?>(p.Key, p.Value))];
217+
}
218+
else if (properties == null && measurements != null)
219+
{
220+
return [.. measurements.Select(m => new KeyValuePair<string, object?>(m.Key, m.Value.ToString()))];
221+
}
222+
else return [ .. properties!.Select(p => new KeyValuePair<string, object?>(p.Key, p.Value)),
223+
.. measurements!.Select(m => new KeyValuePair<string, object?>(m.Key, m.Value.ToString())) ];
224+
}
225+
226+
private static string PrependProducerNamespace(string eventName) => $"dotnet/cli/{eventName}";
227+
228+
private IDictionary<string, double>? GetEventMeasures(IDictionary<string, double>? measurements)
229+
{
230+
if (measurements is null)
231+
{
232+
return _commonMeasurements;
233+
}
234+
if (_commonMeasurements == null)
235+
{
236+
return measurements;
237+
}
238+
239+
IDictionary<string, double> eventMeasurements = new Dictionary<string, double>(_commonMeasurements);
240+
foreach (KeyValuePair<string, double> measurement in measurements)
241+
{
242+
eventMeasurements[measurement.Key] = measurement.Value;
200243
}
201244
return eventMeasurements;
202245
}
203246

204-
private Dictionary<string, string> GetEventProperties(IDictionary<string, string> properties)
247+
private IDictionary<string, string>? GetEventProperties(IDictionary<string, string>? properties)
205248
{
249+
if (properties is null)
250+
{
251+
return _commonProperties;
252+
}
253+
if (_commonProperties == null)
254+
{
255+
return properties;
256+
}
257+
206258
var eventProperties = new Dictionary<string, string>(_commonProperties);
207259
if (properties != null)
208260
{

src/Cli/dotnet/Telemetry/TelemetryCommonProperties.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#nullable disable
55

6+
using System.Collections.Frozen;
67
using Microsoft.DotNet.Cli.Utils;
78
using Microsoft.DotNet.Configurer;
89
using RuntimeEnvironment = Microsoft.DotNet.Cli.Utils.RuntimeEnvironment;
@@ -52,7 +53,7 @@ internal class TelemetryCommonProperties(
5253
private const string MachineIdCacheKey = "MachineId";
5354
private const string IsDockerContainerCacheKey = "IsDockerContainer";
5455

55-
public Dictionary<string, string> GetTelemetryCommonProperties()
56+
public FrozenDictionary<string, string> GetTelemetryCommonProperties()
5657
{
5758
return new Dictionary<string, string>
5859
{
@@ -82,7 +83,7 @@ public Dictionary<string, string> GetTelemetryCommonProperties()
8283
{ProductType, ExternalTelemetryProperties.GetProductType()},
8384
{LibcRelease, ExternalTelemetryProperties.GetLibcRelease()},
8485
{LibcVersion, ExternalTelemetryProperties.GetLibcVersion()}
85-
};
86+
}.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
8687
}
8788

8889
private string GetMachineId()

0 commit comments

Comments
 (0)