Skip to content

Commit

Permalink
Enable Config-File Based Control of EventPipe (dotnet/coreclr#20238)
Browse files Browse the repository at this point in the history
Commit migrated from dotnet/coreclr@4571480
  • Loading branch information
brianrob committed Oct 5, 2018
1 parent 4dbe018 commit 4857d24
Show file tree
Hide file tree
Showing 11 changed files with 377 additions and 270 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -656,8 +656,6 @@ private void SetupDomain(bool allowRedirects, string path, string configFile, st
SetupFusionStore(new AppDomainSetup(), null);
}
}

System.Diagnostics.Tracing.EventPipeController.Initialize();
}

[MethodImpl(MethodImplOptions.InternalCall)]
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ internal static class StartupHookProvider
// containing a startup hook, and call each hook in turn.
private static void ProcessStartupHooks()
{
// Initialize tracing before any user code can be called.
System.Diagnostics.Tracing.EventPipeController.Initialize();

string startupHooksVariable = (string)AppContext.GetData("STARTUP_HOOKS");
if (startupHooksVariable == null)
{
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/src/inc/clrconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_AllowDComReflection, W("AllowDComReflection"),
// EventPipe
//
RETAIL_CONFIG_DWORD_INFO(INTERNAL_EnableEventPipe, W("EnableEventPipe"), 0, "Enable/disable event pipe. Non-zero values enable tracing.")
RETAIL_CONFIG_STRING_INFO(INTERNAL_EventPipeOutputFile, W("EventPipeOutputFile"), "The full path including file name for the trace file that will be written when COMPlus_EnableEventPipe&=1")
RETAIL_CONFIG_STRING_INFO(INTERNAL_EventPipeOutputPath, W("EventPipeOutputPath"), "The full path excluding file name for the trace file that will be written when COMPlus_EnableEventPipe=1")
RETAIL_CONFIG_STRING_INFO(INTERNAL_EventPipeConfig, W("EventPipeConfig"), "Configuration for EventPipe.")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_EventPipeRundown, W("EventPipeRundown"), 1, "Enable/disable eventpipe rundown.")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_EventPipeCircularMB, W("EventPipeCircularMB"), 1024, "The EventPipe circular buffer size in megabytes.")
Expand Down
124 changes: 0 additions & 124 deletions src/coreclr/src/vm/eventpipe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -830,130 +830,6 @@ CrstStatic* EventPipe::GetLock()
return &s_configCrst;
}

void EventPipe::GetConfigurationFromEnvironment(SString &outputPath, EventPipeSession *pSession)
{
LIMITED_METHOD_CONTRACT;

// Set the output path if specified.
CLRConfigStringHolder wszOutputPath(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_EventPipeOutputFile));
if(wszOutputPath != NULL)
{
outputPath.Set(wszOutputPath);
}

// Read the the provider configuration from the environment if specified.
CLRConfigStringHolder wszConfig(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_EventPipeConfig));
if(wszConfig == NULL)
{
pSession->EnableAllEvents();
return;
}

size_t len = wcslen(wszConfig);
if(len <= 0)
{
pSession->EnableAllEvents();
return;
}

// Parses a string with the following format:
//
// ProviderName:Keywords:Level[,]*
//
// For example:
//
// Microsoft-Windows-DotNETRuntime:0xCAFEBABE:2,Microsoft-Windows-DotNETRuntimePrivate:0xDEADBEEF:1
//
// Each provider configuration is separated by a ',' and each component within the configuration is
// separated by a ':'.

const WCHAR ProviderSeparatorChar = ',';
const WCHAR ComponentSeparatorChar = ':';
size_t index = 0;
WCHAR *pProviderName = NULL;
UINT64 keywords = 0;
EventPipeEventLevel level = EventPipeEventLevel::Critical;

while(index < len)
{
WCHAR * pCurrentChunk = &wszConfig[index];
size_t currentChunkStartIndex = index;
size_t currentChunkEndIndex = 0;

// Find the next chunk.
while(index < len && wszConfig[index] != ProviderSeparatorChar)
{
index++;
}
currentChunkEndIndex = index++;

// Split the chunk into components.
size_t chunkIndex = currentChunkStartIndex;

// Get the provider name.
size_t provNameStartIndex = chunkIndex;
size_t provNameEndIndex = currentChunkEndIndex;

while(chunkIndex < currentChunkEndIndex && wszConfig[chunkIndex] != ComponentSeparatorChar)
{
chunkIndex++;
}
provNameEndIndex = chunkIndex++;

size_t provNameLen = provNameEndIndex - provNameStartIndex;
pProviderName = new WCHAR[provNameLen+1];
memcpy(pProviderName, &wszConfig[provNameStartIndex], provNameLen*sizeof(WCHAR));
pProviderName[provNameLen] = '\0';

// Get the keywords.
size_t keywordsStartIndex = chunkIndex;
size_t keywordsEndIndex = currentChunkEndIndex;

while(chunkIndex < currentChunkEndIndex && wszConfig[chunkIndex] != ComponentSeparatorChar)
{
chunkIndex++;
}
keywordsEndIndex = chunkIndex++;

size_t keywordsLen = keywordsEndIndex - keywordsStartIndex;
WCHAR *wszKeywords = new WCHAR[keywordsLen+1];
memcpy(wszKeywords, &wszConfig[keywordsStartIndex], keywordsLen*sizeof(WCHAR));
wszKeywords[keywordsLen] = '\0';
keywords = _wcstoui64(wszKeywords, NULL, 16);
delete[] wszKeywords;
wszKeywords = NULL;

// Get the level.
size_t levelStartIndex = chunkIndex;
size_t levelEndIndex = currentChunkEndIndex;

while(chunkIndex < currentChunkEndIndex && wszConfig[chunkIndex] != ComponentSeparatorChar)
{
chunkIndex++;
}
levelEndIndex = chunkIndex++;

size_t levelLen = levelEndIndex - levelStartIndex;
WCHAR *wszLevel = new WCHAR[levelLen+1];
memcpy(wszLevel, &wszConfig[levelStartIndex], levelLen*sizeof(WCHAR));
wszLevel[levelLen] = '\0';
level = (EventPipeEventLevel) wcstoul(wszLevel, NULL, 16);
delete[] wszLevel;
wszLevel = NULL;

// Add a new EventPipeSessionProvider.
EventPipeSessionProvider *pSessionProvider = new EventPipeSessionProvider(pProviderName, keywords, level);
pSession->AddSessionProvider(pSessionProvider);

// Free the provider name string.
if(pProviderName != NULL)
{
delete[] pProviderName;
pProviderName = NULL;
}
}
}

void EventPipe::SaveCommandLine(LPCWSTR pwzAssemblyPath, int argc, LPCWSTR *argv)
{
CONTRACTL
Expand Down
4 changes: 0 additions & 4 deletions src/coreclr/src/vm/eventpipe.h
Original file line number Diff line number Diff line change
Expand Up @@ -292,10 +292,6 @@ class EventPipe
// Enable the specified EventPipe session.
static EventPipeSessionID Enable(LPCWSTR strOutputPath, EventPipeSession *pSession);

// Get the EnableOnStartup configuration from environment.
static void GetConfigurationFromEnvironment(SString &outputPath, EventPipeSession *pSession);


// Callback function for the stack walker. For each frame walked, this callback is invoked.
static StackWalkAction StackWalkCallback(CrawlFrame *pCf, StackContents *pData);

Expand Down
42 changes: 14 additions & 28 deletions src/coreclr/src/vm/eventpipesession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,6 @@ void EventPipeSession::AddSessionProvider(EventPipeSessionProvider *pProvider)
m_pProviderList->AddSessionProvider(pProvider);
}

void EventPipeSession::EnableAllEvents()
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;

m_pProviderList->EnableAllEvents();
}

