diff --git a/.github/component_owners.yml b/.github/component_owners.yml
index 57fe851901..8bec8de3df 100644
--- a/.github/component_owners.yml
+++ b/.github/component_owners.yml
@@ -37,6 +37,7 @@ components:
- ejsmith
src/OpenTelemetry.Instrumentation.EventCounters/:
- hananiel
+ - mic-max
src/OpenTelemetry.Instrumentation.GrpcCore/:
- pcwiese
src/OpenTelemetry.Instrumentation.Hangfire/:
@@ -99,6 +100,7 @@ components:
- ejsmith
test/OpenTelemetry.Instrumentation.EventCounters.Tests/:
- hananiel
+ - mic-max
test/OpenTelemetry.Instrumentation.GrpcCore.Tests/:
- pcwiese
test/OpenTelemetry.Instrumentation.Hangfire.Tests/:
diff --git a/src/OpenTelemetry.Instrumentation.EventCounters/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.EventCounters/CHANGELOG.md
index 85f97c807a..165812f4e6 100644
--- a/src/OpenTelemetry.Instrumentation.EventCounters/CHANGELOG.md
+++ b/src/OpenTelemetry.Instrumentation.EventCounters/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## Unreleased
+
+* Simplified implementation. EventSources must be explicitly configured to be
+ listened to now.
+ ([#620](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/620))
+
## 0.1.0-alpha.1
Released 2022-Jul-12
diff --git a/src/OpenTelemetry.Instrumentation.EventCounters/EventCounterListener.cs b/src/OpenTelemetry.Instrumentation.EventCounters/EventCounterListener.cs
deleted file mode 100644
index b8ee562d94..0000000000
--- a/src/OpenTelemetry.Instrumentation.EventCounters/EventCounterListener.cs
+++ /dev/null
@@ -1,189 +0,0 @@
-//
-// Copyright The OpenTelemetry Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Diagnostics.Metrics;
-using System.Diagnostics.Tracing;
-using System.Reflection;
-
-namespace OpenTelemetry.Instrumentation.EventCounters;
-
-internal class EventCounterListener : EventListener
-{
- internal static readonly AssemblyName AssemblyName = typeof(EventCounterListener).Assembly.GetName();
- internal static readonly string InstrumentationName = AssemblyName.Name;
- internal static readonly string InstrumentationVersion = AssemblyName.Version.ToString();
-
- private readonly bool isInitialized = false;
- private readonly Meter meter;
- private readonly EventCounterMetricsOptions options;
- private readonly ConcurrentDictionary metricInstruments = new();
- private readonly ConcurrentDictionary lastValue = new();
- private readonly ConcurrentBag eventSources = new();
-
- public EventCounterListener(EventCounterMetricsOptions options)
- {
- this.meter = new Meter(InstrumentationName, InstrumentationVersion);
- this.options = options ?? throw new ArgumentNullException(nameof(options));
-
- this.isInitialized = true;
- this.EnablePendingEventSources(); // Some OnEventSourceCreated may have fired before constructor, enable them
- }
-
- private enum InstrumentType
- {
- ObservableGauge,
- ObservableCounter,
- }
-
- private Dictionary EnableEventArgs => new() { ["EventCounterIntervalSec"] = this.options.RefreshIntervalSecs.ToString(), };
-
- protected override void OnEventSourceCreated(EventSource source)
- {
- // TODO: Add Configuration options to selectively subscribe to EventCounters
- try
- {
- if (!this.isInitialized)
- {
- this.eventSources.Add(source);
- }
- else
- {
- this.EnableEvents(source, EventLevel.Verbose, EventKeywords.All, this.EnableEventArgs);
- }
- }
- catch (Exception ex)
- {
- EventCountersInstrumentationEventSource.Log.ErrorEventCounter(source.Name, ex.Message);
- }
- }
-
- protected override void OnEventWritten(EventWrittenEventArgs eventData)
- {
- if (!eventData.EventName.Equals("EventCounters") || !this.isInitialized)
- {
- return;
- }
-
- try
- {
- if (eventData.Payload.Count > 0 && eventData.Payload[0] is IDictionary eventPayload)
- {
- this.ExtractAndPostMetric(eventData.EventSource.Name, eventPayload);
- }
- else
- {
- EventCountersInstrumentationEventSource.Log.IgnoreEventWrittenAsEventPayloadNotParseable(eventData.EventSource.Name);
- }
- }
- catch (Exception ex)
- {
- EventCountersInstrumentationEventSource.Log.ErrorEventCounter(eventData.EventName, ex.ToString());
- }
- }
-
- private void ExtractAndPostMetric(string eventSourceName, IDictionary eventPayload)
- {
- try
- {
- bool calculateRate = false;
- double actualValue = 0;
-
- string counterName = string.Empty;
- string counterDisplayName = string.Empty;
- InstrumentType instrumentType = InstrumentType.ObservableGauge;
-
- foreach (KeyValuePair payload in eventPayload)
- {
- var key = payload.Key;
- if (key.Equals("Name", StringComparison.OrdinalIgnoreCase))
- {
- counterName = payload.Value.ToString();
- }
- else
- if (key.Equals("DisplayName", StringComparison.OrdinalIgnoreCase))
- {
- counterDisplayName = payload.Value.ToString();
- }
- else if (key.Equals("Mean", StringComparison.OrdinalIgnoreCase))
- {
- instrumentType = InstrumentType.ObservableGauge;
- actualValue = Convert.ToDouble(payload.Value);
- }
- else if (key.Equals("Increment", StringComparison.OrdinalIgnoreCase))
- {
- // Increment indicates we have to calculate rate.
- instrumentType = InstrumentType.ObservableCounter;
- calculateRate = true;
- actualValue = Convert.ToDouble(payload.Value);
- }
- }
-
- this.RecordMetric(eventSourceName, counterName, counterDisplayName, instrumentType, actualValue, calculateRate);
- }
- catch (Exception ex)
- {
- EventCountersInstrumentationEventSource.Log.EventCountersInstrumentationWarning("ExtractMetric", ex.Message);
- }
- }
-
- private void RecordMetric(string eventSourceName, string counterName, string displayName, InstrumentType instrumentType, double value, bool calculateRate)
- {
- var metricKey = new MetricKey(eventSourceName, counterName);
- var description = string.IsNullOrEmpty(displayName) ? counterName : displayName;
- this.lastValue[metricKey] = calculateRate ? value / this.options.RefreshIntervalSecs : value;
- switch (instrumentType)
- {
- case InstrumentType.ObservableCounter:
- this.metricInstruments.TryAdd(metricKey, this.meter.CreateObservableCounter(counterName, () => this.ObserveDouble(metricKey), description: description));
- break;
-
- case InstrumentType.ObservableGauge:
- this.metricInstruments.TryAdd(metricKey, this.meter.CreateObservableGauge(counterName, () => this.ObserveDouble(metricKey), description: description));
- break;
- }
- }
-
- private double ObserveDouble(MetricKey key) => this.lastValue[key];
-
- private void EnablePendingEventSources()
- {
- foreach (var source in this.eventSources)
- {
- this.EnableEvents(source, EventLevel.Verbose, EventKeywords.All, this.EnableEventArgs);
- }
- }
-
- private class MetricKey
- {
- public MetricKey(string eventSourceName, string counterName)
- {
- this.EventSourceName = eventSourceName;
- this.CounterName = counterName;
- }
-
- public string EventSourceName { get; private set; }
-
- public string CounterName { get; private set; }
-
- public override int GetHashCode() => (this.EventSourceName, this.CounterName).GetHashCode();
-
- public override bool Equals(object obj) =>
- obj is MetricKey nextKey && this.EventSourceName == nextKey.EventSourceName && this.CounterName == nextKey.CounterName;
- }
-}
diff --git a/src/OpenTelemetry.Instrumentation.EventCounters/EventCounterMetricsOptions.cs b/src/OpenTelemetry.Instrumentation.EventCounters/EventCounterMetricsOptions.cs
deleted file mode 100644
index 0152da017e..0000000000
--- a/src/OpenTelemetry.Instrumentation.EventCounters/EventCounterMetricsOptions.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-//
-// Copyright The OpenTelemetry Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-namespace OpenTelemetry.Instrumentation.EventCounters;
-
-///
-/// EventCounterMetrics Options.
-///
-public class EventCounterMetricsOptions
-{
- ///
- /// Gets or sets the subscription interval in seconds.
- ///
- public int RefreshIntervalSecs { get; set; } = 60;
-}
diff --git a/src/OpenTelemetry.Instrumentation.EventCounters/EventCountersInstrumentationEventSource.cs b/src/OpenTelemetry.Instrumentation.EventCounters/EventCountersInstrumentationEventSource.cs
index f3d0d431ce..071f963352 100644
--- a/src/OpenTelemetry.Instrumentation.EventCounters/EventCountersInstrumentationEventSource.cs
+++ b/src/OpenTelemetry.Instrumentation.EventCounters/EventCountersInstrumentationEventSource.cs
@@ -22,25 +22,37 @@ namespace OpenTelemetry.Instrumentation.EventCounters;
/// EventSource events emitted from the project.
///
[EventSource(Name = "OpenTelemetry-Instrumentation-EventCounters")]
-internal class EventCountersInstrumentationEventSource : EventSource
+internal sealed class EventCountersInstrumentationEventSource : EventSource
{
public static readonly EventCountersInstrumentationEventSource Log = new();
- [Event(1, Message = "Error occurred while processing eventCounter, EventCounter: {0}, Exception: {2}", Level = EventLevel.Error)]
- public void ErrorEventCounter(string counterName, string exception)
+ [Event(1, Level = EventLevel.Warning, Message = "Error while writing event from source: {0} - {1}.")]
+ internal void ErrorWhileWritingEvent(string eventSourceName, string exceptionMessage)
{
- this.WriteEvent(1, counterName, exception);
+ this.WriteEvent(1, eventSourceName, exceptionMessage);
}
- [Event(2, Level = EventLevel.Warning, Message = @"Ignoring event written from EventSource: {0} as payload is not IDictionary to extract metrics.")]
- public void IgnoreEventWrittenAsEventPayloadNotParseable(string eventSourceName)
+ [Event(2, Level = EventLevel.Warning, Message = "Event data payload not parseable from source: {0}.")]
+ internal void IgnoreEventWrittenEventArgsPayloadNotParseable(string eventSourceName)
+ {
+ this.WriteEvent(2, eventSourceName);
+ }
+
+ [Event(3, Level = EventLevel.Warning, Message = "Event data has no name from source: {0}.")]
+ internal void IgnoreEventWrittenEventArgsWithoutName(string eventSourceName)
+ {
+ this.WriteEvent(3, eventSourceName);
+ }
+
+ [Event(4, Level = EventLevel.Warning, Message = "Event data payload problem with values of Mean, Increment from source: {0}.")]
+ internal void IgnoreMeanIncrementConflict(string eventSourceName)
{
this.WriteEvent(4, eventSourceName);
}
- [Event(3, Level = EventLevel.Warning, Message = @"EventCountersInstrumentation - {0} failed with exception: {1}.")]
- public void EventCountersInstrumentationWarning(string stage, string exceptionMessage)
+ [Event(5, Level = EventLevel.Warning, Message = "Event data has name other than 'EventCounters' from source: {0}.")]
+ internal void IgnoreNonEventCountersName(string eventSourceName)
{
- this.WriteEvent(8, stage, exceptionMessage);
+ this.WriteEvent(5, eventSourceName);
}
}
diff --git a/src/OpenTelemetry.Instrumentation.EventCounters/EventCountersInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.EventCounters/EventCountersInstrumentationOptions.cs
new file mode 100644
index 0000000000..87e1a93e75
--- /dev/null
+++ b/src/OpenTelemetry.Instrumentation.EventCounters/EventCountersInstrumentationOptions.cs
@@ -0,0 +1,59 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace OpenTelemetry.Instrumentation.EventCounters;
+
+///
+/// EventCounterListener Options.
+///
+public class EventCountersInstrumentationOptions
+{
+ private readonly HashSet eventSourceNames = new();
+
+ ///
+ /// Gets or sets the subscription interval in seconds for reading values
+ /// from the configured EventCounters.
+ ///
+ public int RefreshIntervalSecs { get; set; } = 1;
+
+ ///
+ /// Listens to EventCounters from the given EventSource name.
+ ///
+ /// The EventSource names to listen to.
+ public void AddEventSources(params string[] names)
+ {
+ if (names.Contains("System.Runtime"))
+ {
+ throw new NotSupportedException("Use the `OpenTelemetry.Instrumentation.Runtime` or `OpenTelemetry.Instrumentation.Process` instrumentations.");
+ }
+
+ this.eventSourceNames.UnionWith(names);
+ }
+
+ ///
+ /// Returns whether or not an EventSource should be enabled on the EventListener.
+ ///
+ /// The EventSource name.
+ /// true when an EventSource with the name should be enabled.
+ internal bool ShouldListenToSource(string eventSourceName)
+ {
+ return this.eventSourceNames.Contains(eventSourceName);
+ }
+}
diff --git a/src/OpenTelemetry.Instrumentation.EventCounters/EventCountersMetrics.cs b/src/OpenTelemetry.Instrumentation.EventCounters/EventCountersMetrics.cs
new file mode 100644
index 0000000000..e01ce8b968
--- /dev/null
+++ b/src/OpenTelemetry.Instrumentation.EventCounters/EventCountersMetrics.cs
@@ -0,0 +1,137 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics.Metrics;
+using System.Diagnostics.Tracing;
+
+namespace OpenTelemetry.Instrumentation.EventCounters;
+
+///
+/// .NET EventCounters Instrumentation.
+///
+internal sealed class EventCountersMetrics : EventListener
+{
+ internal static readonly Meter MeterInstance = new(typeof(EventCountersMetrics).Assembly.GetName().Name, typeof(EventCountersMetrics).Assembly.GetName().Version.ToString());
+
+ private readonly EventCountersInstrumentationOptions options;
+ private readonly ConcurrentQueue preInitEventSources = new();
+ private readonly ConcurrentDictionary<(string, string), Instrument> instruments = new();
+ private readonly ConcurrentDictionary<(string, string), double> values = new();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The options to define the metrics.
+ public EventCountersMetrics(EventCountersInstrumentationOptions options)
+ {
+ this.options = options;
+
+ while (this.preInitEventSources.TryDequeue(out EventSource eventSource))
+ {
+ if (this.options.ShouldListenToSource(eventSource.Name))
+ {
+ this.EnableEvents(eventSource, EventLevel.LogAlways, EventKeywords.None, GetEnableEventsArguments(this.options));
+ }
+ }
+ }
+
+ ///
+ protected override void OnEventSourceCreated(EventSource source)
+ {
+ if (this.options == null)
+ {
+ this.preInitEventSources.Enqueue(source);
+ }
+ else if (this.options.ShouldListenToSource(source.Name))
+ {
+ this.EnableEvents(source, EventLevel.LogAlways, EventKeywords.None, GetEnableEventsArguments(this.options));
+ }
+ }
+
+ ///
+ protected override void OnEventWritten(EventWrittenEventArgs eventData)
+ {
+ if (this.options == null)
+ {
+ return;
+ }
+
+ var eventSourceName = eventData.EventSource.Name;
+
+ if (eventData.EventName != "EventCounters")
+ {
+ EventCountersInstrumentationEventSource.Log.IgnoreNonEventCountersName(eventSourceName);
+ return;
+ }
+
+ if (eventData.Payload == null || eventData.Payload.Count == 0 || eventData.Payload[0] is not IDictionary payload)
+ {
+ EventCountersInstrumentationEventSource.Log.IgnoreEventWrittenEventArgsPayloadNotParseable(eventSourceName);
+ return;
+ }
+
+ var hasName = payload.TryGetValue("Name", out var nameObj);
+
+ if (!hasName)
+ {
+ EventCountersInstrumentationEventSource.Log.IgnoreEventWrittenEventArgsWithoutName(eventSourceName);
+ return;
+ }
+
+ var name = nameObj.ToString();
+
+ var hasMean = payload.TryGetValue("Mean", out var mean);
+ var hasIncrement = payload.TryGetValue("Increment", out var increment);
+
+ if (!(hasIncrement ^ hasMean))
+ {
+ EventCountersInstrumentationEventSource.Log.IgnoreMeanIncrementConflict(eventSourceName);
+ return;
+ }
+
+ var value = Convert.ToDouble(hasMean ? mean : increment);
+ this.UpdateInstrumentWithEvent(hasMean, eventSourceName, name, value);
+ }
+
+ private static Dictionary GetEnableEventsArguments(EventCountersInstrumentationOptions options) =>
+ new() { { "EventCounterIntervalSec", options.RefreshIntervalSecs.ToString() } };
+
+ private void UpdateInstrumentWithEvent(bool isGauge, string eventSourceName, string name, double value)
+ {
+ try
+ {
+ ValueTuple metricKey = new(eventSourceName, name);
+ _ = this.values.AddOrUpdate(metricKey, value, isGauge ? (_, _) => value : (_, existing) => existing + value);
+
+ var instrumentName = $"EventCounters.{eventSourceName}.{name}";
+
+ if (!this.instruments.ContainsKey(metricKey))
+ {
+ Instrument instrument = isGauge
+ ? MeterInstance.CreateObservableGauge(instrumentName, () => this.values[metricKey])
+ : MeterInstance.CreateObservableCounter(instrumentName, () => this.values[metricKey]);
+ _ = this.instruments.TryAdd(metricKey, instrument);
+ }
+ }
+ catch (Exception ex)
+ {
+ EventCountersInstrumentationEventSource.Log.ErrorWhileWritingEvent(eventSourceName, ex.Message);
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Instrumentation.EventCounters/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.EventCounters/MeterProviderBuilderExtensions.cs
index bb13a85281..daf35c18ef 100644
--- a/src/OpenTelemetry.Instrumentation.EventCounters/MeterProviderBuilderExtensions.cs
+++ b/src/OpenTelemetry.Instrumentation.EventCounters/MeterProviderBuilderExtensions.cs
@@ -31,17 +31,17 @@ public static class MeterProviderBuilderExtensions
/// being configured.
/// Runtime metrics options.
/// The instance of to chain the calls.
- public static MeterProviderBuilder AddEventCounterMetrics(
+ public static MeterProviderBuilder AddEventCountersInstrumentation(
this MeterProviderBuilder builder,
- Action configure = null)
+ Action configure = null)
{
Guard.ThrowIfNull(builder);
- var options = new EventCounterMetricsOptions();
+ var options = new EventCountersInstrumentationOptions();
configure?.Invoke(options);
- var instrumentation = new EventCounterListener(options);
- builder.AddMeter(EventCounterListener.InstrumentationName);
+ var instrumentation = new EventCountersMetrics(options);
+ builder.AddMeter(EventCountersMetrics.MeterInstance.Name);
return builder.AddInstrumentation(() => instrumentation);
}
}
diff --git a/src/OpenTelemetry.Instrumentation.EventCounters/README.md b/src/OpenTelemetry.Instrumentation.EventCounters/README.md
index 7ddad17e82..059f79cc32 100644
--- a/src/OpenTelemetry.Instrumentation.EventCounters/README.md
+++ b/src/OpenTelemetry.Instrumentation.EventCounters/README.md
@@ -1,7 +1,11 @@
-# Event counter instrumentation for OpenTelemetry
+# Event Counters Instrumentation for OpenTelemetry .NET
-This is an [Instrumentation Library](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#instrumentation-library),
-which republishes EventCounters using Metrics Api.
+[![NuGet](https://img.shields.io/nuget/v/OpenTelemetry.Instrumentation.EventCounters.svg)](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.EventCounters)
+[![NuGet](https://img.shields.io/nuget/dt/OpenTelemetry.Instrumentation.EventCounters.svg)](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.EventCounters)
+
+This is an
+[Instrumentation Library](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#instrumentation-library)
+, which **republishes EventCounters using Metrics Api.**
## Steps to enable OpenTelemetry.Instrumentation.EventCounters
@@ -9,62 +13,56 @@ which republishes EventCounters using Metrics Api.
Add a reference to the
[`OpenTelemetry.Instrumentation.EventCounters`](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.EventCounters)
-package. Also, add any other instrumentation & exporters you will need.
+package.
```shell
dotnet add package OpenTelemetry.Instrumentation.EventCounters
```
-### Step 2: Enable EventCounters Instrumentation at application startup
-
-EventCounters instrumentation must be enabled at application startup.
+### Step 2: Enable EventCounters Instrumentation
-The following example demonstrates adding EventCounter events to a
-console application. This example also sets up the OpenTelemetry Console
-exporter, which requires adding the package
-[`OpenTelemetry.Exporter.Console`](https://www.nuget.org/packages/OpenTelemetry.Exporter.Console)
-to the application.
+EventCounters instrumentation should be enabled at application startup using the
+`AddEventCountersInstrumentation` extension on the `MeterProviderBuilder`:
```csharp
-using OpenTelemetry;
-using OpenTelemetry.Metrics;
-
-namespace DotnetMetrics;
-
-public class Program
-{
- public static void Main(string[] args)
- {
- using var meterprovider = Sdk.CreateMeterProviderBuilder()
- .AddEventCounterMetrics(options =>
- {
- options.RefreshIntervalSecs = 5;
- })
- .AddConsoleExporter()
- .Build();
- }
+using var meterProvider = Sdk.CreateMeterProviderBuilder()
+ .AddEventCountersInstrumentation(options => {
+ options.RefreshIntervalSecs = 1;
+ options.Sources.Add("MyEventSourceName");
+ })
+ .AddPrometheusExporter()
+ .Build();
}
```
-Console Output:
-
-```console
+Additionally, this examples sets up the OpenTelemetry Prometheus exporter, which
+requires adding the package
+[`OpenTelemetry.Exporter.Prometheus`](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md)
+to the application.
-Export cpu-usage, CPU Usage, Meter: OpenTelemetry.Instrumentation.EventCounters/0.0.0.0
-(2022-07-12T16:40:37.2639447Z, 2022-07-12T16:40:42.2533747Z] DoubleGauge
-Value: 0
+### Step 3: Create EventCounters
-Export working-set, Working Set, Meter: OpenTelemetry.Instrumentation.EventCounters/0.0.0.0
-(2022-07-12T16:40:37.2666398Z, 2022-07-12T16:40:42.2534452Z] DoubleGauge
-Value: 38
+Learn about
+[EventCounters in .NET](https://docs.microsoft.com/en-us/dotnet/core/diagnostics/event-counters)
+.
-Export gc-heap-size, GC Heap Size, Meter: OpenTelemetry.Instrumentation.EventCounters/0.0.0.0
-(2022-07-12T16:40:37.2667389Z, 2022-07-12T16:40:42.2534456Z] DoubleGauge
-Value: 7
+```csharp
+EventSource eventSource = new("MyEventSource");
+EventCounter eventCounter = new("MyEventCounterName", eventSource);
+eventCounter.WriteMetric(0);
+eventCounter.WriteMetric(1000);
+PollingCounter pollingCounter = new("MyPollingCounterName", eventSource, () => new Random().NextDouble());
```
+## Notes
+
+The metrics will only be available after `EventCounterIntervalSec` seconds.
+Before that nothing will be exported, if anything is present at the Prometheus
+metrics endpoint it is from a prior execution. This is more evident when using
+longer polling intervals.
+
## References
* [OpenTelemetry Project](https://opentelemetry.io/)
diff --git a/test/OpenTelemetry.Instrumentation.EventCounters.Tests/EventCounterListenerTests.cs b/test/OpenTelemetry.Instrumentation.EventCounters.Tests/EventCounterListenerTests.cs
deleted file mode 100644
index eab325222c..0000000000
--- a/test/OpenTelemetry.Instrumentation.EventCounters.Tests/EventCounterListenerTests.cs
+++ /dev/null
@@ -1,199 +0,0 @@
-//
-// Copyright The OpenTelemetry Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using OpenTelemetry.Metrics;
-using Xunit;
-
-namespace OpenTelemetry.Instrumentation.EventCounters.Tests;
-
-public class EventCounterListenerTests
-{
- private const int MaxTimeToAllowForFlush = 10000;
- private MeterProvider meterProvider;
-
- [Fact]
- public async Task SystemMetricsAreCaptured()
- {
- var metricItems = new List();
-
- this.meterProvider = Sdk.CreateMeterProviderBuilder()
- .AddEventCounterMetrics(options =>
- {
- options.RefreshIntervalSecs = 1;
- })
- .AddInMemoryExporter(metricItems)
- .Build();
-
- await Task.Delay(2000);
- this.meterProvider.ForceFlush(MaxTimeToAllowForFlush);
-
- this.meterProvider.Dispose();
-
- Assert.True(metricItems.Count > 1);
- }
-
- [Fact(Skip = "Unstable")]
- public async Task TestEventCounterMetricsAreCaptured()
- {
- const int refreshIntervalSeconds = 1;
- var metricItems = new List();
- this.meterProvider = Sdk.CreateMeterProviderBuilder()
- .AddEventCounterMetrics(options =>
- {
- options.RefreshIntervalSecs = 1;
- })
- .AddInMemoryExporter(metricItems)
- .Build();
- var expected = new[] { 100.3F, 200.1F };
- TestEventCounter.Log.SampleCounter1(expected[0]);
- TestEventCounter.Log.SampleCounter2(expected[1]);
-
- // Wait a little bit over the refresh interval seconds
- await Task.Delay((refreshIntervalSeconds * 1000) + 300);
-
- this.meterProvider.ForceFlush(MaxTimeToAllowForFlush);
-
- this.meterProvider.Dispose();
-
- var counter1 = metricItems.Find(m => m.Name == "mycountername1");
- var counter2 = metricItems.Find(m => m.Name == "mycountername2");
- Assert.NotNull(counter1);
- Assert.NotNull(counter2);
- Assert.Equal(MetricType.DoubleGauge, counter1.MetricType); // EventCounter CounterType is `Mean`
-
- Assert.Equal(expected[0], GetActualValue(counter1));
- Assert.Equal(expected[1], GetActualValue(counter2));
- }
-
- [Fact(Skip = "Unstable")]
- public async Task TestIncrementingEventCounterMetricsAreCaptured()
- {
- const int refreshIntervalSeconds = 1;
- var metricItems = new List();
- this.meterProvider = Sdk.CreateMeterProviderBuilder()
- .AddEventCounterMetrics(options =>
- {
- options.RefreshIntervalSecs = 1;
- })
- .AddInMemoryExporter(metricItems)
- .Build();
-
- TestIncrementingEventCounter.Log.SampleCounter1(1);
- TestIncrementingEventCounter.Log.SampleCounter1(1);
- TestIncrementingEventCounter.Log.SampleCounter1(1);
-
- // Wait a little bit over the refresh interval seconds
- await Task.Delay((refreshIntervalSeconds * 1000) + 300);
-
- this.meterProvider.ForceFlush(MaxTimeToAllowForFlush);
-
- this.meterProvider.Dispose();
-
- var counter = metricItems.Find(m => m.Name == TestIncrementingEventCounter.CounterName);
- Assert.NotNull(counter);
- Assert.Equal(MetricType.DoubleSum, counter.MetricType); // EventCounter CounterType is `Sum`
- Assert.Equal(3, GetActualValue(counter));
- }
-
- [Fact(Skip = "Unstable")]
- public async Task TestPollingCounterMetricsAreCaptured()
- {
- var metricItems = new List();
- const int refreshIntervalSeconds = 1;
-
- this.meterProvider = Sdk.CreateMeterProviderBuilder()
- .AddEventCounterMetrics(options =>
- {
- options.RefreshIntervalSecs = refreshIntervalSeconds;
- })
- .AddInMemoryExporter(metricItems)
- .Build();
-
- int i = 0;
- TestPollingEventCounter.CreateSingleton(() => ++i * 10);
-
- var duration = (refreshIntervalSeconds * 2 * 1000) + 300; // Wait for two refresh intervals to call the valueProvider twice
- await Task.Delay(duration);
-
- this.meterProvider.ForceFlush(MaxTimeToAllowForFlush);
-
- this.meterProvider.Dispose();
-
- var pollingCounter = metricItems.Find(m => m.Name == TestPollingEventCounter.CounterName);
- Assert.NotNull(pollingCounter);
- Assert.Equal(MetricType.DoubleGauge, pollingCounter.MetricType); // Polling Counter is EventCounter CounterType of `Mean`
-
- var expected = i * 10; // The last recorded `Mean` value
- Assert.Equal(expected, GetActualValue(pollingCounter));
- }
-
- [Fact(Skip = "Unstable")]
- public async Task TestIncrementingPollingCounterMetrics()
- {
- var metricItems = new List();
- const int refreshIntervalSeconds = 1;
-
- this.meterProvider = Sdk.CreateMeterProviderBuilder()
- .AddEventCounterMetrics(options =>
- {
- options.RefreshIntervalSecs = refreshIntervalSeconds;
- })
- .AddInMemoryExporter(metricItems)
- .Build();
-
- int i = 1;
-
- TestIncrementingPollingCounter.CreateSingleton(() => i++);
-
- var duration = (refreshIntervalSeconds * 2 * 1000) + 300; // Wait for two refresh intervals to call the valueProvider twice
- await Task.Delay(duration);
-
- this.meterProvider.ForceFlush(MaxTimeToAllowForFlush);
-
- this.meterProvider.Dispose();
-
- var pollingCounter = metricItems.Find(m => m.Name == TestIncrementingPollingCounter.CounterName);
- Assert.NotNull(pollingCounter);
- Assert.Equal(MetricType.DoubleSum, pollingCounter.MetricType); // Polling Counter is EventCounter CounterType of `Sum`
-
- Assert.Equal(1, GetActualValue(pollingCounter));
- }
-
- ///
- /// Event Counters are always Sum or Mean and are always record with `float`.
- ///
- /// Metric to Aggregate.
- /// The Aggregated value.
- private static double GetActualValue(Metric metric)
- {
- double sum = 0;
- foreach (ref readonly var metricPoint in metric.GetMetricPoints())
- {
- if (metric.MetricType.IsSum())
- {
- sum += metricPoint.GetSumDouble();
- }
- else
- {
- sum += metricPoint.GetGaugeLastValueDouble();
- }
- }
-
- return sum;
- }
-}
diff --git a/test/OpenTelemetry.Instrumentation.EventCounters.Tests/EventCountersMetricsTests.cs b/test/OpenTelemetry.Instrumentation.EventCounters.Tests/EventCountersMetricsTests.cs
new file mode 100644
index 0000000000..748e849ed5
--- /dev/null
+++ b/test/OpenTelemetry.Instrumentation.EventCounters.Tests/EventCountersMetricsTests.cs
@@ -0,0 +1,232 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Tracing;
+using System.Threading.Tasks;
+using OpenTelemetry.Metrics;
+using Xunit;
+
+namespace OpenTelemetry.Instrumentation.EventCounters.Tests;
+
+public class EventCountersMetricsTests
+{
+ private const int Delay = 1200;
+
+ [Fact(Skip = "Other tests metrics are being exported here")]
+ public async Task NoMetricsByDefault()
+ {
+ // Arrange
+ List metricItems = new();
+
+ var meterProvider = Sdk.CreateMeterProviderBuilder()
+ .AddEventCountersInstrumentation()
+ .AddInMemoryExporter(metricItems)
+ .Build();
+
+ // Act
+ await Task.Delay(Delay);
+ meterProvider.ForceFlush();
+
+ // Assert
+ Assert.Empty(metricItems);
+ }
+
+ [Fact]
+ public async Task EventCounter()
+ {
+ // Arrange
+ List metricItems = new();
+ EventSource source = new("a");
+ EventCounter counter = new("c", source);
+
+ var meterProvider = Sdk.CreateMeterProviderBuilder()
+ .AddEventCountersInstrumentation(options =>
+ {
+ options.AddEventSources(source.Name);
+ })
+ .AddInMemoryExporter(metricItems)
+ .Build();
+
+ // Act
+ counter.WriteMetric(1997.0202);
+ await Task.Delay(Delay);
+ meterProvider.ForceFlush();
+
+ // Assert
+ var metric = metricItems.Find(x => x.Name == "EventCounters.a.c");
+ Assert.NotNull(metric);
+ Assert.Equal(MetricType.DoubleGauge, metric.MetricType);
+ Assert.Equal(1997.0202, GetActualValue(metric));
+ }
+
+ [Fact]
+ public async Task IncrementingEventCounter()
+ {
+ // Arrange
+ List metricItems = new();
+ EventSource source = new("b");
+ IncrementingEventCounter incCounter = new("inc-c", source);
+
+ var meterProvider = Sdk.CreateMeterProviderBuilder()
+ .AddEventCountersInstrumentation(options =>
+ {
+ options.AddEventSources(source.Name);
+ })
+ .AddInMemoryExporter(metricItems)
+ .Build();
+
+ // Act
+ incCounter.Increment(1);
+ incCounter.Increment(1);
+ incCounter.Increment(1);
+ await Task.Delay(Delay);
+ meterProvider.ForceFlush();
+
+ // Assert
+ var metric = metricItems.Find(x => x.Name == "EventCounters.b.inc-c");
+ Assert.NotNull(metric);
+ Assert.Equal(MetricType.DoubleSum, metric.MetricType);
+ Assert.Equal(3, GetActualValue(metric));
+ }
+
+ [Fact]
+ public async Task PollingCounter()
+ {
+ // Arrange
+ int i = 0;
+ List metricItems = new();
+ EventSource source = new("c");
+ PollingCounter pollCounter = new("poll-c", source, () => ++i * 10);
+
+ var meterProvider = Sdk.CreateMeterProviderBuilder()
+ .AddEventCountersInstrumentation(options =>
+ {
+ options.AddEventSources(source.Name);
+ })
+ .AddInMemoryExporter(metricItems)
+ .Build();
+
+ // Act
+ await Task.Delay(Delay * 2);
+ meterProvider.ForceFlush();
+
+ // Assert
+ var metric = metricItems.Find(x => x.Name == "EventCounters.c.poll-c");
+ Assert.NotNull(metric);
+ Assert.Equal(MetricType.DoubleGauge, metric.MetricType);
+ Assert.Equal(20, GetActualValue(metric));
+ }
+
+ [Fact]
+ public async Task IncrementingPollingCounter()
+ {
+ // Arrange
+ int i = 1;
+ List metricItems = new();
+ EventSource source = new("d");
+ IncrementingPollingCounter incPollCounter = new("inc-poll-c", source, () => i++);
+
+ var meterProvider = Sdk.CreateMeterProviderBuilder()
+ .AddEventCountersInstrumentation(options =>
+ {
+ options.AddEventSources(source.Name);
+ })
+ .AddInMemoryExporter(metricItems)
+ .Build();
+
+ // Act
+ await Task.Delay(Delay * 2);
+ meterProvider.ForceFlush();
+
+ // Assert
+ var metric = metricItems.Find(x => x.Name == "EventCounters.d.inc-poll-c");
+ Assert.NotNull(metric);
+ Assert.Equal(MetricType.DoubleSum, metric.MetricType);
+ Assert.Equal(2, GetActualValue(metric));
+ }
+
+ [Fact]
+ public async Task EventCounterSameNameUsesNewestCreated()
+ {
+ // Arrange
+ List metricItems = new();
+ EventSource source = new("a");
+ EventCounter counter = new("c", source);
+ EventCounter counter2 = new("c", source);
+
+ var meterProvider = Sdk.CreateMeterProviderBuilder()
+ .AddEventCountersInstrumentation(options =>
+ {
+ options.AddEventSources(source.Name);
+ })
+ .AddInMemoryExporter(metricItems)
+ .Build();
+
+ // Act
+ counter2.WriteMetric(1980.1208);
+ counter.WriteMetric(1997.0202);
+ await Task.Delay(Delay);
+ meterProvider.ForceFlush();
+
+ // Assert
+ var metric = metricItems.Find(x => x.Name == "EventCounters.a.c");
+ Assert.NotNull(metric);
+ Assert.Equal(MetricType.DoubleGauge, metric.MetricType);
+
+ // Since `counter2` was created after `counter` it is exported
+ Assert.Equal(1980.1208, GetActualValue(metric));
+ }
+
+ [Fact]
+ public void ThrowExceptionWhenBuilderIsNull()
+ {
+ MeterProviderBuilder builder = null;
+ Assert.Throws(() => builder.AddEventCountersInstrumentation());
+ }
+
+ [Fact]
+ public void ThrowExceptionForUnsupportedEventSources()
+ {
+ var ex = Assert.Throws(() =>
+ {
+ Sdk.CreateMeterProviderBuilder()
+ .AddEventCountersInstrumentation(options =>
+ {
+ options.AddEventSources("System.Runtime");
+ });
+ });
+
+ Assert.Equal("Use the `OpenTelemetry.Instrumentation.Runtime` or `OpenTelemetry.Instrumentation.Process` instrumentations.", ex.Message);
+ }
+
+ // polling and eventcounter with same instrument name?
+
+ private static double GetActualValue(Metric metric)
+ {
+ double sum = 0;
+
+ foreach (ref readonly var metricPoint in metric.GetMetricPoints())
+ {
+ sum += metric.MetricType.IsSum()
+ ? metricPoint.GetSumDouble()
+ : metricPoint.GetGaugeLastValueDouble();
+ }
+
+ return sum;
+ }
+}
diff --git a/test/OpenTelemetry.Instrumentation.EventCounters.Tests/MeterProviderBuilderExtensionsTests.cs b/test/OpenTelemetry.Instrumentation.EventCounters.Tests/MeterProviderBuilderExtensionsTests.cs
deleted file mode 100644
index 22020300ed..0000000000
--- a/test/OpenTelemetry.Instrumentation.EventCounters.Tests/MeterProviderBuilderExtensionsTests.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-// Copyright The OpenTelemetry Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-using System;
-using OpenTelemetry.Metrics;
-using Xunit;
-
-namespace OpenTelemetry.Instrumentation.EventCounters.Tests;
-
-public class MeterProviderBuilderExtensionsTests
-{
- [Fact]
- public void Throws_Exception_When_Builder_Is_Null()
- {
- MeterProviderBuilder builder = null;
-
- Func