--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Diagnostics.TestHelpers;
+using Xunit.Abstractions;
+using TestRunner = Microsoft.Diagnostics.CommonTestRunner.TestRunner;
+
+namespace CommonTestRunner
+{
+ public static class TestRunnerUtilities
+ {
+ public static async Task<TestRunner> StartProcess(TestConfiguration config, string testArguments, ITestOutputHelper outputHelper, int testProcessTimeout = 60_000)
+ {
+ TestRunner runner = await TestRunner.Create(config, outputHelper, "EventPipeTracee", testArguments).ConfigureAwait(true);
+ await runner.Start(testProcessTimeout).ConfigureAwait(true);
+ return runner;
+ }
+
+ public static async Task ExecuteCollection(
+ Func<CancellationToken, Task> executeCollection,
+ TestRunner testRunner,
+ CancellationToken token)
+ {
+ Task collectionTask = executeCollection(token);
+ await ExecuteCollection(collectionTask, testRunner, token).ConfigureAwait(false);
+ }
+
+ public static async Task ExecuteCollection(
+ Task collectionTask,
+ TestRunner testRunner,
+ CancellationToken token,
+ Func<CancellationToken, Task> waitForPipeline = null)
+ {
+ // Begin event production
+ testRunner.WakeupTracee();
+
+ // Wait for event production to be done
+ testRunner.WaitForSignal();
+
+ try
+ {
+ if (waitForPipeline != null)
+ {
+ await waitForPipeline(token).ConfigureAwait(false);
+ }
+
+ await collectionTask.ConfigureAwait(true);
+ }
+ finally
+ {
+ // Signal for debuggee that it's ok to end/move on.
+ testRunner.WakeupTracee();
+ }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Metrics;
+using Constants = DotnetCounters.UnitTests.TestConstants;
+
+namespace EventPipeTracee
+{
+ internal sealed class CustomMetrics : IDisposable
+ {
+ private Meter _meter;
+ private Counter<int> _counter;
+ private Histogram<float> _histogram;
+
+ public CustomMetrics()
+ {
+ _meter = new(Constants.TestMeterName);
+ _counter = _meter.CreateCounter<int>(Constants.TestCounter, "dollars");
+ _histogram = _meter.CreateHistogram<float>(Constants.TestHistogram, "feet");
+ // consider adding gauge/etc. here
+ }
+
+ public void IncrementCounter(int v = 1)
+ {
+ _counter.Add(v);
+ }
+
+ public void RecordHistogram(float v = 10.0f)
+ {
+ KeyValuePair<string, object> tags = new(Constants.TagKey, Constants.TagValue);
+ _histogram.Record(v, tags);
+ }
+
+ public void Dispose() => _meter?.Dispose();
+ }
+}
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework Condition="'$(BuildProjectFramework)' != ''">$(BuildProjectFramework)</TargetFramework>
<TargetFrameworks Condition="'$(BuildProjectFramework)' == ''">net6.0;net7.0;net8.0</TargetFrameworks>
</PropertyGroup>
+ <ItemGroup>
+ <Compile Include="../dotnet-counters/TestConstants.cs" Link="TestConstants.cs" />
+ </ItemGroup>
+
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.EventSource" Version="$(MicrosoftExtensionsLoggingEventSourceVersion)" />
</ItemGroup>
using System.Diagnostics;
using System.IO.Pipes;
using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
bool spinWait10 = args.Length > 2 && "SpinWait10".Equals(args[2], StringComparison.Ordinal);
string loggerCategory = args[1];
+ bool diagMetrics = args.Any("DiagMetrics".Equals);
+
+ Console.WriteLine($"{pid} EventPipeTracee: DiagMetrics {diagMetrics}");
+
Console.WriteLine($"{pid} EventPipeTracee: start process");
Console.Out.Flush();
Console.WriteLine($"{pid} EventPipeTracee: {DateTime.UtcNow} Awaiting start");
Console.Out.Flush();
+ using CustomMetrics metrics = diagMetrics ? new CustomMetrics() : null;
+
// Wait for server to send something
int input = pipeStream.ReadByte();
Console.WriteLine($"{pid} {DateTime.UtcNow} Starting test body '{input}'");
Console.Out.Flush();
+ CancellationTokenSource recordMetricsCancellationTokenSource = new();
+
+ if (diagMetrics)
+ {
+ _ = Task.Run(async () => {
+
+ // Recording a single value appeared to cause test flakiness due to a race
+ // condition with the timing of when dotnet-counters starts collecting and
+ // when these values are published. Publishing values repeatedly bypasses this problem.
+ while (!recordMetricsCancellationTokenSource.Token.IsCancellationRequested)
+ {
+ recordMetricsCancellationTokenSource.Token.ThrowIfCancellationRequested();
+
+ metrics.IncrementCounter();
+ metrics.RecordHistogram(10.0f);
+ await Task.Delay(1000).ConfigureAwait(true);
+ }
+
+ }).ConfigureAwait(true);
+ }
+
TestBodyCore(customCategoryLogger, appCategoryLogger);
Console.WriteLine($"{pid} EventPipeTracee: signal end of test data");
// Wait for server to send something
input = pipeStream.ReadByte();
+ recordMetricsCancellationTokenSource.Cancel();
+
Console.WriteLine($"{pid} EventPipeTracee {DateTime.UtcNow} Ending remote test process '{input}'");
return 0;
}
using System;
using System.Threading;
using System.Threading.Tasks;
+using CommonTestRunner;
using Microsoft.Diagnostics.TestHelpers;
using Xunit.Abstractions;
using TestRunner = Microsoft.Diagnostics.CommonTestRunner.TestRunner;
public static async Task<TestRunner> StartProcess(TestConfiguration config, string testArguments, ITestOutputHelper outputHelper, int testProcessTimeout = 60_000)
{
- TestRunner runner = await TestRunner.Create(config, outputHelper, "EventPipeTracee", testArguments);
- await runner.Start(testProcessTimeout);
- return runner;
+ return await TestRunnerUtilities.StartProcess(config, testArguments, outputHelper, testProcessTimeout);
}
public static async Task ExecutePipelineWithTracee(
{
Task runTask = await startPipelineAsync(pipeline, token);
- // Begin event production
- testRunner.WakeupTracee();
-
- // Wait for event production to be done
- testRunner.WaitForSignal();
-
- try
- {
+ Func<CancellationToken, Task> waitForPipeline = async (cancellationToken) => {
// Optionally wait on caller before allowing the pipeline to stop.
if (null != waitTaskSource)
{
//Signal for the pipeline to stop
await pipeline.StopAsync(token);
+ };
- //After a pipeline is stopped, we should expect the RunTask to eventually finish
- await runTask;
- }
- finally
- {
- // Signal for debugee that's ok to end/move on.
- testRunner.WakeupTracee();
- }
+ await TestRunnerUtilities.ExecuteCollection(runTask, testRunner, token, waitForPipeline);
}
}
}
List<string> lines = File.ReadLines(fileName).ToList();
Assert.Equal(101, lines.Count); // should be 101 including the headers
- string[] headerTokens = lines[0].Split(',');
- Assert.Equal("Provider", headerTokens[1]);
- Assert.Equal("Counter Name", headerTokens[2]);
- Assert.Equal("Counter Type", headerTokens[3]);
- Assert.Equal("Mean/Increment", headerTokens[4]);
+ ValidateHeaderTokens(lines[0]);
for (int i = 1; i < lines.Count; i++)
{
List<string> lines = File.ReadLines(fileName).ToList();
Assert.Equal(11, lines.Count); // should be 11 including the headers
- string[] headerTokens = lines[0].Split(',');
- Assert.Equal("Provider", headerTokens[1]);
- Assert.Equal("Counter Name", headerTokens[2]);
- Assert.Equal("Counter Type", headerTokens[3]);
- Assert.Equal("Mean/Increment", headerTokens[4]);
-
+ ValidateHeaderTokens(lines[0]);
for (int i = 1; i < lines.Count; i++)
{
List<string> lines = File.ReadLines(fileName).ToList();
Assert.Equal(101, lines.Count); // should be 101 including the headers
- string[] headerTokens = lines[0].Split(',');
- Assert.Equal("Provider", headerTokens[1]);
- Assert.Equal("Counter Name", headerTokens[2]);
- Assert.Equal("Counter Type", headerTokens[3]);
- Assert.Equal("Mean/Increment", headerTokens[4]);
+ ValidateHeaderTokens(lines[0]);
for (int i = 1; i < lines.Count; i++)
{
List<string> lines = File.ReadLines(fileName).ToList();
Assert.Equal(101, lines.Count); // should be 101 including the headers
- string[] headerTokens = lines[0].Split(',');
- Assert.Equal("Provider", headerTokens[1]);
- Assert.Equal("Counter Name", headerTokens[2]);
- Assert.Equal("Counter Type", headerTokens[3]);
- Assert.Equal("Mean/Increment", headerTokens[4]);
+ ValidateHeaderTokens(lines[0]);
for (int i = 1; i < lines.Count; i++)
{
List<string> lines = File.ReadLines(fileName).ToList();
Assert.Equal(101, lines.Count); // should be 101 including the headers
- string[] headerTokens = lines[0].Split(',');
- Assert.Equal("Provider", headerTokens[1]);
- Assert.Equal("Counter Name", headerTokens[2]);
- Assert.Equal("Counter Type", headerTokens[3]);
- Assert.Equal("Mean/Increment", headerTokens[4]);
+ ValidateHeaderTokens(lines[0]);
for (int i = 1; i < lines.Count; i++)
{
List<string> lines = File.ReadLines(fileName).ToList();
Assert.Equal(101, lines.Count); // should be 101 including the headers
- string[] headerTokens = lines[0].Split(',');
- Assert.Equal("Provider", headerTokens[1]);
- Assert.Equal("Counter Name", headerTokens[2]);
- Assert.Equal("Counter Type", headerTokens[3]);
- Assert.Equal("Mean/Increment", headerTokens[4]);
+ ValidateHeaderTokens(lines[0]);
for (int i = 1; i < lines.Count; i++)
{
File.Delete(fileName);
}
}
+
+ internal static void ValidateHeaderTokens(string headerLine)
+ {
+ string[] headerTokens = headerLine.Split(',');
+ Assert.Equal("Provider", headerTokens[TestConstants.ProviderIndex]);
+ Assert.Equal("Counter Name", headerTokens[TestConstants.CounterNameIndex]);
+ Assert.Equal("Counter Type", headerTokens[TestConstants.CounterTypeIndex]);
+ Assert.Equal("Mean/Increment", headerTokens[TestConstants.ValueIndex]);
+ }
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.CommandLine.IO;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using CommonTestRunner;
+using Microsoft.Diagnostics.TestHelpers;
+using Microsoft.Diagnostics.Tools.Counters;
+using Xunit;
+using Xunit.Abstractions;
+using Xunit.Extensions;
+using TestRunner = Microsoft.Diagnostics.CommonTestRunner.TestRunner;
+using Constants = DotnetCounters.UnitTests.TestConstants;
+
+namespace DotnetCounters.UnitTests
+{
+ /// <summary>
+ /// Tests the behavior of CounterMonitor's Collect command.
+ /// </summary>
+ public class CounterMonitorPayloadTests
+ {
+ private enum CounterTypes
+ {
+ Metric, Rate
+ }
+
+ private ITestOutputHelper _outputHelper;
+ private static readonly TimeSpan DefaultTimeout = TimeSpan.FromMinutes(2);
+ private static readonly string SystemRuntimeName = "System.Runtime";
+ private static readonly string TagStart = "[";
+
+ private static HashSet<CounterTypes> ExpectedCounterTypes = new() { CounterTypes.Metric, CounterTypes.Rate };
+
+ public CounterMonitorPayloadTests(ITestOutputHelper outputHelper)
+ {
+ _outputHelper = outputHelper;
+ }
+
+ [SkippableTheory, MemberData(nameof(Configurations))]
+ public async Task TestCounterMonitorCustomMetricsJSON(TestConfiguration configuration)
+ {
+ CheckRuntimeOS();
+ CheckFramework(configuration);
+
+ List<MetricComponents> metricComponents = await GetCounterTraceJSON(configuration, new List<string> { Constants.TestMeterName });
+
+ ValidateCustomMetrics(metricComponents, CountersExportFormat.json);
+ }
+
+ [SkippableTheory, MemberData(nameof(Configurations))]
+ public async Task TestCounterMonitorCustomMetricsCSV(TestConfiguration configuration)
+ {
+ CheckRuntimeOS();
+ CheckFramework(configuration);
+
+ List<MetricComponents> metricComponents = await GetCounterTraceCSV(configuration, new List<string> { Constants.TestMeterName });
+
+ ValidateCustomMetrics(metricComponents, CountersExportFormat.csv);
+ }
+
+ [SkippableTheory, MemberData(nameof(Configurations))]
+ public async Task TestCounterMonitorSystemRuntimeMetricsJSON(TestConfiguration configuration)
+ {
+ CheckRuntimeOS();
+
+ List<MetricComponents> metricComponents = await GetCounterTraceJSON(configuration, new List<string> { SystemRuntimeName });
+
+ ValidateSystemRuntimeMetrics(metricComponents);
+ }
+
+ [SkippableTheory, MemberData(nameof(Configurations))]
+ public async Task TestCounterMonitorSystemRuntimeMetricsCSV(TestConfiguration configuration)
+ {
+ CheckRuntimeOS();
+
+ List<MetricComponents> metricComponents = await GetCounterTraceCSV(configuration, new List<string> { SystemRuntimeName });
+
+ ValidateSystemRuntimeMetrics(metricComponents);
+ }
+
+ private void ValidateSystemRuntimeMetrics(List<MetricComponents> metricComponents)
+ {
+ string[] ExpectedProviders = { "System.Runtime" };
+ Assert.Equal(ExpectedProviders, metricComponents.Select(c => c.ProviderName).ToHashSet());
+
+ // Could also just check the number of counter names
+ HashSet<string> expectedCounterNames = new()
+ {
+ "CPU Usage (%)",
+ "Working Set (MB)",
+ "GC Heap Size (MB)",
+ "Gen 0 GC Count (Count / 1 sec)",
+ "Gen 1 GC Count (Count / 1 sec)",
+ "Gen 2 GC Count (Count / 1 sec)",
+ "ThreadPool Thread Count",
+ "Monitor Lock Contention Count (Count / 1 sec)",
+ "ThreadPool Queue Length",
+ "ThreadPool Completed Work Item Count (Count / 1 sec)",
+ "Allocation Rate (B / 1 sec)",
+ "Number of Active Timers",
+ "GC Fragmentation (%)",
+ "GC Committed Bytes (MB)",
+ "Exception Count (Count / 1 sec)",
+ "% Time in GC since last GC (%)",
+ "Gen 0 Size (B)",
+ "Gen 1 Size (B)",
+ "Gen 2 Size (B)",
+ "LOH Size (B)",
+ "POH (Pinned Object Heap) Size (B)",
+ "Number of Assemblies Loaded",
+ "IL Bytes Jitted (B)",
+ "Number of Methods Jitted",
+ "Time spent in JIT (ms / 1 sec)"
+ };
+
+ Assert.Subset(metricComponents.Select(c => c.CounterName).ToHashSet(), expectedCounterNames);
+
+ Assert.Equal(ExpectedCounterTypes, metricComponents.Select(c => c.CounterType).ToHashSet());
+ }
+
+ private async Task<List<MetricComponents>> GetCounterTraceJSON(TestConfiguration configuration, List<string> counterList)
+ {
+ string path = Path.ChangeExtension(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()), "json");
+
+ Func<List<MetricComponents>> createMetricComponents = () =>
+ {
+ using FileStream metricsFile = File.OpenRead(path);
+ JSONCounterTrace trace = JsonSerializer.Deserialize<JSONCounterTrace>(metricsFile, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
+
+ var providers = trace.events.Select(e => e.provider).ToList();
+ var counterNames = trace.events.Select(e => e.name).ToList();
+ var counterTypes = trace.events.Select(e => e.counterType).ToList();
+ var tags = trace.events.Select(e => e.tags).ToList();
+ var values = trace.events.Select(e => e.value).ToList();
+
+ return CreateMetricComponents(providers, counterNames, counterTypes, tags, values);
+ };
+
+ return await GetCounterTrace(configuration, counterList, path, CountersExportFormat.json, createMetricComponents);
+ }
+
+ private async Task<List<MetricComponents>> GetCounterTraceCSV(TestConfiguration configuration, List<string> counterList)
+ {
+ string path = Path.ChangeExtension(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()), "csv");
+
+ Func<List<MetricComponents>> createMetricComponents = () =>
+ {
+ List<string> lines = File.ReadLines(path).ToList();
+ CSVExporterTests.ValidateHeaderTokens(lines[0]);
+ lines.RemoveAt(0); // Trim the header
+
+ IEnumerable<string[]> splitLines = lines.Select(l => l.Split(","));
+
+ var providers = splitLines.Select(line => line[Constants.ProviderIndex]).ToList();
+ var countersList = splitLines.Select(line => line[Constants.CounterNameIndex]).ToList();
+ var counterNames = countersList.Select(counter => counter.Split(TagStart)[0]).ToList();
+ var counterTypes = splitLines.Select(line => line[Constants.CounterTypeIndex]).ToList();
+ var tags = GetCSVTags(countersList);
+ var values = GetCSVValues(splitLines);
+
+ return CreateMetricComponents(providers, counterNames, counterTypes, tags, values);
+ };
+
+ return await GetCounterTrace(configuration, counterList, path, CountersExportFormat.csv, createMetricComponents);
+ }
+
+ private List<MetricComponents> CreateMetricComponents(List<string> providers, List<string> counterNames, List<string> counterTypes, List<string> tags, List<double> values)
+ {
+ List<MetricComponents> metricComponents = new(providers.Count());
+
+ for (int index = 0; index < providers.Count(); ++index)
+ {
+ CounterTypes type;
+ Enum.TryParse(counterTypes[index], out type);
+
+ metricComponents.Add(new MetricComponents()
+ {
+ ProviderName = providers[index],
+ CounterName = counterNames[index],
+ CounterType = type,
+ Tags = tags[index],
+ Value = values[index]
+ });
+ }
+
+ return metricComponents;
+ }
+
+ private async Task<List<MetricComponents>> GetCounterTrace(TestConfiguration configuration, List<string> counterList, string path, CountersExportFormat exportFormat, Func<List<MetricComponents>> CreateMetricComponents)
+ {
+ try
+ {
+ CounterMonitor monitor = new CounterMonitor();
+
+ using CancellationTokenSource source = new CancellationTokenSource(DefaultTimeout);
+
+ await using var testRunner = await TestRunnerUtilities.StartProcess(configuration, "TestCounterMonitor DiagMetrics", _outputHelper);
+
+ await TestRunnerUtilities.ExecuteCollection((ct) => {
+ return Task.Run(async () =>
+ await monitor.Collect(
+ ct: ct,
+ counter_list: counterList,
+ counters: null,
+ console: new TestConsole(),
+ processId: testRunner.Pid,
+ refreshInterval: 1,
+ format: exportFormat,
+ output: path,
+ name: null,
+ diagnosticPort: null,
+ resumeRuntime: false,
+ maxHistograms: 10,
+ maxTimeSeries: 1000,
+ duration: TimeSpan.FromSeconds(10)));
+ }, testRunner, source.Token);
+
+ return CreateMetricComponents();
+ }
+ finally
+ {
+ try
+ {
+ File.Delete(path);
+ }
+ catch { }
+ }
+ }
+
+ private void ValidateCustomMetrics(List<MetricComponents> metricComponents, CountersExportFormat format)
+ {
+ // Currently not validating timestamp due to https://github.com/dotnet/diagnostics/issues/3905
+
+ HashSet<string> expectedProviders = new() { Constants.TestMeterName };
+ Assert.Equal(expectedProviders, metricComponents.Select(c => c.ProviderName).ToHashSet());
+
+ HashSet<string> expectedCounterNames = new() { Constants.TestHistogramName, Constants.TestCounterName };
+ Assert.Equal(expectedCounterNames, metricComponents.Select(c => c.CounterName).ToHashSet());
+
+ Assert.Equal(ExpectedCounterTypes, metricComponents.Select(c => c.CounterType).ToHashSet());
+
+ string tagSeparator = format == CountersExportFormat.csv ? ";" : ",";
+ string tag = Constants.TagKey + "=" + Constants.TagValue + tagSeparator + Constants.PercentileKey + "=";
+ HashSet<string> expectedTags = new() { $"{tag}{Constants.Quantile50}", $"{tag}{Constants.Quantile95}", $"{tag}{Constants.Quantile99}" };
+ Assert.Equal(expectedTags, metricComponents.Where(c => c.CounterName == Constants.TestHistogramName).Select(c => c.Tags).Distinct());
+ Assert.Empty(metricComponents.Where(c => c.CounterName == Constants.TestCounterName).Where(c => c.Tags != string.Empty));
+
+ var actualCounterValues = metricComponents.Where(c => c.CounterName == Constants.TestCounterName).Select(c => c.Value);
+
+ Assert.NotEmpty(actualCounterValues);
+ double histogramValue = Assert.Single(metricComponents.Where(c => c.CounterName == Constants.TestHistogramName).Select(c => c.Value).Distinct());
+ Assert.Equal(10, histogramValue);
+ }
+
+ private List<string> GetCSVTags(List<string> countersList)
+ {
+ var tags = countersList.Select(counter => {
+ var split = counter.Split(TagStart);
+ return split.Length > 1 ? split[1].Remove(split[1].Length - 1) : string.Empty;
+ }).ToList();
+
+ return tags;
+ }
+
+ private List<double> GetCSVValues(IEnumerable<string[]> splitLines)
+ {
+ return splitLines.Select(line => {
+ return double.TryParse(line[Constants.ValueIndex], out double val) ? val : -1;
+ }).ToList();
+ }
+
+ private void CheckFramework(TestConfiguration configuration)
+ {
+ if (configuration.RuntimeFrameworkVersionMajor < 8)
+ {
+ throw new SkipTestException("Not supported on < .NET 8.0");
+ }
+ }
+
+ private void CheckRuntimeOS()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ throw new SkipTestException("Test instability on OSX");
+ }
+ }
+
+ public static IEnumerable<object[]> Configurations => TestRunner.Configurations;
+
+ private sealed class MetricComponents
+ {
+ public string ProviderName { get; set; }
+ public string CounterName { get; set; }
+ public double Value { get; set; }
+ public string Tags { get; set; }
+ public CounterTypes CounterType { get; set; }
+ }
+
+ private sealed class TestConsole : IConsole
+ {
+ private readonly TestStandardStreamWriter _outWriter;
+ private readonly TestStandardStreamWriter _errorWriter;
+
+ private sealed class TestStandardStreamWriter : IStandardStreamWriter
+ {
+ private StringWriter _writer = new();
+ public void Write(string value) => _writer.Write(value);
+ public void WriteLine(string value) => _writer.WriteLine(value);
+ }
+
+ public TestConsole()
+ {
+ _outWriter = new TestStandardStreamWriter();
+ _errorWriter = new TestStandardStreamWriter();
+ }
+
+ public IStandardStreamWriter Out => _outWriter;
+
+ public bool IsOutputRedirected => true;
+
+ public IStandardStreamWriter Error => _errorWriter;
+
+ public bool IsErrorRedirected => true;
+
+ public bool IsInputRedirected => false;
+ }
+ }
+}
<ItemGroup>
<ProjectReference Include="../../Tools/dotnet-counters/dotnet-counters.csproj" />
+ <ProjectReference Include="../../Microsoft.Diagnostics.TestHelpers/Microsoft.Diagnostics.TestHelpers.csproj" />
+ <ProjectReference Include="../CommonTestRunner/CommonTestRunner.csproj" />
</ItemGroup>
<ItemGroup>
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace DotnetCounters.UnitTests
+{
+ public static class TestConstants
+ {
+ public const string TestCounter = "TestCounter";
+ public const string TestCounterName = TestCounter + " (dollars / 1 sec)";
+ public const string TestHistogram = "TestHistogram";
+ public const string TestHistogramName = TestHistogram + " (feet)";
+ public const string PercentileKey = "Percentile";
+ public const string TagKey = "TestTag";
+ public const string TagValue = "5";
+ public const string TestMeterName = "TestMeter";
+ public const string Quantile50 = "50";
+ public const string Quantile95 = "95";
+ public const string Quantile99 = "99";
+
+ public const int ProviderIndex = 1;
+ public const int CounterNameIndex = 2;
+ public const int CounterTypeIndex = 3;
+ public const int ValueIndex = 4;
+ }
+}