Skip to content

Commit

Permalink
Add event to RuntimeEventSource for AppContext switches (#57303)
Browse files Browse the repository at this point in the history
Fixes #56142
  • Loading branch information
agocke authored Aug 20, 2021
1 parent 2a1d100 commit 235915e
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 92 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using System.IO;
using System.Reflection;

Expand Down Expand Up @@ -30,5 +33,52 @@ private static string GetBaseDirectoryCore()

return directory;
}

internal static void LogSwitchValues(RuntimeEventSource ev)
{
if (s_switches is not null)
{
lock (s_switches)
{
foreach (KeyValuePair<string, bool> kvp in s_switches)
{
// Convert bool to int because it's cheaper to log (no boxing)
ev.LogAppContextSwitch(kvp.Key, kvp.Value ? 1 : 0);
}
}
}

if (s_dataStore is not null)
{
lock (s_dataStore)
{
if (s_switches is not null)
{
lock (s_switches)
{
LogDataStore(ev, s_switches);
}
}
else
{
LogDataStore(ev, null);
}

static void LogDataStore(RuntimeEventSource ev, Dictionary<string, bool>? switches)
{
Debug.Assert(s_dataStore is not null);
foreach (KeyValuePair<string, object?> kvp in s_dataStore)
{
if (kvp.Value is string s &&
bool.TryParse(s, out bool isEnabled) &&
switches?.ContainsKey(kvp.Key) != true)
{
ev.LogAppContextSwitch(kvp.Key, isEnabled ? 1 : 0);
}
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ internal sealed partial class RuntimeEventSource : EventSource
{
internal const string EventSourceName = "System.Runtime";

public static class Keywords
{
public const EventKeywords AppContext = (EventKeywords)0x1;
}

private static RuntimeEventSource? s_RuntimeEventSource;
private PollingCounter? _gcHeapSizeCounter;
private IncrementingPollingCounter? _gen0GCCounter;
Expand Down Expand Up @@ -50,6 +55,17 @@ public static void Initialize()
// as you can't make a constructor partial.
private RuntimeEventSource(int _) { }

private enum EventId : int
{
AppContextSwitch = 1
}

[Event((int)EventId.AppContextSwitch, Level = EventLevel.Informational, Keywords = Keywords.AppContext)]
internal void LogAppContextSwitch(string switchName, int value)
{
base.WriteEvent((int)EventId.AppContextSwitch, switchName, value);
}

protected override void OnEventCommand(EventCommandEventArgs command)
{
if (command.Command == EventCommand.Enable)
Expand Down Expand Up @@ -87,6 +103,8 @@ protected override void OnEventCommand(EventCommandEventArgs command)
_ilBytesJittedCounter ??= new PollingCounter("il-bytes-jitted", this, () => System.Runtime.JitInfo.GetCompiledILBytes()) { DisplayName = "IL Bytes Jitted", DisplayUnits = "B" };
_methodsJittedCounter ??= new PollingCounter("methods-jitted-count", this, () => System.Runtime.JitInfo.GetCompiledMethodCount()) { DisplayName = "Number of Methods Jitted" };
_jitTimeCounter ??= new IncrementingPollingCounter("time-in-jit", this, () => System.Runtime.JitInfo.GetCompilationTime().TotalMilliseconds) { DisplayName = "Time spent in JIT", DisplayUnits = "ms", DisplayRateTimeScale = new TimeSpan(0, 0, 1) };

AppContext.LogSwitchValues(this);
}

}
Expand Down
6 changes: 4 additions & 2 deletions src/tests/tracing/eventcounter/runtimecounters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ protected override void OnEventSourceCreated(EventSource source)
{
Dictionary<string, string> refreshInterval = new Dictionary<string, string>();
refreshInterval.Add("EventCounterIntervalSec", "1");
EnableEvents(source, EventLevel.Informational, (EventKeywords)(-1), refreshInterval);
EnableEvents(source, EventLevel.Informational,
(EventKeywords)(-1 & (~1 /* RuntimeEventSource.Keywords.AppContext */)),
refreshInterval);
}
}

Expand Down Expand Up @@ -100,7 +102,7 @@ public static int Main(string[] args)
// Create an EventListener.
using (RuntimeCounterListener myListener = new RuntimeCounterListener())
{
Thread.Sleep(3000);
Thread.Sleep(3000);
if (myListener.Verify())
{
Console.WriteLine("Test passed");
Expand Down
114 changes: 114 additions & 0 deletions src/tests/tracing/runtimeeventsource/NativeRuntimeEventSourceTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Diagnostics.Tracing;
using System.Runtime.CompilerServices;
using System.Threading;
using Tracing.Tests.Common;

namespace Tracing.Tests
{
public sealed class NativeRuntimeEventSourceTest
{
static int Main(string[] args)
{
SimpleEventListener.EnableKeywords = (EventKeywords)0;
using (SimpleEventListener noEventsListener = new SimpleEventListener("NoEvents"))
{
// Create an EventListener.
SimpleEventListener.EnableKeywords = (EventKeywords)0x4c14fccbd;
using (SimpleEventListener listener = new SimpleEventListener("Simple"))
{
// Trigger the allocator task.
System.Threading.Tasks.Task.Run(new Action(Allocator));

// Wait for events.
Thread.Sleep(1000);

// Generate some GC events.
GC.Collect(2, GCCollectionMode.Forced);

// Wait for more events.
Thread.Sleep(1000);

// Ensure that we've seen some events.
Assert.True("listener.EventCount > 0", listener.EventCount > 0);
}

// Generate some more GC events.
GC.Collect(2, GCCollectionMode.Forced);

// Ensure that we've seen no events.
Assert.True("noEventsListener.EventCount == 0", noEventsListener.EventCount == 0);
}

return 100;
}

private static void Allocator()
{
while (true)
{
for(int i=0; i<1000; i++)
GC.KeepAlive(new object());

Thread.Sleep(10);
}
}
}

internal sealed class SimpleEventListener : EventListener
{
private string m_name;

// Keep track of the set of keywords to be enabled.
public static EventKeywords EnableKeywords
{
get;
set;
}

public SimpleEventListener(string name)
{
m_name = name;
}

public int EventCount { get; private set; } = 0;

protected override void OnEventSourceCreated(EventSource eventSource)
{
if (eventSource.Name.Equals("Microsoft-Windows-DotNETRuntime"))
{
if (EnableKeywords != 0)
{
// Enable events.
EnableEvents(eventSource, EventLevel.Verbose, EnableKeywords);
}
else
{
// Enable the provider, but not any keywords, so we should get no events as long as no rundown occurs.
EnableEvents(eventSource, EventLevel.Critical, EnableKeywords);
}
}
}

protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
Console.WriteLine($"[{m_name}] ThreadID = {eventData.OSThreadId} ID = {eventData.EventId} Name = {eventData.EventName}");
Console.WriteLine($"TimeStamp: {eventData.TimeStamp.ToLocalTime()}");
Console.WriteLine($"LocalTime: {DateTime.Now}");
Console.WriteLine($"Difference: {DateTime.UtcNow - eventData.TimeStamp}");
Assert.True("eventData.TimeStamp <= DateTime.UtcNow", eventData.TimeStamp <= DateTime.UtcNow);
for (int i = 0; i < eventData.Payload.Count; i++)
{
string payloadString = eventData.Payload[i] != null ? eventData.Payload[i].ToString() : string.Empty;
Console.WriteLine($"\tName = \"{eventData.PayloadNames[i]}\" Value = \"{payloadString}\"");
}
Console.WriteLine("\n");

EventCount++;
}
}
}
Loading

0 comments on commit 235915e

Please sign in to comment.