-
Notifications
You must be signed in to change notification settings - Fork 357
[Instrumentation.Hangfire] Add metrics support #3258
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
gorbach
wants to merge
23
commits into
open-telemetry:main
Choose a base branch
from
gorbach:feature/2075-hangfire-telemetry
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
Changes from 7 commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
2e804df
[Instrumentation.Hangfire] Add metrics support
gorbach 08198ac
Align metrics with workflow semconv: use duration{state=pending}, mo…
gorbach d43ee2c
Configure metrics in HangfireMetricsInstrumentationOptions class, add…
gorbach bc135e8
Add workflow.status metric to track complete job lifecycle
gorbach 96d8aa8
Use job formatter from hangfire
gorbach 742b2a0
Fix tests
gorbach aa3ac32
Fix and speed up tests
gorbach 1f909bd
Update src/OpenTelemetry.Instrumentation.Hangfire/Implementation/Hang…
gorbach 42476f8
Update src/OpenTelemetry.Instrumentation.Hangfire/Implementation/Hang…
gorbach accd8bc
Update src/OpenTelemetry.Instrumentation.Hangfire/Implementation/Hang…
gorbach 302542a
Update src/OpenTelemetry.Instrumentation.Hangfire/Implementation/Hang…
gorbach 5af844c
Update src/OpenTelemetry.Instrumentation.Hangfire/Implementation/Hang…
gorbach eae641c
Update src/OpenTelemetry.Instrumentation.Hangfire/Implementation/Hang…
gorbach 38883b8
Update src/OpenTelemetry.Instrumentation.Hangfire/Implementation/Hang…
gorbach 2e7cdff
Update src/OpenTelemetry.Instrumentation.Hangfire/Implementation/Hang…
gorbach 3254ed2
Update src/OpenTelemetry.Instrumentation.Hangfire/Implementation/Hang…
gorbach 693d1be
build fix
gorbach 044e500
Rename metrics/attributes
gorbach b58920e
Remove `DisplayNameFunc` from PublicAPI.Unshipped.txt and README.md
gorbach cef13e5
Merge branch 'main' into feature/2075-hangfire-telemetry
gorbach 90022d0
remove wrong link
gorbach ded8e66
update CHANGELOG.md
gorbach 64514b8
Merge branch 'main' into feature/2075-hangfire-telemetry
gorbach 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
24 changes: 24 additions & 0 deletions
24
src/OpenTelemetry.Instrumentation.Hangfire/HangfireMetricsInstrumentationOptions.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,24 @@ | ||
| // Copyright The OpenTelemetry Authors | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| using Hangfire; | ||
|
|
||
| namespace OpenTelemetry.Metrics; | ||
|
|
||
| /// <summary> | ||
| /// Options for Hangfire metrics instrumentation. | ||
| /// </summary> | ||
| public sealed class HangfireMetricsInstrumentationOptions | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets a value indicating whether to record the pending state duration in metrics. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// When enabled, records workflow.execution.duration with state="pending", representing | ||
| /// the time jobs spend waiting in the queue before execution starts. | ||
| /// This requires an additional database call per job execution to retrieve the enqueue timestamp. | ||
| /// Default is <see langword="false"/> to avoid performance impact in high-throughput scenarios. | ||
| /// When disabled, only execution duration (state="executing") is recorded. | ||
| /// </remarks> | ||
| public bool RecordQueueLatency { get; set; } | ||
| } |
90 changes: 90 additions & 0 deletions
90
src/OpenTelemetry.Instrumentation.Hangfire/Implementation/HangfireMetrics.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,90 @@ | ||
| // Copyright The OpenTelemetry Authors | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| using System.Diagnostics.Metrics; | ||
| using System.Reflection; | ||
| using OpenTelemetry.Internal; | ||
|
|
||
| namespace OpenTelemetry.Instrumentation.Hangfire.Implementation; | ||
|
|
||
| /// <summary> | ||
| /// Centralized metrics definitions for Hangfire instrumentation. | ||
| /// </summary> | ||
| internal static class HangfireMetrics | ||
| { | ||
| internal static readonly Assembly Assembly = typeof(HangfireMetrics).Assembly; | ||
| internal static readonly AssemblyName AssemblyName = Assembly.GetName(); | ||
| internal static readonly string MeterName = AssemblyName.Name!; | ||
|
|
||
| private static readonly string InstrumentationVersion = Assembly.GetPackageVersion(); | ||
|
|
||
| /// <summary> | ||
| /// The meter instance for all Hangfire metrics. | ||
| /// </summary> | ||
| private static readonly Meter Meter = new(MeterName, InstrumentationVersion); | ||
|
|
||
| /// <summary> | ||
| /// Counter for the number of task executions which have been initiated. | ||
| /// Follows OpenTelemetry workflow semantic conventions. | ||
| /// </summary> | ||
| public static readonly Counter<long> ExecutionCount = | ||
| Meter.CreateCounter<long>( | ||
| WorkflowMetricNames.ExecutionCount, | ||
| unit: "{executions}", | ||
| description: "The number of task executions which have been initiated."); | ||
|
|
||
| /// <summary> | ||
| /// Histogram for duration of an execution grouped by task, type and result. | ||
| /// Follows OpenTelemetry workflow semantic conventions. | ||
| /// Records duration for different execution phases using workflow.execution.state attribute: | ||
| /// - state=pending: Time spent waiting in queue before execution. | ||
| /// - state=executing: Time spent in actual execution. | ||
| /// </summary> | ||
| public static readonly Histogram<double> ExecutionDuration = | ||
| Meter.CreateHistogram<double>( | ||
| WorkflowMetricNames.ExecutionDuration, | ||
| unit: "s", | ||
| description: "Duration of an execution grouped by task, type and result."); | ||
|
|
||
| /// <summary> | ||
| /// UpDownCounter for the number of actively running tasks grouped by task and state. | ||
| /// Follows OpenTelemetry workflow semantic conventions. | ||
| /// </summary> | ||
| public static readonly UpDownCounter<long> ExecutionStatus = | ||
| Meter.CreateUpDownCounter<long>( | ||
| WorkflowMetricNames.ExecutionStatus, | ||
| unit: "{executions}", | ||
| description: "The number of actively running tasks grouped by task, type and the current state."); | ||
|
|
||
| /// <summary> | ||
| /// Counter for the number of errors encountered in task runs (eg. compile, test failures). | ||
| /// Follows OpenTelemetry workflow semantic conventions. | ||
| /// </summary> | ||
| public static readonly Counter<long> ExecutionErrors = | ||
| Meter.CreateCounter<long>( | ||
| WorkflowMetricNames.ExecutionErrors, | ||
| unit: "{error}", | ||
| description: "The number of errors encountered in task runs (eg. compile, test failures)."); | ||
|
|
||
| /// <summary> | ||
| /// Counter for the number of workflow instances which have been initiated. | ||
| /// Follows OpenTelemetry workflow semantic conventions. | ||
| /// In Hangfire, this tracks individual job completions. For batch workflows, this would track batch completion. | ||
| /// </summary> | ||
| public static readonly Counter<long> WorkflowCount = | ||
| Meter.CreateCounter<long>( | ||
| WorkflowMetricNames.WorkflowCount, | ||
| unit: "{workflows}", | ||
| description: "The number of workflow instances which have been initiated."); | ||
|
|
||
| /// <summary> | ||
| /// UpDownCounter for the number of actively running workflows grouped by definition and state. | ||
| /// Follows OpenTelemetry workflow semantic conventions. | ||
| /// In Hangfire, this tracks workflows that haven't entered the execution pipeline yet (e.g., scheduled jobs). | ||
| /// </summary> | ||
| public static readonly UpDownCounter<long> WorkflowStatus = | ||
| Meter.CreateUpDownCounter<long>( | ||
| WorkflowMetricNames.WorkflowStatus, | ||
| unit: "{workflows}", | ||
| description: "The number of actively running workflows grouped by definition and the current state."); | ||
| } |
40 changes: 40 additions & 0 deletions
40
...nTelemetry.Instrumentation.Hangfire/Implementation/HangfireMetricsErrorFilterAttribute.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,40 @@ | ||
| // Copyright The OpenTelemetry Authors | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| using Hangfire.Common; | ||
| using Hangfire.Server; | ||
| using OpenTelemetry.Metrics; | ||
|
|
||
| namespace OpenTelemetry.Instrumentation.Hangfire.Implementation; | ||
|
|
||
| /// <summary> | ||
| /// Hangfire filter that records OpenTelemetry error metrics for job execution failures. | ||
| /// Follows OpenTelemetry workflow semantic conventions for workflow.execution.errors metric. | ||
| /// </summary> | ||
| internal sealed class HangfireMetricsErrorFilterAttribute : JobFilterAttribute, IServerFilter | ||
| { | ||
| private readonly HangfireMetricsInstrumentationOptions options; | ||
|
|
||
| #pragma warning disable CA1019 // Define accessors for attribute arguments | ||
| public HangfireMetricsErrorFilterAttribute(HangfireMetricsInstrumentationOptions options) | ||
| #pragma warning restore CA1019 // Define accessors for attribute arguments | ||
| { | ||
| this.options = options ?? throw new ArgumentNullException(nameof(options)); | ||
| } | ||
|
|
||
gorbach marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| public void OnPerforming(PerformingContext performingContext) | ||
| { | ||
| } | ||
|
|
||
| public void OnPerformed(PerformedContext performedContext) | ||
| { | ||
| if (performedContext.Exception != null) | ||
| { | ||
| var errorTags = HangfireTagBuilder.BuildErrorTags( | ||
| performedContext.BackgroundJob, | ||
| performedContext.Exception); | ||
gorbach marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| HangfireMetrics.ExecutionErrors.Add(1, errorTags); | ||
| } | ||
| } | ||
| } | ||
42 changes: 42 additions & 0 deletions
42
src/OpenTelemetry.Instrumentation.Hangfire/Implementation/HangfireMetricsInstrumentation.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,42 @@ | ||
| // Copyright The OpenTelemetry Authors | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| using Hangfire; | ||
| using OpenTelemetry.Metrics; | ||
|
|
||
| namespace OpenTelemetry.Instrumentation.Hangfire.Implementation; | ||
|
|
||
| /// <summary> | ||
| /// Hangfire metrics instrumentation following OpenTelemetry workflow semantic conventions. | ||
| /// </summary> | ||
| internal sealed class HangfireMetricsInstrumentation : IDisposable | ||
| { | ||
| private readonly List<object> filters = new(); | ||
|
|
||
| public HangfireMetricsInstrumentation(HangfireMetricsInstrumentationOptions options) | ||
| { | ||
| this.AddFilter(new HangfireMetricsJobFilterAttribute()); | ||
| this.AddFilter(new HangfireMetricsStateFilter()); | ||
| this.AddFilter(new HangfireMetricsErrorFilterAttribute(options)); | ||
gorbach marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Only register pending duration filter if enabled (requires DB call per job) | ||
| if (options.RecordQueueLatency) | ||
| { | ||
| this.AddFilter(new HangfirePendingDurationFilterAttribute()); | ||
| } | ||
| } | ||
|
|
||
| public void Dispose() | ||
| { | ||
| foreach (var filter in this.filters) | ||
| { | ||
| GlobalJobFilters.Filters.Remove(filter); | ||
| } | ||
| } | ||
|
|
||
| private void AddFilter(object filter) | ||
| { | ||
| this.filters.Add(filter); | ||
| GlobalJobFilters.Filters.Add(filter); | ||
| } | ||
| } | ||
68 changes: 68 additions & 0 deletions
68
...penTelemetry.Instrumentation.Hangfire/Implementation/HangfireMetricsJobFilterAttribute.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,68 @@ | ||
| // Copyright The OpenTelemetry Authors | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| using System.Diagnostics; | ||
| using Hangfire.Common; | ||
| using Hangfire.Server; | ||
|
|
||
| namespace OpenTelemetry.Instrumentation.Hangfire.Implementation; | ||
|
|
||
| /// <summary> | ||
| /// Hangfire filter that records OpenTelemetry metrics for job execution. | ||
| /// </summary> | ||
| internal sealed class HangfireMetricsJobFilterAttribute : JobFilterAttribute, IServerFilter | ||
| { | ||
| private const string StopwatchKey = "OpenTelemetry.Metrics.Stopwatch"; | ||
|
|
||
| public void OnPerforming(PerformingContext performingContext) | ||
| { | ||
| performingContext.Items[StopwatchKey] = Stopwatch.StartNew(); | ||
| } | ||
|
|
||
| public void OnPerformed(PerformedContext performedContext) | ||
| { | ||
| // Get recurring job ID if this job was triggered by a recurring job | ||
| string? recurringJobId = null; | ||
| try | ||
| { | ||
| recurringJobId = performedContext.Connection.GetJobParameter( | ||
| performedContext.BackgroundJob.Id, | ||
| "RecurringJobId"); | ||
| } | ||
| catch | ||
| { | ||
| // If we can't get the recurring job ID, treat it as a non-recurring job | ||
| } | ||
|
|
||
| var backgroundJob = performedContext.BackgroundJob; | ||
|
|
||
| // Record execution count (without state attribute per semantic conventions) | ||
| var countTags = HangfireTagBuilder.BuildExecutionCountTags( | ||
| backgroundJob, | ||
| performedContext.Exception); | ||
|
|
||
| HangfireMetrics.ExecutionCount.Add(1, countTags); | ||
|
|
||
| // Record execution duration (with state="executing" to differentiate from pending phase) | ||
| if (performedContext.Items.TryGetValue(StopwatchKey, out var stopwatchObj) && stopwatchObj is Stopwatch stopwatch) | ||
| { | ||
| stopwatch.Stop(); | ||
| var duration = stopwatch.Elapsed.TotalSeconds; | ||
|
|
||
| var durationTags = HangfireTagBuilder.BuildExecutionTags( | ||
| backgroundJob, | ||
| performedContext.Exception, | ||
| workflowState: WorkflowAttributes.WorkflowStateValues.Executing); | ||
|
|
||
| HangfireMetrics.ExecutionDuration.Record(duration, durationTags); | ||
| } | ||
|
|
||
| // Record workflow-level metrics (includes trigger type) | ||
| var workflowTags = HangfireTagBuilder.BuildWorkflowTags( | ||
| backgroundJob, | ||
| performedContext.Exception, | ||
| recurringJobId); | ||
|
|
||
| HangfireMetrics.WorkflowCount.Add(1, workflowTags); | ||
| } | ||
| } |
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.