Skip to content

Commit 61b2569

Browse files
committed
Address Feedback
1 parent 9f1ef6d commit 61b2569

File tree

7 files changed

+88
-79
lines changed

7 files changed

+88
-79
lines changed

src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ private static async Task<int> Collect(CancellationToken ct, CommandLineConfigur
110110

111111
if (profile.Length == 0 && providers.Length == 0 && clrevents.Length == 0)
112112
{
113-
ConsoleWriteLine("No profile or providers specified, defaulting to trace profile 'dotnet-common'");
114-
profile = new[] { "dotnet-common" };
113+
ConsoleWriteLine("No profile or providers specified, defaulting to trace profiles 'dotnet-common' + 'dotnet-sampled-thread-time'.");
114+
profile = new[] { "dotnet-common", "dotnet-sampled-thread-time" };
115115
}
116116

117117
long rundownKeyword = 0;
@@ -121,13 +121,18 @@ private static async Task<int> Collect(CancellationToken ct, CommandLineConfigur
121121
{
122122
foreach (string prof in profile)
123123
{
124-
Profile selectedProfile = ListProfilesCommandHandler.DotNETRuntimeProfiles
124+
Profile selectedProfile = ListProfilesCommandHandler.TraceProfiles
125125
.FirstOrDefault(p => p.Name.Equals(prof, StringComparison.OrdinalIgnoreCase));
126126
if (selectedProfile == null)
127127
{
128128
Console.Error.WriteLine($"Invalid profile name: {prof}");
129129
return (int)ReturnCode.ArgumentError;
130130
}
131+
if (!string.IsNullOrEmpty(selectedProfile.VerbExclusivity) && !string.Equals(selectedProfile.VerbExclusivity, "collect", StringComparison.OrdinalIgnoreCase))
132+
{
133+
Console.Error.WriteLine($"The specified profile '{selectedProfile.Name}' does not apply to `dotnet-trace collect`.");
134+
return (int)ReturnCode.ArgumentError;
135+
}
131136

132137
rundownKeyword |= selectedProfile.RundownKeyword;
133138
if (selectedProfile.RetryStrategy > retryStrategy)
@@ -156,7 +161,7 @@ private static async Task<int> Collect(CancellationToken ct, CommandLineConfigur
156161
}
157162
}
158163

159-
List<EventPipeProvider> providerCollection = ProviderUtils.ToProviders(providers, clrevents, clreventlevel, profile, !IsQuiet);
164+
List<EventPipeProvider> providerCollection = ProviderUtils.ComputeProviderConfig(providers, clrevents, clreventlevel, profile, !IsQuiet, "collect");
160165
if (providerCollection.Count <= 0)
161166
{
162167
Console.Error.WriteLine("No providers were specified to start a trace.");

src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.CommandLine;
66
using System.Diagnostics;
77
using System.IO;
8+
using System.Linq;
89
using System.Runtime.InteropServices;
910
using System.Text;
1011
using System.Threading.Tasks;
@@ -128,19 +129,6 @@ private static List<string> BuildRecordTraceArgs(CollectLinuxArgs args, out stri
128129
{
129130
scriptPath = null;
130131
List<string> recordTraceArgs = new();
131-
132-
foreach (string profile in args.Profiles)
133-
{
134-
if (profile.Equals("kernel-cpu", StringComparison.OrdinalIgnoreCase))
135-
{
136-
recordTraceArgs.Add("--on-cpu");
137-
}
138-
if (profile.Equals("kernel-cswitch", StringComparison.OrdinalIgnoreCase))
139-
{
140-
recordTraceArgs.Add("--off-cpu");
141-
}
142-
}
143-
144132
int pid = args.ProcessId;
145133
if (!string.IsNullOrEmpty(args.Name))
146134
{
@@ -156,22 +144,34 @@ private static List<string> BuildRecordTraceArgs(CollectLinuxArgs args, out stri
156144
recordTraceArgs.Add($"--out");
157145
recordTraceArgs.Add(resolvedOutput);
158146

159-
if (args.Duration != default(TimeSpan))
147+
if (args.Duration != default)
160148
{
161149
recordTraceArgs.Add($"--duration");
162150
recordTraceArgs.Add(args.Duration.ToString());
163151
}
164152

165-
StringBuilder scriptBuilder = new();
166-
167153
string[] profiles = args.Profiles;
168154
if (args.Profiles.Length == 0 && args.Providers.Length == 0 && string.IsNullOrEmpty(args.ClrEvents))
169155
{
170-
Console.WriteLine("No profile or providers specified, defaulting to trace profile 'dotnet-common'");
171-
profiles = new[] { "dotnet-common" };
156+
Console.WriteLine("No profile or providers specified, defaulting to trace profiles 'dotnet-common' + 'cpu-sampling'.");
157+
profiles = new[] { "dotnet-common", "cpu-sampling" };
172158
}
173159

174-
List<EventPipeProvider> providerCollection = ProviderUtils.ToProviders(args.Providers, args.ClrEvents, args.ClrEventLevel, profiles, true);
160+
foreach (string profile in profiles)
161+
{
162+
Profile traceProfile = ListProfilesCommandHandler.TraceProfiles
163+
.FirstOrDefault(p => p.Name.Equals(profile, StringComparison.OrdinalIgnoreCase));
164+
165+
if (!string.IsNullOrEmpty(traceProfile.VerbExclusivity) &&
166+
traceProfile.VerbExclusivity.Equals("collect-linux", StringComparison.OrdinalIgnoreCase))
167+
{
168+
recordTraceArgs.Add(traceProfile.CollectLinuxArgs);
169+
}
170+
}
171+
172+
StringBuilder scriptBuilder = new();
173+
174+
List<EventPipeProvider> providerCollection = ProviderUtils.ComputeProviderConfig(args.Providers, args.ClrEvents, args.ClrEventLevel, profiles, true, "collect-linux");
175175
foreach (EventPipeProvider provider in providerCollection)
176176
{
177177
string providerName = provider.Name;
@@ -237,7 +237,7 @@ private static string ResolveOutputPath(FileInfo output, int processId)
237237
return $"{processMainModuleFileInfo.Name}_{now:yyyyMMdd}_{now:HHmmss}.nettrace";
238238
}
239239

240-
return $"collect_linux_{now:yyyyMMdd}_{now:HHmmss}.nettrace";
240+
return $"trace_{now:yyyyMMdd}_{now:HHmmss}.nettrace";
241241
}
242242

243243
private static int OutputHandler(uint type, IntPtr data, UIntPtr dataLen)
@@ -273,7 +273,7 @@ private static int OutputHandler(uint type, IntPtr data, UIntPtr dataLen)
273273
private static readonly Option<string> PerfEventsOption =
274274
new("--perf-events")
275275
{
276-
Description = @"Comma-separated list of kernel perf events (e.g. syscalls:sys_enter_execve,sched:sched_switch)."
276+
Description = @"Comma-separated list of perf events (e.g. syscalls:sys_enter_execve,sched:sched_switch)."
277277
};
278278

279279
private enum OutputType : uint

src/Tools/dotnet-trace/CommandLine/Commands/ListProfilesCommandHandler.cs

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,25 @@ namespace Microsoft.Diagnostics.Tools.Trace
1515
internal sealed class ListProfilesCommandHandler
1616
{
1717
private static long defaultKeyword = 0x1 | // GC
18-
0x4 | // Loader
19-
0x8 | // AssemblyLoader
18+
0x4 | // AssemblyLoader
19+
0x8 | // Loader
2020
0x10 | // JIT
2121
0x8000 | // Exceptions
2222
0x10000 | // Threading
2323
0x20000 | // JittedMethodILToNativeMap
24-
0x1000000000; // Contention
24+
0x1000000000; // Compilation
2525

2626
private static string dotnetCommonDescription = """
2727
Lightweight .NET runtime diagnostics designed to stay low overhead.
2828
Includes:
2929
GC
3030
AssemblyLoader
31-
Jit
32-
Exception
31+
Loader
32+
JIT
33+
Exceptions
3334
Threading
3435
JittedMethodILToNativeMap
3536
Compilation
36-
Contention
3737
Equivalent to --providers "Microsoft-Windows-DotNETRuntime:0x100003801D:4".
3838
""";
3939

@@ -42,26 +42,12 @@ public static int GetProfiles()
4242
try
4343
{
4444
Console.Out.WriteLine("dotnet-trace collect profiles:");
45-
int profileNameWidth = ProfileNamesMaxWidth(DotNETRuntimeProfiles);
46-
foreach (Profile profile in DotNETRuntimeProfiles)
45+
int profileNameWidth = ProfileNamesMaxWidth(TraceProfiles);
46+
foreach (Profile profile in TraceProfiles)
4747
{
4848
PrintProfile(profile, profileNameWidth);
4949
}
5050

51-
if (OperatingSystem.IsLinux())
52-
{
53-
Console.Out.WriteLine("\ndotnet-trace collect-linux profiles:");
54-
profileNameWidth = Math.Max(profileNameWidth, ProfileNamesMaxWidth(LinuxPerfEventProfiles));
55-
foreach (Profile profile in DotNETRuntimeProfiles)
56-
{
57-
PrintProfile(profile, profileNameWidth);
58-
}
59-
foreach (Profile profile in LinuxPerfEventProfiles)
60-
{
61-
PrintProfile(profile, profileNameWidth);
62-
}
63-
}
64-
6551
return 0;
6652
}
6753
catch (Exception ex)
@@ -81,7 +67,7 @@ public static Command ListProfilesCommand()
8167
return listProfilesCommand;
8268
}
8369

84-
internal static IEnumerable<Profile> DotNETRuntimeProfiles { get; } = new[] {
70+
internal static IEnumerable<Profile> TraceProfiles { get; } = new[] {
8571
new Profile(
8672
"dotnet-common",
8773
new EventPipeProvider[] {
@@ -93,7 +79,7 @@ public static Command ListProfilesCommand()
9379
new EventPipeProvider[] {
9480
new("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational),
9581
},
96-
"Samples .NET thread stacks (~100 Hz) to identify hotspots over time. Uses the runtime sample profiler with managed stacks."),
82+
"Samples .NET thread stacks (~100 Hz) toestimate how much wall clock time code is using.") { VerbExclusivity = "collect" },
9783
new Profile(
9884
"gc-verbose",
9985
new EventPipeProvider[] {
@@ -145,28 +131,30 @@ public static Command ListProfilesCommand()
145131
}
146132
)
147133
},
148-
"Captures ADO.NET and Entity Framework database commands")
149-
};
150-
151-
internal static IEnumerable<Profile> LinuxPerfEventProfiles { get; } = new[] {
134+
"Captures ADO.NET and Entity Framework database commands"),
152135
new Profile(
153-
"kernel-cpu",
136+
"cpu-sampling",
154137
providers: Array.Empty<EventPipeProvider>(),
155-
description: "Kernel CPU sampling (perf-based), emitted as Universal.Events/cpu, for precise on-CPU attribution."),
138+
description: "Kernel CPU sampling events for measuring CPU usage.") { VerbExclusivity = "collect-linux", CollectLinuxArgs = "--on-cpu" },
156139
new Profile(
157-
"kernel-cswitch",
140+
"thread-time",
158141
providers: Array.Empty<EventPipeProvider>(),
159-
description: "Kernel thread context switches, emitted as Universal.Events/cswitch, for on/off-CPU and scheduler analysis.")
142+
description: "Kernel thread context switch events for measuring CPU usage and wall clock time") { VerbExclusivity = "collect-linux", CollectLinuxArgs = "--off-cpu" },
160143
};
161144

162145
private static int ProfileNamesMaxWidth(IEnumerable<Profile> profiles)
163146
{
164147
int maxWidth = 0;
165148
foreach (Profile profile in profiles)
166149
{
167-
if (profile.Name.Length > maxWidth)
150+
int profileNameWidth = profile.Name.Length;
151+
if (!string.IsNullOrEmpty(profile.VerbExclusivity))
168152
{
169-
maxWidth = profile.Name.Length;
153+
profileNameWidth = $"{profile.Name} ({profile.VerbExclusivity})".Length;
154+
}
155+
if (profileNameWidth > maxWidth)
156+
{
157+
maxWidth = profileNameWidth;
170158
}
171159
}
172160

@@ -177,7 +165,13 @@ private static void PrintProfile(Profile profile, int nameColumnWidth)
177165
{
178166
string[] descriptionLines = profile.Description.Replace("\r\n", "\n").Split('\n');
179167

180-
Console.Out.WriteLine($"\t{profile.Name.PadRight(nameColumnWidth)} - {descriptionLines[0]}");
168+
string profileColumn = $"{profile.Name}";
169+
if (!string.IsNullOrEmpty(profile.VerbExclusivity))
170+
{
171+
profileColumn = $"{profile.Name} ({profile.VerbExclusivity})";
172+
}
173+
174+
Console.Out.WriteLine($"\t{profileColumn.PadRight(nameColumnWidth)} - {descriptionLines[0]}");
181175

182176
string continuationPrefix = $"\t{new string(' ', nameColumnWidth)} ";
183177
for (int i = 1; i < descriptionLines.Length; i++)

src/Tools/dotnet-trace/CommandLine/Options/CommonOptions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ internal static class CommonOptions
2020
@"--providers 'KnownProviderName:0x1:1:FilterSpec=\""KnownProviderName/EventName:-Prop1=Prop1;Prop2=Prop2.A.B;\""'. These providers are in " +
2121
@"addition to any providers implied by the --profile argument. If there is any discrepancy for a particular provider, the " +
2222
@"configuration here takes precedence over the implicit configuration from the profile. See documentation for examples."
23-
// TODO: Can we specify an actual type?
2423
};
2524

2625
public static readonly Option<string> CLREventLevelOption =

src/Tools/dotnet-trace/Profile.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,9 @@ public Profile(string name, IEnumerable<EventPipeProvider> providers, string des
2525
public long RundownKeyword { get; set; } = EventPipeSession.DefaultRundownKeyword;
2626

2727
public RetryStrategy RetryStrategy { get; set; } = RetryStrategy.NothingToRetry;
28+
29+
public string VerbExclusivity { get; set; } = string.Empty;
30+
31+
public string CollectLinuxArgs { get; set; } = string.Empty;
2832
}
2933
}

src/Tools/dotnet-trace/ProviderUtils.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ private enum ProviderSource
7171
ProfileArg = 4,
7272
}
7373

74-
public static List<EventPipeProvider> ToProviders(string[] providersArg, string clreventsArg, string clreventlevel, string[] profiles, bool shouldPrintProviders)
74+
public static List<EventPipeProvider> ComputeProviderConfig(string[] providersArg, string clreventsArg, string clreventlevel, string[] profiles, bool shouldPrintProviders = false, string verbExclusivity = null)
7575
{
7676
Dictionary<string, EventPipeProvider> merged = new(StringComparer.OrdinalIgnoreCase);
7777
Dictionary<string, int> providerSources = new(StringComparer.OrdinalIgnoreCase);
@@ -106,15 +106,22 @@ public static List<EventPipeProvider> ToProviders(string[] providersArg, string
106106

107107
foreach (string profile in profiles)
108108
{
109-
Profile dotnetProfile = ListProfilesCommandHandler.DotNETRuntimeProfiles
109+
Profile traceProfile = ListProfilesCommandHandler.TraceProfiles
110110
.FirstOrDefault(p => p.Name.Equals(profile, StringComparison.OrdinalIgnoreCase));
111-
if (dotnetProfile == null)
111+
112+
if (traceProfile == null)
113+
{
114+
throw new ArgumentException($"Invalid profile name: {profile}");
115+
}
116+
117+
if (!string.IsNullOrEmpty(verbExclusivity) &&
118+
!string.IsNullOrEmpty(traceProfile.VerbExclusivity) &&
119+
!string.Equals(traceProfile.VerbExclusivity, verbExclusivity, StringComparison.OrdinalIgnoreCase))
112120
{
113-
// for collect-linux, could be linux perf event profile
114-
continue;
121+
throw new ArgumentException($"The specified profile '{traceProfile.Name}' does not apply to `dotnet-trace {verbExclusivity}`.");
115122
}
116123

117-
IEnumerable<EventPipeProvider> profileProviders = dotnetProfile.Providers;
124+
IEnumerable<EventPipeProvider> profileProviders = traceProfile.Providers;
118125
foreach (EventPipeProvider provider in profileProviders)
119126
{
120127
if (merged.TryAdd(provider.Name, provider))

0 commit comments

Comments
 (0)