Skip to content

Commit e2864ec

Browse files
committed
get good tool install telemetry
1 parent 85f672e commit e2864ec

File tree

3 files changed

+166
-130
lines changed

3 files changed

+166
-130
lines changed

src/Cli/dotnet/Program.cs

Lines changed: 161 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
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-
64
using System.CommandLine;
75
using System.Diagnostics;
86
using Microsoft.DotNet.Cli.CommandFactory;
@@ -32,39 +30,55 @@ public class Program
3230
public static ITelemetry TelemetryClient;
3331
// Create a new OpenTelemetry tracer provider and add the Azure Monitor trace exporter and the OTLP trace exporter.
3432
// It is important to keep the TracerProvider instance active throughout the process lifetime.
35-
private static TracerProvider tracerProvider = Sdk.CreateTracerProviderBuilder()
36-
.ConfigureResource(r =>
37-
{
38-
r.AddService("dotnet-cli", serviceVersion: Product.Version);
39-
})
40-
.AddSource(Activities.s_source.Name)
41-
.AddHttpClientInstrumentation()
42-
.AddOtlpExporter()
43-
.Build();
33+
private static TracerProvider tracerProvider;
4434

4535
// Create a new OpenTelemetry meter provider and add the Azure Monitor metric exporter and the OTLP metric exporter.
4636
// It is important to keep the MetricsProvider instance active throughout the process lifetime.
47-
private static MeterProvider metricsProvider = Sdk.CreateMeterProviderBuilder()
48-
.ConfigureResource(r =>
49-
{
50-
r.AddService("dotnet-cli", serviceVersion: Product.Version);
51-
})
52-
.AddMeter(Activities.s_source.Name)
53-
.AddHttpClientInstrumentation()
54-
.AddRuntimeInstrumentation()
55-
.AddOtlpExporter()
56-
.Build();
37+
private static MeterProvider metricsProvider;
5738

58-
private static ActivityContext s_parentActivityContext = default;
59-
private static ActivityKind s_activityKind = ActivityKind.Internal;
39+
static Activity? s_mainActivity;
40+
private static DateTime s_mainTimeStamp;
41+
private static PosixSignalRegistration s_sigIntRegistration;
42+
private static PosixSignalRegistration s_sigQuitRegistration;
43+
private static PosixSignalRegistration s_sigTermRegistration;
6044

45+
static Program()
46+
{
47+
s_mainTimeStamp = DateTime.Now;
48+
s_sigIntRegistration = PosixSignalRegistration.Create(PosixSignal.SIGINT, Shutdown);
49+
s_sigQuitRegistration = PosixSignalRegistration.Create(PosixSignal.SIGQUIT, Shutdown);
50+
s_sigTermRegistration = PosixSignalRegistration.Create(PosixSignal.SIGTERM, Shutdown);
51+
metricsProvider = Sdk.CreateMeterProviderBuilder()
52+
.ConfigureResource(r =>
53+
{
54+
r.AddService("dotnet-cli", serviceVersion: Product.Version);
55+
})
56+
.AddMeter(Activities.s_source.Name)
57+
.AddHttpClientInstrumentation()
58+
.AddRuntimeInstrumentation()
59+
.AddOtlpExporter()
60+
.Build();
61+
tracerProvider = Sdk.CreateTracerProviderBuilder()
62+
.ConfigureResource(r =>
63+
{
64+
r.AddService("dotnet-cli", serviceVersion: Product.Version);
65+
})
66+
.AddSource(Activities.s_source.Name)
67+
.AddHttpClientInstrumentation()
68+
.AddOtlpExporter()
69+
.SetSampler(new AlwaysOnSampler())
70+
.Build();
71+
(var s_parentActivityContext, var s_activityKind) = DeriveParentActivityContextFromEnv();
72+
s_mainActivity = Activities.s_source.CreateActivity("main", s_activityKind, s_parentActivityContext);
73+
s_mainActivity?.Start();
74+
s_mainActivity?.SetStartTime(Process.GetCurrentProcess().StartTime);
75+
TrackHostStartup(s_mainTimeStamp);
76+
SetupMSBuildEnvironmentInvariants();
77+
TelemetryClient = InitializeTelemetry();
78+
}
6179

