Metrics and dashboard data
Kumiko uses the word “metric” for two different things:
- Dashboard metrics come from the bundled
metricsandmetrics-systemfeatures. A metric declaration becomes a query that aggregates a table and returns a value, a trend and rows for dashboard panels. This guide covers them. - Prometheus metrics come from
r.metric. They are counters and histograms that a monitoring system scrapes. They do not show up on a dashboard screen.
Prerequisites
Section titled “Prerequisites”- An app with the
admin-shellfeature, or your own dashboard screen. - The features that own the tables your metrics read, for example
jobsordelivery.
Mount the metrics features
Section titled “Mount the metrics features”Pass the same metric list to createMetricsFeature, createSystemMetricsFeature
and createAdminShellFeature. The admin console sample mounts seven of the
default metrics:
// No sessions feature in this app, so no active-users metric.const CONSOLE_METRICS = [ jobRunsByStatusMetric, failedJobRunsMetric, tenantJobFailuresMetric, deliveriesByChannelMetric, failedDeliveriesMetric, activeTenantsMetric, auditWritesMetric,] as const;
export const APP_FEATURES = [ localeDe(), createSecretsFeature(), createAuditFeature(), createDeliveryFeature(), createJobsFeature(), createTierEngineFeature(), createMetricsFeature({ metrics: CONSOLE_METRICS }), createSystemMetricsFeature({ metrics: CONSOLE_METRICS }), createAdminShellFeature({ metrics: CONSOLE_METRICS }), homeFeature,] as const;createMetricsFeature registers the tenant metrics (feature metrics), and
createSystemMetricsFeature registers the cross-tenant ones (feature
metrics-system). admin-shell requires both and builds its overview pages
from the metric ids it knows.
Each metric names the feature that owns its table in requires. Boot fails
when that feature is not mounted, so drop the metrics you cannot serve. The
show-pony app has no delivery feature and filters the delivery metrics out
of DEFAULT_METRICS:
import { DEFAULT_TIER, SHOWPONY_TIER_MAP } from "./features/show-pony/tier-map";import { showPonyLegalLayout } from "./marketing/layouts/legal";import { resolveSubdomainPageTenant } from "./tenant-routing";
/** Overview screens + nav only — app-shell owns workspaces host/platform. */const adminShellFeature = createAdminShellFeature({ registerWorkspaces: false, includeTierAdmin: true, metrics: DEFAULT_METRICS,});Add an app metric
Section titled “Add an app metric”defineMetric describes one aggregation over a table. This example counts the
open orders of the caller’s tenant per day:
import { defineMetric, metricQueryName } from "@cosmicdrift/kumiko-bundled-features/metrics";import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";import { orderEntity } from "./order-entity";
const orderTable = buildEntityTable("order", orderEntity);
export const openOrdersMetric = defineMetric({ id: "shop-open-orders", description: "Counts open orders created in the selected range, per day.", source: orderTable, requires: "shop", measure: { fn: "count" }, where: { status: "open" }, timeField: "createdAt", bucket: "day", scopes: ["tenant"],});
export const openOrdersQuery = metricQueryName("tenant", openOrdersMetric.id);Add it to the shared list next to the defaults, for example
const APP_METRICS = [...DEFAULT_METRICS, openOrdersMetric], and pass that list
to all three factories. Each metrics feature only registers the metrics whose
scopes include its own scope, so metrics-system skips a tenant-only metric.
The main options:
| Option | Meaning |
|---|---|
measure | { fn: "count" }, or countDistinct, sum, avg with a field |
where | Fixed filter on the source table |
timeField | Timestamp column. Without it the metric is a snapshot with no window |
window | Fixed range (24h, 7d, 14d, 30d). Without it the caller picks the range |
bucket | hour, day or auto (hour for 24h, otherwise day) |
groupBy, stackBy, groupLabels | Split the result into rows and segments, with i18n keys for the labels |
filters | Payload keys that narrow the result, for example a folder filter |
comparePrevious | Default true. Set false when the previous period cannot be measured |
scopes | tenant, system or both |
What the framework does
Section titled “What the framework does”Every metric becomes one query per scope, named
metrics:query:<id> for the tenant scope and metrics-system:query:<id> for
the system scope. Use metricQueryName(scope, id) instead of writing the
name by hand. Tenant queries are open to tenant admins and always aggregate
the caller’s own tenant. System queries are open to system admins only.
The result carries value, delta and deltaDirection against the previous
period, points for a sparkline, and series and rows for charts and
ranked lists.
Show it on a dashboard
Section titled “Show it on a dashboard”admin-shell only places the metrics it knows. Show an app metric on your own
dashboard screen:
r.screen({ id: "shop-dashboard", type: "dashboard", panels: [ { kind: "stat-group", id: "kpis", stats: [ { kind: "stat", id: "open-orders", label: "shop:dashboard.openOrders", query: openOrdersQuery, valueField: "value", deltaField: "delta", deltaDirectionField: "deltaDirection", sparklineField: "points", }, ], }, ],});Aggregate in your own handler
Section titled “Aggregate in your own handler”A metric query is built on ctx.db.aggregate(table, spec, where). Call it
directly when a query handler needs a sum or a count that does not fit a
metric declaration. Like the other ctx.db reads, it only sees rows of the
caller’s tenant and shared rows of the system tenant:
const rows = await ctx.db.aggregate( orderTable, { measure: { fn: "sum", field: "total" }, groupBy: [{ field: "status" }], orderByValue: "desc", limit: 10, }, { archived: false },);measure takes the same functions as a metric. Each groupBy entry is a
field, or a time field with bucket (hour or day) and a timeZone.
orderByValue, orderByKeys and limit sort and cut the result. Each row
carries one key per groupBy entry and the measured value.
Common gotchas
Section titled “Common gotchas”- Boot fails with a missing feature. A metric’s
requiresnames a feature the app does not mount. Remove the metric from the list. - The admin overview does not show your metric. It only renders known default ids. Add your own dashboard screen.
- The lists differ. Pass one shared constant to all three factories, as the samples do.
See also
Section titled “See also”metricsandmetrics-systemfeature referencer.metricfor Prometheus metrics- Admin console sample