Skip to content

Commit 532b7e6

Browse files
committed
Convert CollectCommandHandler to instanced
1 parent 81463f1 commit 532b7e6

File tree

6 files changed

+183
-152
lines changed

6 files changed

+183
-152
lines changed

src/Tools/dotnet-counters/Exporters/DefaultConsole.cs renamed to src/Tools/Common/DefaultConsole.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using System.IO;
56
using Microsoft.Diagnostics.Tools.Common;
67

7-
namespace Microsoft.Diagnostics.Tools.Counters.Exporters
8+
namespace Microsoft.Diagnostics.Tools.Common
89
{
910
/// <summary>
1011
/// The default implementation of IConsole maps everything to System.Console. In the future
@@ -32,6 +33,18 @@ public DefaultConsole(bool useAnsi)
3233

3334
public int BufferWidth => Console.BufferWidth;
3435

36+
public int BufferHeight => Console.BufferHeight;
37+
38+
public bool IsOutputRedirected => Console.IsOutputRedirected;
39+
40+
public bool IsInputRedirected => Console.IsInputRedirected;
41+
42+
public bool KeyAvailable => Console.KeyAvailable;
43+
44+
public TextWriter Out => Console.Out;
45+
46+
public TextWriter Error => Console.Error;
47+
3548
public void Clear()
3649
{
3750
if (_useAnsi)
@@ -57,6 +70,8 @@ public void SetCursorPosition(int col, int row)
5770
}
5871
public void Write(string text) => Console.Write(text);
5972
public void WriteLine(string text) => Console.WriteLine(text);
60-
public void WriteLine() => Console.WriteLine();
73+
public static void WriteLine() => Console.WriteLine();
74+
public ConsoleKeyInfo ReadKey() => Console.ReadKey();
75+
public ConsoleKeyInfo ReadKey(bool intercept) => Console.ReadKey(intercept);
6176
}
6277
}

src/Tools/Common/IConsole.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
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+
using System;
5+
using System.IO;
6+
47
namespace Microsoft.Diagnostics.Tools.Common
58
{
69
/// <summary>
@@ -14,11 +17,18 @@ internal interface IConsole
1417
bool CursorVisible { get; set; }
1518
int CursorTop { get; }
1619
int BufferWidth { get; }
20+
int BufferHeight { get; }
21+
bool IsOutputRedirected { get; }
22+
bool IsInputRedirected { get; }
23+
bool KeyAvailable { get; }
24+
TextWriter Out { get; }
25+
TextWriter Error { get; }
1726

1827
void Clear();
1928
void SetCursorPosition(int col, int row);
2029
void Write(string text);
2130
void WriteLine(string text);
22-
void WriteLine();
31+
ConsoleKeyInfo ReadKey();
32+
ConsoleKeyInfo ReadKey(bool intercept);
2333
}
2434
}

src/Tools/dotnet-counters/CounterMonitor.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Microsoft.Diagnostics.Monitoring;
1515
using Microsoft.Diagnostics.Monitoring.EventPipe;
1616
using Microsoft.Diagnostics.NETCore.Client;
17+
using Microsoft.Diagnostics.Tools.Common;
1718
using Microsoft.Diagnostics.Tools.Counters.Exporters;
1819
using Microsoft.Internal.Common.Utils;
1920

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

Lines changed: 52 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,26 @@
1313
using System.Threading.Tasks;
1414
using Microsoft.Diagnostics.Monitoring.EventPipe;
1515
using Microsoft.Diagnostics.NETCore.Client;
16+
using Microsoft.Diagnostics.Tools.Common;
1617
using Microsoft.Internal.Common;
1718
using Microsoft.Internal.Common.Utils;
1819

