Skip to content
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

Metrics Part 1 - Add Counter Metric API #1940

Merged
merged 16 commits into from Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
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
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
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
@@ -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;
}
}