Skip to content

Commit

Permalink
Startup hook simplification (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyredondo authored Aug 4, 2023
1 parent 9b74e8f commit fb6e67f
Show file tree
Hide file tree
Showing 20 changed files with 154 additions and 186 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
bin/
obj/
.idea/
.DS_Store
4 changes: 2 additions & 2 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>0.0.14</Version>
<Authors>Tony Redondo</Authors>
<Version>0.0.15</Version>
<Authors>Tony Redondo, Grégory Léocadie</Authors>
<TargetFrameworks>net5.0;net6.0;net7.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
Expand Down
8 changes: 0 additions & 8 deletions src/TimeIt.RuntimeMetrics/TimeIt.RuntimeMetrics.csproj

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,35 @@

namespace TimeIt.RuntimeMetrics;

public class FileStatsd
public class FileStorage
{
private readonly StreamWriter _streamWriter;

public FileStatsd(string filePath)
public FileStorage(string filePath)
{
_streamWriter = new StreamWriter(filePath, true);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Counter(string statName, double value, double sampleRate = 1, string[]? tags = null)
public void Counter(string statName, double value)
{
WritePayload("counter", statName, value);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Gauge(string statName, double value, double sampleRate = 1, string[]? tags = null)
public void Gauge(string statName, double value)
{
WritePayload("gauge", statName, value);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Increment(string statName, int value = 1, double sampleRate = 1, string[]? tags = null)
public void Increment(string statName, int value = 1)
{
WritePayload("increment", statName, value);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Timer(string statName, double value, double sampleRate = 1, string[]? tags = null)
public void Timer(string statName, double value)
{
WritePayload("timer", statName, value);
}
Expand All @@ -49,17 +49,9 @@ private void WritePayload(string type, string name, double value)
{
lock (_streamWriter)
{
_streamWriter.Write("{ \"type\": ");
if (type is null)
{
_streamWriter.Write("null, ");
}
else
{
_streamWriter.Write("\"");
_streamWriter.Write(type);
_streamWriter.Write("\", ");
}
_streamWriter.Write("{ \"type\": \"");
_streamWriter.Write(type);
_streamWriter.Write("\", ");

_streamWriter.Write("\"name\": ");
if (name is null)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ namespace TimeIt.RuntimeMetrics;

public static class ProcessHelpers
{
private static readonly Process CurrentProcess = Process.GetCurrentProcess();

/// <summary>
/// Wrapper around <see cref="Process.GetCurrentProcess"/> and its property accesses
///
Expand All @@ -29,11 +31,11 @@ public static void GetCurrentProcessRuntimeMetrics(
out int threadCount,
out long privateMemorySize)
{
using var process = Process.GetCurrentProcess();
var process = CurrentProcess;
userProcessorTime = process.UserProcessorTime;
systemCpuTime = process.PrivilegedProcessorTime;
totalProcessorTime = systemCpuTime + userProcessorTime;
threadCount = process.Threads.Count;
privateMemorySize = process.PrivateMemorySize64;
totalProcessorTime = systemCpuTime + userProcessorTime;
}
}
60 changes: 30 additions & 30 deletions src/TimeIt.StartupHook/RuntimeMetrics/RuntimeEventListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,38 @@ class RuntimeEventListener : EventListener
private const string RuntimeEventSourceName = "Microsoft-Windows-DotNETRuntime";
private const string AspNetCoreHostingEventSourceName = "Microsoft.AspNetCore.Hosting";
private const string AspNetCoreKestrelEventSourceName = "Microsoft-AspNetCore-Server-Kestrel";

private const int EventGcSuspendBegin = 9;
private const int EventGcRestartEnd = 3;
private const int EventGcHeapStats = 4;
private const int EventContentionStop = 91;
private const int EventGcGlobalHeapHistory = 205;

private static readonly string[] GcCountMetricNames = { MetricsNames.Gen0CollectionsCount, MetricsNames.Gen1CollectionsCount, MetricsNames.Gen2CollectionsCount };

private readonly FileStatsd _statsd;

private readonly FileStorage _storage;
private readonly Timing _contentionTime = new();

private readonly string _delayInSeconds;

private long _contentionCount;

private DateTime? _gcStart;

public RuntimeEventListener(FileStatsd statsd, TimeSpan delay)
public RuntimeEventListener(FileStorage storage, TimeSpan delay)
{
_statsd = statsd;
_storage = storage;
_delayInSeconds = ((int)delay.TotalSeconds).ToString();

EventSourceCreated += (_, e) => EnableEventSource(e.EventSource);
}

public void Refresh()
{
// Can't use a Timing because Dogstatsd doesn't support local aggregation
// It means that the aggregations in the UI would be wrong
_statsd.Gauge(MetricsNames.ContentionTime, _contentionTime.Clear());
_statsd.Counter(MetricsNames.ContentionCount, Interlocked.Exchange(ref _contentionCount, 0));

_statsd.Gauge(MetricsNames.ThreadPoolWorkersCount, ThreadPool.ThreadCount);
_storage.Gauge(MetricsNames.ContentionTime, _contentionTime.Clear());
_storage.Counter(MetricsNames.ContentionCount, Interlocked.Exchange(ref _contentionCount, 0));
_storage.Gauge(MetricsNames.ThreadPoolWorkersCount, ThreadPool.ThreadCount);
}

protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
if (_statsd == null)
if (_storage == null)
{
// I know it sounds crazy at first, but because OnEventSourceCreated is called from the base constructor,
// and EnableEvents is called from OnEventSourceCreated, it's entirely possible that OnEventWritten
Expand All @@ -74,7 +66,7 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
if (_gcStart is { } start)
{
_statsd.Timer(MetricsNames.GcPauseTime, (eventData.TimeStamp - start).TotalMilliseconds);
_storage.Timer(MetricsNames.GcPauseTime, (eventData.TimeStamp - start).TotalMilliseconds);
}
}
else
Expand All @@ -83,10 +75,10 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
var stats = HeapStats.FromPayload(eventData.Payload);

_statsd.Gauge(MetricsNames.Gen0HeapSize, stats.Gen0Size);
_statsd.Gauge(MetricsNames.Gen1HeapSize, stats.Gen1Size);
_statsd.Gauge(MetricsNames.Gen2HeapSize, stats.Gen2Size);
_statsd.Gauge(MetricsNames.LohSize, stats.LohSize);
_storage.Gauge(MetricsNames.Gen0HeapSize, stats.Gen0Size);
_storage.Gauge(MetricsNames.Gen1HeapSize, stats.Gen1Size);
_storage.Gauge(MetricsNames.Gen2HeapSize, stats.Gen2Size);
_storage.Gauge(MetricsNames.LohSize, stats.LohSize);
}
else if (eventData.EventId == EventContentionStop)
{
Expand All @@ -101,14 +93,24 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData)

if (heapHistory.MemoryLoad is { } memoryLoad)
{
_statsd.Gauge(MetricsNames.GcMemoryLoad, memoryLoad);
_storage.Gauge(MetricsNames.GcMemoryLoad, memoryLoad);
}

_statsd.Increment(GcCountMetricNames[heapHistory.Generation], 1);

if (heapHistory.Compacting && heapHistory.Generation == 2)
if (heapHistory.Generation == 0)
{
_storage.Increment(MetricsNames.Gen0CollectionsCount, 1);
}
else if (heapHistory.Generation == 1)
{
_statsd.Increment(MetricsNames.Gen2CompactingCollectionsCount, 1);
_storage.Increment(MetricsNames.Gen1CollectionsCount, 1);
}
else if (heapHistory.Generation == 2)
{
_storage.Increment(MetricsNames.Gen2CollectionsCount, 1);
if (heapHistory.Compacting)
{
_storage.Increment(MetricsNames.Gen2CompactingCollectionsCount, 1);
}
}
}
}
Expand All @@ -130,12 +132,10 @@ private void EnableEventSource(EventSource eventSource)
}
else if (eventSource.Name is AspNetCoreHostingEventSourceName or AspNetCoreKestrelEventSourceName)
{
var settings = new Dictionary<string, string>
EnableEvents(eventSource, EventLevel.Critical, EventKeywords.All, new Dictionary<string, string>
{
["EventCounterIntervalSec"] = _delayInSeconds
};

EnableEvents(eventSource, EventLevel.Critical, EventKeywords.All, settings);
});
}
}

Expand All @@ -162,7 +162,7 @@ private void ExtractCounters(ReadOnlyCollection<object> payload)
if (eventPayload.TryGetValue("Mean", out var rawValue) ||
eventPayload.TryGetValue("Increment", out rawValue))
{
_statsd.Gauge(statName, (double)rawValue);
_storage.Gauge(statName, (double)rawValue);
}
}
}
Expand Down
41 changes: 14 additions & 27 deletions src/TimeIt.StartupHook/RuntimeMetrics/RuntimeMetricsWriter.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
using System.Collections.Concurrent;
using System.Runtime.ExceptionServices;
using System.Runtime.ExceptionServices;

