This commit is contained in:
Wiktor Kopec 2023-05-15 12:06:49 -07:00 коммит произвёл GitHub
Родитель 3ae4837a4b
Коммит 6fe1f4f7dd
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
26 изменённых файлов: 397 добавлений и 684 удалений

Просмотреть файл

@ -10,9 +10,9 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
/// <summary>
/// TODO This is currently a duplication of the src\Tools\dotnet-counters\CounterPayload.cs stack. The two will be unified in a separate change.
/// </summary>
internal class CounterPayload : ICounterPayload
internal abstract class CounterPayload : ICounterPayload
{
public CounterPayload(DateTime timestamp,
protected CounterPayload(DateTime timestamp,
string provider,
string name,
string displayName,
@ -20,7 +20,9 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
double value,
CounterType counterType,
float interval,
string metadata)
int series,
string metadata,
EventType eventType)
{
Timestamp = timestamp;
Name = name;
@ -30,30 +32,11 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
CounterType = counterType;
Provider = provider;
Interval = interval;
Series = series;
Metadata = metadata;
EventType = EventType.Gauge;
}
// Copied from dotnet-counters
public CounterPayload(string providerName,
string name,
string metadata,
double value,
DateTime timestamp,
string type,
EventType eventType)
{
Provider = providerName;
Name = name;
Metadata = metadata;
Value = value;
Timestamp = timestamp;
CounterType = (CounterType)Enum.Parse(typeof(CounterType), type);
EventType = eventType;
}
public string Namespace { get; }
public string Name { get; }
public string DisplayName { get; protected set; }
@ -73,12 +56,50 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
public string Metadata { get; }
public EventType EventType { get; set; }
public virtual bool IsMeter => false;
public int Series { get; }
}
internal class GaugePayload : CounterPayload
internal sealed class StandardCounterPayload : CounterPayload
{
public StandardCounterPayload(DateTime timestamp,
string provider,
string name,
string displayName,
string unit,
double value,
CounterType counterType,
float interval,
int series,
string metadata) : base(timestamp, provider, name, displayName, unit, value, counterType, interval, series, metadata, EventType.Gauge)
{
}
}
internal abstract class MeterPayload : CounterPayload
{
protected MeterPayload(DateTime timestamp,
string provider,
string name,
string displayName,
string unit,
double value,
CounterType counterType,
string metadata,
EventType eventType)
: base(timestamp, provider, name, displayName, unit, value, counterType, 0.0f, 0, metadata, eventType)
{
}
public override bool IsMeter => true;
}
internal sealed class GaugePayload : MeterPayload
{
public GaugePayload(string providerName, string name, string displayName, string displayUnits, string metadata, double value, DateTime timestamp) :
base(providerName, name, metadata, value, timestamp, "Metric", EventType.Gauge)
base(timestamp, providerName, name, displayName, displayUnits, value, CounterType.Metric, metadata, EventType.Gauge)
{
// In case these properties are not provided, set them to appropriate values.
string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
@ -86,10 +107,10 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
}
}
internal class UpDownCounterPayload : CounterPayload
internal class UpDownCounterPayload : MeterPayload
{
public UpDownCounterPayload(string providerName, string name, string displayName, string displayUnits, string metadata, double value, DateTime timestamp) :
base(providerName, name, metadata, value, timestamp, "Metric", EventType.UpDownCounter)
base(timestamp, providerName, name, displayName, displayUnits, value, CounterType.Metric, metadata, EventType.UpDownCounter)
{
// In case these properties are not provided, set them to appropriate values.
string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
@ -97,19 +118,26 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
}
}
internal class CounterEndedPayload : CounterPayload
internal sealed class InstrumentationStartedPayload : MeterPayload
{
public CounterEndedPayload(string providerName, string name, DateTime timestamp)
: base(providerName, name, null, 0.0, timestamp, "Metric", EventType.CounterEnded)
public InstrumentationStartedPayload(string providerName, string name, DateTime timestamp)
: base(timestamp, providerName, name, string.Empty, string.Empty, 0.0, CounterType.Metric, null, EventType.InstrumentationStarted)
{
}
}
internal class RatePayload : CounterPayload
internal sealed class CounterEndedPayload : MeterPayload
{
public CounterEndedPayload(string providerName, string name, DateTime timestamp)
: base(timestamp, providerName, name, string.Empty, string.Empty, 0.0, CounterType.Metric, null, EventType.CounterEnded)
{
}
}
internal sealed class RatePayload : MeterPayload
{
public RatePayload(string providerName, string name, string displayName, string displayUnits, string metadata, double value, double intervalSecs, DateTime timestamp) :
base(providerName, name, metadata, value, timestamp, "Rate", EventType.Rate)
base(timestamp, providerName, name, displayName, displayUnits, value, CounterType.Rate, metadata, EventType.Rate)
{
// In case these properties are not provided, set them to appropriate values.
string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
@ -119,10 +147,12 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
}
}
internal class PercentilePayload : CounterPayload
internal record struct Quantile(double Percentage, double Value);
internal sealed class PercentilePayload : MeterPayload
{
public PercentilePayload(string providerName, string name, string displayName, string displayUnits, string metadata, IEnumerable<Quantile> quantiles, DateTime timestamp) :
base(providerName, name, metadata, 0.0, timestamp, "Metric", EventType.Histogram)
base(timestamp, providerName, name, displayName, displayUnits, 0.0, CounterType.Metric, metadata, EventType.Histogram)
{
// In case these properties are not provided, set them to appropriate values.
string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
@ -133,21 +163,18 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
public Quantile[] Quantiles { get; }
}
internal record struct Quantile(double Percentage, double Value);
internal class ErrorPayload : CounterPayload
internal sealed class ErrorPayload : MeterPayload
{
public ErrorPayload(string errorMessage) : this(errorMessage, DateTime.UtcNow)
{
}
public ErrorPayload(string errorMessage, DateTime timestamp) :
base(string.Empty, string.Empty, null, 0.0, timestamp, "Metric", EventType.Error)
public ErrorPayload(string errorMessage, DateTime timestamp, ErrorType errorType = ErrorType.NonFatal)
: base(timestamp, string.Empty, string.Empty, string.Empty, string.Empty, 0.0, CounterType.Metric, null, EventType.Error)
{
ErrorMessage = errorMessage;
ErrorType = errorType;
}
public string ErrorMessage { get; private set; }
public string ErrorMessage { get; }
public ErrorType ErrorType { get; }
}
internal enum EventType : int
@ -157,6 +184,14 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
Histogram,
UpDownCounter,
Error,
InstrumentationStarted,
CounterEnded
}
internal enum ErrorType : int
{
NonFatal,
TracingError,
SessionStartupError
}
}

Просмотреть файл

@ -1,21 +1,45 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
namespace Microsoft.Diagnostics.Monitoring.EventPipe
{
internal static class CounterPayloadExtensions
{
public static string GetDisplay(this ICounterPayload counterPayload)
internal enum DisplayRenderingMode
{
if (counterPayload.CounterType == CounterType.Rate)
Default,
DotnetCounters
}
public static string GetDisplay(this ICounterPayload counterPayload, DisplayRenderingMode displayRenderingMode = DisplayRenderingMode.Default)
{
if (!counterPayload.IsMeter)
{
return $"{counterPayload.DisplayName} ({counterPayload.Unit} / {counterPayload.Interval} sec)";
}
if (!string.IsNullOrEmpty(counterPayload.Unit))
{
return $"{counterPayload.DisplayName} ({counterPayload.Unit})";
if (counterPayload.CounterType == CounterType.Rate)
{
return $"{counterPayload.DisplayName} ({GetUnit(counterPayload.Unit, displayRenderingMode)} / {GetInterval(counterPayload, displayRenderingMode)} sec)";
}
if (!string.IsNullOrEmpty(counterPayload.Unit))
{
return $"{counterPayload.DisplayName} ({counterPayload.Unit})";
}
}
return $"{counterPayload.DisplayName}";
}
private static string GetUnit(string unit, DisplayRenderingMode displayRenderingMode)
{
if (displayRenderingMode == DisplayRenderingMode.DotnetCounters && string.Equals(unit, "count", StringComparison.OrdinalIgnoreCase))
{
return "Count";
}
return unit;
}
private static string GetInterval(ICounterPayload payload, DisplayRenderingMode displayRenderingMode) =>
displayRenderingMode == DisplayRenderingMode.DotnetCounters ? payload.Series.ToString() : payload.Interval.ToString();
}
}