EventPipeSessionProvider* EventPipeSession::GetSessionProvider(EventPipeProvider *pProvider)
{
CONTRACTL
Expand Down Expand Up @@ -118,12 +105,21 @@ EventPipeSessionProviderList::EventPipeSessionProviderList(
for(unsigned int i=0; i<numConfigs; i++)
{
EventPipeProviderConfiguration *pConfig = &pConfigs[i];
EventPipeSessionProvider *pProvider = new EventPipeSessionProvider(
pConfig->GetProviderName(),
pConfig->GetKeywords(),
(EventPipeEventLevel)pConfig->GetLevel());

m_pProviders->InsertTail(new SListElem<EventPipeSessionProvider*>(pProvider));
// Enable all events if the provider name == '*', all keywords are on and the requested level == verbose.
if((wcscmp(W("*"), pConfig->GetProviderName()) == 0) && (pConfig->GetKeywords() == 0xFFFFFFFFFFFFFFFF) && ((EventPipeEventLevel)pConfig->GetLevel() == EventPipeEventLevel::Verbose) && (m_pCatchAllProvider == NULL))
{
m_pCatchAllProvider = new EventPipeSessionProvider(NULL, 0xFFFFFFFFFFFFFFFF, EventPipeEventLevel::Verbose);
}
else
{
EventPipeSessionProvider *pProvider = new EventPipeSessionProvider(
pConfig->GetProviderName(),
pConfig->GetKeywords(),
(EventPipeEventLevel)pConfig->GetLevel());

m_pProviders->InsertTail(new SListElem<EventPipeSessionProvider*>(pProvider));
}
}
}

Expand Down Expand Up @@ -176,16 +172,6 @@ void EventPipeSessionProviderList::AddSessionProvider(EventPipeSessionProvider *
}
}

void EventPipeSessionProviderList::EnableAllEvents()
{
LIMITED_METHOD_CONTRACT;

if(m_pCatchAllProvider == NULL)
{
m_pCatchAllProvider = new EventPipeSessionProvider(NULL, 0xFFFFFFFFFFFFFFFF, EventPipeEventLevel::Verbose);
}
}

EventPipeSessionProvider* EventPipeSessionProviderList::GetSessionProvider(
EventPipeProvider *pProvider)
{
Expand Down
11 changes: 1 addition & 10 deletions src/coreclr/src/vm/eventpipesession.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,6 @@ class EventPipeSession
return m_sessionStartTimeStamp;
}

// Enable all events.
// This is used for testing and is controlled via COMPLUS_EnableEventPipe.
void EnableAllEvents();

// Add a new provider to the session.
void AddSessionProvider(EventPipeSessionProvider *pProvider);

Expand All @@ -115,8 +111,7 @@ class EventPipeSessionProviderList
// The list of providers.
SList<SListElem<EventPipeSessionProvider*>> *m_pProviders;

// A catch-all provider used when tracing is enabled at start-up
// under (COMPlus_PerformanceTracing & 1) == 1.
// A catch-all provider used when tracing is enabled for all events.
EventPipeSessionProvider *m_pCatchAllProvider;

public:
Expand All @@ -125,10 +120,6 @@ class EventPipeSessionProviderList
EventPipeSessionProviderList(EventPipeProviderConfiguration *pConfigs, unsigned int numConfigs);
~EventPipeSessionProviderList();

// Enable all events.
// This is used for testing and is controlled via COMPLUS_EnableEventPipe.
void EnableAllEvents();

// Add a new session provider to the list.
void AddSessionProvider(EventPipeSessionProvider *pProvider);

Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/tests/issues.targets
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,12 @@
</ExcludeList>
</ItemGroup>

<ItemGroup Condition="'$(XunitTestBinBase)' != '' and '$(TargetsWindows)' == 'true'">
<ExcludeList Include="$(XunitTestBinBase)/tracing/tracecontrol/tracecontrol/*">
<Issue>Unable to write config file to app location</Issue>
</ExcludeList>
</ItemGroup>