#nullable disable

namespace TimeIt.RuntimeMetrics;

internal class RuntimeMetricsWriter : IDisposable
{
private static readonly Func<FileStatsd, TimeSpan, RuntimeEventListener> InitializeListenerFunc = InitializeListener;

private readonly TimeSpan _delay;
private readonly FileStatsd _statsd;
private readonly FileStorage _storage;
private readonly Timer _timer;
private readonly RuntimeEventListener _listener;
private readonly bool _enableProcessMetrics;
Expand All @@ -20,15 +17,10 @@ internal class RuntimeMetricsWriter : IDisposable
private TimeSpan _previousTotalCpu;
private int _exceptionCounts;

public RuntimeMetricsWriter(FileStatsd statsd, TimeSpan delay)
: this(statsd, delay, InitializeListenerFunc)
{
}

internal RuntimeMetricsWriter(FileStatsd statsd, TimeSpan delay, Func<FileStatsd, TimeSpan, RuntimeEventListener> initializeListener)
internal RuntimeMetricsWriter(FileStorage storage, TimeSpan delay)
{
_delay = delay;
_statsd = statsd;
_storage = storage;
_timer = new Timer(_ => PushEvents(), null, delay, delay);

try
Expand All @@ -43,7 +35,6 @@ internal RuntimeMetricsWriter(FileStatsd statsd, TimeSpan delay, Func<FileStatsd
try
{
ProcessHelpers.GetCurrentProcessRuntimeMetrics(out var totalCpu, out var userCpu, out var systemCpu, out _, out _);

_previousUserCpu = userCpu;
_previousSystemCpu = systemCpu;
_previousTotalCpu = totalCpu;
Expand All @@ -57,7 +48,7 @@ internal RuntimeMetricsWriter(FileStatsd statsd, TimeSpan delay, Func<FileStatsd

try
{
_listener = initializeListener(statsd, delay);
_listener = new RuntimeEventListener(storage, delay);
}
catch
{
Expand Down Expand Up @@ -95,24 +86,25 @@ internal void PushEvents()
// What we want is the number of cores attributed to the container, which is the behavior in 3.1.2+ (and, I believe, in 2.x)
var maximumCpu = Environment.ProcessorCount * _delay.TotalMilliseconds;

_statsd.Gauge(MetricsNames.ThreadsCount, threadCount);
_storage.Gauge(MetricsNames.ThreadsCount, threadCount);

_statsd.Gauge(MetricsNames.CommittedMemory, memoryUsage);
_statsd.Gauge(MetricsNames.PrivateBytes, memoryUsage);
_storage.Gauge(MetricsNames.CommittedMemory, memoryUsage);
_storage.Gauge(MetricsNames.PrivateBytes, memoryUsage);

// Get CPU time in milliseconds per second
_statsd.Gauge(MetricsNames.CpuUserTime, userCpu.TotalMilliseconds / _delay.TotalSeconds);
_statsd.Gauge(MetricsNames.CpuSystemTime, systemCpu.TotalMilliseconds / _delay.TotalSeconds);
_statsd.Gauge(MetricsNames.ProcessorTime, totalCpu.TotalMilliseconds / _delay.TotalSeconds);
var totalSeconds = _delay.TotalSeconds;
_storage.Gauge(MetricsNames.CpuUserTime, userCpu.TotalMilliseconds / totalSeconds);
_storage.Gauge(MetricsNames.CpuSystemTime, systemCpu.TotalMilliseconds / totalSeconds);
_storage.Gauge(MetricsNames.ProcessorTime, totalCpu.TotalMilliseconds / totalSeconds);

_statsd.Gauge(MetricsNames.CpuPercentage,
_storage.Gauge(MetricsNames.CpuPercentage,
Math.Round(totalCpu.TotalMilliseconds * 100 / maximumCpu, 1, MidpointRounding.AwayFromZero));
}

var exceptionCounts = Interlocked.Exchange(ref _exceptionCounts, 0);
if (exceptionCounts > 0)
{
_statsd.Increment(MetricsNames.ExceptionsCount, exceptionCounts);
_storage.Increment(MetricsNames.ExceptionsCount, exceptionCounts);
}
}
catch
Expand All @@ -121,11 +113,6 @@ internal void PushEvents()
}
}

private static RuntimeEventListener InitializeListener(FileStatsd statsd, TimeSpan delay)
{
return new RuntimeEventListener(statsd, delay);
}

private void FirstChanceException(object sender, FirstChanceExceptionEventArgs e)
{
Interlocked.Increment(ref _exceptionCounts);
Expand Down
File renamed without changes.
28 changes: 0 additions & 28 deletions src/TimeIt.StartupHook/RuntimeMetricsInitializer.cs

This file was deleted.

Loading

0 comments on commit fb6e67f

Please sign in to comment.