Skip to content

Commit f7425ee

Browse files
committed
NCBC-4139: Implement tracing spans and metrics for Management API
Motivation ---------- Our management operations should emit distributed tracing spans and record overall duration metrics, including error states and resource tags. Modifications ------------- - Added request tracing span creation (`IRequestSpan`) to all Management API managers and threaded custom spans through their Options classes. - Added `MetricTracker.Management.TrackOperation` to handle formatting and dispatching OTel metric tags for management services. - Added `OperationTracker` disposable struct to capture operation latency and error types with minimal boilerplate. - Updated `LoggingMeter` to collect histogram metrics for management. Results ------- All Management operations now correctly emit both distributed traces and latency metrics (`db.client.operation.duration`), bringing the SDK into full compliance with the observability RFC. Change-Id: I72c9c874bf038c083acd9c8a3ef68bf4383a2d6f Reviewed-on: https://review.couchbase.org/c/couchbase-net-client/+/241266 Tested-by: Build Bot <build@couchbase.com> Reviewed-by: Jeffry Morris <jeffrymorris@gmail.com>
1 parent 47de416 commit f7425ee

82 files changed

Lines changed: 1956 additions & 236 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/Couchbase/Core/Diagnostics/Metrics/LoggingMeter.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class LoggingMeter : IMeter, IEnumerable<HistogramCollectorSet>
2424
private LoggingMeterValueRecorder? _searchQueryHistograms;
2525
private LoggingMeterValueRecorder? _analyticsQueryHistograms;
2626
private LoggingMeterValueRecorder? _viewQueryHistograms;
27+
private LoggingMeterValueRecorder? _managementHistograms;
2728

2829
private readonly uint _intervalMilliseconds;
2930

@@ -101,6 +102,9 @@ public IValueRecorder ValueRecorder(string name, IDictionary<string, string>? ta
101102
case OuterRequestSpans.ServiceSpan.ViewQuery:
102103
return GetOrCreateValueRecorder(ref _viewQueryHistograms, OuterRequestSpans.ServiceSpan.ViewQuery);
103104

105+
case OuterRequestSpans.ServiceSpan.Management:
106+
return GetOrCreateValueRecorder(ref _managementHistograms, OuterRequestSpans.ServiceSpan.Management);
107+
104108
default:
105109
return NoopValueRecorder.Instance;
106110
}
@@ -146,6 +150,11 @@ IEnumerator<HistogramCollectorSet> IEnumerable<HistogramCollectorSet>.GetEnumera
146150
{
147151
yield return _viewQueryHistograms.Histograms;
148152
}
153+
154+
if (_managementHistograms is not null)
155+
{
156+
yield return _managementHistograms.Histograms;
157+
}
149158
}
150159

151160
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<HistogramCollectorSet>)this).GetEnumerator();

src/Couchbase/Core/Diagnostics/Metrics/MetricTracker.cs

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#nullable enable
12
using System;
23
using System.Collections.Generic;
34
using System.Diagnostics;
@@ -13,7 +14,6 @@
1314
using Couchbase.Utils;
1415
using Couchbase.Views;
1516

16-
#nullable enable
1717

1818
namespace Couchbase.Core.Diagnostics.Metrics
1919
{
@@ -425,6 +425,117 @@ public static void TrackOperation(ViewQuery viewQuery, TimeSpan duration, Type?
425425
}
426426
}
427427

428+
public static class Management
429+
{
430+
/// <summary>
431+
/// Starts tracking a management operation. Dispose to record the metric.
432+
/// </summary>
433+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
434+
public static OperationTracker StartOperation(
435+
string operationName,
436+
IRequestSpan? span,
437+
string? bucketName = null,
438+
string? scopeName = null,
439+
string? collectionName = null)
440+
{
441+
return new OperationTracker(operationName, span, bucketName, scopeName, collectionName);
442+
}
443+
444+
/// <summary>
445+
/// A disposable tracker for management operations. Records metrics on dispose.
446+
/// </summary>
447+
public struct OperationTracker : IDisposable
448+
{
449+
private readonly string _operationName;
450+
private readonly IRequestSpan? _span;
451+
private readonly string? _bucketName;
452+
private readonly string? _scopeName;
453+
private readonly string? _collectionName;
454+
private readonly LightweightStopwatch _stopwatch;
455+
private Type? _errorType;
456+
457+
internal OperationTracker(
458+
string operationName,
459+
IRequestSpan? span,
460+
string? bucketName,
461+
string? scopeName,
462+
string? collectionName)
463+
{
464+
_operationName = operationName;
465+
_span = span;
466+
_bucketName = bucketName;
467+
_scopeName = scopeName;
468+
_collectionName = collectionName;
469+
_stopwatch = LightweightStopwatch.StartNew();
470+
_errorType = null;
471+
}
472+
473+
/// <summary>
474+
/// Records an error type for the operation.
475+
/// </summary>
476+
public void SetError(Exception e) => _errorType = e.GetType();
477+
478+
/// <summary>
479+
/// Records the operation metrics.
480+
/// </summary>
481+
public void Dispose()
482+
{
483+
TrackOperation(
484+
_operationName,
485+
_stopwatch.Elapsed,
486+
_errorType,
487+
_bucketName,
488+
_scopeName,
489+
_collectionName,
490+
_span);
491+
}
492+
}
493+
494+
/// <summary>
495+
/// Tracks the first attempt of an operation.
496+
/// </summary>
497+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
498+
public static void TrackOperation(
499+
string operationName,
500+
TimeSpan duration,
501+
Type? errorType,
502+
string? bucketName = null,
503+
string? scopeName = null,
504+
string? collectionName = null,
505+
IRequestSpan? span = null)
506+
{
507+
// Legacy metrics
508+
var legacyTags = new TagList
509+
{
510+
new(OuterRequestSpans.Attributes.Service, OuterRequestSpans.ServiceSpan.Management),
511+
new(OuterRequestSpans.Attributes.Operation, operationName),
512+
new(OuterRequestSpans.Attributes.Outcome, GetOutcome(errorType))
513+
};
514+
// Add these tags conditionally to prevent increasing cardinality unnecessarily
515+
if (bucketName is not null) legacyTags.Add(OuterRequestSpans.Attributes.BucketName, bucketName);
516+
if (scopeName is not null) legacyTags.Add(OuterRequestSpans.Attributes.ScopeName, scopeName);
517+
if (collectionName is not null) legacyTags.Add(OuterRequestSpans.Attributes.CollectionName, collectionName);
518+
519+
legacyTags.AddClusterLabelsIfProvided(span);
520+
LegacyOperations.Record(duration.ToMicroseconds(), legacyTags);
521+
522+
// Modern metrics
523+
var modernTags = new TagList
524+
{
525+
new(ModernAttributes.Service, OuterRequestSpans.ServiceSpan.Management),
526+
new(ModernAttributes.Operation, operationName),
527+
new(ModernAttributes.Outcome, GetOutcome(errorType))
528+
};
529+
// Add these tags conditionally
530+
if (bucketName is not null) modernTags.Add(ModernAttributes.Namespace, bucketName);
531+
if (scopeName is not null) modernTags.Add(ModernAttributes.ScopeName, scopeName);
532+
if (collectionName is not null) modernTags.Add(ModernAttributes.CollectionName, collectionName);
533+
534+
AddModernClusterLabels(ref modernTags, span);
535+
ModernOperations.Record(duration.TotalSeconds, modernTags);
536+
}
537+
}
538+
428539
private static void AddModernClusterLabels(ref TagList tagList, IRequestSpan? span)
429540
{
430541
if (span is RequestSpanWrapper wrapper)

src/Couchbase/Core/Diagnostics/Tracing/OuterRequestSpans.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public static class ServiceSpan
8888
public const string SearchQuery = "search";
8989
public const string Eventing = "eventing";
9090
public const string Transaction = "transaction";
91+
public const string Management = "management";
9192

9293
public static class Kv
9394
{
@@ -167,6 +168,11 @@ public static class Analytics
167168
public const string GetPendingMutations = "manager_analytics_get_pending_mutations";
168169
public const string GetAllIndexes = " manager_analytics_get_all_indexes";
169170
public const string GetAllDataverses = " manager_analytics_get_all_dataverses";
171+
public const string CreateLink = "manager_analytics_create_link";
172+
public const string ReplaceLink = "manager_analytics_replace_link";
173+
public const string DropLink = "manager_analytics_drop_link";
174+
public const string GetLinks = "manager_analytics_get_links";
175+
170176
}
171177

172178
public static class Query
@@ -178,6 +184,14 @@ public static class Query
178184
public const string DropPrimaryIndex = "manager_query_drop_primary_index";
179185
public const string GetAllIndexes = "manager_query_get_all_indexes";
180186
public const string WatchIndexes = "manager_query_watch_indexes";
187+
public const string BuildDeferredQueryIndex = BuildDeferredIndexes;
188+
public const string CreateQueryIndex = CreateIndex;
189+
public const string CreatePrimaryQueryIndex = CreatePrimaryIndex;
190+
public const string DropQueryIndex = DropIndex;
191+
public const string DropPrimaryQueryIndex = DropPrimaryIndex;
192+
public const string GetAllQueryIndexes = GetAllIndexes;
193+
public const string WatchQueryIndex = WatchIndexes;
194+
181195
}
182196

183197
public static class Bucket
@@ -197,6 +211,8 @@ public static class Collections
197211
public const string DropCollection = "manager_collections_drop_collection";
198212
public const string DropScope = "manager_collections_drop_scope";
199213
public const string GetAllScopes = "manager_collections_get_all_scopes";
214+
public const string UpdateCollection = "manager_collections_update_collection";
215+
200216
}
201217

202218
public static class Search
@@ -217,6 +233,7 @@ public static class Search
217233

218234
public static class Users
219235
{
236+
public const string ChangePassword = "manager_users_change_password";
220237
public const string DropGroup = "manager_users_drop_group";
221238
public const string DropUser = "manager_users_drop_user";
222239
public const string GetAllGroups = "manager_users_get_all_groups";

0 commit comments

Comments
 (0)