6280
public static int Main(string[] args)
6381
{
64-
// capture the time to we can compute muxer/host startup overhead
65-
DateTime mainTimeStamp = DateTime.Now;
66-
(s_parentActivityContext, s_activityKind) = DeriveParentActivityContextFromEnv();
67-
using var _mainActivity = Activities.s_source.StartActivity("main", s_activityKind, s_parentActivityContext);
6882
using AutomaticEncodingRestorer _encodingRestorer = new();
6983

7084
// Setting output encoding is not available on those platforms
@@ -75,51 +89,50 @@ public static int Main(string[] args)
7589

7690
DebugHelper.HandleDebugSwitch(ref args);
7791

78-
TrackHostStartup(mainTimeStamp);
79-
80-
SetupMSBuildEnvironmentInvariants();
92+
InitializeProcess();
8193

8294
try
8395
{
84-
InitializeProcess();
96+
return ProcessArgs(args);
97+
}
98+
catch (Exception e) when (e.ShouldBeDisplayedAsError())
99+
{
100+
Reporter.Error.WriteLine(CommandLoggingContext.IsVerbose
101+
? e.ToString().Red().Bold()
102+
: e.Message.Red().Bold());
85103

86-
try
104+
var commandParsingException = e as CommandParsingException;
105+
if (commandParsingException != null && commandParsingException.ParseResult != null)
87106
{
88-
return ProcessArgs(args);
107+
commandParsingException.ParseResult.ShowHelp();
89108
}
90-
catch (Exception e) when (e.ShouldBeDisplayedAsError())
91-
{
92-
Reporter.Error.WriteLine(CommandLoggingContext.IsVerbose
93-
? e.ToString().Red().Bold()
94-
: e.Message.Red().Bold());
95-
96-
var commandParsingException = e as CommandParsingException;
97-
if (commandParsingException != null && commandParsingException.ParseResult != null)
98-
{
99-
commandParsingException.ParseResult.ShowHelp();
100-
}
101109

102-
return 1;
103-
}
104-
catch (Exception e) when (!e.ShouldBeDisplayedAsError())
105-
{
106-
// If telemetry object has not been initialized yet. It cannot be collected
107-
TelemetryEventEntry.SendFiltered(e);
108-
Reporter.Error.WriteLine(e.ToString().Red().Bold());
110+
return 1;
111+
}
112+
catch (Exception e) when (!e.ShouldBeDisplayedAsError())
113+
{
114+
// If telemetry object has not been initialized yet. It cannot be collected
115+
TelemetryEventEntry.SendFiltered(e);
116+
Reporter.Error.WriteLine(e.ToString().Red().Bold());
109117

110-
return 1;
111-
}
112-
finally
113-
{
114-
PerformanceLogEventSource.Log.CLIStop();
115-
}
118+
return 1;
116119
}
117120
finally
118121
{
119-
tracerProvider?.ForceFlush();
120-
metricsProvider?.ForceFlush();
121-
Activities.s_source.Dispose();
122+
Shutdown(default!);
122123
}
124+
125+
}
126+
127+
public static void Shutdown(PosixSignalContext context)
128+
{
129+
s_sigIntRegistration.Dispose();
130+
s_sigQuitRegistration.Dispose();
131+
s_sigTermRegistration.Dispose();
132+
s_mainActivity?.Stop();
133+
tracerProvider?.ForceFlush();
134+
metricsProvider?.ForceFlush();
135+
Activities.s_source.Dispose();
123136
}
124137