1920
namespace Microsoft.Diagnostics.Tools.Trace
2021
{
21-
internal static class CollectCommandHandler
22+
internal class CollectCommandHandler
2223
{
23-
internal static bool IsQuiet { get; set; }
24+
internal bool IsQuiet { get; set; }
2425

25-
private static void ConsoleWriteLine(string str)
26+
public CollectCommandHandler()
27+
{
28+
Console = new DefaultConsole(false);
29+
StartTraceSessionAsync = async (client, config, ct) => new CollectSession(await client.StartEventPipeSessionAsync(config, ct).ConfigureAwait(false));
30+
ResumeRuntimeAsync = (client, ct) => client.ResumeRuntimeAsync(ct);
31+
CollectSessionEventStream = name => new FileStream(name, FileMode.Create, FileAccess.Write);
32+
IsOutputRedirected = Console.IsOutputRedirected;
33+
}
34+
35+
private void ConsoleWriteLine(string str)
2636
{
2737
if (!IsQuiet)
2838
{
@@ -54,7 +64,7 @@ private static void ConsoleWriteLine(string str)
5464
/// <param name="stoppingEventPayloadFilter">A string, parsed as [payload_field_name]:[payload_field_value] pairs separated by commas, that will stop the trace upon hitting an event with a matching payload. Requires `--stopping-event-provider-name` and `--stopping-event-event-name` to be set.</param>
5565
/// <param name="rundown">Collect rundown events.</param>
5666
/// <returns></returns>
57-
internal static async Task<int> Collect(CancellationToken ct, CommandLineConfiguration cliConfig, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration, string clrevents, string clreventlevel, string name, string diagnosticPort, bool showchildio, bool resumeRuntime, string stoppingEventProviderName, string stoppingEventEventName, string stoppingEventPayloadFilter, bool? rundown, string dsrouter)
67+
internal async Task<int> Collect(CancellationToken ct, CommandLineConfiguration cliConfig, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration, string clrevents, string clreventlevel, string name, string diagnosticPort, bool showchildio, bool resumeRuntime, string stoppingEventProviderName, string stoppingEventEventName, string stoppingEventPayloadFilter, bool? rundown, string dsrouter)
5868
{
5969
bool collectionStopped = false;
6070
bool cancelOnEnter = true;
@@ -79,7 +89,7 @@ internal static async Task<int> Collect(CancellationToken ct, CommandLineConfigu
7989
{
8090
cancelOnCtrlC = true;
8191
cancelOnEnter = !Console.IsInputRedirected;
82-
printStatusOverTime = !IsOutputRedirected();
92+
printStatusOverTime = !IsOutputRedirected;
8393
}
8494

8595
if (!cancelOnCtrlC)
@@ -357,10 +367,10 @@ internal static async Task<int> Collect(CancellationToken ct, CommandLineConfigu
357367

358368
LineRewriter rewriter = null;
359369

360-
using (FileStream fs = new(output.FullName, FileMode.Create, FileAccess.Write))
370+
using (Stream eventStream = CollectSessionEventStream(output.FullName))
361371
{
362372
ConsoleWriteLine($"Process : {processMainModuleFileName}");
363-
ConsoleWriteLine($"Output File : {fs.Name}");
373+
ConsoleWriteLine($"Output File : {output.FullName}");
364374
if (shouldStopAfterDuration)
365375
{
366376
ConsoleWriteLine($"Trace Duration : {duration:dd\\:hh\\:mm\\:ss}");
@@ -383,14 +393,14 @@ internal static async Task<int> Collect(CancellationToken ct, CommandLineConfigu
383393
onPayloadFilterMismatch: (traceEvent) => {
384394
ConsoleWriteLine($"One or more field names specified in the payload filter for event '{traceEvent.ProviderName}/{traceEvent.EventName}' do not match any of the known field names: '{string.Join(' ', traceEvent.PayloadNames)}'. As a result the requested stopping event is unreachable; will continue to collect the trace for the remaining specified duration.");
385395
},
386-
eventStream: new PassthroughStream(session.EventStream, fs, (int)buffersize, leaveDestinationStreamOpen: true),
396+
eventStream: new PassthroughStream(session.EventStream, eventStream, (int)buffersize, leaveDestinationStreamOpen: true),
387397
callOnEventOnlyOnce: true);
388398

389399
copyTask = eventMonitor.ProcessAsync(CancellationToken.None);
390400
}
391401
else
392402
{
393-
copyTask = session.EventStream.CopyToAsync(fs);
403+
copyTask = session.EventStream.CopyToAsync(eventStream);
394404
}
395405
Task shouldExitTask = copyTask.ContinueWith(
396406
(task) => shouldExit.Set(),
@@ -400,7 +410,7 @@ internal static async Task<int> Collect(CancellationToken ct, CommandLineConfigu
400410

401411
if (printStatusOverTime)
402412
{
403-
if (AreCursorOperationsSupported())
413+
if (CursorOperationsSupported)
404414
{
405415
rewriter = new LineRewriter { LineToClear = Console.CursorTop - 1 };
406416
Console.CursorVisible = false;
@@ -506,7 +516,7 @@ internal static async Task<int> Collect(CancellationToken ct, CommandLineConfigu
506516
}
507517
finally
508518
{
509-
if (printStatusOverTime && AreCursorOperationsSupported())
519+
if (printStatusOverTime && CursorOperationsSupported)
510520
{
511521
if (!Console.IsOutputRedirected)
512522
{
@@ -527,7 +537,7 @@ internal static async Task<int> Collect(CancellationToken ct, CommandLineConfigu
527537
return ret;
528538
}
529539

530-
private static void PrintProviders(IReadOnlyList<EventPipeProvider> providers, Dictionary<string, string> enabledBy)
540+
private void PrintProviders(IReadOnlyList<EventPipeProvider> providers, Dictionary<string, string> enabledBy)
531541
{
532542
ConsoleWriteLine("");
533543
ConsoleWriteLine(string.Format("{0, -40}", "Provider Name") + string.Format("{0, -20}", "Keywords") +
@@ -588,27 +598,30 @@ public static Command CollectCommand()
588598
collectCommand.TreatUnmatchedTokensAsErrors = false; // see the logic in Program.Main that handles UnmatchedTokens
589599
collectCommand.Description = "Collects a diagnostic trace from a currently running process or launch a child process and trace it. Append -- to the collect command to instruct the tool to run a command and trace it immediately. When tracing a child process, the exit code of dotnet-trace shall be that of the traced process unless the trace process encounters an error.";
590600

591-
collectCommand.SetAction((parseResult, ct) => Collect(
592-
ct,
593-
cliConfig: parseResult.Configuration,
594-
processId: parseResult.GetValue(CommonOptions.ProcessIdOption),
595-
output: parseResult.GetValue(OutputPathOption),
596-
buffersize: parseResult.GetValue(CircularBufferOption),
597-
providers: parseResult.GetValue(ProvidersOption) ?? string.Empty,
598-
profile: parseResult.GetValue(ProfileOption) ?? string.Empty,
599-
format: parseResult.GetValue(CommonOptions.FormatOption),
600-
duration: parseResult.GetValue(DurationOption),
601-
clrevents: parseResult.GetValue(CLREventsOption) ?? string.Empty,
602-
clreventlevel: parseResult.GetValue(CLREventLevelOption) ?? string.Empty,
603-
name: parseResult.GetValue(CommonOptions.NameOption),
604-
diagnosticPort: parseResult.GetValue(DiagnosticPortOption) ?? string.Empty,
605-
showchildio: parseResult.GetValue(ShowChildIOOption),
606-
resumeRuntime: parseResult.GetValue(ResumeRuntimeOption),
607-
stoppingEventProviderName: parseResult.GetValue(StoppingEventProviderNameOption),
608-
stoppingEventEventName: parseResult.GetValue(StoppingEventEventNameOption),
609-
stoppingEventPayloadFilter: parseResult.GetValue(StoppingEventPayloadFilterOption),
610-
rundown: parseResult.GetValue(RundownOption),
611-
dsrouter: parseResult.GetValue(DSRouterOption)));
601+
collectCommand.SetAction((parseResult, ct) =>
602+
{
603+
CollectCommandHandler handler = new();
604+
return handler.Collect(ct,
605+
cliConfig: parseResult.Configuration,
606+
processId: parseResult.GetValue(CommonOptions.ProcessIdOption),
607+
output: parseResult.GetValue(OutputPathOption),
608+
buffersize: parseResult.GetValue(CircularBufferOption),
609+
providers: parseResult.GetValue(ProvidersOption) ?? string.Empty,
610+
profile: parseResult.GetValue(ProfileOption) ?? string.Empty,
611+
format: parseResult.GetValue(CommonOptions.FormatOption),
612+
duration: parseResult.GetValue(DurationOption),
613+
clrevents: parseResult.GetValue(CLREventsOption) ?? string.Empty,
614+
clreventlevel: parseResult.GetValue(CLREventLevelOption) ?? string.Empty,
615+
name: parseResult.GetValue(CommonOptions.NameOption),
616+
diagnosticPort: parseResult.GetValue(DiagnosticPortOption) ?? string.Empty,
617+
showchildio: parseResult.GetValue(ShowChildIOOption),
618+
resumeRuntime: parseResult.GetValue(ResumeRuntimeOption),
619+
stoppingEventProviderName: parseResult.GetValue(StoppingEventProviderNameOption),
620+
stoppingEventEventName: parseResult.GetValue(StoppingEventEventNameOption),
621+
stoppingEventPayloadFilter: parseResult.GetValue(StoppingEventPayloadFilterOption),
622+
rundown: parseResult.GetValue(RundownOption),
623+
dsrouter: parseResult.GetValue(DSRouterOption));
624+
});
612625

613626
return collectCommand;
614627
}
@@ -735,14 +748,12 @@ private sealed class CollectSession : ICollectSession
735748
public void Dispose() => _session.Dispose();
736749
}
737750

738-
internal static Func<DiagnosticsClient, EventPipeSessionConfiguration, CancellationToken, Task<ICollectSession>> StartTraceSessionAsync { get; set; }
739-
= async (client, config, ct) => new CollectSession(await client.StartEventPipeSessionAsync(config, ct).ConfigureAwait(false));
740-
741-
internal static Func<DiagnosticsClient, CancellationToken, Task> ResumeRuntimeAsync { get; set; }
742-
= (client, ct) => client.ResumeRuntimeAsync(ct);
743-
744-
internal static Func<bool> IsOutputRedirected { get; set; } = () => Console.IsOutputRedirected;
745-
internal static Func<bool> AreCursorOperationsSupported { get; set; } = () => true;
751+
internal Func<DiagnosticsClient, EventPipeSessionConfiguration, CancellationToken, Task<ICollectSession>> StartTraceSessionAsync { get; set; }
752+
internal Func<DiagnosticsClient, CancellationToken, Task> ResumeRuntimeAsync { get; set; }
753+
internal bool CursorOperationsSupported { get; set; } = true;
754+
internal Func<string, Stream> CollectSessionEventStream { get; set; }
755+
internal IConsole Console { get; set; }
756+
internal bool IsOutputRedirected { get; set; }
746757
#endregion
747758
}
748759
}

src/tests/Common/MockConsole.cs

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ public MockConsole(int width, int height)
3535

3636
public int BufferWidth { get; private set; }
3737

38+
public int BufferHeight { get; private set; }
39+
40+
public bool IsOutputRedirected { get; private set; }
41+
42+
public bool IsInputRedirected { get; private set; }
43+
44+
public bool KeyAvailable { get; private set; }
45+
46+
public TextWriter Out => this;
47+
48+
public TextWriter Error => this;
49+
3850
public void Clear()
3951
{
4052
_chars = new char[WindowHeight][];
@@ -102,6 +114,10 @@ public override void WriteLine(string text)
102114

103115
public string GetLineText(int row) => new string(_chars[row]).TrimEnd();
104116

117+
public ConsoleKeyInfo ReadKey() => Console.ReadKey();
118+
119+
public ConsoleKeyInfo ReadKey(bool intercept) => Console.ReadKey(intercept);
120+
105121
public string[] Lines
106122
{
107123
get
@@ -143,30 +159,30 @@ public void AssertLinesEqual(int startLine, params string[] expectedLines)
143159
// Asserts that the sanitized version of the console lines exactly equals the expected lines.
144160
// The sanitizer receives the raw Lines array and should return a transformed array (e.g., with
145161
// blank lines removed, whitespace collapsed, dynamic values normalized). Equality is ordinal.
146-
public void AssertSanitizedLinesEqual(Func<string[], string[]> sanitizer, params string[] expectedSanitizedLines)
162+
public void AssertSanitizedLinesEqual(Func<string[], string[]> sanitizer, params string[] expectedLines)
147163
{
148-
if (sanitizer is null)
149-
{
150-
throw new ArgumentNullException(nameof(sanitizer));
151-
}
152-
string[] actualSanitized = sanitizer(Lines);
153-
if (actualSanitized.Length != expectedSanitizedLines.Length)
164+
string[] actualLines = Lines;
165+
if (sanitizer is not null)
154166
{
155-
Assert.Fail("Sanitized console output length mismatch." + Environment.NewLine +
156-
$"Expected: {expectedSanitizedLines.Length}" + Environment.NewLine +
157-
$"Actual : {actualSanitized.Length}");
167+
actualLines = sanitizer(actualLines);
158168
}
159-
for (int i = 0; i < expectedSanitizedLines.Length; i++)
169+
Assert.True(actualLines.Length >= expectedLines.Length, "Sanitized console output had fewer lines than expected." + Environment.NewLine +
170+
$"Expected line count: {expectedLines.Length}" + Environment.NewLine +
171+
$"Actual line count: {actualLines.Length}");
172+
173+
for (int i = 0; i < expectedLines.Length; i++)
160174
{
161-
string expected = expectedSanitizedLines[i];
162-
string actual = actualSanitized[i];
163-
if (!string.Equals(expected, actual, StringComparison.Ordinal))
175+
if (!string.Equals(expectedLines[i], actualLines[i], StringComparison.Ordinal))
164176
{
165177
Assert.Fail("Sanitized console output mismatch." + Environment.NewLine +
166-
$"Line {i,2} Expected: {expected}" + Environment.NewLine +
167-
$"Line {i,2} Actual : {actual}");
178+
$"Line {i,2} Expected: {expectedLines[i]}" + Environment.NewLine +
179+
$"Line {i,2} Actual : {actualLines[i]}");
168180
}
169181
}
182+
for (int i = expectedLines.Length; i < actualLines.Length; i++)
183+
{
184+
Assert.True(string.IsNullOrWhiteSpace(actualLines[i]), "Actual line beyond expected lines is not empty: " + actualLines[i]);
185+
}
170186
}
171187
}
172188
}

0 commit comments

Comments
 (0)