@absolutejs/metering (帮助)
租户付费+多租户Bun运行时间的预算执行.
为在一个主机下运行许多小型 Bun 应用程序的 PaaS 供应商所建. 消费
handlerMetrics 从 @absolutejs/sync
和生命周期事件 @absolutejs/runtime, (中文(简体) ),
每个租户翻来翻去 任何租户一到就开断路器
超出预算范围。 运行时间和运行时间之间的库 SB-6 层
下游的收费/可观察管道.
import { createMeter, consoleSink } from "@absolutejs/metering";
const meter = createMeter({
sinks: [consoleSink, influxSink],
budgets: {
"*": { cpuMs: 60_000, requests: 10_000 }, // free-tier default
"acme-prod": { cpuMs: 600_000, requests: 1_000_000 }, // paid override
},
onBreach: ({ tenant, dimension, observed, limit }) => {
suspendAtRouter(tenant, { dimension, observed, limit });
},
});
// Wire it into a sync engine: sync handlerMetrics records → meter.record(...)
syncEngine.handlerMetrics = (record) => {
meter.record({
type: "handler",
tenant: currentTenantId(),
mutationName: record.mutationName,
durationMs: record.durationMs,
cpuMs: record.cpuMs,
heapBytes: record.heapBytes,
ok: record.ok,
errorName: record.errorName,
});
};
// And a runtime: spawn/idle-kill/exit transitions → meter.record(...)
runtime.options.onTransition = (event) => {
meter.record({
type: "process",
tenant: event.key,
transition: event.type,
durationMs: event.durationMs,
});
};
// And @absolutejs/[email protected]'s Linux observation events:
runtime.options.onMetrics = (event) => {
if (event.type === "observation") {
meter.record({
type: "observation",
tenant: event.key,
cpuMs: event.cpuMs,
rssBytes: event.rssBytes,
at: event.at,
});
}
};
// Record one terminal AI agent run. `costMicros` is caller-calculated so model
// pricing stays an operator policy instead of becoming stale library data.
meter.record({
type: "ai",
tenant: tenantId,
requestId,
provider: "anthropic",
model: "claude-sonnet-4-6",
inputTokens: finish.usage.inputTokens,
outputTokens: finish.usage.outputTokens,
cacheReadInputTokens: finish.usage.cacheReadInputTokens,
cacheWriteInputTokens: finish.usage.cacheWriteInputTokens,
durationMs: finish.durationMs,
turns: finish.turns,
costMicros,
ok: finish.reason === "complete",
stopReason: finish.reason,
});
// Inside a hosted workload, hand handler/request events to the authenticated
// host-local collector. The PaaS injects this endpoint, token, and per-process
// source id; no tenant id is accepted from application code.
import { createWorkloadMeterReporter } from "@absolutejs/metering/reporter";
import { workloadMeterElysia } from "@absolutejs/metering/elysia";
const reporter = createWorkloadMeterReporter({
endpoint: process.env.ABSOLUTE_METER_ENDPOINT!,
sourceId: process.env.ABSOLUTE_METER_SOURCE_ID!,
token: process.env.ABSOLUTE_METER_TOKEN!,
});
const app = new Elysia().use(workloadMeterElysia({ reporter }));
syncEngine.handlerMetrics = reporter.handlerMetrics;
// In your request handler, gate on the meter:
if (!meter.allow(tenantId))
return new Response("Quota exceeded", { status: 429 });
水面 (0.2.0) (中文(简体) )
| API 密码 | 目的 |
|---|---|
createMeter(options) | 工厂。 回返a Meter。 。 。 . |
meter.record(event) | 接受一个 MeterEvent —— 说 handler, (中文(简体) ), process, (中文(简体) ), observation,或 ai。更新滚动, 扇出到下沉, 可能绊倒断路器. |
meter.allow(tenant) | 机前出门. 回返 false 如果任何累积预算跌倒,任何滚动窗口规则目前已经结束,或 reset() 粘稠的累计旅行之后还没有被叫来. |
meter.usage(tenant) | 卷起的快照: cpuMs, (中文(简体) ), processCpuMs, (中文(简体) ), bytesEgress, (中文(简体) ), hibernationGbSeconds, (中文(简体) ), processRssBytesPeak等 (中文(简体) ). |
meter.rollingSum(tenant, dimension, windowMs) | 目前滚动窗口总计 (tenant, dimension, window)对于客户端“您在此窗口中保留了 N 请求” 显示. |
meter.rollingFor(tenant) | 积极滚动规则. |
meter.reset(tenant) | 清除一个累积断路器,而不将累积的用法零化。 滚窗出行 自动清空 当事件排出. |
meter.clear(tenant) | 零积用并清取出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出. |
meter.tenants() | 至今为止,每个房客都见过. |
meter.budget(tenant) | 积极累计预算. |
meter.tripped(tenant) | 重新评价滚动规则;称它可以解开窗户已耗尽的租户的电话. |
meter.snapshot() 编号 : restore(snap) | 相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相相接相相接相接相接相相相相相相相相接相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相 活生生硬地重启;账单不重置为零. |
meter.dispose() | 等待每一个水槽 flush?,则 close?。 。 。 . |
银色
A 类 MeterSink 要么是一个函数 (event) => void | Promise<void> 或对象
{ ingest, flush?, close? }辛克被按顺序扇出. 掷出或
从一个汇中拒绝不会停止以后的汇 - 计分器在
收费的关键路径。 错误被登录到 stderr; 记录器继续运行 .
打开 dispose(),每个物体形状的槽 flush() 正在等待 (横跨
然后是每个 close() 正在等待中。 倒冲被记录为+被吞;
之后的汇仍然被冲走. 这就是批量适配器(Stripe, Influx, ClickHouse)
在关闭时不要放弃最后几起事件.
捆绑: consoleSink. 后作为 " 通流 " / " 普罗米修斯 " / " 花纹 " 船的适配器
兄弟姐妹的包裹.
累计预算
budgets['*'] 是默认的;按租户计算的条目将覆盖它。 任意维度
击出其极限出行的断裂器; onBreach 火灾 每次旅行一次 (电话)
reset() 重新武装。 之后的事件仍在积累——法案保留
即使门关了 也还是会不断增长 这和真正的收费方法相符.
尺寸 : cpuMs, (中文(简体) ), processCpuMs, (中文(简体) ), bytesEgress, (中文(简体) ), requests, (中文(简体) ), errors, (中文(简体) ), hibernationGbSeconds, (中文(简体) ), aiRequests, (中文(简体) ), aiInputTokens, (中文(简体) ), aiOutputTokens, (中文(简体) ), aiCostMicros。 。 。 .
滚动窗口预算
createMeter({
rollingBudgets: {
"*": [
{ dimension: "errors", windowMs: 5 * 60_000, limit: 50 }, // 50 errors / 5 min trips the breaker
{ dimension: "requests", windowMs: 1 * 60_000, limit: 1_000 }, // 1k req / min rate cap
],
"acme-prod": [
{ dimension: "cpuMs", windowMs: 60_000, limit: 50_000 }, // 50s sandbox CPU / minute
],
},
});
滚动金额到达时的滚动窗口规则出行 limit。 。 。 . 它会关闭
自动 由于事件排出窗口——没有 reset() 需要帮助。 这是
与累计预算的差额,该差额一直持续到 reset()两者
(a) 可在同一租户上设置各种类型; allow() 实值 false 如果有任何规则旅行.
观察会计
@absolutejs/[email protected] 发射 { type: 'observation', cpuMs, rssBytes } 打开
一个可配置的间隔。 计数器处理 cpuMs 自产卵以来的共性
从上次观察开始就把三角洲充电 A 类 process 事件
transition === 'spawn', (中文(简体) ), 'adopt', (中文(简体) ), 'restored',或 'exit' 重新设置
因此,新程序或新监管程序不会重复收费。 仅a
真实 spawn 增加产卵计数器.
休眠会计
@absolutejs/runtime 发射 idle-kill 编号 : lru-evict 转换; 计量
电话员负责计算 GB 秒的租户的架子,同时
冬眠并传入 hibernationGbSeconds 编辑 process 活动.
计数器总结了它看到的值——它没有推断出这些值.
抓图+还原
const json = JSON.stringify(meter.snapshot());
await persistToDisk('/var/lib/meter/state.json', json);
// On shard restart:
const restored = createMeter({ ... same config ... });
restored.restore(JSON.parse(await readFromDisk('/var/lib/meter/state.json')));
该快照捕捉每个租户的用法,被绊倒的状态,滚动的窗口状态, 和最后观测基准,因此下一次观测 a 明智的三角洲,而不是跳到 累积的- 自进程启动值.
建筑作用
@absolutejs/sync- 发射handlerMetrics记录每个沙盒变异.@absolutejs/runtime- 在每个产卵上发出生命周期事件/闲置杀死/退出.@absolutejs/metering—— 说 这个图书馆将每个租户的门翻出.@absolutejs/router(计划)——消费meter.allow()拒绝交通 超量租户在边缘.
许可证
BSL 1.1为主机多租户计量/成本分配/每租户计费类别(Stripe Metered Billing, Orb, Metronome, Lago, Amberflo, Cloudflare Workers计费, Convex使用仪表板,Vercel使用仪表板)设置了命名的例外. 见 自由主义. 更改日期: 4 从首次发布起; 更改许可证: Apache 2.0.