-
Notifications
You must be signed in to change notification settings - Fork 642
Amehlem/opentelemetry #1574
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
Open
AMehlem
wants to merge
18
commits into
microsoft:main
Choose a base branch
from
AMehlem:amehlem/opentelemetry
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Amehlem/opentelemetry #1574
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
d7746cf
Started implementing OpenTelemetry-Metrics-Export.
eb64562
Cleaned up the options and added validation, removed and renamed some…
4be2566
Added default configuration values for OpenTelemetry.
11419be
Added Tests for OpenTelemetry.
500e6c9
Merge branch 'main' into amehlem/opentelemetry
AMehlem 5e2c718
Update libs/server/Metrics/GarnetOpenTelemetrySessionMetrics.cs
AMehlem 0c4bf70
Update libs/server/Metrics/GarnetOpenTelemetryServerMonitor.cs
AMehlem d567634
Update libs/server/Metrics/GarnetOpenTelemetrySessionMetrics.cs
AMehlem 6359004
Update libs/host/defaults.conf
AMehlem a6770c1
Added missing file header.
4638eed
Merge remote-tracking branch 'origin/amehlem/opentelemetry' into ameh…
fefdd57
OpenTelemetry-Tests: Added using to dispose MeterListener.
27ecce5
Disposing MeterProvider in GarnetOpenTelemetryServerMonitor.
bbe2094
OpenTelemetry Metrics: Implemented clamping of long values as propose…
e3c5b94
OpenTelemetryTests: Increased test completeness as proposed by Copilot.
845c57d
Removed newlines at end of OpenTelemetry source files.
757f542
Merge branch 'main' into amehlem/opentelemetry
AMehlem 3b184d4
Merge branch 'main' into amehlem/opentelemetry
AMehlem 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 hidden or 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
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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,60 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT license. | ||
|
|
||
| using System; | ||
AMehlem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| using System.Diagnostics.Metrics; | ||
|
|
||
| namespace Garnet.server.Metrics | ||
| { | ||
| /// <summary> | ||
| /// Provides OpenTelemetry-compatible metrics for Garnet server using <see cref="Meter"/>. | ||
| /// Consumers can subscribe to these metrics using the OpenTelemetry SDK or any other <see cref="MeterListener"/>. | ||
| /// The command-rate and network rates are not exposed as metrics as they can be calculated based on the other exposed metrics. | ||
| /// </summary> | ||
| internal sealed class GarnetOpenTelemetryServerMetrics : IDisposable | ||
| { | ||
| /// <summary> | ||
| /// The meter name used by Garnet server metrics. | ||
| /// </summary> | ||
| public const string MeterName = "Microsoft.Garnet.Server"; | ||
|
|
||
| private readonly Meter meter; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="GarnetOpenTelemetryServerMetrics"/> class, | ||
| /// creating observable instruments that expose server connection metrics via a <see cref="Meter"/>. | ||
| /// </summary> | ||
| /// <param name="serverMetrics"> | ||
| /// The <see cref="GarnetServerMetrics"/> instance whose connection counters | ||
| /// (active, received, and disposed) are observed by the created instruments. | ||
| /// </param> | ||
| internal GarnetOpenTelemetryServerMetrics(GarnetServerMetrics serverMetrics) | ||
| { | ||
| meter = new Meter(MeterName); | ||
|
|
||
| meter.CreateObservableGauge( | ||
| "garnet.server.connections.active", | ||
| () => serverMetrics.total_connections_active, | ||
| unit: "{connection}", | ||
| description: "Number of currently active client connections."); | ||
|
|
||
| meter.CreateObservableCounter( | ||
| "garnet.server.connections.received", | ||
| () => serverMetrics.total_connections_received, | ||
| unit: "{connection}", | ||
| description: "Total number of client connections received."); | ||
|
|
||
| meter.CreateObservableCounter( | ||
| "garnet.server.connections.disposed", | ||
| () => serverMetrics.total_connections_disposed, | ||
| unit: "{connection}", | ||
| description: "Total number of client connections disposed."); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public void Dispose() | ||
| { | ||
| meter.Dispose(); | ||
| } | ||
| } | ||
| } | ||
117 changes: 117 additions & 0 deletions
117
libs/server/Metrics/GarnetOpenTelemetryServerMonitor.cs
This file contains hidden or 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,117 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT license. | ||
|
|
||
| using System; | ||
| using System.Reflection; | ||
| using Garnet.server.Metrics.Latency; | ||
| using OpenTelemetry; | ||
| using OpenTelemetry.Metrics; | ||
| using OpenTelemetry.Resources; | ||
|
|
||
| namespace Garnet.server.Metrics | ||
| { | ||
| /// <summary> | ||
| /// Registers OpenTelemetry metrics for Garnet server and manages their lifecycle. This includes server-level metrics, session-level metrics, and latency metrics. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// This class acts as the central coordinator for all OpenTelemetry metrics in the Garnet server. | ||
| /// It wraps the raw metrics sources (<see cref="GarnetServerMetrics"/> and <see cref="GarnetSessionMetrics"/>) | ||
| /// into OpenTelemetry-compatible instruments exposed via <see cref="System.Diagnostics.Metrics.Meter"/> instances: | ||
| /// </para> | ||
| /// <list type="bullet"> | ||
| /// <item><description><see cref="GarnetOpenTelemetryServerMetrics"/> — connection-level metrics (active, received, disposed).</description></item> | ||
| /// <item><description><see cref="GarnetOpenTelemetrySessionMetrics"/> — session-level metrics (commands processed, network I/O, cache lookups).</description></item> | ||
| /// <item><description><see cref="GarnetOpenTelemetryLatencyMetrics"/> — latency histograms and counters (command latency, bytes/ops per receive call).</description></item> | ||
| /// </list> | ||
| /// <para> | ||
| /// Call <see cref="Start"/> after construction to configure the OTLP exporter and begin metric collection. | ||
| /// The exporter endpoint, protocol, timeout, and interval are controlled by the corresponding | ||
| /// properties on <see cref="GarnetServerOptions"/>. | ||
| /// </para> | ||
| /// <para> | ||
| /// This class implements <see cref="IDisposable"/>; disposing it tears down all underlying meters | ||
| /// and the latency metrics singleton. | ||
| /// </para> | ||
| /// </remarks> | ||
| internal sealed class GarnetOpenTelemetryServerMonitor : IDisposable | ||
| { | ||
| private readonly GarnetServerOptions options; | ||
| private readonly GarnetOpenTelemetryServerMetrics serverMetrics; | ||
| private readonly GarnetOpenTelemetrySessionMetrics sessionMetrics; | ||
| private MeterProvider meterProvider; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="GarnetOpenTelemetryServerMonitor"/> class, | ||
| /// creating the OpenTelemetry metric wrappers for server and session metrics and initializing | ||
| /// the latency metrics singleton. | ||
| /// </summary> | ||
| /// <param name="options"> | ||
| /// The <see cref="GarnetServerOptions"/> that control OpenTelemetry export behavior, including | ||
| /// <see cref="GarnetServerOptions.OpenTelemetryEndpoint"/>, | ||
| /// <see cref="GarnetServerOptions.OpenTelemetryExportProtocol"/>, | ||
| /// <see cref="GarnetServerOptions.OpenTelemetryExportTimeout"/> and | ||
| /// <see cref="GarnetServerOptions.OpenTelemetryExportInterval"/> | ||
| /// </param> | ||
| /// <param name="serverMetrics"> | ||
| /// The <see cref="GarnetServerMetrics"/> instance that provides raw server and session counters. | ||
| /// If <see cref="GarnetServerMetrics.globalSessionMetrics"/> is <c>null</c>, session-level | ||
| /// metrics will not be registered. | ||
| /// </param> | ||
| public GarnetOpenTelemetryServerMonitor(GarnetServerOptions options, GarnetServerMetrics serverMetrics) | ||
| { | ||
| this.options = options; | ||
| this.serverMetrics = new GarnetOpenTelemetryServerMetrics(serverMetrics); | ||
| this.sessionMetrics = serverMetrics.globalSessionMetrics != null | ||
| ? new GarnetOpenTelemetrySessionMetrics(serverMetrics.globalSessionMetrics) | ||
| : null; | ||
|
|
||
| GarnetOpenTelemetryLatencyMetrics.Initialize(options.LatencyMonitor); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Initializes and configures OpenTelemetry metrics exporting if an endpoint is specified in the options. | ||
| /// </summary> | ||
| /// <remarks>Call this method to enable OpenTelemetry metrics collection and exporting for the | ||
| /// service. Metrics will be exported using the configured endpoint and protocol. If no endpoint is specified, | ||
| /// metrics exporting will not be enabled.</remarks> | ||
| public void Start() | ||
| { | ||
| if (this.options.OpenTelemetryEndpoint != null) | ||
| { | ||
| this.meterProvider = Sdk.CreateMeterProviderBuilder() | ||
| .ConfigureResource(rb => rb.AddService("Microsoft.Garnet", serviceVersion: Assembly.GetEntryAssembly()?.GetName()?.Version?.ToString() ?? "unknown")) | ||
| .AddMeter(GarnetOpenTelemetryServerMetrics.MeterName, GarnetOpenTelemetrySessionMetrics.MeterName, GarnetOpenTelemetryLatencyMetrics.MeterName) | ||
| .AddOtlpExporter(opts => | ||
| { | ||
| opts.Endpoint = this.options.OpenTelemetryEndpoint; | ||
|
|
||
| if (this.options.OpenTelemetryExportProtocol.HasValue) | ||
| { | ||
| opts.Protocol = this.options.OpenTelemetryExportProtocol.Value; | ||
| } | ||
|
|
||
| if (this.options.OpenTelemetryExportTimeout != 0) | ||
| { | ||
| opts.TimeoutMilliseconds = this.options.OpenTelemetryExportTimeout; | ||
| } | ||
|
|
||
| if (this.options.OpenTelemetryExportInterval != 0) | ||
| { | ||
| opts.BatchExportProcessorOptions.ScheduledDelayMilliseconds = this.options.OpenTelemetryExportInterval; | ||
| } | ||
| }) | ||
| .Build(); | ||
| } | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public void Dispose() | ||
| { | ||
| this.serverMetrics.Dispose(); | ||
| this.sessionMetrics?.Dispose(); | ||
| GarnetOpenTelemetryLatencyMetrics.DisposeInstance(); | ||
| this.meterProvider?.Dispose(); | ||
| } | ||
| } | ||
| } |
127 changes: 127 additions & 0 deletions
127
libs/server/Metrics/GarnetOpenTelemetrySessionMetrics.cs
This file contains hidden or 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,127 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT license. | ||
|
|
||
| using System; | ||
| using System.Diagnostics.Metrics; | ||
|
|
||
| namespace Garnet.server.Metrics | ||
| { | ||
| /// <summary> | ||
| /// Exposes Garnet server session metrics as OpenTelemetry instruments using <see cref="Meter"/>. | ||
| /// Registers observable counters and gauges that report command processing, network I/O, | ||
| /// cache lookup, and session exception statistics from a <see cref="GarnetSessionMetrics"/> instance. | ||
| /// </summary> | ||
| internal sealed class GarnetOpenTelemetrySessionMetrics : IDisposable | ||
| { | ||
| /// <summary> | ||
| /// The meter name used by Garnet session metrics. | ||
| /// </summary> | ||
| public const string MeterName = "Microsoft.Garnet.Server.Session"; | ||
|
|
||
| /// <summary> | ||
| /// The <see cref="Meter"/> instance used to create and manage OpenTelemetry instruments | ||
| /// for session-level metrics. | ||
| /// </summary> | ||
| private readonly Meter meter; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="GarnetOpenTelemetrySessionMetrics"/> class, | ||
| /// creating observable counters and gauges that report session-level statistics from the | ||
| /// specified <paramref name="globalSessionMetrics"/> instance. | ||
| /// </summary> | ||
| /// <param name="globalSessionMetrics"> | ||
| /// The <see cref="GarnetSessionMetrics"/> instance that supplies the aggregated session statistics. | ||
| /// Must not be <see langword="null"/>. | ||
| /// </param> | ||
| /// <exception cref="ArgumentNullException"> | ||
| /// Thrown when <paramref name="globalSessionMetrics"/> is <see langword="null"/>. | ||
| /// </exception> | ||
| internal GarnetOpenTelemetrySessionMetrics(GarnetSessionMetrics globalSessionMetrics) | ||
| { | ||
| if (globalSessionMetrics == null) | ||
| { | ||
| throw new ArgumentNullException(nameof(globalSessionMetrics)); | ||
| } | ||
|
|
||
| meter = new Meter(MeterName); | ||
|
|
||
| meter.CreateObservableCounter( | ||
| "garnet.server.commands.processed", | ||
| () => Convert.ToInt64(globalSessionMetrics.get_total_commands_processed()), | ||
| unit: "{command}", | ||
| description: "Total number of commands processed."); | ||
|
|
||
| meter.CreateObservableCounter( | ||
| "garnet.server.transaction.commands.received", | ||
| () => Convert.ToInt64(globalSessionMetrics.get_total_transaction_commands_received()), | ||
| unit: "{command}", | ||
| description: "Total number of transaction commands received."); | ||
|
|
||
| meter.CreateObservableCounter( | ||
| "garnet.server.transaction.commands.failed", | ||
| () => Convert.ToInt64(globalSessionMetrics.get_total_transaction_commands_execution_failed()), | ||
| unit: "{command}", | ||
| description: "Total number of transaction command executions that failed."); | ||
|
|
||
| meter.CreateObservableCounter( | ||
| "garnet.server.write.commands.processed", | ||
| () => Convert.ToInt64(globalSessionMetrics.get_total_write_commands_processed()), | ||
| unit: "{command}", | ||
| description: "Total number of write commands processed."); | ||
|
|
||
| meter.CreateObservableCounter( | ||
| "garnet.server.read.commands.processed", | ||
| () => Convert.ToInt64(globalSessionMetrics.get_total_read_commands_processed()), | ||
| unit: "{command}", | ||
| description: "Total number of read commands processed."); | ||
|
|
||
| meter.CreateObservableCounter( | ||
| "garnet.server.cluster.commands.processed", | ||
| () => Convert.ToInt64(globalSessionMetrics.get_total_cluster_commands_processed()), | ||
| unit: "{command}", | ||
| description: "Total number of cluster commands processed."); | ||
|
|
||
| meter.CreateObservableCounter( | ||
| "garnet.server.network.bytes.received", | ||
| () => Convert.ToInt64(globalSessionMetrics.get_total_net_input_bytes()), | ||
| unit: "By", | ||
| description: "Total number of bytes received from the network."); | ||
|
|
||
| meter.CreateObservableCounter( | ||
| "garnet.server.network.bytes.sent", | ||
| () => Convert.ToInt64(globalSessionMetrics.get_total_net_output_bytes()), | ||
| unit: "By", | ||
| description: "Total number of bytes sent to the network."); | ||
|
|
||
| meter.CreateObservableCounter( | ||
| "garnet.server.cache.lookups", | ||
| () => Convert.ToInt64(globalSessionMetrics.get_total_found()) + Convert.ToInt64(globalSessionMetrics.get_total_notfound()), | ||
| unit: "{lookup}", | ||
| description: "Total number of cache lookups."); | ||
|
|
||
| meter.CreateObservableCounter( | ||
| "garnet.server.cache.lookups.missed", | ||
| () => Convert.ToInt64(globalSessionMetrics.get_total_notfound()), | ||
| unit: "{miss}", | ||
| description: "Total number of cache misses (unsuccessful key lookups)."); | ||
|
|
||
| meter.CreateObservableGauge( | ||
| "garnet.server.operations.pending", | ||
| () => Convert.ToInt64(globalSessionMetrics.get_total_pending()), | ||
| unit: "{operation}", | ||
| description: "Current number of pending operations."); | ||
|
|
||
| meter.CreateObservableCounter( | ||
| "garnet.server.resp.session.exceptions", | ||
| () => Convert.ToInt64(globalSessionMetrics.get_total_number_resp_server_session_exceptions()), | ||
| unit: "{exception}", | ||
| description: "Total number of RESP server session exceptions."); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public void Dispose() | ||
| { | ||
| meter.Dispose(); | ||
| } | ||
| } | ||
| } |
This file contains hidden or 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
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.
Uh oh!
There was an error while loading. Please reload this page.