<!-- Windows x64 specific excludes -->
<ItemGroup Condition="'$(XunitTestBinBase)' != '' and '$(BuildArch)' == 'x64' and '$(TargetsWindows)' == 'true'">
<ExcludeList Include="$(XunitTestBinBase)/Loader/classloader/TypeGeneratorTests/TypeGeneratorTest612/Generated612/*">
Expand Down
113 changes: 113 additions & 0 deletions src/coreclr/tests/src/tracing/tracecontrol/TraceControl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Tracing.Tests.Common;

using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers.Clr;

namespace Tracing.Tests
{
public static class TraceControlTest
{
private static string ConfigFileContents = @"
OutputPath=.
CircularMB=2048
Providers=*:0xFFFFFFFFFFFFFFFF:5
";

private const int BytesInOneMB = 1024 * 1024;

/// <summary>
/// This test collects a trace of itself and then performs some basic validation on the trace.
/// </summary>
public static int Main(string[] args)
{
// Calculate the path to the config file.
string configFileName = Assembly.GetEntryAssembly().GetName().Name + ".eventpipeconfig";
string configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, configFileName);
Console.WriteLine("Calculated config file path: " + configFilePath);

// Write the config file to disk.
File.WriteAllText(configFilePath, ConfigFileContents);
Console.WriteLine("Wrote contents of config file.");

// Wait 5 seconds to ensure that tracing has started.
Console.WriteLine("Waiting 5 seconds for the config file to be picked up by the next poll operation.");
Thread.Sleep(TimeSpan.FromSeconds(5));

// Do some work that we can look for in the trace.
Console.WriteLine("Do some work that will be captured by the trace.");
GC.Collect(2, GCCollectionMode.Forced);
Console.WriteLine("Done with the work.");

// Delete the config file to start tracing.
File.Delete(configFilePath);
Console.WriteLine("Deleted the config file.");

// Build the full path to the trace file.
string[] traceFiles = Directory.GetFiles(".", "*.netperf", SearchOption.TopDirectoryOnly);
Assert.Equal("traceFiles.Length == 1", traceFiles.Length, 1);
string traceFilePath = traceFiles[0];

// Poll the file system and wait for the trace file to be written.
Console.WriteLine("Wait for the config file deletion to be picked up and for the trace file to be written.");

// Wait for 1 second, which is the poll time when tracing is enabled.
Thread.Sleep(TimeSpan.FromSeconds(1));

// Poll for file size changes to the trace file itself. When the size of the trace file hasn't changed for 5 seconds, consider it fully written out.
Console.WriteLine("Waiting for the trace file to be written. Poll every second to watch for 5 seconds of no file size changes.");
long lastSizeInBytes = 0;
DateTime timeOfLastChangeUTC = DateTime.UtcNow;
do
{
FileInfo traceFileInfo = new FileInfo(traceFilePath);
long currentSizeInBytes = traceFileInfo.Length;
Console.WriteLine("Trace file size: " + ((double)currentSizeInBytes / BytesInOneMB));

if (currentSizeInBytes > lastSizeInBytes)
{
lastSizeInBytes = currentSizeInBytes;
timeOfLastChangeUTC = DateTime.UtcNow;
}

Thread.Sleep(TimeSpan.FromSeconds(1));

} while (DateTime.UtcNow.Subtract(timeOfLastChangeUTC) < TimeSpan.FromSeconds(5));

int retVal = 0;

// Use TraceEvent to consume the trace file and look for the work that we did.
Console.WriteLine("Using TraceEvent to parse the file to find the work that was done during trace capture.");
using (var trace = TraceEventDispatcher.GetDispatcherFromFileName(traceFilePath))
{
string gcReasonInduced = GCReason.Induced.ToString();
string providerName = "Microsoft-Windows-DotNETRuntime";
string gcTriggeredEventName = "GC/Triggered";

trace.Clr.GCTriggered += delegate (GCTriggeredTraceData data)
{
if (gcReasonInduced.Equals(data.Reason.ToString()))
{
Console.WriteLine("Detected an induced GC");
retVal = 100;
}
};

trace.Process();
}

// Clean-up the resulting trace file.
File.Delete(traceFilePath);

return retVal;
}
}
}
Loading

0 comments on commit 4857d24

Please sign in to comment.