Custom metrics
Skill almasumdev/awesome-mobile-observability-agent-skills/.github/skills/metrics/custom-metrics
Agent skills for logging, metrics, tracing, crash reporting, and analytics in mobile apps.
npx -y skills add almasumdev/awesome-mobile-observability-agent-skills --skill custom-metricsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Emit counters, gauges, and histograms from mobile apps using OpenTelemetry metrics, with patterns for batching, labels, and avoiding cardinality explosions. Use when adding product or engineering metrics beyond the core RUM set.
SKILL.md
6.4 KB, as published. Nobody here has run it
Custom Metrics on Mobile
Instructions
When the core RUM SLIs are not enough, emit custom metrics. Keep them few, bounded, and named consistently.
1. Choose the Instrument Type
- Counter: monotonic increments. Good for "X happened". Example:
checkout.attempt.count. - UpDownCounter: increments and decrements. Good for "how many X are in flight". Example:
media.player.active. - Gauge (observable): instantaneous value observed on a schedule. Good for "current battery level", "pending queue size".
- Histogram: distribution of values. Good for durations, sizes, counts-per-event. Example:
image.decode.duration_ms.
Do not use a counter where a histogram is appropriate; you lose percentiles forever.
2. Naming Convention
<domain>.<subject>.<measure>[.<unit>], all snake_case.
checkout.payment.submit.countcheckout.payment.submit.duration_ms(histogram)feed.scroll.jank_frame.countmedia.decode.bytes(histogram of bytes)
Include the unit in the name when it's not obvious. The unit also goes in setUnit(...) on the instrument for vendor dashboards.
3. Android (Kotlin, OTel)
object Metrics {
private val meter = GlobalOpenTelemetry.getMeter("my-app")
val checkoutSubmits: LongCounter = meter.counterBuilder("checkout.payment.submit.count")
.setDescription("Number of payment submit attempts")
.build()
val checkoutDuration: DoubleHistogram = meter.histogramBuilder("checkout.payment.submit.duration_ms")
.setUnit("ms")
.setExplicitBucketBoundariesAdvice(listOf(50.0, 100.0, 250.0, 500.0, 1000.0, 2500.0, 5000.0))
.build()
}
fun submitPayment(method: String) {
val start = System.nanoTime()
val result = runCatching { paymentApi.submit() }
val duration = (System.nanoTime() - start) / 1_000_000.0
val attrs = Attributes.of(
AttributeKey.stringKey("method"), method,
AttributeKey.stringKey("outcome"), if (result.isSuccess) "ok" else "error"
)
Metrics.checkoutSubmits.add(1, attrs)
Metrics.checkoutDuration.record(duration, attrs)
}
4. iOS (Swift, OTel)
let meter = OpenTelemetry.instance.meterProvider.get(instrumentationName: "my-app")
let submits = meter.createIntCounter(name: "checkout.payment.submit.count")
let duration = meter.createDoubleHistogram(name: "checkout.payment.submit.duration_ms",
unit: "ms")
func submitPayment(method: String) async {
let start = DispatchTime.now()
let outcome: String
do { try await paymentApi.submit(); outcome = "ok" }
catch { outcome = "error" }
let ms = Double(DispatchTime.now().uptimeNanoseconds - start.uptimeNanoseconds) / 1_000_000
let labels: [String: AttributeValue] = [
"method": .string(method),
"outcome": .string(outcome)
]
submits.add(value: 1, labels: labels)
duration.record(value: ms, labels: labels)
}
5. Flutter (Dart)
Dart OTel is still maturing; the pragmatic path is to emit metrics via Sentry (Sentry.metrics()) or Firebase Performance custom metrics, and convert to OTel at the collector.
Sentry.metrics().increment('checkout.payment.submit.count',
value: 1, tags: {'method': method, 'outcome': outcome});
Sentry.metrics().distribution('checkout.payment.submit.duration_ms',
value: durationMs, unit: DurationSentryMeasurementUnit.milliSecond,
tags: {'method': method, 'outcome': outcome});
6. React Native (TypeScript)
import { metrics } from '@opentelemetry/api';
const meter = metrics.getMeter('my-app');
const submits = meter.createCounter('checkout.payment.submit.count');
const duration = meter.createHistogram('checkout.payment.submit.duration_ms', {
unit: 'ms',
advice: { explicitBucketBoundaries: [50, 100, 250, 500, 1000, 2500, 5000] },
});
export async function submitPayment(method: string) {
const start = performance.now();
let outcome = 'ok';
try { await paymentApi.submit(); } catch { outcome = 'error'; throw; }
finally {
const attrs = { method, outcome };
submits.add(1, attrs);
duration.record(performance.now() - start, attrs);
}
}
7. Emission Patterns
- Emit once per business event, not once per UI tick.
- Always record outcome: success and failure counts must be the same instrument with an
outcomelabel; otherwise dashboards cannot compute error rate. - Pre-allocate instruments at module load; do not create a new histogram per call.
- Avoid side effects: emitting a metric must never throw or block. Wrap in a try/catch at the call site if the SDK is not guaranteed safe.
8. Label Budget
Hard cap: 4 labels per metric. If you need more dimensions, emit multiple separate metrics.
Typical labels:
outcome(ok,error,timeout)method/action(bounded enum)screen(from the registry)error_class(only whenoutcome != ok)
Forbidden as labels: user_id, session_id, trace_id, raw URLs, free-text input.
9. Aggregation Strategy
- Delta temporality for mobile: you cannot rely on a long-lived accumulator because processes die often. Configure the exporter with
AggregationTemporality.DELTA. - PeriodicMetricReader flush interval: 60 s in foreground, disabled in background (drain on
onPause/applicationWillResignActive).
val reader = PeriodicMetricReader.builder(otlpMetricExporter)
.setInterval(Duration.ofSeconds(60))
.build()
10. Dashboards
- A per-feature dashboard with
count,error_rate = error_count / total_count, and the histogram's p50/p95. - Always include a "volume" panel so you can distinguish "SLI improved" from "traffic dropped".
Checklist
- Each metric has a clear instrument type (counter / up-down / gauge / histogram).
- Names follow
<domain>.<subject>.<measure>[.<unit>]and includesetUnit. -
outcomelabel is present on every operation metric. - Label count is <= 4 and all label values are bounded enums.
- Histograms have explicit bucket boundaries chosen for the expected range.
- Instruments are allocated once, not per call.
- Delta temporality is configured; exporter flushes on background transition.
- Dashboards include count, error rate, and histogram percentiles.