зеркало из https://github.com/dotnet/diagnostics.git
Unify counters (#3875)
This commit is contained in:
Родитель
3ae4837a4b
Коммит
6fe1f4f7dd
|
@ -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();
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче