Skip to content

Commit ff2ff3d

Browse files
committed
feat(metrics): add service operation to metrics label
1 parent 8332367 commit ff2ff3d

8 files changed

Lines changed: 120 additions & 40 deletions

File tree

core/core/src/raw/operation.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,13 @@ impl From<Operation> for String {
8686
v.into_static().to_string()
8787
}
8888
}
89+
90+
/// A service-specific operation name attached to HTTP requests.
91+
///
92+
/// While [`Operation`] describes the OpenDAL-level operation (e.g., read, write, list),
93+
/// `ServiceOperation` describes the specific backend API call being made as a supplement.
94+
///
95+
/// Services attach this as an HTTP request extension so that observability layers
96+
/// can provide finer-grained breakdowns of HTTP traffic.
97+
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
98+
pub struct ServiceOperation(pub &'static str);

core/layers/fastmetrics/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ impl LabelSetSchema for OperationLabels {
498498
observe::LABEL_OPERATION,
499499
observe::LABEL_ERROR,
500500
observe::LABEL_STATUS_CODE,
501+
observe::LABEL_SERVICE_OPERATION,
501502
];
502503
Some(NAMES)
503504
}
@@ -517,6 +518,9 @@ impl EncodeLabelSet for OperationLabels {
517518
if let Some(code) = &self.labels.status_code {
518519
encoder.encode(&(observe::LABEL_STATUS_CODE, code.as_str()))?;
519520
}
521+
if let Some(service_operation) = self.labels.service_operation {
522+
encoder.encode(&(observe::LABEL_SERVICE_OPERATION, service_operation))?;
523+
}
520524
Ok(())
521525
}
522526
}

core/layers/metrics/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,13 @@ impl OperationLabels {
191191
));
192192
}
193193

194+
if let Some(service_operation) = self.0.service_operation {
195+
labels.push(Label::new(
196+
observe::LABEL_SERVICE_OPERATION,
197+
service_operation,
198+
));
199+
}
200+
194201
labels
195202
}
196203
}

core/layers/observe-metrics-common/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ pub static LABEL_OPERATION: &str = "operation";
202202
pub static LABEL_ERROR: &str = "error";
203203
/// The metric label for the http code.
204204
pub static LABEL_STATUS_CODE: &str = "status_code";
205+
/// The metric label for the service-specific operation (e.g., "GetObject", "UploadPart").
206+
pub static LABEL_SERVICE_OPERATION: &str = "service_operation";
205207

206208
/// MetricLabels are the labels for the metrics.
207209
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
@@ -226,6 +228,8 @@ pub struct MetricLabels {
226228
/// Only populated for `HttpStatusErrorsTotal` metric.
227229
/// Used to track frequency of specific HTTP error status codes.
228230
pub status_code: Option<StatusCode>,
231+
/// The service-specific operation name as an optional supplement for operation name.
232+
pub service_operation: Option<&'static str>,
229233
}
230234

231235
impl MetricLabels {
@@ -569,14 +573,15 @@ impl<I: MetricsIntercept> Drop for ExecutingGuard<I> {
569573

570574
impl<I: MetricsIntercept> HttpFetch for MetricsHttpFetcher<I> {
571575
async fn fetch(&self, req: http::Request<Buffer>) -> Result<http::Response<HttpBody>> {
572-
let labels = MetricLabels::new(
576+
let mut labels = MetricLabels::new(
573577
self.info.clone(),
574578
req.extensions()
575579
.get::<Operation>()
576580
.copied()
577581
.map(Operation::into_static)
578582
.unwrap_or("unknown"),
579583
);
584+
labels.service_operation = req.extensions().get::<ServiceOperation>().map(|s| s.0);
580585

581586
let start = Instant::now();
582587
let req_size = req.body().len();

core/layers/otelmetrics/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,13 @@ impl OtelMetricsInterceptor {
449449
));
450450
}
451451

452+
if let Some(service_operation) = attrs.service_operation {
453+
attributes.push(KeyValue::new(
454+
observe::LABEL_SERVICE_OPERATION,
455+
service_operation,
456+
));
457+
}
458+
452459
attributes
453460
}
454461
}

core/layers/prometheus-client/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,9 @@ impl EncodeLabelSet for OperationLabels {
506506
if let Some(code) = &self.labels.status_code {
507507
(observe::LABEL_STATUS_CODE, code.as_str()).encode(encoder.encode_label())?;
508508
}
509+
if let Some(service_operation) = self.labels.service_operation {
510+
(observe::LABEL_SERVICE_OPERATION, service_operation).encode(encoder.encode_label())?;
511+
}
509512
Ok(())
510513
}
511514
}

