查询指标
Mastra 通过三种界面公开相同的五种 OLAP 查询(getMetricAggregate、getMetricBreakdown、getMetricTimeSeries、getMetricPercentiles 以及 discovery helper):进程内 store accessor、运行时 HTTP API 和 mastra api metric CLI。三者接受相同的 Zod 验证输入结构,因此你可以从一次性的 CLI 调查转向编程式仪表板 Tool,而无需重新学习 API。
何时使用何时使用的直接链接
- 在 Studio 之外构建自定义仪表板或 KPI 卡片。
- 为 token 成本或延迟超过阈值时触发的定时告警提供数据。
- 为 Agent 提供读取自身性能指标并在聊天中解释这些指标的 Tool。
- 从终端使用
mastra api metric ...进行一次性调查。
有关可观测性存储本身的设置,请参阅指标概览。可查询的指标名称列表请参阅自动指标参考。
指标查询由 observability domain 提供,它需要支持 OLAP 的存储(本地使用 DuckDB,生产环境使用 ClickHouse)。设置方法请参阅指标概览。如果未配置可观测性存储,getStore('observability') 返回 null。
使用界面使用界面的直接链接
进程内进程内的直接链接
在 Tool、server 路由或 Workflow 步骤内,从 Mastra storage 获取可观测性存储:
import { createTool } from '@mastra/core/tools'
import { z } from 'zod'
export const agentLatencyTool = createTool({
id: 'agentLatency',
description: 'Average agent latency over the last hour.',
inputSchema: z.object({}),
execute: async (_input, context) => {
const observability = await context.mastra!.getStorage()!.getStore('observability')
if (!observability) {
throw new Error('Observability domain is not configured (requires DuckDB or ClickHouse)')
}
const result = await observability.getMetricAggregate({
name: ['mastra_agent_duration_ms'],
aggregation: 'avg',
filters: {
timestamp: { start: new Date(Date.now() - 60 * 60 * 1000) },
},
})
return { averageMs: result.value }
},
})
当配置的后端不支持 OLAP 查询时,getStore('observability') 返回 null。
HTTPHTTP的直接链接
mastra dev server(以及任何已部署的 Mastra 运行时)在 /api/observability/metrics/* 下公开相同查询。Aggregate、breakdown、time series 和 percentile endpoint 使用 POST 接收 JSON body。Discovery endpoint 使用带查询参数的 GET。
curl -sS -X POST http://localhost:4111/api/observability/metrics/aggregate \
-H "content-type: application/json" \
-d '{"name":["mastra_agent_duration_ms"],"aggregation":"avg"}'
可用路由:
POST /api/observability/metrics/aggregatePOST /api/observability/metrics/breakdownPOST /api/observability/metrics/timeseriesPOST /api/observability/metrics/percentilesGET /api/observability/metrics(原始行,分页)GET /api/observability/discovery/metric-namesGET /api/observability/discovery/metric-label-keysGET /api/observability/discovery/metric-label-values
@mastra/client-js SDK 将相同路由包装为 mastraClient.getMetricAggregate(...)、getMetricBreakdown(...) 等方法。
CLICLI的直接链接
mastra api metric ... 使用一个 JSON 参数调用相同 endpoint,因此 Agent 或 shell 脚本无需编写代码即可获取指标:
mastra api metric aggregate \
'{"name":["mastra_agent_duration_ms"],"aggregation":"avg"}' \
--url http://localhost:4111
CLI 默认以托管的 Mastra 可观测性(https://observability.mastra.ai)为目标。传入 --url http://localhost:4111 可查询本地 mastra dev server。完整命令列表请参阅 mastra api metric aggregate 及其附近条目。
查询查询的直接链接
getMetricAggregategetmetricaggregate的直接链接
返回单个标量,是 KPI 卡片的构建基础。
输入:
name:一个或多个指标名称的数组。aggregation:'sum' | 'avg' | 'min' | 'max' | 'count' | 'count_distinct' | 'last'之一。filters:可选的过滤器对象。comparePeriod:可选的'previous_period' | 'previous_day' | 'previous_week',用于周期对比。
响应:
value、previousValue、changePercent。- 对于 token 指标,还包括
estimatedCost、costUnit、previousEstimatedCost、costChangePercent。
const observability = await mastra.getStorage()!.getStore('observability')
const cost = await observability!.getMetricAggregate({
name: ['mastra_model_total_input_tokens', 'mastra_model_total_output_tokens'],
aggregation: 'sum',
comparePeriod: 'previous_day',
})
console.log(cost.value, cost.estimatedCost, cost.costUnit, cost.changePercent)
getMetricBreakdowngetmetricbreakdown的直接链接
按一个或多个维度对行分组,并聚合每个组,是 top-N 表格(例如“按 Agent 划分的 token”)的构建基础。
输入:
name:指标名称数组。groupBy:用于分组的字段数组(例如['entityName'])。aggregation:与上文相同的枚举。limit:server 端 top-K 上限。高基数groupBy必须提供。orderDirection:'ASC' | 'DESC'(默认为DESC)。filters:可选。
响应:groups[],每项包含 dimensions(组键到值的记录)、value 和 estimatedCost。
const byAgent = await observability!.getMetricBreakdown({
name: ['mastra_model_total_input_tokens'],
groupBy: ['entityName'],
aggregation: 'sum',
limit: 10,
orderDirection: 'DESC',
})
getMetricTimeSeriesgetmetrictimeseries的直接链接
按固定间隔对值分桶,是折线图和柱状图的构建基础。
输入:
name:指标名称数组。interval:'1m' | '5m' | '15m' | '1h' | '1d'之一。aggregation:与上文相同的枚举。groupBy:可选。省略时,多个指标名称会合并为一个序列。要将指标分开,请为每个指标分别调用一次。filters:可选。
响应:series[],每项包含 name、costUnit,以及由 { timestamp, value, estimatedCost } 组成的 points[]。
const inputTokens = await observability!.getMetricTimeSeries({
name: ['mastra_model_total_input_tokens'],
aggregation: 'sum',
interval: '1h',
filters: {
timestamp: { start: new Date(Date.now() - 24 * 60 * 60 * 1000) },
},
})
getMetricPercentilesgetmetricpercentiles的直接链接
返回按时间分桶的百分位值,是延迟图表的构建基础。
输入:
name:单个指标名称(字符串,而不是数组)。percentiles:0到1之间的数字数组,例如[0.5, 0.95, 0.99]。interval:与getMetricTimeSeries相同的枚举。filters:可选。
响应:series[],每项包含 percentile 和由 { timestamp, value } 组成的 points[]。
const latency = await observability!.getMetricPercentiles({
name: 'mastra_agent_duration_ms',
percentiles: [0.5, 0.95],
interval: '1h',
})
DiscoveryDiscovery的直接链接
使用这些 endpoint 填充下拉列表,或为 Agent 提供可用于过滤的值菜单。所有 discovery 路由都使用 GET,位于 /api/observability/discovery/ 下。
指标专用(也作为 mastra api metric 子命令公开):
| 方法 | 参数 | 路径后缀 | CLI |
|---|---|---|---|
getMetricNames | { prefix?, limit? } | metric-names | mastra api metric names |
getMetricLabelKeys | { metricName } | metric-label-keys | mastra api metric label-keys |
getMetricLabelValues | { metricName, labelKey, prefix?, limit? } | metric-label-values | mastra api metric label-values |
与 Trace 和日志共享(仅 HTTP,没有专用 CLI 子命令):
| 方法 | 参数 | 路径后缀 |
|---|---|---|
getEntityTypes | {} | entity-types |
getEntityNames | { entityType? } | entity-names |
getServiceNames | {} | service-names |
getEnvironments | {} | environments |
getTags | { entityType? } | tags |
过滤过滤的直接链接
每个查询都接受相同的 filters 对象。最常用的字段包括:
name:限制为特定指标名称。(Aggregate/breakdown/time series 顶层的name已经实现此功能。当需要在单个查询中混合多个指标时,请使用filters.name。)timestamp:{ start, end, startExclusive, endExclusive }。两个边界均可选。省略end表示“直到现在”。provider、model、costUnit:用于 token 和成本指标。labels:精确匹配指标标签的键值,例如持续时间指标使用{ status: 'error' }。- 关联字段:
entityType、entityName、parentEntityName、rootEntityName、userId、organizationId、resourceId、runId、sessionId、threadId、requestId、executionSource、environment、serviceName、experimentId、tags。
相同的 filters 结构适用于三种界面:
// In-process
await observability!.getMetricAggregate({
name: ['mastra_tool_duration_ms'],
aggregation: 'avg',
filters: { entityName: 'weatherTool', labels: { status: 'error' } },
})
# CLI
mastra api metric aggregate \
'{"name":["mastra_tool_duration_ms"],"aggregation":"avg","filters":{"entityName":"weatherTool","labels":{"status":"error"}}}' \
--url http://localhost:4111
# HTTP
curl -sS -X POST http://localhost:4111/api/observability/metrics/aggregate \
-H "content-type: application/json" \
-d '{"name":["mastra_tool_duration_ms"],"aggregation":"avg","filters":{"entityName":"weatherTool","labels":{"status":"error"}}}'
始终提供时间范围始终提供时间范围的直接链接
filters.timestamp 是可选的,但对于针对生产存储运行的任何查询,都应将其视为必需。可观测性表通常按事件时间分区(TimescaleDB 则分块)。提供 timestamp.start(最好也提供 end)后,后端可以裁剪到与范围重叠的分区,通常只有一两个。没有时间范围时,planner 必须扫描每个分区;在保留一年数据的情况下可能有数百个片段,这是基于 Postgres 的存储中 OLAP 查询变慢最常见的原因。
临时查询的安全默认值是过去 24 小时;告警和仪表板应匹配其实际评估窗口:
await observability!.getMetricAggregate({
name: ['mastra_agent_duration_ms'],
aggregation: 'p95',
filters: {
timestamp: { start: new Date(Date.now() - 24 * 60 * 60 * 1000) },
},
})
此建议适用于所有后端(ClickHouse、Postgres v-next、DuckDB),但对 Postgres v-next 尤其重要,因为每缺少一个时间边界都会直接增加一次分区扫描。
示例:构建自定义 KPI 卡片示例:构建自定义 KPI 卡片的直接链接
以下 Tool 返回过去一小时的输入 token 数量和预估成本。Agent 或仪表板可以将其作为 structuredContent 调用,而无需重新实现查询。
import { createTool } from '@mastra/core/tools'
import { z } from 'zod'
export const tokenKpiTool = createTool({
id: 'tokenKpi',
description: 'Returns input-token volume and estimated cost for the last hour.',
inputSchema: z.object({}),
outputSchema: z.object({
inputTokens: z.number().nullable(),
estimatedCost: z.number().nullable(),
costUnit: z.string().nullable(),
changePercent: z.number().nullable(),
}),
execute: async (_input, context) => {
const observability = await context.mastra!.getStorage()!.getStore('observability')
if (!observability) {
throw new Error('Observability domain is not configured (requires DuckDB or ClickHouse)')
}
const result = await observability.getMetricAggregate({
name: ['mastra_model_total_input_tokens'],
aggregation: 'sum',
filters: {
timestamp: { start: new Date(Date.now() - 60 * 60 * 1000) },
},
comparePeriod: 'previous_period',
})
return {
inputTokens: result.value,
estimatedCost: result.estimatedCost ?? null,
costUnit: result.costUnit ?? null,
changePercent: result.changePercent ?? null,
}
},
})