125138
/// <summary>
@@ -131,20 +144,23 @@ private static (ActivityContext parentActivityContext, ActivityKind kind) Derive
131144
{
132145
var traceParent = Env.GetEnvironmentVariable(Activities.DOTNET_CLI_TRACEPARENT);
133146
var traceState = Env.GetEnvironmentVariable(Activities.DOTNET_CLI_TRACESTATE);
134-
static IEnumerable<string> GetValueFromCarrier(Dictionary<string, IEnumerable<string>> carrier, string key)
147+
static IEnumerable<string>? GetValueFromCarrier(Dictionary<string, IEnumerable<string>?> carrier, string key)
135148
{
136-
return carrier.TryGetValue(key, out var value) ? value : Enumerable.Empty<string>();
149+
return carrier.TryGetValue(key, out var value) ? value : null;
137150
}
138151

139152
if (string.IsNullOrEmpty(traceParent))
140153
{
141154
return (default, ActivityKind.Internal);
142155
}
143-
var carriermap = new Dictionary<string, IEnumerable<string>>
156+
var carriermap = new Dictionary<string, IEnumerable<string>?>
144157
{
145158
{ "traceparent", [traceParent] },
146-
{ "tracestate", [traceState] }
147159
};
160+
if (!string.IsNullOrEmpty(traceState))
161+
{
162+
carriermap.Add("tracestate", [traceState]);
163+
}
148164

149165
// Use the OpenTelemetry Propagator to extract the parent activity context and kind. For some reason this isn't set by the OTel SDK like docs say it should be.
150166
Sdk.SetDefaultTextMapPropagator(new CompositeTextMapPropagator([
@@ -159,7 +175,7 @@ static IEnumerable<string> GetValueFromCarrier(Dictionary<string, IEnumerable<st
159175

160176
private static void TrackHostStartup(DateTime mainTimeStamp)
161177
{
162-
var hostStartupActivity = Activities.s_source.CreateActivity("host-startup", s_activityKind, s_parentActivityContext);
178+
var hostStartupActivity = Activities.s_source.StartActivity("host-startup");
163179
hostStartupActivity?.SetStartTime(Process.GetCurrentProcess().StartTime);
164180
hostStartupActivity?.SetEndTime(mainTimeStamp);
165181
hostStartupActivity?.SetStatus(ActivityStatusCode.Ok);
@@ -191,38 +207,113 @@ private static string GetCommandName(ParseResult r)
191207
parentNames.Reverse();
192208
return string.Join(' ', parentNames);
193209
}
194-
private static void SetDisplayName(Activity activity, ParseResult parseResult)
210+
private static void SetDisplayName(Activity? activity, ParseResult parseResult)
195211
{
196212
if (activity == null)
197213
{
198214
return;
199215
}
200216
var name = GetCommandName(parseResult);
217+
201218
// Set the display name to the full command name
202219
activity.DisplayName = name;
203220

204221
// Set the command name as an attribute for better filtering in telemetry
205222
activity.SetTag("command.name", name);
206223
}
207224

208-
internal static int ProcessArgs(string[] args, ITelemetry telemetryClient = null)
225+
internal static int ProcessArgs(string[] args)
226+
{
227+
ParseResult parseResult = ParseArgs(args);
228+
SetupDotnetFirstRun(parseResult);
229+
230+
if (parseResult.CanBeInvoked())
231+
{
232+
return Invoke(parseResult);
233+
}
234+
else
235+
{
236+
try
237+
{
238+
return LookupAndExecuteCommand(args, parseResult);
239+
}
240+
catch (CommandUnknownException e)
241+
{
242+
Reporter.Error.WriteLine(e.Message.Red());
243+
Reporter.Output.WriteLine(e.InstructionMessage);
244+
return 1;
245+
}
246+
}
247+
}
248+
249+
private static int LookupAndExecuteCommand(string[] args, ParseResult parseResult)
250+
{
251+
var _lookupExternalCommandActivity = Activities.s_source.StartActivity("lookup-external-command");
252+
var resolvedCommand = CommandFactoryUsingResolver.Create(
253+
"dotnet-" + parseResult.GetValue(Parser.DotnetSubCommand),
254+
args.GetSubArguments(),
255+
FrameworkConstants.CommonFrameworks.NetStandardApp15);
256+
_lookupExternalCommandActivity?.Dispose();
257+
258+
var _executionActivity = Activities.s_source.StartActivity("execute-extensible-command");
259+
var result = resolvedCommand.Execute();
260+
_executionActivity?.Dispose();
261+
262+
return result.ExitCode;
263+
}
264+
265+
private static int Invoke(ParseResult parseResult)
266+
{
267+
using var _invocationActivity = Activities.s_source.StartActivity("invocation");
268+
try
269+
{
270+
var exitCode = parseResult.Invoke();
271+
return AdjustExitCode(parseResult, exitCode);
272+
}
273+
catch (Exception exception)
274+
{
275+
return Parser.ExceptionHandler(exception, parseResult);
276+
}
277+
}
278+
279+
private static ITelemetry InitializeTelemetry()
280+
{
281+
var telemetryClient = new Telemetry.Telemetry();
282+
TelemetryEventEntry.Subscribe(telemetryClient.TrackEvent);
283+
TelemetryEventEntry.TelemetryFilter = new TelemetryFilter(Sha256Hasher.HashWithNormalizedCasing);
284+
285+
if (CommandLoggingContext.IsVerbose)
286+
{
287+
Console.WriteLine($"Telemetry is: {(telemetryClient.Enabled ? "Enabled" : "Disabled")}");
288+
}
289+
290+
return telemetryClient;
291+
}
292+
293+
private static ParseResult ParseArgs(string[] args)
209294
{
210295
ParseResult parseResult;
211-
using (var _parseActivity = Activities.s_source.StartActivity("parse", s_activityKind, s_parentActivityContext))
296+
using (var _parseActivity = Activities.s_source.StartActivity("parse"))
212297
{
213298
// If we get C# file path as the first argument, parse as `dotnet run file.cs`.
214299
parseResult = args is [{ } filePath, ..] && VirtualProjectBuildingCommand.IsValidEntryPointPath(filePath)
215300
? Parser.Instance.Parse(["run", .. args])
216301
: Parser.Instance.Parse(args);
217302

218-
SetDisplayName(_parseActivity, parseResult);
219303
// Avoid create temp directory with root permission and later prevent access in non sudo
220304
// This method need to be run very early before temp folder get created
221305
// https://github.com/dotnet/sdk/issues/20195
222306
SudoEnvironmentDirectoryOverride.OverrideEnvironmentVariableToTmp(parseResult);
223307
}
224308

225-
using (var _firstTimeUseActivity = Activities.s_source.StartActivity("first-time-use", s_activityKind, s_parentActivityContext))
309+
SetDisplayName(s_mainActivity, parseResult);
310+
311+
return parseResult;
312+
}
313+
314+
private static void SetupDotnetFirstRun(ParseResult parseResult)
315+
{
316+
using (var _firstTimeUseActivity = Activities.s_source.StartActivity("first-time-use"))
226317
{
227318
IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = new FirstTimeUseNoticeSentinel();
228319

@@ -276,57 +367,6 @@ internal static int ProcessArgs(string[] args, ITelemetry telemetryClient = null
276367
environmentProvider,
277368
skipFirstTimeUseCheck: getStarOptionPassed);
278369
}
279-
280-
TelemetryClient = new Telemetry.Telemetry();
281-
TelemetryEventEntry.Subscribe(TelemetryClient.TrackEvent);
282-
TelemetryEventEntry.TelemetryFilter = new TelemetryFilter(Sha256Hasher.HashWithNormalizedCasing);
283-
284-
if (CommandLoggingContext.IsVerbose)
285-
{
286-
Console.WriteLine($"Telemetry is: {(TelemetryClient.Enabled ? "Enabled" : "Disabled")}");
287-
}
288-
289-
int exitCode;
290-
if (parseResult.CanBeInvoked())
291-
{
292-
using var _invocationActivity = Activities.s_source.StartActivity("invocation", s_activityKind, s_parentActivityContext);
293-
try
294-
{
295-
exitCode = parseResult.Invoke();
296-
exitCode = AdjustExitCode(parseResult, exitCode);
297-
}
298-
catch (Exception exception)
299-
{
300-
exitCode = Parser.ExceptionHandler(exception, parseResult);
301-
}
302-
}
303-
else
304-
{
305-
try
306-
{
307-
var _lookupExternalCommandActivity = Activities.s_source.StartActivity("lookup-external-command", s_activityKind, s_parentActivityContext);
308-
var resolvedCommand = CommandFactoryUsingResolver.Create(
309-
"dotnet-" + parseResult.GetValue(Parser.DotnetSubCommand),
310-
args.GetSubArguments(),
311-
FrameworkConstants.CommonFrameworks.NetStandardApp15);
312-
_lookupExternalCommandActivity?.Dispose();
313-
314-
var _executionActivity = Activities.s_source.StartActivity("execute-extensible-command", s_activityKind, s_parentActivityContext);
315-
var result = resolvedCommand.Execute();
316-
_executionActivity?.Dispose();
317-
318-
exitCode = result.ExitCode;
319-
}
320-
catch (CommandUnknownException e)
321-
{
322-
Reporter.Error.WriteLine(e.Message.Red());
323-
Reporter.Output.WriteLine(e.InstructionMessage);
324-
exitCode = 1;
325-
}
326-
}
327-
328-
TelemetryClient.Dispose();
329-
return exitCode;
330370
}
331371

332372
private static int AdjustExitCode(ParseResult parseResult, int exitCode)

0 commit comments

Comments
 (0)