core/layers/prometheus/src/lib.rs

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -375,12 +375,13 @@ impl PrometheusLayerBuilder {
375375
.map_err(parse_prometheus_error)?
376376
};
377377

378+
let http_labels = OperationLabels::names().with_service_operation();
378379
let http_executing = {
379380
let metric = observe::MetricValue::HttpExecuting(0);
380381
register_int_gauge_vec_with_registry!(
381382
metric.name(),
382383
metric.help(),
383-
labels.as_ref(),
384+
http_labels.as_ref(),
384385
registry
385386
)
386387
.map_err(parse_prometheus_error)?
@@ -390,7 +391,7 @@ impl PrometheusLayerBuilder {
390391
register_histogram_vec_with_registry!(
391392
metric.name(),
392393
metric.help(),
393-
labels.as_ref(),
394+
http_labels.as_ref(),
394395
self.bytes_buckets.clone(),
395396
registry
396397
)
@@ -401,7 +402,7 @@ impl PrometheusLayerBuilder {
401402
register_histogram_vec_with_registry!(
402403
metric.name(),
403404
metric.help(),
404-
labels.as_ref(),
405+
http_labels.as_ref(),
405406
self.bytes_rate_buckets.clone(),
406407
registry
407408
)
@@ -412,7 +413,7 @@ impl PrometheusLayerBuilder {
412413
register_histogram_vec_with_registry!(
413414
metric.name(),
414415
metric.help(),
415-
labels.as_ref(),
416+
http_labels.as_ref(),
416417
self.duration_seconds_buckets.clone(),
417418
registry
418419
)
@@ -423,7 +424,7 @@ impl PrometheusLayerBuilder {
423424
register_histogram_vec_with_registry!(
424425
metric.name(),
425426
metric.help(),
426-
labels.as_ref(),
427+
http_labels.as_ref(),
427428
self.bytes_buckets,
428429
registry
429430
)
@@ -434,7 +435,7 @@ impl PrometheusLayerBuilder {
434435
register_histogram_vec_with_registry!(
435436
metric.name(),
436437
metric.help(),
437-
labels.as_ref(),
438+
http_labels.as_ref(),
438439
self.bytes_rate_buckets,
439440
registry
440441
)
@@ -445,7 +446,7 @@ impl PrometheusLayerBuilder {
445446
register_histogram_vec_with_registry!(
446447
metric.name(),
447448
metric.help(),
448-
labels.as_ref(),
449+
http_labels.as_ref(),
449450
self.duration_seconds_buckets,
450451
registry
451452
)
@@ -456,19 +457,21 @@ impl PrometheusLayerBuilder {
456457
register_int_counter_vec_with_registry!(
457458
metric.name(),
458459
metric.help(),
459-
labels.as_ref(),
460+
http_labels.as_ref(),
460461
registry
461462
)
462463
.map_err(parse_prometheus_error)?
463464
};
464465

465-
let labels_with_status_code = OperationLabels::names().with_status_code();
466+
let http_labels_with_status_code = OperationLabels::names()
467+
.with_service_operation()
468+
.with_status_code();
466469
let http_status_errors_total = {
467470
let metric = observe::MetricValue::HttpStatusErrorsTotal;
468471
register_int_counter_vec_with_registry!(
469472
metric.name(),
470473
metric.help(),
471-
labels_with_status_code.as_ref(),
474+
http_labels_with_status_code.as_ref(),
472475
registry
473476
)
474477
.map_err(parse_prometheus_error)?
@@ -562,72 +565,72 @@ impl observe::MetricsIntercept for PrometheusInterceptor {
562565
match value {
563566
observe::MetricValue::OperationBytes(v) => self
564567
.operation_bytes
565-
.with_label_values(&labels.values())
568+
.with_label_values(&labels.op_values())
566569
.observe(v as f64),
567570
observe::MetricValue::OperationBytesRate(v) => self
568571
.operation_bytes_rate
569-
.with_label_values(&labels.values())
572+
.with_label_values(&labels.op_values())
570573
.observe(v),
571574
observe::MetricValue::OperationEntries(v) => self
572575
.operation_entries
573-
.with_label_values(&labels.values())
576+
.with_label_values(&labels.op_values())
574577
.observe(v as f64),
575578
observe::MetricValue::OperationEntriesRate(v) => self
576579
.operation_entries_rate
577-
.with_label_values(&labels.values())
580+
.with_label_values(&labels.op_values())
578581
.observe(v),
579582
observe::MetricValue::OperationDurationSeconds(v) => self
580583
.operation_duration_seconds
581-
.with_label_values(&labels.values())
584+
.with_label_values(&labels.op_values())
582585
.observe(v.as_secs_f64()),
583586
observe::MetricValue::OperationErrorsTotal => self
584587
.operation_errors_total
585-
.with_label_values(&labels.values())
588+
.with_label_values(&labels.op_values())
586589
.inc(),
587590
observe::MetricValue::OperationExecuting(v) => self
588591
.operation_executing
589-
.with_label_values(&labels.values())
592+
.with_label_values(&labels.op_values())
590593
.add(v as i64),
591594
observe::MetricValue::OperationTtfbSeconds(v) => self
592595
.operation_ttfb_seconds
593-
.with_label_values(&labels.values())
596+
.with_label_values(&labels.op_values())
594597
.observe(v.as_secs_f64()),
595598

596599
observe::MetricValue::HttpExecuting(v) => self
597600
.http_executing
598-
.with_label_values(&labels.values())
601+
.with_label_values(&labels.http_values())
599602
.add(v as i64),
600603
observe::MetricValue::HttpRequestBytes(v) => self
601604
.http_request_bytes
602-
.with_label_values(&labels.values())
605+
.with_label_values(&labels.http_values())
603606
.observe(v as f64),
604607
observe::MetricValue::HttpRequestBytesRate(v) => self
605608
.http_request_bytes_rate
606-
.with_label_values(&labels.values())
609+
.with_label_values(&labels.http_values())
607610
.observe(v),
608611
observe::MetricValue::HttpRequestDurationSeconds(v) => self
609612
.http_request_duration_seconds
610-
.with_label_values(&labels.values())
613+
.with_label_values(&labels.http_values())
611614
.observe(v.as_secs_f64()),
612615
observe::MetricValue::HttpResponseBytes(v) => self
613616
.http_response_bytes
614-
.with_label_values(&labels.values())
617+
.with_label_values(&labels.http_values())
615618
.observe(v as f64),
616619
observe::MetricValue::HttpResponseBytesRate(v) => self
617620
.http_response_bytes_rate
618-
.with_label_values(&labels.values())
621+
.with_label_values(&labels.http_values())
619622
.observe(v),
620623
observe::MetricValue::HttpResponseDurationSeconds(v) => self
621624
.http_response_duration_seconds
622-
.with_label_values(&labels.values())
625+
.with_label_values(&labels.http_values())
623626
.observe(v.as_secs_f64()),
624627
observe::MetricValue::HttpConnectionErrorsTotal => self
625628
.http_connection_errors_total
626-
.with_label_values(&labels.values())
629+
.with_label_values(&labels.http_values())
627630
.inc(),
628631
observe::MetricValue::HttpStatusErrorsTotal => self
629632
.http_status_errors_total
630-
.with_label_values(&labels.values())
633+
.with_label_values(&labels.http_values())
631634
.inc(),
632635
_ => {}
633636
}
@@ -652,6 +655,11 @@ impl OperationLabelNames {
652655
self.0.push(observe::LABEL_STATUS_CODE);
653656
self
654657
}
658+
659+
fn with_service_operation(mut self) -> Self {
660+
self.0.push(observe::LABEL_SERVICE_OPERATION);
661+
self
662+
}
655663
}
656664

657665
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
@@ -667,15 +675,29 @@ impl OperationLabels {
667675
])
668676
}
669677

670-
fn values(&self) -> Vec<&str> {
671-
let mut labels = Vec::with_capacity(6);
678+
fn op_values(&self) -> Vec<&str> {
679+
let mut labels = vec![
680+
self.0.scheme,
681+
self.0.namespace.as_ref(),
682+
self.0.root.as_ref(),
683+
self.0.operation,
684+
];
685+
686+
if let Some(error) = self.0.error {
687+
labels.push(error.into_static());
688+
}
689+
690+
labels
691+
}
672692

673-
labels.extend([
693+
fn http_values(&self) -> Vec<&str> {
694+
let mut labels = vec![
674695
self.0.scheme,
675696
self.0.namespace.as_ref(),
676697
self.0.root.as_ref(),
677698
self.0.operation,
678-
]);
699+
self.0.service_operation.unwrap_or("unknown"),
700+
];
679701

680702
if let Some(error) = self.0.error {
681703
labels.push(error.into_static());

0 commit comments

Comments
 (0)