-
Notifications
You must be signed in to change notification settings - Fork 293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create an EventCounter Listener that subscribes to all event counters #365
Merged
utpilla
merged 18 commits into
open-telemetry:main
from
hananiel:EventCounterListenerWithoutConfig
Jul 12, 2022
Merged
Changes from 4 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
c87eef6
Create a EventCounter Listener that subscribes to all event counters
hananiel 7debc15
Fix lint and format errors
hananiel 2cbde61
Merge branch 'main' into EventCounterListenerWithoutConfig
utpilla 290d309
CR feedback
hananiel ef1b937
CR feedback
hananiel 92e9433
Resolve merge conflicts
hananiel 71a56ec
Fix file encoding
hananiel d8bac49
fix line endings
hananiel 5c7bff8
Fix timing issues on build server; Add net6.0
hananiel 002c659
Merge branch 'main' into EventCounterListenerWithoutConfig
cijothomas 89004e1
Add workflow, owners, issueTemplate, resolve CR feedback
hananiel c5d1b5a
Fix MD lint
hananiel 6bfe5c5
Add Changelog
hananiel e36fa65
Merge branch 'main' into EventCounterListenerWithoutConfig
utpilla d44e6ea
Add release date to changelog
hananiel 3788097
Fix markdown
hananiel 9d18b7b
Skip unstable tests
hananiel 362558a
Remove check for dictionary on tryAdd
hananiel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
229 changes: 229 additions & 0 deletions
229
src/OpenTelemetry.Instrumentation.EventCounters/EventCounterListener.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
// <copyright file="EventCounterListener.cs" company="OpenTelemetry Authors"> | ||
// 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. | ||
// </copyright> | ||
|
||
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<MetricKey, Instrument> metricInstruments = new(); | ||
private readonly ConcurrentDictionary<MetricKey, long> lastLongValue = new(); | ||
private readonly ConcurrentDictionary<MetricKey, double> lastDoubleValue = new(); | ||
private readonly ConcurrentBag<EventSource> 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 | ||
{ | ||
Gauge, | ||
Counter, | ||
} | ||
|
||
private Dictionary<string, string> EnableEventArgs => new Dictionary<string, string> { ["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 (!this.isInitialized || !eventData.EventName.Equals("EventCounters")) | ||
{ | ||
return; | ||
} | ||
|
||
try | ||
{ | ||
if (eventData.Payload.Count > 0 && eventData.Payload[0] is IDictionary<string, object> 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<string, object> eventPayload) | ||
{ | ||
try | ||
{ | ||
bool calculateRate = false; | ||
string actualValue = string.Empty; | ||
|
||
string counterName = string.Empty; | ||
string counterDisplayName = string.Empty; | ||
InstrumentType instrumentType = InstrumentType.Counter; | ||
|
||
foreach (KeyValuePair<string, object> 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.Counter; | ||
cijothomas marked this conversation as resolved.
Show resolved
Hide resolved
hananiel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
actualValue = payload.Value.ToString(); | ||
} | ||
else if (key.Equals("Increment", StringComparison.OrdinalIgnoreCase)) | ||
hananiel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
// Increment indicates we have to calculate rate. | ||
instrumentType = InstrumentType.Gauge; | ||
calculateRate = true; | ||
hananiel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
actualValue = payload.Value.ToString(); | ||
} | ||
} | ||
|
||
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, string value, bool calculateRate) | ||
{ | ||
var metricKey = new MetricKey(eventSourceName, counterName); | ||
var description = string.IsNullOrEmpty(displayName) ? counterName : displayName; | ||
bool isLong = long.TryParse(value, out long longValue); | ||
bool isDouble = double.TryParse(value, out double doubleValue); | ||
|
||
if (isLong) | ||
{ | ||
this.lastLongValue[metricKey] = calculateRate ? longValue / this.options.RefreshIntervalSecs : longValue; | ||
} | ||
else if (isDouble) | ||
{ | ||
this.lastDoubleValue[metricKey] = calculateRate ? doubleValue / this.options.RefreshIntervalSecs : doubleValue; | ||
} | ||
|
||
switch (instrumentType) | ||
{ | ||
case InstrumentType.Counter when isLong: | ||
|
||
if (!this.metricInstruments.ContainsKey(metricKey)) | ||
utpilla marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
this.metricInstruments[metricKey] = this.meter.CreateObservableCounter<long>(counterName, () => this.ObserveLong(metricKey), description: description); | ||
} | ||
|
||
break; | ||
|
||
case InstrumentType.Counter when isDouble: | ||
if (!this.metricInstruments.ContainsKey(metricKey)) | ||
{ | ||
this.metricInstruments[metricKey] = this.meter.CreateObservableCounter<double>(counterName, () => this.ObserveDouble(metricKey), description: description); | ||
} | ||
|
||
break; | ||
|
||
case InstrumentType.Gauge when isLong: | ||
if (!this.metricInstruments.ContainsKey(metricKey)) | ||
{ | ||
this.metricInstruments[metricKey] = this.meter.CreateObservableGauge<long>(counterName, () => this.ObserveLong(metricKey), description: description); | ||
} | ||
|
||
break; | ||
case InstrumentType.Gauge when isDouble: | ||
|
||
if (!this.metricInstruments.TryGetValue(metricKey, out Instrument instrument)) | ||
{ | ||
this.metricInstruments[metricKey] = this.meter.CreateObservableGauge<double>(counterName, () => this.ObserveDouble(metricKey), description: description); | ||
} | ||
|
||
break; | ||
} | ||
} | ||
|
||
private long ObserveLong(MetricKey key) => this.lastLongValue[key]; | ||
|
||
private double ObserveDouble(MetricKey key) => this.lastDoubleValue[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; | ||
} | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
src/OpenTelemetry.Instrumentation.EventCounters/EventCounterMetricsOptions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// <copyright file="EventCounterMetricsOptions.cs" company="OpenTelemetry Authors"> | ||
// 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. | ||
// </copyright> | ||
|
||
namespace OpenTelemetry.Instrumentation.EventCounters | ||
{ | ||
/// <summary> | ||
/// EventCounterMetrics Options. | ||
/// </summary> | ||
public class EventCounterMetricsOptions | ||
{ | ||
/// <summary> | ||
/// Gets or sets the subscription interval in seconds. | ||
/// </summary> | ||
public int RefreshIntervalSecs { get; set; } = 60; | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
src/OpenTelemetry.Instrumentation.EventCounters/EventCountersInstrumentationEventSource.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// <copyright file="EventCountersInstrumentationEventSource.cs" company="OpenTelemetry Authors"> | ||
// 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. | ||
// </copyright> | ||
|
||
using System.Diagnostics.Tracing; | ||
|
||
namespace OpenTelemetry.Instrumentation.EventCounters | ||
{ | ||
/// <summary> | ||
/// EventSource events emitted from the project. | ||
/// </summary> | ||
[EventSource(Name = "OpenTelemetry-Instrumentation-EventCounters")] | ||
internal class EventCountersInstrumentationEventSource : EventSource | ||
{ | ||
public static readonly EventCountersInstrumentationEventSource Log = new EventCountersInstrumentationEventSource(); | ||
|
||
[Event(1, Message = "Error occurred while processing eventCounter, EventCounter: {0}, Exception: {2}", Level = EventLevel.Error)] | ||
public void ErrorEventCounter(string counterName, string exception) | ||
{ | ||
this.WriteEvent(1, counterName, exception); | ||
} | ||
|
||
[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) | ||
{ | ||
this.WriteEvent(4, eventSourceName); | ||
} | ||
|
||
[Event(3, Level = EventLevel.Warning, Message = @"EventCountersInstrumentation - {0} failed with exception: {1}.")] | ||
public void EventCountersInstrumentationWarning(string stage, string exceptionMessage) | ||
{ | ||
this.WriteEvent(8, stage, exceptionMessage); | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does the "EventCounters" name come from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is hardhoded in the implementation class: https://github.com/dotnet/runtime/blob/e026f392b2bb93bc9509ac5403d3e8cd7d3eee72/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventCounter.cs#L129
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I think it might be better to swap the order of the conditions here.