-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Source generators/timing #55892
Source generators/timing #55892
Conversation
208eb44
to
d5aeab6
Compare
@@ -13,7 +13,9 @@ Microsoft.CodeAnalysis.GeneratorDriver.WithUpdatedParseOptions(Microsoft.CodeAna | |||
Microsoft.CodeAnalysis.GeneratorDriverOptions | |||
Microsoft.CodeAnalysis.GeneratorDriverOptions.GeneratorDriverOptions() -> void | |||
Microsoft.CodeAnalysis.GeneratorDriverOptions.GeneratorDriverOptions(Microsoft.CodeAnalysis.IncrementalGeneratorOutputKind disabledOutputs) -> void | |||
Microsoft.CodeAnalysis.GeneratorDriverRunResult.RunTime.get -> System.TimeSpan |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@333fred These are new public APIs, but nothing major. Can we just get consensus over email?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spoke in the API review meeting and agreed to call them ElapsedTime
@@ -78,6 +79,8 @@ private GeneratorState(GeneratorInfo info, ImmutableArray<GeneratedSyntaxTree> p | |||
|
|||
internal Exception? Exception { get; } | |||
|
|||
internal TimeSpan RunTime { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jkoritzinsky We probably want to add timing info to the individual steps as well, but we can figure that out when we merge your changes in.
@dotnet/roslyn-compiler for review please :) |
Love the visualizations, the extension to view it in real time and the foresight to add an ETW event. 👏 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎆 This is awesome!
public const EventKeywords Performance = (EventKeywords)1; | ||
} | ||
|
||
public class Tasks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need Tasks here. I think they are only necessary/useful when you have "op codes":
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're correct @eerhardt. In this case though it looks like tasks are actually more desirable than the current setup. Please correct me if I read this wrong since I only skimmed the code, but it looks like this patch currently:
- adds a
Timer
to the state of the source generators - sends a completion event with the duration for a given generator
- sends a completion event with the duration for the generator driver
I'd maybe look at changing this to use the tasks here by doing the following:
- emit a
GeneratorDriverStart
/GeneratorDriverStop
event pair using the driver task and op codes - emit a
GeneratorStart
/GeneratorStop
event pair using the generator task and op codes - calculate the run time by diffing the timestamps of the events (the infrastructure should already do this by embedding the duration in the
*Stop
events)
What's currently written should be fine, but would subject the times in the events to any issues the timer has with waking up etc. The times are already being provided by the event infrastructure so you can take advantage of that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only used them so I could distinguish them in the viewer linked above. Happy to remove them but it seemed like a useful concept. Especially if we convert to a pair of start / stop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so I could distinguish them in the viewer linked above
Where are you distinguishing them in that viewer? Is it this code?
If so, I think you can just rename the ReportGeneratorDriverRunTime
method => GeneratorDriverRunTime
and that will be the name of your Event. But this is getting into "beyond my EventSource knowledge" area.
Especially if we convert to a pair of start / stop.
Yeah, if you convert to start / stop, then the Tasks would be useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The EventSource changes LGTM.
Nice work! I think this is really going to help in diagnosing perf issues related to source generators.
@chsienki @jaredpar - any thoughts on also backporting this to VS 2019 / Roslyn 3.x? It may be useful to have there for diagnostic purposes when customers are running into sluggish VS performance (e.g. dotnet/runtime#56702). |
@eerhardt Sadly if we wanted to do that it'd have to be a manual port, as we've significantly altered the driver architecture between releases, so it seems unlikely. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API shape looks good, I can't speak to the implementation or the EventSource stuff so somebody else needs to sign off on that.
internal void StartSingleGeneratorRunTime(string generatorName, string assemblyPath, string id) => WriteEvent(3, generatorName, assemblyPath, id); | ||
|
||
[Event(4, Message = "Generator {0} ran for {2} ticks", Keywords = Keywords.Performance, Level = EventLevel.Informational, Opcode = EventOpcode.Stop, Task = Tasks.SingleGeneratorRunTime)] | ||
internal void StopSingleGeneratorRunTime(string generatorName, string assemblyPath, long elapsedTicks, string id) => WriteEvent(4, generatorName, assemblyPath, elapsedTicks, id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does assemblyPath
get used here given there is no {1}
in the Message
string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's still available in the ETW event, just not directly in the message attached to it. See https://github.com/chsienki/GeneratorTracer/blob/master/GeneratorTracer/Program.cs#L35
src/Compilers/Core/Portable/SourceGeneration/GeneratorTimerExtensions.cs
Outdated
Show resolved
Hide resolved
@chsienki Hmm, one thought: what happens in the case of cancellation? It'd be good that if the IDE requests cancellation of a run, we still can see how much time was spent before we abandoned it. Practically a slow generator might get cancelled a lot in our case, and that's the actual indicator of the problem. |
@jasonmalinowski That's an interesting point. Currently we just let the Given that, I think we should take this as-is for now, and work on the cancellation later |
If this wasn't going to be a public API I'd heartily agree; in the current form though I'm not sure the IDE will get the benefit we want -- any telemetry we try reporting from would be skewed in a problematic way -- the long generators (that are regularly being cancelled) will be underrepresented in the data. |
This won't work for IDE needs, so we'll need something else.
@jasonmalinowski Ok, that makes sense to me. @jaredpar Should we split out the public API and ETW parts into two parts? I wanted to get the ETW timing in ASAP, and don't want to delay if we need to redesign the public API parts. |
@chsienki: we can make the API internal, right? The API wasn't being used by another layer I don't think in this? |
@jasonmalinowski Yep, we can do that. I actually have a change that mostly enables the behaviour the IDE needs, although we need to go through API review again because it adds a |
Given the above, i'll make the API internal for now, and we can turn it back to public with the Cancellation tracking change afterwards. |
df25032
to
47a230e
Compare
[EventSource(Name = "Microsoft-CodeAnalysis-General")] | ||
internal sealed class CodeAnalysisEventSource : EventSource | ||
{ | ||
public static readonly CodeAnalysisEventSource Log = new CodeAnalysisEventSource(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, seems like a good idea.
@@ -155,10 +156,12 @@ static ImmutableArray<GeneratedSourceResult> getGeneratorSources(GeneratorState | |||
|
|||
internal GeneratorDriverState RunGeneratorsCore(Compilation compilation, DiagnosticBag? diagnosticsBag, CancellationToken cancellationToken = default) | |||
{ | |||
using var timer = CodeAnalysisEventSource.Log.CreateGeneratorDriverRunTimer(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{ | ||
} | ||
|
||
/// <summary> | ||
/// Creates a new generator state that contains an exception and the associated diagnostic | ||
/// </summary> | ||
public GeneratorState(GeneratorInfo info, Exception e, Diagnostic error) | ||
: this(info, ImmutableArray<GeneratedSyntaxTree>.Empty, ImmutableArray<ISyntaxInputNode>.Empty, ImmutableArray<IIncrementalGeneratorOutputNode>.Empty, ImmutableArray<GeneratedSyntaxTree>.Empty, ImmutableArray.Create(error), exception: e) | ||
public GeneratorState(GeneratorInfo info, Exception e, Diagnostic error, TimeSpan runTime) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
47a230e
to
0d3943a
Compare
Addressed feedback + rebased to rel/dev17.0 |
{ | ||
private readonly SharedStopwatch _timer; | ||
private readonly Action<TimeSpan> _callback; | ||
private readonly bool _shouldExecuteCallback; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than having a bool
indicating whether to execute the callback, consider moving the check to the callback itself or set the callback to null
or timeSpan => { }
for the cases where we're not timing.
@@ -108,6 +115,11 @@ internal GeneratorRunResult(ISourceGenerator generator, ImmutableArray<Generated | |||
/// collection will contain a single diagnostic indicating that the generator failed. | |||
/// </remarks> | |||
public Exception? Exception { get; } | |||
|
|||
/// <summary> | |||
/// The wallclock time that elapsed while this generator was running. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// The wallclock time that elapsed while this generator was running. | |
/// The wall clock time that elapsed while this generator was running. |
_callback = callback; | ||
_shouldExecuteCallback = shouldExecuteCallback; | ||
|
||
// start twice to improve accuracy. See AnalyzerExecutor::ExecuteAndCatchIfThrows for more details |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// start twice to improve accuracy. See AnalyzerExecutor::ExecuteAndCatchIfThrows for more details | |
// start twice to improve accuracy. See AnalyzerExecutor.ExecuteAndCatchIfThrows for more details |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One suggestion to drop the bool RunTimer._shouldExecuteCallback
field if possible, but not blocking.
Expose via runrunresult and ETW
- Extract out helper from event source - Add extension methods to create a start/stop pair of events the driver can use
Co-authored-by: Jared Parsons <[email protected]>
0d3943a
to
2bfa9cb
Compare
Implements wall clock timing for the generator driver and each generator running within it.
Also implements a roslyn ETW event source that can be used to emit performance data. For now we're only using it for generators but we'll likely want to add other things as we go.
Fixes #54770
As we're emitting ETW you can view the data live coming out of visual studio. I've created a small app you can run that allows you to view the data in realtime: https://github.com/chsienki/GeneratorTracer