Skip to content
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

Implement Text-Based Performance Log #5861

Merged
merged 9 commits into from
Nov 6, 2020
Merged

Implement Text-Based Performance Log #5861

merged 9 commits into from
Nov 6, 2020

Conversation

brianrob
Copy link
Member

@brianrob brianrob commented Nov 5, 2020

Implements a text-based performance log scheme that writes coarse-grained performance logs:

  • No logs are written unless the user opts-in by setting the log location via the environment variable DOTNET_PERFLOG_DIR. This is currently enabled when a user opts-in via a CLI command, but can be set adhoc as well.
  • Most logs will be produced at the request of CLI commands. The lifetime of these logs is managed by the CLI - see Implement Text-Based Performance Log sdk#14409 for details.
  • Data in the logs are emitted via MSBuildEventSource so that the events can be consumed through other profilers, such as those that use ETW.

cc: @marcpopMSFT, @davidfowl, @DamianEdwards, @bwadswor

@@ -244,14 +249,14 @@ public void ExpandGlobStop(string rootDirectory, string glob, string excludedPat
/// Call this method to notify listeners of timing related to loading an XmlDocumentWithLocation from a path.
/// <param name="fullPath">Path to the document to load.</param>
/// </summary>
[Event(29)]
[Event(29, Keywords = Keywords.PerformanceLog)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did you choose which events to add this to?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I chose is based on two criteria:

  1. Gives a coarse view of what's happening, and time spans for those actions.
  2. Keeps the rate of events relatively low so that the impact on performance isn't too much.

If you think there are other events that should be included, I'm definitely open to that - just being careful with the perf overhead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe RAROverallStart/Stop, BuildStart/Stop, and BuildProjectStart/Stop? I'd put those higher than LoadDocument, Parse, or MSBuildExe.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thanks for the feedback on this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For command line scenarios I suspect Build and MSBuildExe will be fairly redundant: all MSBuild.exe does is start a build. But let's leave it in for v1.

@@ -85,6 +87,15 @@ public static string RunProcessAndGetOutput(string process, string parameters, o
UseShellExecute = false,
Arguments = parameters
};

if(environmentVariables != null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've worked around the inability to set environment variables in a different way elsewhere. (See here.) Do you think it would be reasonable to combine them?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine so. Are you proposing a separate class, or moving the usage in XMake_Tests to use what I've done here, or something else?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd do the latter or move what's in XMake_Tests here and call it. I don't have a strong preference between them, but I'd like it to be in one spot.

// It's most important to ensure that at least one file shows up because any others that show up will be there because MSBuild properly
// enabled this functionality.
string[] files = Directory.GetFiles(perfLogDir, "perf-*.log");
Assert.NotEmpty(files);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
Assert.NotEmpty(files);
files.ShouldNotBeEmpty();

And also convert the rest to Shouldly?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think you should also check to make sure those files didn't exist before? I don't think GetTempPath returns a truly random path each time.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the rest to Shoudly. Thanks for pointing this out.

With regard to the directory, GetTempPath doesn't return a random path, but the test creates a random GUID directory to ensure that the directory is empty, and then creates it, which would result in a thrown exception if it already existed.

Directory.CreateDirectory(perfLogDir);
Directory.CreateDirectory(projectDir);

string content = ObjectModelHelpers.CleanupFileContents("<Project ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'><Target Name='t'><Warning Text='[A=$(A)]'/></Target></Project>");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:
split across multiple lines?

Comment on lines 63 to 64
bool successfulExit;
RunnerUtilities.ExecMSBuild(RunnerUtilities.PathToCurrentlyRunningMsBuildExe, msbuildParameters, out successfulExit, environmentVariables: environmentVariables);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tiny nit:

Suggested change
bool successfulExit;
RunnerUtilities.ExecMSBuild(RunnerUtilities.PathToCurrentlyRunningMsBuildExe, msbuildParameters, out successfulExit, environmentVariables: environmentVariables);
RunnerUtilities.ExecMSBuild(RunnerUtilities.PathToCurrentlyRunningMsBuildExe, msbuildParameters, out bool successfulExit, environmentVariables: environmentVariables);

public void TestPerfLogEnabledProducedLogFile()
{
// Setup perf log.
string perfLogDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all this would be easier with a TestEnvironment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please point me to an example that I should follow?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this example, but I'm a little biased. You can also use env.SetEnvironmentVariable(<name>, <value>);.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the test environment should make this simpler (since it handles cleanup), and allow avoiding the RunnerUtilities changes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this definitely cleaned things up.

s_builder.Clear();
}

s_builder.Append($"[{DateTime.UtcNow.ToString("o")}] Event={eventData.EventSource.Name}/{eventData.EventName} ProcessID={_processIDStr} ThreadID={System.Threading.Thread.CurrentThread.ManagedThreadId}\t ");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an AppendFormat method for StringBuilder. I remember having tested whether it was faster than this, but I don't remember the answer. I think .Append over and over was fastest but also ugliest.


lock (this)
{
if (_writer != null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is _writer null, and should we error?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My goal is to make this log as inobtrusive as possible. So if something goes wrong, I want the log to fail silently and/or just drop data rather than error. Essentially, preserve correctness.

{
if (eventListener != null)
{
eventListener.Dispose();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (eventListener != null && eventListener threw an exception) shouldn't we return null or throw?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's a good catch. Thanks.

src/MSBuild/PerformanceLogEventListener.cs Show resolved Hide resolved
public void TestPerfLogEnabledProducedLogFile()
{
// Setup perf log.
string perfLogDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the test environment should make this simpler (since it handles cleanup), and allow avoiding the RunnerUtilities changes.

@brianrob
Copy link
Member Author

brianrob commented Nov 6, 2020

Thank you both for the reviews. I think I've covered all of the feedback.

Copy link
Member

@Forgind Forgind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@brianrob brianrob merged commit 7452552 into dotnet:master Nov 6, 2020
@brianrob brianrob deleted the perf-log branch November 6, 2020 21:59
@marcpopMSFT
Copy link
Member

@brianrob Note that you checked this in for 6.0. If you want it included in 5.0.2xx for earlier testing, it'll need to go in that branch as well.

@rainersigwald
Copy link
Member

@marcpopMSFT this is MSBuild :) it's in the 2xx branch already

@marcpopMSFT
Copy link
Member

@rainersigwald DOH! I was just in the SDK repo and got my wires crossed. Carry on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants