Skip to content

Commit

Permalink
Metrics Part 1 - Add Counter Metric API (#1940)
Browse files Browse the repository at this point in the history
Add Counter Metric API (part 1) (#1940)
* added Counter Metric API
* added Metric Aggregation, with hub/client integration and close
* added metric tag normalization
* added send of statsd envelope type

Add other metric types and weight (part 2) (#1949)
* added crc32_utils.dart, taken from archive library
* added Gauge, Distribution and Set metrics
* added weight to Metrics and auto flush when weight is too much

Add timing metric and beforeMetric callback (part 3) (#1954)
* added SentryOptions.beforeMetricCallback
* added beforeMetricCallback logic in metrics_aggregator.dart
* added timing metric api with span auto start
* timing api uses span duration as value for the emitted metric if possible

Add metrics span summary (part 4) (#1958)
* added local_metrics_aggregator.dart to spans
* metrics_aggregator.dart now adds to current span's localMetricsAggregator
* added metric_summary.dart
* added metricSummary to spans and transaction JSONs

Add rate limit (part 5) (#1973)
* added metric_bucket data category for rate limits
* updated metric normalization rules
* added rate limit for metrics
  • Loading branch information
stefanosiano authored Apr 9, 2024
1 parent 61e71ec commit 84bc635
Show file tree
Hide file tree
Showing 48 changed files with 3,021 additions and 81 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

## Unreleased

### Features

- Experimental: Add support for Sentry Developer Metrics ([#1940](https://github.com/getsentry/sentry-dart/pull/1940), [#1949](https://github.com/getsentry/sentry-dart/pull/1949), [#1954](https://github.com/getsentry/sentry-dart/pull/1954), [#1958](https://github.com/getsentry/sentry-dart/pull/1958))
Use the Metrics API to track processing time, download sizes, user signups, and conversion rates and correlate them back to tracing data in order to get deeper insights and solve issues faster. Our API supports counters, distributions, sets, gauges and timers, and it's easy to get started:
```dart
Sentry.metrics()
.increment(
'button_login_click', // key
value: 1.0,
unit: null,
tags: {"provider": "e-mail"}
);
```
To learn more about Sentry Developer Metrics, head over to our [Dart](https://docs.sentry.io/platforms/dart/metrics/) and [Flutter](https://docs.sentry.io/platforms/flutter/metrics/) docs page.

### Dependencies

- Expand `package_info_plus` version range to `6.0.0` ([#1948](https://github.com/getsentry/sentry-dart/pull/1948))
Expand Down
48 changes: 48 additions & 0 deletions dart/lib/src/hub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import 'dart:async';
import 'dart:collection';

import 'package:meta/meta.dart';
import 'metrics/metric.dart';
import 'metrics/metrics_aggregator.dart';
import 'metrics/metrics_api.dart';
import 'profiling.dart';
import 'propagation_context.dart';
import 'transport/data_category.dart';
Expand Down Expand Up @@ -38,6 +41,14 @@ class Hub {

late final _WeakMap _throwableToSpan;

late final MetricsApi _metricsApi;

@internal
MetricsApi get metricsApi => _metricsApi;

@internal
MetricsAggregator? get metricsAggregator => _peek().client.metricsAggregator;

factory Hub(SentryOptions options) {
_validateOptions(options);

Expand All @@ -49,6 +60,7 @@ class Hub {
_stack.add(_StackItem(_getClient(_options), Scope(_options)));
_isEnabled = true;
_throwableToSpan = _WeakMap(_options);
_metricsApi = MetricsApi(hub: this);
}

static void _validateOptions(SentryOptions options) {
Expand Down Expand Up @@ -554,6 +566,42 @@ class Hub {
return sentryId;
}

@internal
Future<SentryId> captureMetrics(
Map<int, Iterable<Metric>> metricsBuckets) async {
var sentryId = SentryId.empty();

if (!_isEnabled) {
_options.logger(
SentryLevel.warning,
"Instance is disabled and this 'captureMetrics' call is a no-op.",
);
} else if (!_options.enableMetrics) {
_options.logger(
SentryLevel.info,
"Metrics are disabled and this 'captureMetrics' call is a no-op.",
);
} else if (metricsBuckets.isEmpty) {
_options.logger(
SentryLevel.info,
"Metrics are empty and this 'captureMetrics' call is a no-op.",
);
} else {
final item = _peek();
try {
sentryId = await item.client.captureMetrics(metricsBuckets);
} catch (exception, stackTrace) {
_options.logger(
SentryLevel.error,
'Error while capturing metrics.',
exception: exception,
stackTrace: stackTrace,
);
}
}
return sentryId;
}

@internal
void setSpanContext(
dynamic throwable,
Expand Down
15 changes: 15 additions & 0 deletions dart/lib/src/hub_adapter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import 'package:meta/meta.dart';
import 'hint.dart';

import 'hub.dart';
import 'metrics/metric.dart';
import 'metrics/metrics_aggregator.dart';
import 'metrics/metrics_api.dart';
import 'profiling.dart';
import 'protocol.dart';
import 'scope.dart';
Expand All @@ -23,6 +26,10 @@ class HubAdapter implements Hub {
@internal
SentryOptions get options => Sentry.currentHub.options;

@override
@internal
MetricsApi get metricsApi => Sentry.currentHub.metricsApi;

factory HubAdapter() {
return _instance;
}
Expand Down Expand Up @@ -181,4 +188,12 @@ class HubAdapter implements Hub {

@override
Scope get scope => Sentry.currentHub.scope;

@override
Future<SentryId> captureMetrics(Map<int, Iterable<Metric>> metricsBuckets) =>
Sentry.currentHub.captureMetrics(metricsBuckets);

@override
MetricsAggregator? get metricsAggregator =>
Sentry.currentHub.metricsAggregator;
}
37 changes: 37 additions & 0 deletions dart/lib/src/metrics/local_metrics_aggregator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'dart:core';
import 'package:meta/meta.dart';
import '../protocol/metric_summary.dart';
import 'metric.dart';

@internal
class LocalMetricsAggregator {
// format: <export key, <metric key, gauge>>
final Map<String, Map<String, GaugeMetric>> _buckets = {};

void add(final Metric metric, final num value) {
final bucket =
_buckets.putIfAbsent(metric.getSpanAggregationKey(), () => {});

bucket.update(metric.getCompositeKey(), (m) => m..add(value),
ifAbsent: () => Metric.fromType(
type: MetricType.gauge,
key: metric.key,
value: value,
unit: metric.unit,
tags: metric.tags) as GaugeMetric);
}

Map<String, List<MetricSummary>> getSummaries() {
final Map<String, List<MetricSummary>> summaries = {};
for (final entry in _buckets.entries) {
final String exportKey = entry.key;

final metricSummaries = entry.value.values
.map((gauge) => MetricSummary.fromGauge(gauge))
.toList();

summaries[exportKey] = metricSummaries;
}
return summaries;
}
}
Loading

0 comments on commit 84bc635

Please sign in to comment.