docs: consolidate design documentation and standardize crate English
This commit is contained in:
@@ -1,39 +1,12 @@
|
||||
# session-metrics
|
||||
|
||||
Session 単位の append-only な観測値を既存 session-log に記録し、明示的な
|
||||
metrics 読取 / JSONL export 経路で取り出すための小さなヘルパークレートです。
|
||||
`session-metrics` records measurements in append-only Session logs and retrieves them
|
||||
through dedicated read/export paths. It handles measurement types and Segment lineage;
|
||||
callers choose what to measure and calculate charges. Measurements are not exposed in
|
||||
ordinary conversation snapshots.
|
||||
|
||||
- 保存先は `session-store` の `LogEntry::Extension`
|
||||
- extension domain は `metrics`
|
||||
- metric は `name / ts / dimensions / value / correlation_id` の最小 envelope
|
||||
- `record_metric` で指定した Session / Segment に append する
|
||||
- `read_segment_metrics` は 1 Segment、`read_session_metrics` は Session 内の全
|
||||
Segment を読み、各 metric に `segment_id` と `compacted_from` を付ける
|
||||
- `export_metrics_jsonl` はその located metric を newline-delimited JSON にする
|
||||
- 通常の Session snapshot / Worker list / Worker detail は Extension を公開しない
|
||||
## Design entry points
|
||||
|
||||
compaction は `compact.start` を source Segment、`compact.finish` と
|
||||
`compact.post_request` を結果 Segment に記録する。同じ `correlation_id` と
|
||||
`SegmentStart.compacted_from` により、Segment をまたぐ attempt と次の通常 LLM
|
||||
request を結合できる。
|
||||
|
||||
```rust,ignore
|
||||
use session_metrics::{
|
||||
Metric, export_metrics_jsonl, read_session_metrics, record_metric,
|
||||
};
|
||||
|
||||
let metric = Metric::now("compact.start")
|
||||
.with_value(12_345.0)
|
||||
.with_dimension("trigger", "pre_run")
|
||||
.with_correlation_id("018f6f8a-9822-7b11-8b35-706f30313700");
|
||||
record_metric(
|
||||
&store,
|
||||
location.session_id,
|
||||
location.segment_id,
|
||||
&metric,
|
||||
)?;
|
||||
|
||||
let records = read_session_metrics(&store, location.session_id)?;
|
||||
let jsonl = export_metrics_jsonl(&records)?;
|
||||
# Ok::<(), Box<dyn std::error::Error>>(())
|
||||
```
|
||||
- [`Metric`]: missing values and correlation with later measurements
|
||||
- [`LocatedMetric`]: storage locations and compaction lineage
|
||||
- [`read_session_metrics`]: read boundary for aggregation across Segments
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
//! Session metrics — generic ad-hoc measurement lane on top of
|
||||
//! `LogEntry::Extension { domain: "metrics" }`.
|
||||
//!
|
||||
//! セッション中に積み上げて後で引きたい値(prune の発火頻度・Hook の実行
|
||||
//! 時間・ツールリトライ回数 等)を session-log に乗せるための薄い層。
|
||||
//! session-store は payload を不透明な `serde_json::Value` として扱うので、
|
||||
//! このクレートは型と読み書きヘルパーだけを提供する。
|
||||
//!
|
||||
//! # 設計
|
||||
//!
|
||||
//! - 厳格な label set は持たない。次元は sparse な `BTreeMap<String,String>`、
|
||||
//! 観測できない値は `None` で明示する
|
||||
//! - 「後から埋まる値」(例: prune 発火直後の `cache_read_tokens`)は前 entry に
|
||||
//! 書き戻さず、`correlation_id` を共有する別 metric として流す。集計は読み手で join
|
||||
//! - 集計 / 可視化には [`read_session_metrics`] / [`read_segment_metrics`] /
|
||||
//! [`export_metrics_jsonl`] の明示的な metrics 専用経路を使う。通常の
|
||||
//! Session snapshot は `Extension` を公開しない。
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@@ -26,28 +10,33 @@ use session_store::{
|
||||
/// Domain tag used in `LogEntry::Extension` for all metrics records.
|
||||
pub const DOMAIN: &str = "metrics";
|
||||
|
||||
/// 単発の計測値。`name` は `namespace.metric` 形式の自由文字列
|
||||
/// (例: `"prune.fire"`、`"hook.duration"`)。
|
||||
/// A single measurement appended to a Session log.
|
||||
///
|
||||
/// Stored as an extension payload and read separately from ordinary conversation
|
||||
/// snapshots. Labels are unrestricted; unobservable dimensions are omitted and
|
||||
/// missing values use `None`. Values learned later are appended as separate
|
||||
/// measurements with the same `correlation_id`, not written back to old records.
|
||||
/// This allows more measurements without changing conversation bodies or existing records.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Metric {
|
||||
/// `namespace.metric` 形式の名前。
|
||||
/// Name in `namespace.metric` form.
|
||||
pub name: String,
|
||||
/// epoch ms。
|
||||
/// Epoch milliseconds.
|
||||
pub ts: u64,
|
||||
/// sparse な次元(label)。観測できないものはキー自体を入れない。
|
||||
/// Sparse dimensions (labels). Omit keys for unobservable dimensions.
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub dimensions: BTreeMap<String, String>,
|
||||
/// 主スカラ値。dimension では表現したくない数値を載せる場所。
|
||||
/// Primary scalar value for numbers not represented as dimensions.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub value: Option<f64>,
|
||||
/// 関連 metric を join するためのキー。同一 ID を持つ複数 metric は
|
||||
/// 「同じ事象を多面的に観測している」という意味付けで読まれる。
|
||||
/// Key for joining related metrics. Metrics with the same ID represent
|
||||
/// different observations of the same event.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub correlation_id: Option<String>,
|
||||
}
|
||||
|
||||
impl Metric {
|
||||
/// 最小コンストラクタ。`ts` は呼び出し時刻(epoch ms)で埋める。
|
||||
/// Minimal constructor; sets `ts` to the call time in epoch milliseconds.
|
||||
pub fn now(name: impl Into<String>) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
@@ -74,10 +63,10 @@ impl Metric {
|
||||
}
|
||||
}
|
||||
|
||||
/// `LogEntry::Extension { domain: "metrics", payload: <metric> }` を append する。
|
||||
/// Appends `LogEntry::Extension { domain: "metrics", payload: <metric> }`.
|
||||
///
|
||||
/// `save_extension` の薄い wrapper。書き込み失敗は呼び出し側に返す
|
||||
/// (メトリクスのために本体処理を止めるかは呼び出し側の判断)。
|
||||
/// Thin wrapper around `save_extension`. Returns write failures to the caller,
|
||||
/// who decides whether a metrics failure should stop the main operation.
|
||||
pub fn record_metric(
|
||||
store: &impl Store,
|
||||
session_id: SessionId,
|
||||
@@ -88,10 +77,10 @@ pub fn record_metric(
|
||||
save_extension(store, session_id, segment_id, DOMAIN, payload)
|
||||
}
|
||||
|
||||
/// `RestoredState.extensions` から metrics domain の payload を順に取り出し、
|
||||
/// `Metric` 列に fold する。
|
||||
/// Collects metrics-domain payloads from `RestoredState.extensions` in order
|
||||
/// into a sequence of `Metric` values.
|
||||
///
|
||||
/// schema 変更で deserialize できない payload は無視する(後方互換)。
|
||||
/// Skips payloads that cannot deserialize after schema changes for backward compatibility.
|
||||
pub fn metrics_from_extensions(extensions: &[(String, serde_json::Value)]) -> Vec<Metric> {
|
||||
extensions
|
||||
.iter()
|
||||
@@ -100,11 +89,11 @@ pub fn metrics_from_extensions(extensions: &[(String, serde_json::Value)]) -> Ve
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// A metric together with its durable Session/Segment origin.
|
||||
/// A measurement with its storage location and Segment lineage.
|
||||
///
|
||||
/// `compacted_from` is copied from the Segment start record so readers can
|
||||
/// reconstruct compaction lineage without inferring relationships from metric
|
||||
/// names or timestamps.
|
||||
/// Takes `compacted_from` from the Segment's initial record rather than inferring
|
||||
/// compaction order from names or timestamps. Use `correlation_id` to relate the
|
||||
/// same attempt, and `compacted_from` to relate source and resulting Segments.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct LocatedMetric {
|
||||
pub session_id: SessionId,
|
||||
@@ -214,7 +203,12 @@ pub fn read_segment_metrics(
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Read every metric for a Session across all of its Segments.
|
||||
/// Reads measurements from all Segments with lineage.
|
||||
///
|
||||
/// Sorts by timestamp, measurement phase, Segment, and log position for reproducible
|
||||
/// exports under the same conditions. This display order does not prove causality;
|
||||
/// use `correlation_id` and `compacted_from` for relationships. Rejects Segments
|
||||
/// without initial records and Session mismatches.
|
||||
pub fn read_session_metrics(
|
||||
store: &dyn Store,
|
||||
session_id: SessionId,
|
||||
@@ -277,7 +271,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn metric_serializes_minimal_form_compactly() {
|
||||
// dimensions が空 / value/correlation_id が None の時は出力に含めない。
|
||||
// Omit empty dimensions and None values for value/correlation_id.
|
||||
let metric = Metric {
|
||||
name: "x".into(),
|
||||
ts: 1,
|
||||
@@ -410,7 +404,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn fold_skips_undeserializable_payloads() {
|
||||
// 将来 schema が変わって読めない payload も skip して落ちない。
|
||||
// Skip unreadable payloads from future schema changes without failing.
|
||||
let extensions = vec![
|
||||
(DOMAIN.into(), serde_json::json!({ "garbage": true })),
|
||||
(
|
||||
|
||||
Reference in New Issue
Block a user