Просмотреть файл

@ -41,6 +41,10 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
/// </summary>
string Metadata { get; }
EventType EventType { get; set; }
EventType EventType { get; }
bool IsMeter { get; }
int Series { get; }
}
}

Просмотреть файл

@ -59,7 +59,12 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
eventSource.Dynamic.All += traceEvent => {
try
{
if (traceEvent.TryGetCounterPayload(_filter, _sessionId, out ICounterPayload counterPayload))
if (traceEvent.TryGetCounterPayload(new CounterConfiguration(_filter)
{
SessionId = _sessionId,
MaxHistograms = Settings.MaxHistograms,
MaxTimeseries = Settings.MaxTimeSeries
}, out ICounterPayload counterPayload))
{
ExecuteCounterLoggerAction((metricLogger) => metricLogger.Log(counterPayload));
}

Просмотреть файл

@ -8,9 +8,25 @@ using Microsoft.Diagnostics.Tracing;
namespace Microsoft.Diagnostics.Monitoring.EventPipe
{
internal class CounterConfiguration
{
public CounterConfiguration(CounterFilter filter)
{
CounterFilter = filter ?? throw new ArgumentNullException(nameof(filter));
}
public CounterFilter CounterFilter { get; }
public string SessionId { get; set; }
public int MaxHistograms { get; set; }
public int MaxTimeseries { get; set; }
}
internal static class TraceEventExtensions
{
public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterFilter filter, string sessionId, out ICounterPayload payload)
public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterConfiguration counterConfiguration, out ICounterPayload payload)
{
payload = null;
@ -24,12 +40,12 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
string counterName = payloadFields["Name"].ToString();
string metadata = payloadFields["Metadata"].ToString();
int seriesValue = GetInterval(series);
//CONSIDER
//Concurrent counter sessions do not each get a separate interval. Instead the payload
//for _all_ the counters changes the Series to be the lowest specified interval, on a per provider basis.
//Currently the CounterFilter will remove any data whose Series doesn't match the requested interval.
if (!filter.IsIncluded(traceEvent.ProviderName, counterName, GetInterval(series)))
if (!counterConfiguration.CounterFilter.IsIncluded(traceEvent.ProviderName, counterName, seriesValue))
{
return false;
}
@ -58,7 +74,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
// Note that dimensional data such as pod and namespace are automatically added in prometheus and azure monitor scenarios.
// We no longer added it here.
payload = new CounterPayload(
payload = new StandardCounterPayload(
traceEvent.TimeStamp,
traceEvent.ProviderName,
counterName, displayName,
@ -66,68 +82,69 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
value,
counterType,
intervalSec,
seriesValue / 1000,
metadata);
return true;
}
if (sessionId != null && MonitoringSourceConfiguration.SystemDiagnosticsMetricsProviderName.Equals(traceEvent.ProviderName))
if (counterConfiguration.SessionId != null && MonitoringSourceConfiguration.SystemDiagnosticsMetricsProviderName.Equals(traceEvent.ProviderName))
{
if (traceEvent.EventName == "BeginInstrumentReporting")
{
// Do we want to log something for this?
//HandleBeginInstrumentReporting(traceEvent);
HandleBeginInstrumentReporting(traceEvent, counterConfiguration, out payload);
}
if (traceEvent.EventName == "HistogramValuePublished")
{
HandleHistogram(traceEvent, filter, sessionId, out payload);
HandleHistogram(traceEvent, counterConfiguration, out payload);
}
else if (traceEvent.EventName == "GaugeValuePublished")
{
HandleGauge(traceEvent, filter, sessionId, out payload);
HandleGauge(traceEvent, counterConfiguration, out payload);
}
else if (traceEvent.EventName == "CounterRateValuePublished")
{
HandleCounterRate(traceEvent, filter, sessionId, out payload);
HandleCounterRate(traceEvent, counterConfiguration, out payload);
}
else if (traceEvent.EventName == "UpDownCounterRateValuePublished")
{
HandleUpDownCounterValue(traceEvent, filter, sessionId, out payload);
HandleUpDownCounterValue(traceEvent, counterConfiguration, out payload);
}
else if (traceEvent.EventName == "TimeSeriesLimitReached")
{
HandleTimeSeriesLimitReached(traceEvent, sessionId, out payload);
HandleTimeSeriesLimitReached(traceEvent, counterConfiguration, out payload);
}
else if (traceEvent.EventName == "HistogramLimitReached")
{
HandleHistogramLimitReached(traceEvent, sessionId, out payload);
HandleHistogramLimitReached(traceEvent, counterConfiguration, out payload);
}
else if (traceEvent.EventName == "Error")
{
HandleError(traceEvent, sessionId, out payload);
HandleError(traceEvent, counterConfiguration, out payload);
}
else if (traceEvent.EventName == "ObservableInstrumentCallbackError")
{
HandleObservableInstrumentCallbackError(traceEvent, sessionId, out payload);
HandleObservableInstrumentCallbackError(traceEvent, counterConfiguration, out payload);
}
else if (traceEvent.EventName == "MultipleSessionsNotSupportedError")
{
HandleMultipleSessionsNotSupportedError(traceEvent, sessionId, out payload);
HandleMultipleSessionsNotSupportedError(traceEvent, counterConfiguration, out payload);
}
return payload != null;
}
return false;
}
private static void HandleGauge(TraceEvent obj, CounterFilter filter, string sessionId, out ICounterPayload payload)
private static void HandleGauge(TraceEvent obj, CounterConfiguration counterConfiguration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)obj.PayloadValue(0);
if (payloadSessionId != sessionId)
if (payloadSessionId != counterConfiguration.SessionId)
{
return;
}
@ -139,7 +156,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
string tags = (string)obj.PayloadValue(5);
string lastValueText = (string)obj.PayloadValue(6);
if (!filter.IsIncluded(meterName, instrumentName))
if (!counterConfiguration.CounterFilter.IsIncluded(meterName, instrumentName))
{
return;
}
@ -157,13 +174,30 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
}
}
private static void HandleCounterRate(TraceEvent traceEvent, CounterFilter filter, string sessionId, out ICounterPayload payload)
private static void HandleBeginInstrumentReporting(TraceEvent traceEvent, CounterConfiguration configuration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)traceEvent.PayloadValue(0);
if (payloadSessionId != configuration.SessionId)
{
return;
}
string meterName = (string)traceEvent.PayloadValue(1);
//string meterVersion = (string)obj.PayloadValue(2);
string instrumentName = (string)traceEvent.PayloadValue(3);
payload = new InstrumentationStartedPayload(meterName, instrumentName, traceEvent.TimeStamp);
}
private static void HandleCounterRate(TraceEvent traceEvent, CounterConfiguration counterConfiguration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)traceEvent.PayloadValue(0);
if (payloadSessionId != sessionId)
if (payloadSessionId != counterConfiguration.SessionId)
{
return;
}
@ -175,31 +209,27 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
string tags = (string)traceEvent.PayloadValue(5);
string rateText = (string)traceEvent.PayloadValue(6);
if (!filter.IsIncluded(meterName, instrumentName))
if (double.TryParse(rateText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double value))
{
return;
}
if (double.TryParse(rateText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double rate))
{
payload = new RatePayload(meterName, instrumentName, null, unit, tags, rate, filter.DefaultIntervalSeconds, traceEvent.TimeStamp);
// UpDownCounter reports the value, not the rate - this is different than how Counter behaves.
payload = new UpDownCounterPayload(meterName, instrumentName, null, unit, tags, value, traceEvent.TimeStamp);
}
else
{
// for observable instruments we assume the lack of data is meaningful and remove it from the UI
// this happens when the ObservableCounter callback function throws an exception
// or when the ObservableCounter doesn't include a measurement for a particular set of tag values.
// this happens when the ObservableUpDownCounter callback function throws an exception
// or when the ObservableUpDownCounter doesn't include a measurement for a particular set of tag values.
payload = new CounterEndedPayload(meterName, instrumentName, traceEvent.TimeStamp);
}
}
private static void HandleUpDownCounterValue(TraceEvent traceEvent, CounterFilter filter, string sessionId, out ICounterPayload payload)
private static void HandleUpDownCounterValue(TraceEvent traceEvent, CounterConfiguration configuration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)traceEvent.PayloadValue(0);
if (payloadSessionId != sessionId || traceEvent.Version < 1) // Version 1 added the value field.
if (payloadSessionId != configuration.SessionId || traceEvent.Version < 1) // Version 1 added the value field.
{
return;
}
@ -212,19 +242,16 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
//string rateText = (string)traceEvent.PayloadValue(6); // Not currently using rate for UpDownCounters.
string valueText = (string)traceEvent.PayloadValue(7);
if (!filter.IsIncluded(meterName, instrumentName))
{
if (!configuration.CounterFilter.IsIncluded(meterName, instrumentName)) {
return;
}
if (double.TryParse(valueText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double value))
{
if (double.TryParse(valueText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double value)) {
// UpDownCounter reports the value, not the rate - this is different than how Counter behaves.
payload = new UpDownCounterPayload(meterName, instrumentName, null, unit, tags, value, traceEvent.TimeStamp);
}
else
{
else {
// for observable instruments we assume the lack of data is meaningful and remove it from the UI
// this happens when the ObservableUpDownCounter callback function throws an exception
// or when the ObservableUpDownCounter doesn't include a measurement for a particular set of tag values.
@ -232,12 +259,13 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
}
}
private static void HandleHistogram(TraceEvent obj, CounterFilter filter, string sessionId, out ICounterPayload payload)
private static void HandleHistogram(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)obj.PayloadValue(0);
if (payloadSessionId != sessionId)
if (payloadSessionId != configuration.SessionId)
{
return;
}
@ -249,7 +277,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
string tags = (string)obj.PayloadValue(5);
string quantilesText = (string)obj.PayloadValue(6);
if (!filter.IsIncluded(meterName, instrumentName))
if (!configuration.CounterFilter.IsIncluded(meterName, instrumentName))
{
return;
}
@ -261,60 +289,61 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
private static void HandleHistogramLimitReached(TraceEvent obj, string sessionId, out ICounterPayload payload)
private static void HandleHistogramLimitReached(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)obj.PayloadValue(0);
if (payloadSessionId != sessionId)
if (payloadSessionId != configuration.SessionId)
{
return;
}
string errorMessage = $"Warning: Histogram tracking limit reached. Not all data is being shown. The limit can be changed with maxHistograms but will use more memory in the target process.";
payload = new ErrorPayload(errorMessage);
}
private static void HandleTimeSeriesLimitReached(TraceEvent obj, string sessionId, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)obj.PayloadValue(0);
if (payloadSessionId != sessionId)
{
return;
}
string errorMessage = "Warning: Time series tracking limit reached. Not all data is being shown. The limit can be changed with maxTimeSeries but will use more memory in the target process.";
string errorMessage = $"Warning: Histogram tracking limit ({configuration.MaxHistograms}) reached. Not all data is being shown." + Environment.NewLine +
"The limit can be changed but will use more memory in the target process.";
payload = new ErrorPayload(errorMessage, obj.TimeStamp);
}
private static void HandleError(TraceEvent obj, string sessionId, out ICounterPayload payload)
private static void HandleTimeSeriesLimitReached(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)obj.PayloadValue(0);
if (payloadSessionId != configuration.SessionId)
{
return;
}
string errorMessage = $"Warning: Time series tracking limit ({configuration.MaxTimeseries}) reached. Not all data is being shown. The limit can be changed but will use more memory in the target process.";
payload = new ErrorPayload(errorMessage, obj.TimeStamp);
}
private static void HandleError(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)obj.PayloadValue(0);
string error = (string)obj.PayloadValue(1);
if (sessionId != payloadSessionId)
if (configuration.SessionId != payloadSessionId)
{
return;
}
string errorMessage = "Error reported from target process:" + Environment.NewLine + error;
payload = new ErrorPayload(errorMessage, obj.TimeStamp);
payload = new ErrorPayload(errorMessage, obj.TimeStamp, ErrorType.TracingError);
}
private static void HandleMultipleSessionsNotSupportedError(TraceEvent obj, string sessionId, out ICounterPayload payload)
private static void HandleMultipleSessionsNotSupportedError(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)obj.PayloadValue(0);
if (payloadSessionId == sessionId)
if (payloadSessionId == configuration.SessionId)
{
// If our session is the one that is running then the error is not for us,
// it is for some other session that came later
@ -325,18 +354,18 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
string errorMessage = "Error: Another metrics collection session is already in progress for the target process, perhaps from another tool? " + Environment.NewLine +
"Concurrent sessions are not supported.";
payload = new ErrorPayload(errorMessage, obj.TimeStamp);
payload = new ErrorPayload(errorMessage, obj.TimeStamp, ErrorType.SessionStartupError);
}
}
private static void HandleObservableInstrumentCallbackError(TraceEvent obj, string sessionId, out ICounterPayload payload)
private static void HandleObservableInstrumentCallbackError(TraceEvent obj, CounterConfiguration configuration, out ICounterPayload payload)
{
payload = null;
string payloadSessionId = (string)obj.PayloadValue(0);
string error = (string)obj.PayloadValue(1);
if (payloadSessionId != sessionId)
if (payloadSessionId != configuration.SessionId)
{
return;
}
@ -358,11 +387,11 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
{
continue;
}
if (!double.TryParse(keyValParts[0], out double key))
if (!double.TryParse(keyValParts[0], NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double key))
{
continue;
}
if (!double.TryParse(keyValParts[1], out double val))
if (!double.TryParse(keyValParts[1], NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double val))
{
continue;
}

Просмотреть файл

@ -39,7 +39,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
_sessionStarted = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
}
public async Task Process(DiagnosticsClient client, TimeSpan duration, CancellationToken token)
public async Task Process(DiagnosticsClient client, TimeSpan duration, bool resumeRuntime, CancellationToken token)
{
//No need to guard against reentrancy here, since the calling pipeline does this already.
IDisposable registration = token.Register(() => TryCancelCompletionSources(token));
@ -53,7 +53,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
// Allows the event handling routines to stop processing before the duration expires.
Func<Task> stopFunc = () => Task.Run(() => { streamProvider.StopProcessing(); });
Stream sessionStream = await streamProvider.ProcessEvents(client, duration, token).ConfigureAwait(false);
Stream sessionStream = await streamProvider.ProcessEvents(client, duration, resumeRuntime, token).ConfigureAwait(false);
if (!_sessionStarted.TrySetResult(true))
{

Просмотреть файл

@ -21,7 +21,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
_stopProcessingSource = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
}
public async Task<Stream> ProcessEvents(DiagnosticsClient client, TimeSpan duration, CancellationToken cancellationToken)
public async Task<Stream> ProcessEvents(DiagnosticsClient client, TimeSpan duration, bool resumeRuntime, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
@ -29,6 +29,17 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
try
{
session = await client.StartEventPipeSessionAsync(_sourceConfig.GetProviders(), _sourceConfig.RequestRundown, _sourceConfig.BufferSizeInMB, cancellationToken).ConfigureAwait(false);
if (resumeRuntime)
{
try
{
await client.ResumeRuntimeAsync(cancellationToken).ConfigureAwait(false);
}
catch (UnsupportedCommandException)
{
// Noop if the command is unknown since the target process is most likely a 3.1 app.
}
}
}
catch (EndOfStreamException e)
{

Просмотреть файл

@ -35,7 +35,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
{
try
{
return _processor.Value.Process(Client, Settings.Duration, token);
return _processor.Value.Process(Client, Settings.Duration, Settings.ResumeRuntime, token);
}
catch (InvalidOperationException e)
{

Просмотреть файл

@ -8,5 +8,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
internal class EventSourcePipelineSettings
{
public TimeSpan Duration { get; set; }
public bool ResumeRuntime { get; set; }
}
}

Просмотреть файл

@ -39,6 +39,8 @@
<ItemGroup>
<InternalsVisibleTo Include="dotnet-monitor" />
<InternalsVisibleTo Include="dotnet-counters" />
<InternalsVisibleTo Include="DotnetCounters.UnitTests" />
<InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests" />
<InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.WebApi" />
<InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.Tool.UnitTests" />

Просмотреть файл

@ -31,7 +31,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
{
//It is important that the underlying stream be completely read, or disposed.
//If rundown is enabled, the underlying stream must be drained or disposed, or the app hangs.
using Stream eventStream = await _provider.Value.ProcessEvents(Client, Settings.Duration, token).ConfigureAwait(false);
using Stream eventStream = await _provider.Value.ProcessEvents(Client, Settings.Duration, Settings.ResumeRuntime, token).ConfigureAwait(false); ;
await _onStreamAvailable(eventStream, token).ConfigureAwait(false);
}

Просмотреть файл

@ -58,7 +58,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.Triggers.EventCounter
public bool HasSatisfiedCondition(TraceEvent traceEvent)
{
// Filter to the counter of interest before forwarding to the implementation
if (traceEvent.TryGetCounterPayload(_filter, null, out ICounterPayload payload))
if (traceEvent.TryGetCounterPayload(new CounterConfiguration(_filter), out ICounterPayload payload))
{
return _impl.HasSatisfiedCondition(payload);
}

Просмотреть файл

@ -56,7 +56,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.Triggers.SystemDiagnosticsM
public bool HasSatisfiedCondition(TraceEvent traceEvent)
{
// Filter to the counter of interest before forwarding to the implementation
if (traceEvent.TryGetCounterPayload(_filter, _sessionId, out ICounterPayload payload))
if (traceEvent.TryGetCounterPayload(new CounterConfiguration(_filter) { SessionId = _sessionId }, out ICounterPayload payload))
{
return _impl.HasSatisfiedCondition(payload);
}

Просмотреть файл

@ -26,6 +26,8 @@
<ItemGroup>
<InternalsVisibleTo Include="dotnet-monitor" />
<InternalsVisibleTo Include="dotnet-counters" />
<InternalsVisibleTo Include="DotnetCounters.UnitTests" />
<InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.EventPipe" />
<InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests" />
<InternalsVisibleTo Include="Microsoft.Diagnostics.Monitoring.Tool.UnitTests" />

Просмотреть файл

@ -7,40 +7,32 @@ using System.CommandLine;
using System.CommandLine.IO;
using System.CommandLine.Rendering;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Diagnostics.Monitoring;
using Microsoft.Diagnostics.Monitoring.EventPipe;
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Diagnostics.Tools.Counters.Exporters;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Internal.Common.Utils;
namespace Microsoft.Diagnostics.Tools.Counters
{
public class CounterMonitor
internal class CounterMonitor : ICountersLogger
{
private const int BufferDelaySecs = 1;
private int _processId;
private int _interval;
private CounterSet _counterList;
private CancellationToken _ct;
private IConsole _console;
private ICounterRenderer _renderer;
private string _output;
private bool _pauseCmdSet;
private readonly TaskCompletionSource<int> _shouldExit;
private bool _resumeRuntime;
private readonly TaskCompletionSource<ReturnCode> _shouldExit;
private DiagnosticsClient _diagnosticsClient;
private EventPipeSession _session;
private readonly string _metricsEventSourceSessionId;
private int _maxTimeSeries;
private int _maxHistograms;
private TimeSpan _duration;
private string _metricsEventSourceSessionId;
private MetricsPipelineSettings _settings;
private class ProviderEventState
{
@ -54,10 +46,10 @@ namespace Microsoft.Diagnostics.Tools.Counters
{
_pauseCmdSet = false;
_metricsEventSourceSessionId = Guid.NewGuid().ToString();
_shouldExit = new TaskCompletionSource<int>();
_shouldExit = new TaskCompletionSource<ReturnCode>();
}
private void DynamicAllMonitor(TraceEvent obj)
private void DynamicAllMonitor(ICounterPayload obj)
{
if (_shouldExit.Task.IsCompleted)
{
@ -69,51 +61,32 @@ namespace Microsoft.Diagnostics.Tools.Counters
// If we are paused, ignore the event.
// There's a potential race here between the two tasks but not a huge deal if we miss by one event.
_renderer.ToggleStatus(_pauseCmdSet);
if (obj.ProviderName == "System.Diagnostics.Metrics")
if (obj is ErrorPayload errorPayload)
{
if (obj.EventName == "BeginInstrumentReporting")
_renderer.SetErrorText(errorPayload.ErrorMessage);
switch (errorPayload.ErrorType)
{
HandleBeginInstrumentReporting(obj);
}
if (obj.EventName == "HistogramValuePublished")
{
HandleHistogram(obj);
}
else if (obj.EventName == "GaugeValuePublished")
{
HandleGauge(obj);
}
else if (obj.EventName == "CounterRateValuePublished")
{
HandleCounterRate(obj);
}
else if (obj.EventName == "UpDownCounterRateValuePublished")
{
HandleUpDownCounterValue(obj);
}
else if (obj.EventName == "TimeSeriesLimitReached")
{
HandleTimeSeriesLimitReached(obj);
}
else if (obj.EventName == "HistogramLimitReached")
{
HandleHistogramLimitReached(obj);
}
else if (obj.EventName == "Error")
{
HandleError(obj);
}
else if (obj.EventName == "ObservableInstrumentCallbackError")
{
HandleObservableInstrumentCallbackError(obj);
}
else if (obj.EventName == "MultipleSessionsNotSupportedError")
{
HandleMultipleSessionsNotSupportedError(obj);
case ErrorType.SessionStartupError:
_shouldExit.TrySetResult(ReturnCode.SessionCreationError);
break;
case ErrorType.TracingError:
_shouldExit.TrySetResult(ReturnCode.TracingError);
break;
}
}
else if (obj.EventName == "EventCounters")
else if (obj is CounterEndedPayload counterEnded)
{
_renderer.CounterStopped(counterEnded);
}
else if (obj.IsMeter)
{
MeterInstrumentEventObserved(obj.Provider, obj.Timestamp);
if (obj is not InstrumentationStartedPayload)
{
_renderer.CounterPayloadReceived((CounterPayload)obj, _pauseCmdSet);
}
}
else
{
HandleDiagnosticCounter(obj);
}
@ -137,245 +110,16 @@ namespace Microsoft.Diagnostics.Tools.Counters
}
}
private void HandleBeginInstrumentReporting(TraceEvent obj)
private void HandleDiagnosticCounter(ICounterPayload payload)
{
string sessionId = (string)obj.PayloadValue(0);
string meterName = (string)obj.PayloadValue(1);
// string instrumentName = (string)obj.PayloadValue(3);
if (sessionId != _metricsEventSourceSessionId)
{
return;
}
MeterInstrumentEventObserved(meterName, obj.TimeStamp);
}
private void HandleCounterRate(TraceEvent obj)
{
string sessionId = (string)obj.PayloadValue(0);
string meterName = (string)obj.PayloadValue(1);
//string meterVersion = (string)obj.PayloadValue(2);
string instrumentName = (string)obj.PayloadValue(3);
string unit = (string)obj.PayloadValue(4);
string tags = (string)obj.PayloadValue(5);
string rateText = (string)obj.PayloadValue(6);
if (sessionId != _metricsEventSourceSessionId)
{
return;
}
MeterInstrumentEventObserved(meterName, obj.TimeStamp);
// the value might be an empty string indicating no measurement was provided this collection interval
if (double.TryParse(rateText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double rate))
{
CounterPayload payload = new RatePayload(meterName, instrumentName, null, unit, tags, rate, _interval, obj.TimeStamp);
_renderer.CounterPayloadReceived(payload, _pauseCmdSet);
}
}
private void HandleGauge(TraceEvent obj)
{
string sessionId = (string)obj.PayloadValue(0);
string meterName = (string)obj.PayloadValue(1);
//string meterVersion = (string)obj.PayloadValue(2);
string instrumentName = (string)obj.PayloadValue(3);
string unit = (string)obj.PayloadValue(4);
string tags = (string)obj.PayloadValue(5);
string lastValueText = (string)obj.PayloadValue(6);
if (sessionId != _metricsEventSourceSessionId)
{
return;
}
MeterInstrumentEventObserved(meterName, obj.TimeStamp);
// the value might be an empty string indicating no measurement was provided this collection interval
if (double.TryParse(lastValueText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double lastValue))
{
CounterPayload payload = new GaugePayload(meterName, instrumentName, null, unit, tags, lastValue, obj.TimeStamp);
_renderer.CounterPayloadReceived(payload, _pauseCmdSet);
}
else
{
// for observable instruments we assume the lack of data is meaningful and remove it from the UI
CounterPayload payload = new RatePayload(meterName, instrumentName, null, unit, tags, 0, _interval, obj.TimeStamp);
_renderer.CounterStopped(payload);
}
}
private void HandleUpDownCounterValue(TraceEvent obj)
{
if (obj.Version < 1) // Version 1 added the value field.
{
return;
}
string sessionId = (string)obj.PayloadValue(0);
string meterName = (string)obj.PayloadValue(1);
//string meterVersion = (string)obj.PayloadValue(2);
string instrumentName = (string)obj.PayloadValue(3);
string unit = (string)obj.PayloadValue(4);
string tags = (string)obj.PayloadValue(5);
//string rateText = (string)obj.PayloadValue(6); // Not currently using rate for UpDownCounters.
string valueText = (string)obj.PayloadValue(7);
if (sessionId != _metricsEventSourceSessionId)
{
return;
}
MeterInstrumentEventObserved(meterName, obj.TimeStamp);
// the value might be an empty string indicating no measurement was provided this collection interval
if (double.TryParse(valueText, NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double value))
{
// UpDownCounter reports the value, not the rate - this is different than how Counter behaves, and is thus treated as a gauge.
CounterPayload payload = new GaugePayload(meterName, instrumentName, null, unit, tags, value, obj.TimeStamp);
_renderer.CounterPayloadReceived(payload, _pauseCmdSet);
}
else
{
// for observable instruments we assume the lack of data is meaningful and remove it from the UI
CounterPayload payload = new RatePayload(meterName, instrumentName, null, unit, tags, 0, _interval, obj.TimeStamp);
_renderer.CounterStopped(payload);
}
}
private void HandleHistogram(TraceEvent obj)
{
string sessionId = (string)obj.PayloadValue(0);
string meterName = (string)obj.PayloadValue(1);
//string meterVersion = (string)obj.PayloadValue(2);
string instrumentName = (string)obj.PayloadValue(3);
string unit = (string)obj.PayloadValue(4);
string tags = (string)obj.PayloadValue(5);
string quantilesText = (string)obj.PayloadValue(6);
if (sessionId != _metricsEventSourceSessionId)
{
return;
}
MeterInstrumentEventObserved(meterName, obj.TimeStamp);
KeyValuePair<double, double>[] quantiles = ParseQuantiles(quantilesText);
foreach ((double key, double val) in quantiles)
{
CounterPayload payload = new PercentilePayload(meterName, instrumentName, null, unit, AppendQuantile(tags, $"Percentile={key * 100}"), val, obj.TimeStamp);
_renderer.CounterPayloadReceived(payload, _pauseCmdSet);
}
}
private void HandleHistogramLimitReached(TraceEvent obj)
{
string sessionId = (string)obj.PayloadValue(0);
if (sessionId != _metricsEventSourceSessionId)
{
return;
}
_renderer.SetErrorText(
$"Warning: Histogram tracking limit ({_maxHistograms}) reached. Not all data is being shown." + Environment.NewLine +
"The limit can be changed with --maxHistograms but will use more memory in the target process."
);
}
private void HandleTimeSeriesLimitReached(TraceEvent obj)
{
string sessionId = (string)obj.PayloadValue(0);
if (sessionId != _metricsEventSourceSessionId)
{
return;
}
_renderer.SetErrorText(
$"Warning: Time series tracking limit ({_maxTimeSeries}) reached. Not all data is being shown." + Environment.NewLine +
"The limit can be changed with --maxTimeSeries but will use more memory in the target process."
);
}
private void HandleError(TraceEvent obj)
{
string sessionId = (string)obj.PayloadValue(0);
string error = (string)obj.PayloadValue(1);
if (sessionId != _metricsEventSourceSessionId)
{
return;
}
_renderer.SetErrorText(
"Error reported from target process:" + Environment.NewLine +
error
);
_shouldExit.TrySetResult((int)ReturnCode.TracingError);
}
private void HandleObservableInstrumentCallbackError(TraceEvent obj)
{
string sessionId = (string)obj.PayloadValue(0);
string error = (string)obj.PayloadValue(1);
if (sessionId != _metricsEventSourceSessionId)
{
return;
}
_renderer.SetErrorText(
"Exception thrown from an observable instrument callback in the target process:" + Environment.NewLine +
error
);
}
private void HandleMultipleSessionsNotSupportedError(TraceEvent obj)
{
string runningSessionId = (string)obj.PayloadValue(0);
if (runningSessionId == _metricsEventSourceSessionId)
{
// If our session is the one that is running then the error is not for us,
// it is for some other session that came later
return;
}
_renderer.SetErrorText(
"Error: Another metrics collection session is already in progress for the target process, perhaps from another tool?" + Environment.NewLine +
"Concurrent sessions are not supported.");
_shouldExit.TrySetResult((int)ReturnCode.SessionCreationError);
}
private static KeyValuePair<double, double>[] ParseQuantiles(string quantileList)
{
string[] quantileParts = quantileList.Split(';', StringSplitOptions.RemoveEmptyEntries);
List<KeyValuePair<double, double>> quantiles = new();
foreach (string quantile in quantileParts)
{
string[] keyValParts = quantile.Split('=', StringSplitOptions.RemoveEmptyEntries);
if (keyValParts.Length != 2)
{
continue;
}
if (!double.TryParse(keyValParts[0], NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double key))
{
continue;
}
if (!double.TryParse(keyValParts[1], NumberStyles.Number | NumberStyles.Float, CultureInfo.InvariantCulture, out double val))
{
continue;
}
quantiles.Add(new KeyValuePair<double, double>(key, val));
}
return quantiles.ToArray();
}
private static string AppendQuantile(string tags, string quantile) => string.IsNullOrEmpty(tags) ? quantile : $"{tags},{quantile}";
private void HandleDiagnosticCounter(TraceEvent obj)
{
IDictionary<string, object> payloadVal = (IDictionary<string, object>)(obj.PayloadValue(0));
IDictionary<string, object> payloadFields = (IDictionary<string, object>)(payloadVal["Payload"]);
// If it's not a counter we asked for, ignore it.
string name = payloadFields["Name"].ToString();
if (!_counterList.Contains(obj.ProviderName, name))
{
return;
}
// init providerEventState if this is the first time we've seen an event from this provider
if (!_providerEventStates.TryGetValue(obj.ProviderName, out ProviderEventState providerState))
if (!_providerEventStates.TryGetValue(payload.Provider, out ProviderEventState providerState))
{
providerState = new ProviderEventState()
{
FirstReceiveTimestamp = obj.TimeStamp
FirstReceiveTimestamp = payload.Timestamp
};
_providerEventStates.Add(obj.ProviderName, providerState);
_providerEventStates.Add(payload.Provider, providerState);
}
// we give precedence to instrument events over diagnostic counter events. If we are seeing
@ -385,46 +129,21 @@ namespace Microsoft.Diagnostics.Tools.Counters
return;
}
CounterPayload payload;
if (payloadFields["CounterType"].Equals("Sum"))
{
payload = new RatePayload(
obj.ProviderName,
name,
payloadFields["DisplayName"].ToString(),
payloadFields["DisplayUnits"].ToString(),
null,
(double)payloadFields["Increment"],
_interval,
obj.TimeStamp);
}
else
{
payload = new GaugePayload(
obj.ProviderName,
name,
payloadFields["DisplayName"].ToString(),
payloadFields["DisplayUnits"].ToString(),
null,
(double)payloadFields["Mean"],
obj.TimeStamp);
}
// If we saw the first event for this provider recently then a duplicate instrument event may still be
// coming. We'll buffer this event for a while and then render it if it remains unduplicated for
// a while.
// This is all best effort, if we do show the DiagnosticCounter event and then an instrument event shows up
// later the renderer may obsserve some odd behavior like changes in the counter metadata, oddly timed reporting
// later the renderer may observe some odd behavior like changes in the counter metadata, oddly timed reporting
// intervals, or counters that stop reporting.
// I'm gambling this is good enough that the behavior will never be seen in practice, but if it is we could
// either adjust the time delay or try to improve how the renderers handle it.
if (providerState.FirstReceiveTimestamp + TimeSpan.FromSeconds(BufferDelaySecs) >= obj.TimeStamp)
if (providerState.FirstReceiveTimestamp + TimeSpan.FromSeconds(BufferDelaySecs) >= payload.Timestamp)
{
_bufferedEvents.Enqueue(payload);
_bufferedEvents.Enqueue((CounterPayload)payload);
}
else
{
_renderer.CounterPayloadReceived(payload, _pauseCmdSet);
_renderer.CounterPayloadReceived((CounterPayload)payload, _pauseCmdSet);
}
}
@ -439,7 +158,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
while (_bufferedEvents.Count != 0)
{
CounterPayload payload = _bufferedEvents.Peek();
ProviderEventState providerEventState = _providerEventStates[payload.ProviderName];
ProviderEventState providerEventState = _providerEventStates[payload.Provider];
if (providerEventState.InstrumentEventObserved)
{
_bufferedEvents.Dequeue();
@ -461,37 +180,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
}
}
private void StopMonitor()
{
try
{
_session?.Stop();
}
catch (EndOfStreamException ex)
{
// If the app we're monitoring exits abruptly, this may throw in which case we just swallow the exception and exit gracefully.
Debug.WriteLine($"[ERROR] {ex}");
}
// We may time out if the process ended before we sent StopTracing command. We can just exit in that case.
catch (TimeoutException)
{
}
// On Unix platforms, we may actually get a PNSE since the pipe is gone with the process, and Runtime Client Library
// does not know how to distinguish a situation where there is no pipe to begin with, or where the process has exited
// before dotnet-counters and got rid of a pipe that once existed.
// Since we are catching this in StopMonitor() we know that the pipe once existed (otherwise the exception would've
// been thrown in StartMonitor directly)
catch (PlatformNotSupportedException)
{
}
// On non-abrupt exits, the socket may be already closed by the runtime and we won't be able to send a stop request through it.
catch (ServerNotAvailableException)
{
}
_renderer.Stop();
}
public async Task<int> Monitor(
public async Task<ReturnCode> Monitor(
CancellationToken ct,
List<string> counter_list,
string counters,
@ -515,7 +204,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
ValidateNonNegative(maxTimeSeries, nameof(maxTimeSeries));
if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ValidateArgumentsForAttach(processId, name, diagnosticPort, out _processId))
{
return (int)ReturnCode.ArgumentError;
return ReturnCode.ArgumentError;
}
ct.Register(() => _shouldExit.TrySetResult((int)ReturnCode.Ok));
@ -526,7 +215,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
bool useAnsi = vTerm.IsEnabled;
if (holder == null)
{
return (int)ReturnCode.Ok;
return ReturnCode.Ok;
}
try
{
@ -535,37 +224,37 @@ namespace Microsoft.Diagnostics.Tools.Counters
// provider list so we need to ignore it in that case
_counterList = ConfigureCounters(counters, _processId != 0 ? counter_list : null);
_ct = ct;
_interval = refreshInterval;
_maxHistograms = maxHistograms;
_maxTimeSeries = maxTimeSeries;
_renderer = new ConsoleWriter(useAnsi);
_diagnosticsClient = holder.Client;
_resumeRuntime = resumeRuntime;
_duration = duration;
int ret = await Start().ConfigureAwait(false);
_settings = new MetricsPipelineSettings();
_settings.Duration = duration == TimeSpan.Zero ? Timeout.InfiniteTimeSpan : duration;
_settings.MaxHistograms = maxHistograms;
_settings.MaxTimeSeries = maxTimeSeries;
_settings.CounterIntervalSeconds = refreshInterval;
_settings.ResumeRuntime = resumeRuntime;
_settings.CounterGroups = GetEventPipeProviders();
await using MetricsPipeline eventCounterPipeline = new(holder.Client, _settings, new[] { this });
ReturnCode ret = await Start(eventCounterPipeline, ct).ConfigureAwait(false);
ProcessLauncher.Launcher.Cleanup();
return ret;
}
catch (OperationCanceledException)
{
try
{
_session.Stop();
}
catch (Exception) { } // Swallow all exceptions for now.
//Cancellation token should automatically stop the session
console.Out.WriteLine($"Complete");
return (int)ReturnCode.Ok;
return ReturnCode.Ok;
}
}
}
catch (CommandLineErrorException e)
{
console.Error.WriteLine(e.Message);
return (int)ReturnCode.ArgumentError;
return ReturnCode.ArgumentError;
}
}
public async Task<int> Collect(
public async Task<ReturnCode> Collect(
CancellationToken ct,
List<string> counter_list,
string counters,
@ -591,7 +280,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
ValidateNonNegative(maxTimeSeries, nameof(maxTimeSeries));
if (!ProcessLauncher.Launcher.HasChildProc && !CommandUtils.ValidateArgumentsForAttach(processId, name, diagnosticPort, out _processId))
{
return (int)ReturnCode.ArgumentError;
return ReturnCode.ArgumentError;
}
ct.Register(() => _shouldExit.TrySetResult((int)ReturnCode.Ok));
@ -611,16 +300,19 @@ namespace Microsoft.Diagnostics.Tools.Counters
// provider list so we need to ignore it in that case
_counterList = ConfigureCounters(counters, _processId != 0 ? counter_list : null);
_ct = ct;
_interval = refreshInterval;
_maxHistograms = maxHistograms;
_maxTimeSeries = maxTimeSeries;
_settings = new MetricsPipelineSettings();
_settings.Duration = duration == TimeSpan.Zero ? Timeout.InfiniteTimeSpan : duration;
_settings.MaxHistograms = maxHistograms;
_settings.MaxTimeSeries = maxTimeSeries;
_settings.CounterIntervalSeconds = refreshInterval;
_settings.ResumeRuntime = resumeRuntime;
_settings.CounterGroups = GetEventPipeProviders();
_output = output;
_diagnosticsClient = holder.Client;
_duration = duration;
if (_output.Length == 0)
{
_console.Error.WriteLine("Output cannot be an empty string");
return (int)ReturnCode.ArgumentError;
return ReturnCode.ArgumentError;
}
if (format == CountersExportFormat.csv)
{
@ -644,27 +336,24 @@ namespace Microsoft.Diagnostics.Tools.Counters
else
{
_console.Error.WriteLine($"The output format {format} is not a valid output format.");
return (int)ReturnCode.ArgumentError;
return ReturnCode.ArgumentError;
}
_resumeRuntime = resumeRuntime;
int ret = await Start().ConfigureAwait(false);
await using MetricsPipeline eventCounterPipeline = new(holder.Client, _settings, new[] { this });
ReturnCode ret = await Start(pipeline: eventCounterPipeline, ct).ConfigureAwait(false);
return ret;
}
catch (OperationCanceledException)
{
try
{
_session.Stop();
}
catch (Exception) { } // session.Stop() can throw if target application already stopped before we send the stop command.
return (int)ReturnCode.Ok;
//Cancellation token should automatically stop the session
return ReturnCode.Ok;
}
}
}
catch (CommandLineErrorException e)
{
console.Error.WriteLine(e.Message);
return (int)ReturnCode.ArgumentError;
return ReturnCode.ArgumentError;
}
}
@ -814,71 +503,20 @@ namespace Microsoft.Diagnostics.Tools.Counters
}
}
private EventPipeProvider[] GetEventPipeProviders()
{
// EventSources support EventCounter based metrics directly
IEnumerable<EventPipeProvider> eventCounterProviders = _counterList.Providers.Select(
providerName => new EventPipeProvider(providerName, EventLevel.Error, 0, new Dictionary<string, string>()
{{ "EventCounterIntervalSec", _interval.ToString() }}));
//System.Diagnostics.Metrics EventSource supports the new Meter/Instrument APIs
const long TimeSeriesValues = 0x2;
StringBuilder metrics = new();
foreach (string provider in _counterList.Providers)
private EventPipeCounterGroup[] GetEventPipeProviders() =>
_counterList.Providers.Select(provider => new EventPipeCounterGroup
{
if (metrics.Length != 0)
{
metrics.Append(',');
}
if (_counterList.IncludesAllCounters(provider))
{
metrics.Append(provider);
}
else
{
string[] providerCounters = _counterList.GetCounters(provider).Select(counter => $"{provider}\\{counter}").ToArray();
metrics.Append(string.Join(',', providerCounters));
}
}
EventPipeProvider metricsEventSourceProvider =
new("System.Diagnostics.Metrics", EventLevel.Informational, TimeSeriesValues,
new Dictionary<string, string>()
{
{ "SessionId", _metricsEventSourceSessionId },
{ "Metrics", metrics.ToString() },
{ "RefreshInterval", _interval.ToString() },
{ "MaxTimeSeries", _maxTimeSeries.ToString() },
{ "MaxHistograms", _maxHistograms.ToString() }
}
);
ProviderName = provider,
CounterNames = _counterList.GetCounters(provider).ToArray()
}).ToArray();
return eventCounterProviders.Append(metricsEventSourceProvider).ToArray();
}
private Task<int> Start()
private async Task<ReturnCode> Start(MetricsPipeline pipeline, CancellationToken token)
{
EventPipeProvider[] providers = GetEventPipeProviders();
_renderer.Initialize();
Task monitorTask = new(() => {
Task monitorTask = new(async () => {
try
{
_session = _diagnosticsClient.StartEventPipeSession(providers, false, 10);
if (_resumeRuntime)
{
try
{
_diagnosticsClient.ResumeRuntime();
}
catch (UnsupportedCommandException)
{
// Noop if the command is unknown since the target process is most likely a 3.1 app.
}
}
EventPipeEventSource source = new(_session.EventStream);
source.Dynamic.All += DynamicAllMonitor;
_renderer.EventPipeSourceConnected();
source.Process();
await (await pipeline.StartAsync(token).ConfigureAwait(false)).ConfigureAwait(false);
}
catch (DiagnosticsClientException ex)
{
@ -895,15 +533,8 @@ namespace Microsoft.Diagnostics.Tools.Counters
});
monitorTask.Start();
bool shouldStopAfterDuration = _duration != default(TimeSpan);
Stopwatch durationStopwatch = null;
if (shouldStopAfterDuration)
{
durationStopwatch = Stopwatch.StartNew();
}
while (!_shouldExit.Task.Wait(250))
while (!_shouldExit.Task.Wait(250, token))
{
HandleBufferedEvents();
if (!Console.IsInputRedirected && Console.KeyAvailable)
@ -922,16 +553,37 @@ namespace Microsoft.Diagnostics.Tools.Counters
_pauseCmdSet = false;
}
}
if (shouldStopAfterDuration && durationStopwatch.Elapsed >= _duration)
{
durationStopwatch.Stop();
break;
}
}
StopMonitor();
return _shouldExit.Task;
try
{
await pipeline.StopAsync(token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
catch (PipelineException)
{
}
return await _shouldExit.Task.ConfigureAwait(false);
}
public void Log(ICounterPayload counter)
{
DynamicAllMonitor(counter);
}
public Task PipelineStarted(CancellationToken token)
{
_renderer.EventPipeSourceConnected();
return Task.CompletedTask;
}
public Task PipelineStopped(CancellationToken token)
{
_renderer.Stop();
return Task.CompletedTask;
}
}
}

Просмотреть файл

@ -1,63 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
namespace Microsoft.Diagnostics.Tools.Counters
{
public class CounterPayload
{
public CounterPayload(string providerName, string name, string displayName, string displayUnits, string tags, double value, DateTime timestamp, string type)
{
ProviderName = providerName;
Name = name;
Tags = tags;
Value = value;
Timestamp = timestamp;
CounterType = type;
}
public string ProviderName { get; private set; }
public string Name { get; private set; }
public double Value { get; private set; }
public virtual string DisplayName { get; protected set; }
public string CounterType { get; private set; }
public DateTime Timestamp { get; private set; }
public string Tags { get; private set; }
}
internal class GaugePayload : CounterPayload
{
public GaugePayload(string providerName, string name, string displayName, string displayUnits, string tags, double value, DateTime timestamp) :
base(providerName, name, displayName, displayUnits, tags, value, timestamp, "Metric")
{
// In case these properties are not provided, set them to appropriate values.
string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
DisplayName = !string.IsNullOrEmpty(displayUnits) ? $"{counterName} ({displayUnits})" : counterName;
}
}
internal class RatePayload : CounterPayload
{
public RatePayload(string providerName, string name, string displayName, string displayUnits, string tags, double value, double intervalSecs, DateTime timestamp) :
base(providerName, name, displayName, displayUnits, tags, value, timestamp, "Rate")
{
// In case these properties are not provided, set them to appropriate values.
string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
string unitsName = string.IsNullOrEmpty(displayUnits) ? "Count" : displayUnits;
string intervalName = intervalSecs.ToString() + " sec";
DisplayName = $"{counterName} ({unitsName} / {intervalName})";
}
}
internal class PercentilePayload : CounterPayload
{
public PercentilePayload(string providerName, string name, string displayName, string displayUnits, string tags, double val, DateTime timestamp) :
base(providerName, name, displayName, displayUnits, tags, val, timestamp, "Metric")
{
// In case these properties are not provided, set them to appropriate values.
string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
DisplayName = !string.IsNullOrEmpty(displayUnits) ? $"{counterName} ({displayUnits})" : counterName;
}
}
}

Просмотреть файл

@ -5,6 +5,7 @@ using System;
using System.Globalization;
using System.IO;
using System.Text;
using Microsoft.Diagnostics.Monitoring.EventPipe;
namespace Microsoft.Diagnostics.Tools.Counters.Exporters
{
@ -70,11 +71,11 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
builder
.Append(payload.Timestamp.ToString()).Append(',')
.Append(payload.ProviderName).Append(',')
.Append(payload.DisplayName);
if (!string.IsNullOrEmpty(payload.Tags))
.Append(payload.Provider).Append(',')
.Append(payload.GetDisplay(CounterPayloadExtensions.DisplayRenderingMode.DotnetCounters));
if (!string.IsNullOrEmpty(payload.Metadata))
{
builder.Append('[').Append(payload.Tags.Replace(',', ';')).Append(']');
builder.Append('[').Append(payload.Metadata.Replace(',', ';')).Append(']');
}
builder.Append(',')
.Append(payload.CounterType).Append(',')

Просмотреть файл

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.Diagnostics.Monitoring.EventPipe;
namespace Microsoft.Diagnostics.Tools.Counters.Exporters
{
@ -12,7 +13,7 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
/// ConsoleWriter is an implementation of ICounterRenderer for rendering the counter values in real-time
/// to the console. This is the renderer for the `dotnet-counters monitor` command.
/// </summary>
public class ConsoleWriter : ICounterRenderer
internal class ConsoleWriter : ICounterRenderer
{
/// <summary>Information about an observed provider.</summary>
private class ObservedProvider
@ -257,9 +258,9 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
return;
}
string providerName = payload.ProviderName;
string providerName = payload.Provider;
string name = payload.Name;
string tags = payload.Tags;
string tags = payload.Metadata;
bool redraw = false;
if (!_providers.TryGetValue(providerName, out ObservedProvider provider))
@ -313,9 +314,9 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
{
lock (_lock)
{
string providerName = payload.ProviderName;
string providerName = payload.Provider;
string counterName = payload.Name;
string tags = payload.Tags;
string tags = payload.Metadata;
if (!_providers.TryGetValue(providerName, out ObservedProvider provider))
{

Просмотреть файл

@ -1,16 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Diagnostics.Monitoring.EventPipe;
namespace Microsoft.Diagnostics.Tools.Counters.Exporters
{
public interface ICounterRenderer
internal interface ICounterRenderer
{
void Initialize();
void EventPipeSourceConnected();
void ToggleStatus(bool paused);
void Initialize(); //Maps to started?
void EventPipeSourceConnected(); // PipelineStarted
void ToggleStatus(bool paused); //Occurs every event
void CounterPayloadReceived(CounterPayload payload, bool paused);
void CounterStopped(CounterPayload payload);
void SetErrorText(string errorText);
void Stop();
void Stop(); //Maps to pipeline stopped
}
}

Просмотреть файл

@ -5,6 +5,7 @@ using System;
using System.Globalization;
using System.IO;
using System.Text;
using Microsoft.Diagnostics.Monitoring.EventPipe;
namespace Microsoft.Diagnostics.Tools.Counters.Exporters
{
@ -72,10 +73,10 @@ namespace Microsoft.Diagnostics.Tools.Counters.Exporters
}
builder
.Append("{ \"timestamp\": \"").Append(DateTime.Now.ToString("u")).Append("\", ")
.Append(" \"provider\": \"").Append(JsonEscape(payload.ProviderName)).Append("\", ")
.Append(" \"name\": \"").Append(JsonEscape(payload.DisplayName)).Append("\", ")
.Append(" \"tags\": \"").Append(JsonEscape(payload.Tags)).Append("\", ")
.Append(" \"counterType\": \"").Append(JsonEscape(payload.CounterType)).Append("\", ")
.Append(" \"provider\": \"").Append(JsonEscape(payload.Provider)).Append("\", ")
.Append(" \"name\": \"").Append(JsonEscape(payload.GetDisplay(CounterPayloadExtensions.DisplayRenderingMode.DotnetCounters))).Append("\", ")
.Append(" \"tags\": \"").Append(JsonEscape(payload.Metadata)).Append("\", ")
.Append(" \"counterType\": \"").Append(JsonEscape(payload.CounterType.ToString())).Append("\", ")
.Append(" \"value\": ").Append(payload.Value.ToString(CultureInfo.InvariantCulture)).Append(" },");
}
}

Просмотреть файл

@ -21,7 +21,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
internal static class Program
{
private delegate Task<int> CollectDelegate(
private delegate Task<ReturnCode> CollectDelegate(
CancellationToken ct,
List<string> counter_list,
string counters,
@ -37,7 +37,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
int maxTimeSeries,
TimeSpan duration);
private delegate Task<int> MonitorDelegate(
private delegate Task<ReturnCode> MonitorDelegate(
CancellationToken ct,
List<string> counter_list,
string counters,

Просмотреть файл

@ -23,6 +23,7 @@
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.NETCore.Client\Microsoft.Diagnostics.NETCore.Client.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Diagnostics.Monitoring.EventPipe\Microsoft.Diagnostics.Monitoring.EventPipe.csproj" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -57,11 +57,11 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests
public IEnumerable<ICounterPayload> Metrics => _metrics.Values;
public void Log(ICounterPayload metric)
public void Log(ICounterPayload payload)
{
string key = CreateKey(metric);
string key = CreateKey(payload);
_metrics[key] = metric;
_metrics[key] = payload;
// Complete the task source if the last expected key was removed.
if (_expectedCounters.Remove(key) && _expectedCounters.Count == 0)

Просмотреть файл

@ -477,7 +477,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests
// Add some variance between -5 and 5 milliseconds to simulate "real" timestamp
_lastTimestamp = _lastTimestamp.Value.AddMilliseconds((10 * _random.NextDouble()) - 5);
return new CounterPayload(
return new StandardCounterPayload(
_lastTimestamp.Value,
EventCounterConstants.RuntimeProviderName,
EventCounterConstants.CpuUsageCounterName,
@ -486,6 +486,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe.UnitTests
value,
CounterType.Metric,
actualInterval,
(int)_intervalSeconds,
null);
}
}

Просмотреть файл

@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using Microsoft.Diagnostics.Tools.Counters;
using Microsoft.Diagnostics.Tools.Counters.Exporters;
using Microsoft.Diagnostics.Monitoring.EventPipe;
using Xunit;
namespace DotnetCounters.UnitTests
@ -25,7 +26,7 @@ namespace DotnetCounters.UnitTests
DateTime start = DateTime.Now;
for (int i = 0; i < 100; i++)
{
exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", "", "", i, 1, start + TimeSpan.FromSeconds(i)), false);
exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", string.Empty, string.Empty, i, 1, start + TimeSpan.FromSeconds(i)), false);
}
exporter.Stop();
@ -67,7 +68,7 @@ namespace DotnetCounters.UnitTests
DateTime start = DateTime.Now;
for (int i = 0; i < 10; i++)
{
exporter.CounterPayloadReceived(new GaugePayload("myProvider", "counterOne", "Counter One", "", "", i, start + TimeSpan.FromSeconds(i)), false);
exporter.CounterPayloadReceived(new GaugePayload("myProvider", "counterOne", "Counter One", string.Empty, null, i, start + TimeSpan.FromSeconds(i)), false);
}
exporter.Stop();
@ -110,7 +111,7 @@ namespace DotnetCounters.UnitTests
DateTime start = DateTime.Now;
for (int i = 0; i < 100; i++)
{
exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", "", "", i, 60, start + TimeSpan.FromSeconds(i)), false);
exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", string.Empty, null, i, 60, start + TimeSpan.FromSeconds(i)), false);
}
exporter.Stop();
@ -152,7 +153,7 @@ namespace DotnetCounters.UnitTests
DateTime start = DateTime.Now;
for (int i = 0; i < 100; i++)
{
exporter.CounterPayloadReceived(new RatePayload("myProvider", "allocRateGen", "Allocation Rate Gen", "MB", "", i, 60, start + TimeSpan.FromSeconds(i)), false);
exporter.CounterPayloadReceived(new RatePayload("myProvider", "allocRateGen", "Allocation Rate Gen", "MB", string.Empty, i, 60, start + TimeSpan.FromSeconds(i)), false);
}
exporter.Stop();

Просмотреть файл

@ -6,6 +6,8 @@ using System.IO;
using Microsoft.Diagnostics.Tools.Counters;
using Microsoft.Diagnostics.Tools.Counters.Exporters;
using Newtonsoft.Json;
using Microsoft.Diagnostics.Tools.Counters;
using Microsoft.Diagnostics.Monitoring.EventPipe;
using Xunit;
#pragma warning disable CA1507 // Use nameof to express symbol names
@ -26,7 +28,7 @@ namespace DotnetCounters.UnitTests
DateTime start = DateTime.Now;
for (int i = 0; i < 10; i++)
{
exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", "", "", 1, 1, start + TimeSpan.FromSeconds(i)), false);
exporter.CounterPayloadReceived(new RatePayload("myProvider", "incrementingCounterOne", "Incrementing Counter One", string.Empty, string.Empty, 1, 1, start + TimeSpan.FromSeconds(i)), false);
}
exporter.Stop();
@ -57,7 +59,7 @@ namespace DotnetCounters.UnitTests
DateTime start = DateTime.Now;
for (int i = 0; i < 10; i++)
{
exporter.CounterPayloadReceived(new GaugePayload("myProvider", "counterOne", "Counter One", "", "", 1, start + TimeSpan.FromSeconds(i)), false);
exporter.CounterPayloadReceived(new GaugePayload("myProvider", "counterOne", "Counter One", string.Empty, string.Empty, 1, start + TimeSpan.FromSeconds(i)), false);
}
exporter.Stop();
@ -88,7 +90,7 @@ namespace DotnetCounters.UnitTests
DateTime start = DateTime.Now;
for (int i = 0; i < 20; i++)
{
exporter.CounterPayloadReceived(new GaugePayload("myProvider", "heapSize", "Heap Size", "MB", "", i, start + TimeSpan.FromSeconds(i)), false);
exporter.CounterPayloadReceived(new GaugePayload("myProvider", "heapSize", "Heap Size", "MB", string.Empty, i, start + TimeSpan.FromSeconds(i)), false);
}
exporter.Stop();
@ -122,7 +124,7 @@ namespace DotnetCounters.UnitTests
DateTime start = DateTime.Now;
for (int i = 0; i < 20; i++)
{
exporter.CounterPayloadReceived(new RatePayload("myProvider", "heapSize", "Heap Size", "MB", "", 0, 60, start + TimeSpan.FromSeconds(i)), false);
exporter.CounterPayloadReceived(new RatePayload("myProvider", "heapSize", "Heap Size", "MB", string.Empty, 0, 60, start + TimeSpan.FromSeconds(i)), false);
}
exporter.Stop();