feat(m1): synthesis — components + anomaly/event/missing (T1.3)
- generate_series: baseline + trend + seasonality + noise (opt heavy-tail)
- inject_anomaly: spike / level_shift / variance_change / missing_segment / drift
* non-overlapping segments (TDD caught an overlap bug) -> labels exactly
match contiguous [start,end) runs
* returns (series, point labels, segment metadata)
- couple_event: persistent per-channel step at t + event text
- add_missing: NaN drop at given rate
- tests/test_synthesis.py (14 tests, RED->GREEN; fixed overlap regression)
Exit criteria: seed-deterministic; labels cover segments; event step persistent.
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
- [x] 1.1 项目脚手架:创建 `pyproject.toml`(torch/transformers/peft/accelerate/datasets/numpy/tqdm/pyyaml/pytest)、`src/tsmm/` 模块骨架、`configs/`、`scripts/`、`tests/`,并建 `data/`、`checkpoints/` 加 `.gitignore`(验证:`python -c "import tsmm"` 不报错;`pytest tests/` 可跑)
|
- [x] 1.1 项目脚手架:创建 `pyproject.toml`(torch/transformers/peft/accelerate/datasets/numpy/tqdm/pyyaml/pytest)、`src/tsmm/` 模块骨架、`configs/`、`scripts/`、`tests/`,并建 `data/`、`checkpoints/` 加 `.gitignore`(验证:`python -c "import tsmm"` 不报错;`pytest tests/` 可跑)
|
||||||
- [x] 1.2 属性词表与采样 `data/attributes.py`:定义变量名词表/单位/采样率档位,`sample_attributes(n_channels)` 返回结构化属性(验证:单测字段齐全、可复现)
|
- [x] 1.2 属性词表与采样 `data/attributes.py`:定义变量名词表/单位/采样率档位,`sample_attributes(n_channels)` 返回结构化属性(验证:单测字段齐全、可复现)
|
||||||
- [ ] 1.3 成分模型与时序合成 `data/synthesis.py`:`generate_series`(趋势+周期+基线+噪声)、`inject_anomaly`(尖刺/水平偏移/方差膨胀/缺失段/缓慢漂移,返回逐点 labels+段元数据)、`couple_event`(t 处阶跃+事件文本)、`add_missing`(NaN 占位)(验证:给定 seed 确定;异常段与 labels 一致;事件处有阶跃)
|
- [x] 1.3 成分模型与时序合成 `data/synthesis.py`:`generate_series`(趋势+周期+基线+噪声)、`inject_anomaly`(尖刺/水平偏移/方差膨胀/缺失段/缓慢漂移,返回逐点 labels+段元数据)、`couple_event`(t 处阶跃+事件文本)、`add_missing`(NaN 占位)(验证:给定 seed 确定;异常段与 labels 一致;事件处有阶跃)
|
||||||
- [ ] 1.4 指令与回答生成 `data/instruct.py`:6 类指令模板(描述/异常/根因/预测/比较/事件关联)+ Evol-Instruct 演化,`build_instruction` 产 JSON 区间/数值/文本回答(验证:异常类 JSON 可 `json.loads` 且段与 labels 吻合)
|
- [ ] 1.4 指令与回答生成 `data/instruct.py`:6 类指令模板(描述/异常/根因/预测/比较/事件关联)+ Evol-Instruct 演化,`build_instruction` 产 JSON 区间/数值/文本回答(验证:异常类 JSON 可 `json.loads` 且段与 labels 吻合)
|
||||||
- [ ] 1.5 离线生成脚本 `scripts/gen_synthetic.py`:多进程产 `align.jsonl`(50万)/`sft.jsonl`(10万)/`eval_synth.jsonl`(2k held-out),支持 `--n/--out/--seed/--workers` + tqdm(验证:`--n 1000` 跑通,抽样 5 条 schema 合规)
|
- [ ] 1.5 离线生成脚本 `scripts/gen_synthetic.py`:多进程产 `align.jsonl`(50万)/`sft.jsonl`(10万)/`eval_synth.jsonl`(2k held-out),支持 `--n/--out/--seed/--workers` + tqdm(验证:`--n 1000` 跑通,抽样 5 条 schema 合规)
|
||||||
- [ ] 1.6 真实 benchmark 加载 `data/real_bench.py`:加载 SMD/MSL/SMAP/SWaT/PSM(至少 2 个),`load_windows(T=512, stride=256)` 滑窗+归一化+标签,异常段改写问答对存 `eval_real.jsonl`(验证:窗口形状 `[N,T,C]`、标签 `[N,T]`、问答非空)
|
- [ ] 1.6 真实 benchmark 加载 `data/real_bench.py`:加载 SMD/MSL/SMAP/SWaT/PSM(至少 2 个),`load_windows(T=512, stride=256)` 滑窗+归一化+标签,异常段改写问答对存 `eval_real.jsonl`(验证:窗口形状 `[N,T,C]`、标签 `[N,T]`、问答非空)
|
||||||
|
|||||||
@@ -0,0 +1,328 @@
|
|||||||
|
"""Component model + time-series synthesis (T1.3).
|
||||||
|
|
||||||
|
Synthesizes multivariate time series as a superposition of interpretable
|
||||||
|
**components** (baseline + trend + seasonality + noise) and supports controlled
|
||||||
|
anomaly / event injection for supervised data generation.
|
||||||
|
|
||||||
|
All functions are deterministic given the same seed.
|
||||||
|
|
||||||
|
Conventions
|
||||||
|
-----------
|
||||||
|
* Series shape: ``(T, C)`` — ``T`` timesteps, ``C`` channels.
|
||||||
|
* ``labels`` are point-wise ``0/1`` of shape ``(T,)`` (an anomaly covers all
|
||||||
|
channels within a segment's time window).
|
||||||
|
* Segments are dicts with at least ``{"type", "start", "end"}`` describing the
|
||||||
|
half-open time interval ``[start, end)`` that is labeled anomalous.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# ─── baseline series generation ────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def generate_series(
|
||||||
|
T: int,
|
||||||
|
C: int,
|
||||||
|
seed: int | None = None,
|
||||||
|
*,
|
||||||
|
baseline_range: tuple[float, float] = (20.0, 80.0),
|
||||||
|
trend_strength: float = 0.02,
|
||||||
|
seasonal_periods: tuple[int, ...] = (16, 64),
|
||||||
|
noise_std: float = 1.0,
|
||||||
|
student_t: bool = False,
|
||||||
|
) -> np.ndarray:
|
||||||
|
"""Generate a multivariate series as baseline + trend + seasonality + noise.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
T, C:
|
||||||
|
Length and number of channels.
|
||||||
|
seed:
|
||||||
|
RNG seed.
|
||||||
|
baseline_range:
|
||||||
|
Per-channel baseline level sampled uniformly from this range.
|
||||||
|
trend_strength:
|
||||||
|
Max slope magnitude of a linear trend per channel.
|
||||||
|
seasonal_periods:
|
||||||
|
Periods (in timesteps) of additive sinusoidal components.
|
||||||
|
noise_std:
|
||||||
|
Std of additive Gaussian noise. If ``student_t`` additionally draws
|
||||||
|
heavy-tailed noise (Student-t via Gaussian mixture) to mimic outliers.
|
||||||
|
student_t:
|
||||||
|
Use heavy-tailed noise (heavier tails than Gaussian).
|
||||||
|
"""
|
||||||
|
rng = np.random.default_rng(seed)
|
||||||
|
t = np.arange(T, dtype=np.float64)
|
||||||
|
|
||||||
|
# per-channel baseline level
|
||||||
|
levels = rng.uniform(*baseline_range, size=C)
|
||||||
|
series = np.broadcast_to(levels, (T, C)).astype(np.float64).copy()
|
||||||
|
|
||||||
|
# per-channel linear trend
|
||||||
|
slopes = rng.uniform(-trend_strength, trend_strength, size=C)
|
||||||
|
series += slopes[None, :] * t[:, None]
|
||||||
|
|
||||||
|
# additive seasonality: each channel picks amplitude + phase per period
|
||||||
|
for period in seasonal_periods:
|
||||||
|
amps = rng.uniform(0.5, 3.0, size=C)
|
||||||
|
phases = rng.uniform(0, 2 * np.pi, size=C)
|
||||||
|
series += amps[None, :] * np.sin(2 * np.pi * t[:, None] / period + phases[None, :])
|
||||||
|
|
||||||
|
# noise
|
||||||
|
noise = rng.normal(0.0, noise_std, size=(T, C))
|
||||||
|
if student_t:
|
||||||
|
# heavy tail: occasionally inflate a few points
|
||||||
|
mask = rng.random((T, C)) < 0.02
|
||||||
|
noise = np.where(mask, noise * rng.uniform(5, 10, size=(T, C)), noise)
|
||||||
|
series += noise
|
||||||
|
return series
|
||||||
|
|
||||||
|
|
||||||
|
# ─── anomaly injection ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Each injector mutates a series copy in-place over [start, end) and returns
|
||||||
|
# the segment metadata dict. They share a label array filled by the caller.
|
||||||
|
|
||||||
|
_MIN_SEG_LEN = 8 # minimum segment length to keep labels meaningful
|
||||||
|
|
||||||
|
|
||||||
|
def _random_window(rng: np.random.Generator, T: int, length: int, occupied=None) -> tuple[int, int]:
|
||||||
|
"""Draw a half-open window ``[start, end)`` of ~``length`` that does not
|
||||||
|
overlap any interval in ``occupied`` (list of ``(start, end)``).
|
||||||
|
|
||||||
|
Falls back to allowing overlap if no free window exists after several tries.
|
||||||
|
"""
|
||||||
|
occupied = occupied or []
|
||||||
|
length = min(max(length, _MIN_SEG_LEN), T)
|
||||||
|
for _ in range(50):
|
||||||
|
start = int(rng.integers(0, T - length + 1))
|
||||||
|
cand = (start, start + length)
|
||||||
|
if all(cand[1] <= s or cand[0] >= e for (s, e) in occupied):
|
||||||
|
return cand
|
||||||
|
# could not find a non-overlapping window; return best effort
|
||||||
|
return int(rng.integers(0, T - length + 1)), int(rng.integers(0, T - length + 1)) + length
|
||||||
|
|
||||||
|
|
||||||
|
def _inject_spike(rng, series, T, C, occupied):
|
||||||
|
for _ in range(50):
|
||||||
|
start = int(rng.integers(0, T))
|
||||||
|
end = min(start + int(rng.integers(1, 4)), T) # 1-3 points
|
||||||
|
if all(end <= s or start >= e for (s, e) in occupied):
|
||||||
|
break
|
||||||
|
mag = rng.uniform(6, 12, size=C)
|
||||||
|
series[start:end] += mag[None, :]
|
||||||
|
return {"type": "spike", "start": start, "end": end, "magnitude": mag.tolist()}
|
||||||
|
|
||||||
|
|
||||||
|
def _inject_level_shift(rng, series, T, C, occupied):
|
||||||
|
start, end = _random_window(rng, T, length=int(rng.integers(40, 90)), occupied=occupied)
|
||||||
|
offset = rng.uniform(10, 30, size=C) * rng.choice([-1, 1], size=C)
|
||||||
|
series[start:end] += offset[None, :]
|
||||||
|
return {
|
||||||
|
"type": "level_shift",
|
||||||
|
"start": start,
|
||||||
|
"end": end,
|
||||||
|
"offset": offset.tolist(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _inject_variance_change(rng, series, T, C, occupied):
|
||||||
|
start, end = _random_window(rng, T, length=int(rng.integers(40, 90)), occupied=occupied)
|
||||||
|
factor = rng.uniform(3, 6)
|
||||||
|
local_std = np.std(series[start:end], axis=0, keepdims=True)
|
||||||
|
# re-draw noise scaled up within the window, centered on the local mean
|
||||||
|
mean = np.mean(series[start:end], axis=0, keepdims=True)
|
||||||
|
series[start:end] = mean + rng.normal(0, 1, size=(end - start, C)) * (local_std * factor)
|
||||||
|
return {
|
||||||
|
"type": "variance_change",
|
||||||
|
"start": start,
|
||||||
|
"end": end,
|
||||||
|
"factor": float(factor),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _inject_missing_segment(rng, series, T, C, occupied):
|
||||||
|
start, end = _random_window(rng, T, length=int(rng.integers(20, 50)), occupied=occupied)
|
||||||
|
series[start:end] = np.nan
|
||||||
|
return {"type": "missing_segment", "start": start, "end": end}
|
||||||
|
|
||||||
|
|
||||||
|
def _inject_drift(rng, series, T, C, occupied):
|
||||||
|
start, end = _random_window(rng, T, length=int(rng.integers(60, 120)), occupied=occupied)
|
||||||
|
# gradual ramp added across the segment
|
||||||
|
ramp = np.linspace(0, 1, end - start)[:, None]
|
||||||
|
slope = rng.uniform(8, 20, size=C) * rng.choice([-1, 1], size=C)
|
||||||
|
series[start:end] += slope[None, :] * ramp
|
||||||
|
return {
|
||||||
|
"type": "drift",
|
||||||
|
"start": start,
|
||||||
|
"end": end,
|
||||||
|
"slope": slope.tolist(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_INJECTORS = {
|
||||||
|
"spike": _inject_spike,
|
||||||
|
"level_shift": _inject_level_shift,
|
||||||
|
"variance_change": _inject_variance_change,
|
||||||
|
"missing_segment": _inject_missing_segment,
|
||||||
|
"drift": _inject_drift,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def inject_anomaly(
|
||||||
|
series: np.ndarray,
|
||||||
|
types: list[str] | tuple[str, ...] | str = "spike",
|
||||||
|
*,
|
||||||
|
seed: int | None = None,
|
||||||
|
) -> tuple[np.ndarray, np.ndarray, list[dict[str, Any]]]:
|
||||||
|
"""Inject one or more anomaly segments into a *copy* of ``series``.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
series:
|
||||||
|
Array of shape ``(T, C)``.
|
||||||
|
types:
|
||||||
|
One anomaly type or a list of types. Each entry produces one segment.
|
||||||
|
Supported: ``spike``, ``level_shift``, ``variance_change``,
|
||||||
|
``missing_segment``, ``drift``.
|
||||||
|
seed:
|
||||||
|
RNG seed.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
out:
|
||||||
|
Mutated copy of ``series`` (NaN where ``missing_segment`` applied).
|
||||||
|
labels:
|
||||||
|
Point-wise ``0/1`` array of shape ``(T,)`` — ``1`` inside any segment.
|
||||||
|
segments:
|
||||||
|
List of metadata dicts; each has at least ``type``, ``start``, ``end``.
|
||||||
|
"""
|
||||||
|
if isinstance(types, str):
|
||||||
|
types = [types]
|
||||||
|
unknown = [t for t in types if t not in _INJECTORS]
|
||||||
|
if unknown:
|
||||||
|
raise ValueError(f"unknown anomaly type(s): {unknown}")
|
||||||
|
|
||||||
|
rng = np.random.default_rng(seed)
|
||||||
|
out = np.array(series, dtype=np.float64, copy=True)
|
||||||
|
T = out.shape[0]
|
||||||
|
C = out.shape[1] if out.ndim == 2 else 1
|
||||||
|
if out.ndim == 1: # tolerate 1-D input gracefully
|
||||||
|
out = out[:, None]
|
||||||
|
|
||||||
|
labels = np.zeros(T, dtype=np.int8)
|
||||||
|
segments: list[dict[str, Any]] = []
|
||||||
|
occupied: list[tuple[int, int]] = []
|
||||||
|
for t in types:
|
||||||
|
seg = _INJECTORS[t](rng, out, T, C, occupied)
|
||||||
|
labels[seg["start"]: seg["end"]] = 1
|
||||||
|
occupied.append((seg["start"], seg["end"]))
|
||||||
|
segments.append(seg)
|
||||||
|
|
||||||
|
if series.ndim == 1:
|
||||||
|
out = out[:, 0]
|
||||||
|
return out, labels, segments
|
||||||
|
|
||||||
|
|
||||||
|
# ─── event coupling ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
_EVENT_TEMPLATES = {
|
||||||
|
"deploy": "deployed new version v{ver} of service {svc}",
|
||||||
|
"rollback": "rolled back service {svc} to v{ver}",
|
||||||
|
"incident": "incident declared on {svc} (severity {sev})",
|
||||||
|
"config": "configuration changed for {svc}",
|
||||||
|
"scale": "scaled {svc} from {n0} to {n1} replicas",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def couple_event(
|
||||||
|
series: np.ndarray,
|
||||||
|
t: int,
|
||||||
|
kind: str = "deploy",
|
||||||
|
*,
|
||||||
|
seed: int | None = None,
|
||||||
|
) -> tuple[np.ndarray, str]:
|
||||||
|
"""Inject a persistent step change at time ``t`` and return event text.
|
||||||
|
|
||||||
|
The series before ``t`` is left untouched; from ``t`` onward a per-channel
|
||||||
|
offset is added (modeling the persistent effect of the event).
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
series:
|
||||||
|
Array of shape ``(T, C)``.
|
||||||
|
t:
|
||||||
|
Event time index (clamped to ``[0, T-1]``).
|
||||||
|
kind:
|
||||||
|
Event key; see :data:`_EVENT_TEMPLATES`.
|
||||||
|
seed:
|
||||||
|
RNG seed.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
out:
|
||||||
|
Mutated copy with a step change at ``t``.
|
||||||
|
text:
|
||||||
|
Human-readable description of the event.
|
||||||
|
"""
|
||||||
|
rng = np.random.default_rng(seed)
|
||||||
|
if kind not in _EVENT_TEMPLATES:
|
||||||
|
raise ValueError(f"unknown event kind: {kind!r}")
|
||||||
|
out = np.array(series, dtype=np.float64, copy=True)
|
||||||
|
if out.ndim == 1:
|
||||||
|
out = out[:, None]
|
||||||
|
squeeze = True
|
||||||
|
else:
|
||||||
|
squeeze = False
|
||||||
|
|
||||||
|
T, C = out.shape
|
||||||
|
t = int(np.clip(t, 0, T - 1))
|
||||||
|
offset = rng.uniform(8, 25, size=C) * rng.choice([-1, 1], size=C)
|
||||||
|
out[t:] += offset[None, :]
|
||||||
|
|
||||||
|
# render event text
|
||||||
|
ctx = {
|
||||||
|
"ver": f"{rng.integers(1, 9)}.{rng.integers(0, 20)}.{rng.integers(0, 10)}",
|
||||||
|
"svc": f"svc-{chr(ord('a') + int(rng.integers(0, 8)))}",
|
||||||
|
"sev": f"SEV{int(rng.integers(1, 4))}",
|
||||||
|
"n0": int(rng.integers(1, 10)),
|
||||||
|
}
|
||||||
|
ctx["n1"] = max(1, ctx["n0"] + int(rng.integers(-3, 6)))
|
||||||
|
text = _EVENT_TEMPLATES[kind].format(**ctx)
|
||||||
|
|
||||||
|
if squeeze:
|
||||||
|
out = out[:, 0]
|
||||||
|
return out, text
|
||||||
|
|
||||||
|
|
||||||
|
# ─── missing values ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def add_missing(
|
||||||
|
series: np.ndarray,
|
||||||
|
rate: float = 0.05,
|
||||||
|
*,
|
||||||
|
seed: int | None = None,
|
||||||
|
) -> np.ndarray:
|
||||||
|
"""Randomly drop a fraction ``rate`` of points, replacing them with NaN.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
series:
|
||||||
|
Array of shape ``(T, C)``.
|
||||||
|
rate:
|
||||||
|
Expected fraction of points to drop (clamped to ``[0, 0.5]``).
|
||||||
|
seed:
|
||||||
|
RNG seed.
|
||||||
|
"""
|
||||||
|
rate = float(np.clip(rate, 0.0, 0.5))
|
||||||
|
rng = np.random.default_rng(seed)
|
||||||
|
out = np.array(series, dtype=np.float64, copy=True)
|
||||||
|
mask = rng.random(out.shape) < rate
|
||||||
|
out[mask] = np.nan
|
||||||
|
return out
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
"""Tests for time-series synthesis (T1.3)."""
|
||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from tsmm.data.synthesis import (
|
||||||
|
generate_series,
|
||||||
|
inject_anomaly,
|
||||||
|
couple_event,
|
||||||
|
add_missing,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ─── generate_series ───────────────────────────────────────────────────────
|
||||||
|
def test_generate_series_shape():
|
||||||
|
s = generate_series(T=256, C=3, seed=0)
|
||||||
|
assert s.shape == (256, 3)
|
||||||
|
assert np.isfinite(s).all(), "baseline series must be finite (no NaN before inject)"
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_series_reproducible():
|
||||||
|
a = generate_series(T=128, C=2, seed=7)
|
||||||
|
b = generate_series(T=128, C=2, seed=7)
|
||||||
|
assert np.array_equal(a, b)
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_series_different_seed_differs():
|
||||||
|
a = generate_series(T=128, C=2, seed=1)
|
||||||
|
b = generate_series(T=128, C=2, seed=2)
|
||||||
|
assert not np.array_equal(a, b)
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_series_has_structure():
|
||||||
|
# baseline + trend + seasonality + noise: variance should be > 0 and the
|
||||||
|
# series should not be constant.
|
||||||
|
s = generate_series(T=512, C=2, seed=3)
|
||||||
|
assert np.std(s) > 0
|
||||||
|
assert np.ptp(s) > 0 # peak-to-peak
|
||||||
|
|
||||||
|
|
||||||
|
# ─── inject_anomaly ────────────────────────────────────────────────────────
|
||||||
|
def test_inject_anomaly_returns_series_and_labels_and_segments():
|
||||||
|
s = generate_series(T=256, C=2, seed=0)
|
||||||
|
out, labels, segments = inject_anomaly(s, types=["spike"], seed=0)
|
||||||
|
assert out.shape == s.shape
|
||||||
|
assert labels.shape == (256,)
|
||||||
|
assert set(np.unique(labels)).issubset({0, 1})
|
||||||
|
assert isinstance(segments, list)
|
||||||
|
|
||||||
|
|
||||||
|
def test_inject_anomaly_labels_cover_segments():
|
||||||
|
s = generate_series(T=256, C=2, seed=0)
|
||||||
|
out, labels, segments = inject_anomaly(s, types=["level_shift", "variance_change"], seed=1)
|
||||||
|
# Each segment must mark a contiguous run of label==1 exactly matching [start,end)
|
||||||
|
for seg in segments:
|
||||||
|
st, en = seg["start"], seg["end"]
|
||||||
|
assert 0 <= st < en <= len(labels)
|
||||||
|
assert labels[st:en].all(), f"segment {seg} not fully labeled"
|
||||||
|
# immediately outside (if exists) must be 0
|
||||||
|
if st > 0:
|
||||||
|
assert labels[st - 1] == 0
|
||||||
|
if en < len(labels):
|
||||||
|
assert labels[en] == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_inject_anomaly_changes_the_series_at_segments():
|
||||||
|
s = generate_series(T=256, C=2, seed=0)
|
||||||
|
out, labels, segments = inject_anomaly(s, types=["spike"], seed=0)
|
||||||
|
assert np.any(labels == 1), "expected at least one anomaly segment"
|
||||||
|
# at least somewhere the injected series differs from baseline
|
||||||
|
assert not np.allclose(out, s)
|
||||||
|
|
||||||
|
|
||||||
|
def test_inject_anomaly_each_supported_type():
|
||||||
|
s = generate_series(T=256, C=2, seed=0)
|
||||||
|
for t in ["spike", "level_shift", "variance_change", "missing_segment", "drift"]:
|
||||||
|
out, labels, segments = inject_anomaly(s, types=[t], seed=0)
|
||||||
|
# missing_segment may place NaN, so use labels to assert effect
|
||||||
|
assert np.any(labels == 1), f"type {t} produced no anomaly segment"
|
||||||
|
|
||||||
|
|
||||||
|
def test_inject_anomaly_missing_segment_inserts_nan():
|
||||||
|
s = generate_series(T=256, C=2, seed=0)
|
||||||
|
out, labels, segments = inject_anomaly(s, types=["missing_segment"], seed=0)
|
||||||
|
assert np.isnan(out).any(), "missing_segment must inject NaN values"
|
||||||
|
|
||||||
|
|
||||||
|
def test_inject_anomaly_reproducible():
|
||||||
|
s = generate_series(T=256, C=2, seed=0)
|
||||||
|
o1, l1, _ = inject_anomaly(s, types=["spike", "level_shift"], seed=5)
|
||||||
|
o2, l2, _ = inject_anomaly(s, types=["spike", "level_shift"], seed=5)
|
||||||
|
assert np.array_equal(o1, o2) and np.array_equal(l1, l2)
|
||||||
|
|
||||||
|
|
||||||
|
# ─── couple_event ──────────────────────────────────────────────────────────
|
||||||
|
def test_couple_event_introduces_step_and_returns_text():
|
||||||
|
s = generate_series(T=256, C=2, seed=0)
|
||||||
|
before = s.copy()
|
||||||
|
out, text = couple_event(s, t=128, kind="deploy", seed=0)
|
||||||
|
assert isinstance(text, str) and len(text) > 0
|
||||||
|
# a step change near t should alter the series magnitude after t
|
||||||
|
assert not np.allclose(out[130:], before[130:])
|
||||||
|
# immediately before t the series is unchanged
|
||||||
|
assert np.allclose(out[:128], before[:128])
|
||||||
|
|
||||||
|
|
||||||
|
def test_couple_event_step_has_persistent_offset():
|
||||||
|
s = generate_series(T=256, C=1, seed=0)
|
||||||
|
out, _ = couple_event(s, t=100, kind="incident", seed=1)
|
||||||
|
# mean of post-event window differs from pre-event baseline
|
||||||
|
pre = out[:100].mean()
|
||||||
|
post = out[120:].mean()
|
||||||
|
assert abs(post - pre) > 1e-6
|
||||||
|
|
||||||
|
|
||||||
|
# ─── add_missing ───────────────────────────────────────────────────────────
|
||||||
|
def test_add_missing_introduces_nan_within_rate():
|
||||||
|
s = generate_series(T=512, C=2, seed=0)
|
||||||
|
out = add_missing(s, rate=0.1, seed=0)
|
||||||
|
assert out.shape == s.shape
|
||||||
|
frac = np.isnan(out).mean()
|
||||||
|
# within reasonable tolerance of requested rate
|
||||||
|
assert 0.0 < frac <= 0.15
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_missing_reproducible():
|
||||||
|
s = generate_series(T=256, C=2, seed=0)
|
||||||
|
a = add_missing(s, rate=0.1, seed=3)
|
||||||
|
b = add_missing(s, rate=0.1, seed=3)
|
||||||
|
assert np.array_equal(a, b, equal_nan=True)
|
||||||
@@ -0,0 +1,830 @@
|
|||||||
|
# 主流时序分析平台「时序探索」交互设计调研报告
|
||||||
|
|
||||||
|
> **调研日期**:2026-06-29
|
||||||
|
> **调研范围**:Grafana Explore、Prometheus、Datadog、Honeycomb、Kibana Lens、Azure Monitor、Dynatrace、Chronosphere、阿里云 ARMS、腾讯云监控、华为云 AOM、夜莺 Nightingale 等 10+ 国内外主流平台
|
||||||
|
> **关注重点**:时间序列探索(Time Series Exploration)环节的交互设计模式
|
||||||
|
> **适用读者**:可观测性平台、AIOps、时序分析产品的产品经理、UX 设计师、前端工程师
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、执行摘要
|
||||||
|
|
||||||
|
时间序列探索(Time Series Exploration)以**查询驱动 + 即时反馈 + 上下钻取**为核心特征,区别于静态仪表板。本报告调研的 10+ 平台呈现出七类核心交互模式(入口与导航、查询构建、时间选择、可视化、下钻联动、对比叠加、异常检测与变量模板),并提炼出 **7 大设计亮点**(Honeycomb BubbleUp、Grafana Split View、Kibana Lens Suggestions、Datadog NLQ + Change Overlay、Dynatrace Correlated Metrics、Chronosphere Control Plane、Prometheus Native Histograms + Exemplars)。
|
||||||
|
|
||||||
|
**关键趋势**:
|
||||||
|
- **可分享的探索 URL + LLM 副驾** 成为标配
|
||||||
|
- **BubbbleUp 风格"选择 vs 基线"对比** 是高基数根因定位的有效范式
|
||||||
|
- **自然语言查询(NLQ)** 在 LLM 时代快速普及
|
||||||
|
- **Split View + 时间同步** 是对比分析的标配交互
|
||||||
|
- **Point Adjustment 协议滥用**导致 F1 虚高 2-5 倍(详见配套 MTSAD 报告)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、平台对比矩阵
|
||||||
|
|
||||||
|
| 平台 | 入口定位 | 查询构建 | 默认时间范围 | 关键可视化 | 核心差异点 |
|
||||||
|
|------|---------|---------|------------|----------|----------|
|
||||||
|
| **Grafana Explore** | 左侧菜单"Explore" | 文本查询(按数据源)+ 部分数据源图形化 | 最近 1 小时 | 折线/柱状/表格/Logs/Traces | Split View 分屏对比、Query Inspector、Outline 导航 |
|
||||||
|
| **Prometheus** | `/graph` | PromQL 纯文本 | 最近 1 小时 | Graph / Table / Console | Native Histograms、Exemplars、新版 UI、Recording Rules |
|
||||||
|
| **Datadog Metrics Explorer** | 顶部"Metrics → Explorer" | NLQ 自然语言 + 文本 + 函数 | 最近 1 小时 | Timeseries/Top List/Heatmap/Query Value/Map | NLQ "Ask"、Change Overlay、Watchdog AI、Notebooks |
|
||||||
|
| **Honeycomb** | "New Query" | Query Builder (SELECT/WHERE/GROUP BY) | 自定义 | Heatmap/折线/Table/Count | **BubbleUp** 选择基线对比、Faceted Search、wide events |
|
||||||
|
| **Kibana Lens** | "Visualize Library → Create visualization" | 拖拽字段 + Formula | 跟随 Data View | Line/Area/Bar/Table | **Suggestion 自动推荐**、Formula(shift/overall_sum)、Sampling |
|
||||||
|
| **AWS CloudWatch Metrics Insights** | CloudWatch → Metrics → Explorer | SQL-like(SELECT/FROM/WHERE/GROUP BY) | 最近 3 小时(可调) | 折线/堆叠/数字 | 类 SQL 语法、跨账号聚合、Time Range Comparison |
|
||||||
|
| **Azure Monitor Metrics Explorer** | Azure Portal → Metrics | GUI 维度选择 + PromQL(部分) | 最近 24 小时 | 折线/柱状/面积 | Y 轴锁定、Drill into Logs、Pin to Grafana |
|
||||||
|
| **Dynatrace Data Explorer** | Observe → Metrics → Data Explorer | 分步表单 + Metrics API v2 高级模式 | 自定义 | Graph/Stacked column/area/Pie/Single value/Table/Top list/Heatmap/Honeycomb | **Davis AI 相关指标推荐**、Timeshift、Seasonal Baseline |
|
||||||
|
| **Chronosphere Control Plane** | "Metrics" 入口 | PromQL 兼容 | 自定义 | 折线为主 | 89% 降本、Quotas 配额、Query Accelerator 自动预聚合 |
|
||||||
|
| **阿里云 ARMS** | 应用监控/Prometheus 监控 | PromQL + GUI | 最近 1 小时 | 折线/Top N/分布 | **调用链分析升级**、LLM 告警收敛、OpenTelemetry 兼容 |
|
||||||
|
| **腾讯云监控 CLS / CAT** | 腾讯云控制台 → 监控 | PromQL + GUI | 最近 1 小时 | 折线/Top N | 全球拨测点、APM/RUM/PTS 套件联动 |
|
||||||
|
| **华为云 APM / AOM** | 控制台 → APM / AOM | PromQL + SQL | 最近 1 小时 | 折线/Top N/调用链 | AOM(应用运维管理)统一入口、调用链详情 |
|
||||||
|
| **夜莺 Nightingale** | Flashcat 文档中心 | PromQL | 最近 1 小时 | 折线/柱状 | 开源、VictoriaMetrics/TDengine 兼容、生态丰富 |
|
||||||
|
|
||||||
|
> **说明**:部分国内平台通过 WebFetch 未能成功抓取详细功能页,对应条目基于产品总览与公开资料整合,标注 `*` 或在正文中标注。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、核心交互模式拆解
|
||||||
|
|
||||||
|
### 3.1 入口与导航:探索与仪表板的"分与合"
|
||||||
|
|
||||||
|
平台对"探索"环节的定位有三种典型路径:
|
||||||
|
|
||||||
|
#### 3.1.1 独立一级入口
|
||||||
|
|
||||||
|
**代表**:Grafana Explore、Prometheus `/graph`
|
||||||
|
|
||||||
|
- Grafana Explore 占据左侧主菜单,与 Dashboards、Alerting 并列
|
||||||
|
- Prometheus `/graph` 与 `/alerts`、`/targets` 并列
|
||||||
|
|
||||||
|
**设计哲学**:强调"探索 ≠ 仪表板",鼓励 ad-hoc 工作流。
|
||||||
|
|
||||||
|
#### 3.1.2 嵌入主菜单但保持轻量
|
||||||
|
|
||||||
|
**代表**:Datadog Metrics Explorer、Honeycomb New Query、Dynatrace Data Explorer
|
||||||
|
|
||||||
|
- 都定位在 "Metrics/Data → Explorer"
|
||||||
|
- 与"仪表板"并列但通常无需先建仪表板即可使用
|
||||||
|
|
||||||
|
#### 3.1.3 隐藏在 Widget 创建中
|
||||||
|
|
||||||
|
**代表**:Kibana(在 Dashboards 内创建 Lens widget)、Azure Monitor(在资源页 "Monitoring" 部分打开 Explorer)
|
||||||
|
|
||||||
|
- 探索结果需要 Pin to Dashboard 才能持久化
|
||||||
|
|
||||||
|
#### 3.1.4 设计观察
|
||||||
|
|
||||||
|
**Honeycomb 走得更远**——其 UI 设计**刻意模糊仪表板与查询的边界**,通过 BubbleUp 把每一次探索都变成可分享的 URL,鼓励"无持久化的探索也是结果"。
|
||||||
|
|
||||||
|
### 3.2 查询构建方式:文本 DSL、图形化、自然语言的三角权衡
|
||||||
|
|
||||||
|
| 形态 | 代表平台 | 优势 | 劣势 |
|
||||||
|
|------|---------|------|------|
|
||||||
|
| **纯文本 DSL** | Prometheus、CloudWatch Metrics Insights、Chronosphere | 高表达力、可版本化、可脚本化 | 学习成本高、错误难调试 |
|
||||||
|
| **分步表单(GUI)** | Dynatrace Data Explorer、Azure Monitor | 维度零学习成本 | 表达力受限、复杂查询难构造 |
|
||||||
|
| **拖拽字段** | Kibana Lens | 极低门槛、自动建议 | 不适合复杂表达式 |
|
||||||
|
| **Query Builder 模式** | Honeycomb(SELECT/WHERE/GROUP BY 块)、New Relic NRQL Builder | 兼顾结构化与可读性 | 仍需理解查询模型 |
|
||||||
|
| **自然语言(NLQ)** | Datadog "Ask"、ARMS Copilot | 真正零门槛 | 表达力边界模糊、需后端语义理解 |
|
||||||
|
|
||||||
|
#### 3.2.1 Grafana Explore 的混合策略
|
||||||
|
|
||||||
|
默认是数据源专属的查询编辑器(如 PromQL 编辑器、LogQL 编辑器、Elasticsearch DSL 编辑器),同时提供 **Kick start your query** 预设列表 + "点击 label 键值对构建"快捷方式。
|
||||||
|
|
||||||
|
#### 3.2.2 Kibana Lens 的 Suggestions
|
||||||
|
|
||||||
|
把"拖一个字段就出来一张图"做到极致——拖入字段后底部出现 Suggestions 按钮,一键切换所有推荐可视化([来源](https://www.elastic.co/guide/en/kibana/current/lens.html))。
|
||||||
|
|
||||||
|
#### 3.2.3 Datadog 的 NLQ
|
||||||
|
|
||||||
|
在搜索框点击 "Ask" 即可用自然语言描述需求,平台自动翻译为结构化 metric query([来源](https://docs.datadoghq.com/metrics/explorer/))。这是 LLM 时代探索型工具的明确方向。
|
||||||
|
|
||||||
|
### 3.3 时间选择器:从"绝对相对"到"对比与同步"
|
||||||
|
|
||||||
|
#### 3.3.1 基础能力
|
||||||
|
|
||||||
|
时间选择器的基本能力各平台大同小异:
|
||||||
|
|
||||||
|
- **相对时间**:now-1h、now-7d、now-1w 等预设
|
||||||
|
- **绝对时间**:手选起止日期 + 时区(Timezone)
|
||||||
|
- **缩放粒度**:从秒到年的多档粒度切换
|
||||||
|
- **自动刷新**:通常 10s–1h,随时间范围变化(Datadog 公开仪表板固定 30s,[来源](https://docs.datadoghq.com/dashboards/))
|
||||||
|
|
||||||
|
#### 3.3.2 创新交互
|
||||||
|
|
||||||
|
**Grafana Split View 的时间联动**:分屏后每侧都有"时间同步"按钮,点击后两侧时间选择器进入"链接"状态,改任一侧另一侧同步([来源](https://grafana.com/docs/grafana/latest/explore/get-started-with-explore/))。
|
||||||
|
|
||||||
|
**Azure Monitor 的 Time Brush 平移与缩放**:图表边缘的左右箭头按钮使时间范围按图表时间跨度的一半前后移动(Pan);通过拖动 brush 选区进行缩放(Zoom),粒度为 Automatic 时会自动选择更小的时间粒度([来源](https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/metrics-getting-started))。
|
||||||
|
|
||||||
|
#### 3.3.3 时间对比(Time Shift)
|
||||||
|
|
||||||
|
被独立设计为一种查询参数:
|
||||||
|
|
||||||
|
- **Datadog**:`timeshift()` 函数
|
||||||
|
- **Dynatrace** Data Explorer:Timeshift 组件(`±2 minute` 格式)
|
||||||
|
- **Kibana Lens**:Advanced → Time shift(输入 `1w` 表示对比上周)
|
||||||
|
- **CloudWatch**:"Compare with previous period"
|
||||||
|
|
||||||
|
**这是探索场景里最常用的模式之一**。
|
||||||
|
|
||||||
|
### 3.4 结果可视化:选图逻辑与多面板拆分
|
||||||
|
|
||||||
|
#### 3.4.1 默认图表
|
||||||
|
|
||||||
|
几乎都是折线图(time series),但扩展能力差异明显:
|
||||||
|
|
||||||
|
- **Grafana Explore**:根据数据源返回类型智能选择,可显式切换 Time Series / Table / Logs / Trace 视图
|
||||||
|
- **Kibana Lens**:在拖入字段后**自动选**聚合函数(Date histogram / Intervals / Top values),并提供 5 种 Missing Values 处理策略(Hide/Zero/Linear/Last/Next),且可设 End values 扩展方式与虚线显示([来源](https://www.elastic.co/guide/en/kibana/current/lens.html))
|
||||||
|
- **Datadog**:根据 widget 类型分化为 Timeseries / Query Value / Top List / Table / Distribution / Pie Chart / Heatmap / Geomap
|
||||||
|
- **Dynatrace Data Explorer**:可选 Graph / Stacked column / Stacked area / Pie / Single value / Table / Top list / **Heatmap** / **Honeycomb**(六边形图),其中 Honeycomb 适合高基数分组浏览([来源](https://docs.dynatrace.com/docs/observe-and-explore/explorer))
|
||||||
|
|
||||||
|
#### 3.4.2 多面板拆分
|
||||||
|
|
||||||
|
三种实现方式:
|
||||||
|
|
||||||
|
**方式 A:水平/垂直分屏**
|
||||||
|
- Grafana Split View
|
||||||
|
- Azure Monitor "New chart"
|
||||||
|
- Datadog Screenboard
|
||||||
|
|
||||||
|
**方式 B:按维度自动拆分**
|
||||||
|
- Datadog "Split Graph" 按钮按 region/service/environment 等标签值自动拆为多张小图([来源](https://docs.datadoghq.com/metrics/explorer/))
|
||||||
|
- Azure Monitor "Apply splitting" 按维度值拆为多条线,可设数量限制 1-50、Descending/Ascending 排序
|
||||||
|
|
||||||
|
**方式 C:分面(Faceting)**
|
||||||
|
- Honeycomb 的 GROUP BY + BubbleUp 联动
|
||||||
|
- Kibana Discover 的字段 Facet 侧栏
|
||||||
|
|
||||||
|
### 3.5 下钻与联动:从指标到事件再到 Trace
|
||||||
|
|
||||||
|
**这是"探索"区别于"展示"的关键**——能**从图表上的一点跳转到根因**:
|
||||||
|
|
||||||
|
#### 3.5.1 Honeycomb BubbleUp
|
||||||
|
|
||||||
|
在 Heatmap 上画框 → 自动生成所有字段的"选区 vs 基线"对比图 → 一键添加 `GROUP BY` / `WHERE` 验证假设。**闭环**做得最彻底([来源](https://docs.honeycomb.io/working-with-data/bubbleup/))。
|
||||||
|
|
||||||
|
#### 3.5.2 Grafana Exemplar 链接
|
||||||
|
|
||||||
|
通过 `--enable-feature=exemplar-storage` 启用后,OpenMetrics scrape 目标可在指标点附加 exemplar(典型为 trace_id),UI 中显示为图中"示例点",点击即跳转到对应 trace([来源](https://prometheus.io/docs/prometheus/latest/feature_flags/))。
|
||||||
|
|
||||||
|
#### 3.5.3 Azure Monitor Drill into Logs
|
||||||
|
|
||||||
|
在指标异常处点击 → 跳转 Logs 子查询,预填 Activity log / Resource logs / Recommended log 三类选项([来源](https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/metrics-getting-started))。
|
||||||
|
|
||||||
|
#### 3.5.4 Dynatrace Correlated Metrics
|
||||||
|
|
||||||
|
基于 Davis AI 的形状相似度分析,在 Graph + Split by 维度上点击线 → "See correlated metrics" 自动加入当前查询。
|
||||||
|
|
||||||
|
#### 3.5.5 阿里云 ARMS
|
||||||
|
|
||||||
|
2024-09 升级"调用链分析能力",明确把"端到端调用链数据全采集与多维度分析"作为联动底座([来源](https://www.aliyun.com/product/arms))。
|
||||||
|
|
||||||
|
### 3.6 对比与叠加:多维度的"看同一时序"
|
||||||
|
|
||||||
|
#### 3.6.1 时间偏移对比
|
||||||
|
|
||||||
|
**Time Shift 是标配**,几乎所有平台都支持(见 3.3.3)。
|
||||||
|
|
||||||
|
#### 3.6.2 多指标叠加/分屏
|
||||||
|
|
||||||
|
- Datadog Split Graph
|
||||||
|
- Azure Monitor Multi-metric / New chart
|
||||||
|
- Grafana Split View
|
||||||
|
|
||||||
|
#### 3.6.3 分组聚合
|
||||||
|
|
||||||
|
- Honeycomb GROUP BY
|
||||||
|
- Dynatrace Split by
|
||||||
|
- CloudWatch GROUP BY
|
||||||
|
|
||||||
|
#### 3.6.4 Change Overlay(变更叠加)—— Datadog 独有设计
|
||||||
|
|
||||||
|
在图表上**自动叠加部署/事件变更线**([来源](https://docs.datadoghq.com/dashboards/)),无需手动 mark,可视化"异常是否与某次发布相关"。
|
||||||
|
|
||||||
|
#### 3.6.5 Annotations
|
||||||
|
|
||||||
|
所有平台都支持手动/自动添加注释线,标记重要事件(部署、告警、运维动作)。
|
||||||
|
|
||||||
|
### 3.7 异常检测与标注
|
||||||
|
|
||||||
|
#### 3.7.1 阈值线
|
||||||
|
|
||||||
|
Datadog、Azure Monitor、Grafana 都支持在图表上叠加水平/垂直参考线。
|
||||||
|
|
||||||
|
#### 3.7.2 置信区间 / Baseline
|
||||||
|
|
||||||
|
- **Dynatrace Data Explorer** 的 Seasonal baseline 模型,根据分辨率自动选择前 14 天或前 5 年作为基线([来源](https://docs.dynatrace.com/docs/observe-and-explore/explorer))
|
||||||
|
- Prometheus 工具链中常通过 `predict_linear()` 或第三方项目实现
|
||||||
|
|
||||||
|
#### 3.7.3 Anomaly 标注
|
||||||
|
|
||||||
|
- **Datadog Watchdog** 自动异常检测
|
||||||
|
- **Honeycomb BubbleUp Insights** 给出自然语言总结与字段严重程度评级([来源](https://docs.honeycomb.io/working-with-data/bubbleup/))
|
||||||
|
- **阿里云 ARMS** 的 LLM 告警收敛与故障洞察是新一代路径
|
||||||
|
|
||||||
|
#### 3.7.4 事件叠加
|
||||||
|
|
||||||
|
把日志事件、Trace 错误、变更事件以 marker 形式标注在时序图上。
|
||||||
|
|
||||||
|
### 3.8 变量与模板
|
||||||
|
|
||||||
|
- **Grafana Template Variables**:用 `$var` 在 Explore 和 Dashboard 间共享过滤器,配合下拉选择器
|
||||||
|
- **Datadog Template Variables**:在 widget 上方定义 dynamic filter
|
||||||
|
- **Honeycomb Saved Queries + URL**:把当前 query 编码进 URL,配合 BubbleUp 实现 permalink 分享
|
||||||
|
- **Azure Monitor "Apply splitting" 数量限制**:1-50 控制输出卡片数,避免变量爆炸
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、7 大设计亮点
|
||||||
|
|
||||||
|
### 4.1 Honeycomb BubbleUp:把"高基数根因搜索"变成"并排柱状图视觉对比"
|
||||||
|
|
||||||
|
**核心抽象层**:
|
||||||
|
- Selection(黄色)+ Baseline(蓝色)+ 逐字段对比图
|
||||||
|
- 三种入口:Heatmap 画框、Chart 选线、Results 悬停行
|
||||||
|
- 闭环:差异 → 一键 WHERE / GROUP BY → 重跑查询验证
|
||||||
|
|
||||||
|
**借鉴价值**:可应用于任何带"全集 vs 子集"语义的场景(如告警事件 vs 正常事件、慢请求 vs 快请求)。
|
||||||
|
|
||||||
|
### 4.2 Grafana Split View:把"分屏"做到极致的时间同步
|
||||||
|
|
||||||
|
**核心交互**:
|
||||||
|
- 一个 Split 按钮 → 自动复制查询 → 左右两个独立面板
|
||||||
|
- time-sync 按钮实现"链接时间"模式
|
||||||
|
|
||||||
|
**借鉴价值**:做对比分析(Staging vs Production、昨日 vs 今日、多 region)时显著降低认知负担。
|
||||||
|
|
||||||
|
### 4.3 Kibana Lens Suggestions:让"不知道用什么图"不再是问题
|
||||||
|
|
||||||
|
**核心交互**:
|
||||||
|
- 拖字段 → 自动生成候选
|
||||||
|
- 底部 Suggestions 按钮切换所有备选
|
||||||
|
- Formula 把 KQL/Elasticsearch 聚合包装成可读公式(`percentile(..., shift='1w')`、`overall_sum(...)`)
|
||||||
|
|
||||||
|
**借鉴价值**:拖拽 + 公式双轨设计,对新手友好同时不丢专业表达力。
|
||||||
|
|
||||||
|
### 4.4 Datadog NLQ "Ask" + Change Overlay:让探索"开箱即用"
|
||||||
|
|
||||||
|
**核心交互**:
|
||||||
|
- 自然语言 → 结构化查询
|
||||||
|
- 变更线自动叠加 → 异常与发布强相关时一眼可见
|
||||||
|
|
||||||
|
**借鉴价值**:探索型工具应最大限度减少"为得到第一张图而做的准备"。
|
||||||
|
|
||||||
|
### 4.5 Dynatrace Correlated Metrics:把"找相关"从手动变自动
|
||||||
|
|
||||||
|
**核心交互**:
|
||||||
|
- Davis AI 按形状相似度推荐相关指标
|
||||||
|
- 解决的是"我不知道该和什么指标一起看"
|
||||||
|
|
||||||
|
**借鉴价值**:内置 ML 助手降低"经验依赖"。
|
||||||
|
|
||||||
|
### 4.6 Chronosphere Control Plane:探索前的"成本-质量"权衡显式化
|
||||||
|
|
||||||
|
**核心思想**:
|
||||||
|
- Quotas(团队/服务容量分配)
|
||||||
|
- Query Accelerator(自动预聚合)
|
||||||
|
|
||||||
|
**借鉴价值**:在大规模可观测性场景,"用什么查询"直接影响"成本/性能",应让用户看见。
|
||||||
|
|
||||||
|
### 4.7 Prometheus Native Histograms + Exemplars + PromQL 新特性
|
||||||
|
|
||||||
|
**核心创新**:
|
||||||
|
- Native Histograms 解决"高基数分布"无损存储
|
||||||
|
- Exemplars 把指标点与 trace 绑定
|
||||||
|
- 新特性:
|
||||||
|
- `promql-extended-range-selectors`(`anchored`/`smoothed`)
|
||||||
|
- `promql-binop-fill-modifiers`(`fill(0)`)
|
||||||
|
- `promql-duration-expr`(`[5m * 2]`)
|
||||||
|
|
||||||
|
**借鉴价值**:在底层语言里支持"探索语义"——而不是只在 UI 上贴。
|
||||||
|
|
||||||
|
### 4.8 共同趋势:可分享的探索 URL + AI 副驾
|
||||||
|
|
||||||
|
- 几乎所有平台都把查询状态编码进 URL(Honecyomb permalinks、Grafana /goto/:uid、Datadog Copy link)
|
||||||
|
- LLM 副驾(ARMS Copilot、Datadog Bits AI、New Relic NRAI、Grafana LLM)正在把"自然语言探索"变成标配
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、可借鉴的 UX 模式 Checklist(产品设计参考)
|
||||||
|
|
||||||
|
### 5.1 探索入口
|
||||||
|
|
||||||
|
- [ ] 与 Dashboard 并列的独立入口,明确传达"探索 ≠ 持久化"
|
||||||
|
- [ ] 在 Dashboard 面板上提供"Open in Explore"快捷入口,把已有查询带入探索态
|
||||||
|
- [ ] 探索结果可一键 Save to Dashboard / Notebook / Incident
|
||||||
|
|
||||||
|
### 5.2 查询构建
|
||||||
|
|
||||||
|
- [ ] 默认文本编辑器(DSL) + GUI 切换 + 自然语言三层并存
|
||||||
|
- [ ] 查询历史(Query History)自动保存
|
||||||
|
- [ ] 已保存查询(Saved Queries)可复用
|
||||||
|
- [ ] Kick start / 预设模板
|
||||||
|
- [ ] 字段自动补全、语法高亮、错误提示
|
||||||
|
- [ ] 字段名点击即加入查询(low-code 路径)
|
||||||
|
|
||||||
|
### 5.3 时间选择
|
||||||
|
|
||||||
|
- [ ] 相对 + 绝对 + 自定义
|
||||||
|
- [ ] 时区切换
|
||||||
|
- [ ] 缩放粒度
|
||||||
|
- [ ] 自动刷新(与时间范围联动)
|
||||||
|
- [ ] Pan/Zoom/Brush
|
||||||
|
- [ ] Time Shift 时间偏移对比
|
||||||
|
- [ ] Split View 时两侧时间可同步
|
||||||
|
|
||||||
|
### 5.4 可视化
|
||||||
|
|
||||||
|
- [ ] 默认折线,5+ 图表类型可切换
|
||||||
|
- [ ] 按维度自动 Split / 分屏
|
||||||
|
- [ ] 多图表多指标独立查看
|
||||||
|
- [ ] Missing Values 处理策略(Hide/Zero/Linear/Last/Next)
|
||||||
|
- [ ] 公式与自定义计算
|
||||||
|
- [ ] 性能:采样、降采样、聚合下推
|
||||||
|
- [ ] Legend 中可显示统计(Avg/Median/Min/Max/Last/Diff%)
|
||||||
|
|
||||||
|
### 5.5 下钻与联动
|
||||||
|
|
||||||
|
- [ ] 图表点 → Trace / Log / Event 跳转
|
||||||
|
- [ ] Exemplar 链接(指标点直接带 trace_id)
|
||||||
|
- [ ] 跨数据源联动(指标 ↔ 日志 ↔ 链路)
|
||||||
|
- [ ] 自动推荐相关指标(Dynatrace Davis 风格)
|
||||||
|
- [ ] BubbleUp 风格的"选择 vs 基线"对比
|
||||||
|
|
||||||
|
### 5.6 对比与异常
|
||||||
|
|
||||||
|
- [ ] 多指标叠加 + 多指标分屏
|
||||||
|
- [ ] 时间偏移对比 / 周环比
|
||||||
|
- [ ] 阈值线 / 参考线
|
||||||
|
- [ ] 置信区间 / Baseline
|
||||||
|
- [ ] Anomaly 标注 + AI 解释
|
||||||
|
- [ ] Change Overlay(变更自动叠加)
|
||||||
|
- [ ] Annotations(手动/自动事件标注)
|
||||||
|
|
||||||
|
### 5.7 协作与分享
|
||||||
|
|
||||||
|
- [ ] 查询状态 URL 化、可分享
|
||||||
|
- [ ] 短链接(避免 IM 截断)
|
||||||
|
- [ ] Embed / iFrame
|
||||||
|
- [ ] 评论与多人协作(Notebook 模式)
|
||||||
|
- [ ] 权限控制(公开/私有/团队)
|
||||||
|
|
||||||
|
### 5.8 性能与可用性
|
||||||
|
|
||||||
|
- [ ] 百万级时序的采样/降采样
|
||||||
|
- [ ] 服务端预聚合(Query Accelerator)
|
||||||
|
- [ ] 加载态/部分加载的友好展示
|
||||||
|
- [ ] 错误提示 + 自动修复建议
|
||||||
|
- [ ] 键盘可访问性(Kibana Lens 风格的 Tab+Space 拖拽)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、各平台详细功能分析
|
||||||
|
|
||||||
|
### 6.1 Grafana Explore
|
||||||
|
|
||||||
|
**来源**:[https://grafana.com/docs/grafana/latest/explore/](https://grafana.com/docs/grafana/latest/explore/)
|
||||||
|
|
||||||
|
#### 核心特性
|
||||||
|
|
||||||
|
- **Split View 分屏对比**:分屏后每侧都有"时间同步"按钮,点击后两侧时间选择器进入"链接"状态,改任一侧另一侧同步
|
||||||
|
- **Query Inspector**:4 个 Tab(Stats、Query、JSON、Data),便于调试查询
|
||||||
|
- **Query Management**:历史(Query History)+ 已保存查询
|
||||||
|
- **Outline 导航**:左侧 Outline 列出当前查询涉及的所有字段
|
||||||
|
- **Time Picker**:绝对/相对/时区/会计年度支持
|
||||||
|
- **Mixed Data Source**:跨数据源关联查询
|
||||||
|
- **URL 分享**:支持 `/goto/:uid` 短链接
|
||||||
|
- **Correlation Editor**:相关字段编辑器
|
||||||
|
- **Trace 集成**:从指标点跳到 trace
|
||||||
|
- **Exemplar 链接**:支持
|
||||||
|
|
||||||
|
#### 设计亮点
|
||||||
|
|
||||||
|
1. **Split View + Time Sync**:最经典的探索模式,适合做对比分析
|
||||||
|
2. **Query Inspector**:对调试复杂查询至关重要
|
||||||
|
3. **Outline 导航**:帮助用户理解查询结构
|
||||||
|
|
||||||
|
### 6.2 Prometheus
|
||||||
|
|
||||||
|
**来源**:[https://prometheus.io/docs/prometheus/latest/querying/basics/](https://prometheus.io/docs/prometheus/latest/querying/basics/)
|
||||||
|
|
||||||
|
#### 核心特性
|
||||||
|
|
||||||
|
- **PromQL Expression 类型**:Instant vector、Range vector、Scalar、String
|
||||||
|
- **Time Series Selectors**:带 label matchers 的选择器
|
||||||
|
- **Table View + Graph View**:两种默认视图
|
||||||
|
- **Offset 和 @ 修饰符**:支持时间偏移和"@ 时间戳"语法
|
||||||
|
- **Native Histograms**:原生直方图,高基数分布无损存储
|
||||||
|
- **Exemplar Storage**:通过 `--enable-feature=exemplar-storage` 启用
|
||||||
|
- **PromQL 新特性**:
|
||||||
|
- `promql-extended-range-selectors`(`anchored`/`smoothed`)
|
||||||
|
- `promql-duration-expr`(`[5m * 2]`)
|
||||||
|
- `promql-binop-fill-modifiers`(`fill(0)`)
|
||||||
|
- **Old UI 回退**:可通过 `--enable-feature=old-ui` 回退到 2.x 风格 Web UI
|
||||||
|
- **Recording Rules**:记录规则
|
||||||
|
- **Console Templates**:控制台模板
|
||||||
|
|
||||||
|
#### 设计亮点
|
||||||
|
|
||||||
|
1. **Exemplar 绑定 trace_id**:让指标和链路打通
|
||||||
|
2. **Native Histograms**:解决高基数分布的痛点
|
||||||
|
3. **PromQL 探索语义内建**:不是 UI 包装,是底层语言支持
|
||||||
|
|
||||||
|
### 6.3 Datadog Metrics Explorer
|
||||||
|
|
||||||
|
**来源**:[https://docs.datadoghq.com/metrics/explorer/](https://docs.datadoghq.com/metrics/explorer/)
|
||||||
|
|
||||||
|
#### 核心特性
|
||||||
|
|
||||||
|
- **NLQ "Ask"**:自然语言查询入口
|
||||||
|
- **Query Editor**:传统文本编辑器
|
||||||
|
- **Scope Filtering**:scope 过滤
|
||||||
|
- **Space Aggregation**:Average / Max / Min / Sum
|
||||||
|
- **Split Graph by tag values**:按 tag 值自动拆图
|
||||||
|
- **Export**:link、incident、monitor、dashboard、notebook
|
||||||
|
- **Quick Graphs**:快速图表
|
||||||
|
- **Functions**:
|
||||||
|
- algorithmic、arithmetic、count
|
||||||
|
- exclusion、interpolation、rank
|
||||||
|
- rate、regression、rollup
|
||||||
|
- smoothing、telemetry source、timeshift
|
||||||
|
- **Widget 类型**:timeseries、query value、top list、table、distribution、pie chart、list/SLO/architecture
|
||||||
|
- **Timeboards vs Screenboards vs Dashboards**:三种仪表板模式
|
||||||
|
- **Refresh Rate**:随时间范围变化
|
||||||
|
|
||||||
|
#### 设计亮点
|
||||||
|
|
||||||
|
1. **NLQ "Ask"**:LLM 时代探索的标杆
|
||||||
|
2. **Change Overlay**:自动叠加变更线,独有设计
|
||||||
|
3. **Watchdog AI**:自动异常检测 + 根因定位
|
||||||
|
4. **Notebooks**:多人协作探索
|
||||||
|
|
||||||
|
### 6.4 Honeycomb
|
||||||
|
|
||||||
|
**来源**:[https://docs.honeycomb.io/working-with-data/bubbleup/](https://docs.honeycomb.io/working-with-data/bubbleup/)
|
||||||
|
|
||||||
|
#### 核心特性
|
||||||
|
|
||||||
|
- **Query Builder (SELECT/WHERE/GROUP BY)**:分块式查询构建
|
||||||
|
- **BubbleUp**:选择基线对比
|
||||||
|
- **BubbleUp Insights**(Honeycomb Intelligence):自然语言总结
|
||||||
|
- **Permalinks via URL**:通过 URL 分享
|
||||||
|
- **BubbleUp Filter**:字段过滤
|
||||||
|
- **三种入口**:
|
||||||
|
- Heatmap 画框
|
||||||
|
- Chart 选线
|
||||||
|
- Results 悬停行
|
||||||
|
- **Dimensions vs Measures**:维度与度量区分
|
||||||
|
- **Click Actions**:Group by Field、Show only where、Copy field name
|
||||||
|
- **Wide Events**:宽事件范式
|
||||||
|
|
||||||
|
#### 设计亮点
|
||||||
|
|
||||||
|
1. **BubbleUp 视觉对比**:黄(选区)vs 蓝(基线),全字段柱状图对比
|
||||||
|
2. **Wide Events**:每个事件携带 50+ 字段,避免预聚合损失
|
||||||
|
3. **Faceted Search**:字段 facet 侧栏
|
||||||
|
4. **无持久化探索**:URL 即结果
|
||||||
|
|
||||||
|
### 6.5 Kibana Lens
|
||||||
|
|
||||||
|
**来源**:[https://www.elastic.co/guide/en/kibana/current/lens.html](https://www.elastic.co/guide/en/kibana/current/lens.html)
|
||||||
|
|
||||||
|
#### 核心特性
|
||||||
|
|
||||||
|
- **Suggestions**:拖字段自动生成候选可视化
|
||||||
|
- **Drag-and-drop with full keyboard accessibility**:拖拽 + 键盘可访问
|
||||||
|
- **Formula 语法**:
|
||||||
|
- `count(kql='...')/count()`
|
||||||
|
- `shift='1w'`
|
||||||
|
- `overall_sum()`
|
||||||
|
- **Time shift**:在 Advanced settings 中设置
|
||||||
|
- **Missing values 处理策略**:Hide/Zero/Linear/Last/Next
|
||||||
|
- **Sampling slider**:性能采样滑块
|
||||||
|
- **Legend statistics**:Average/Median/Min/Max/Range/Last/Diff%/Sum/Count
|
||||||
|
- **Normalize by unit / Customize time interval**:单位归一化/时间间隔自定义
|
||||||
|
- **"Explore data in Discover" link**:跳转到 Discover
|
||||||
|
- **Inspector with CSV download**:检查器 + CSV 下载
|
||||||
|
|
||||||
|
#### 设计亮点
|
||||||
|
|
||||||
|
1. **Suggestions 智能推荐**:拖字段即出图,底部切换备选
|
||||||
|
2. **Formula 双轨设计**:对新手友好且不丢专业表达力
|
||||||
|
3. **键盘可访问性**:Tab + Space 拖拽,无障碍设计典范
|
||||||
|
|
||||||
|
### 6.6 Azure Monitor Metrics Explorer
|
||||||
|
|
||||||
|
**来源**:[https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/metrics-getting-started](https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/metrics-getting-started)
|
||||||
|
|
||||||
|
#### 核心特性
|
||||||
|
|
||||||
|
- **Single or multiple resources**:单/多资源
|
||||||
|
- **Dimension filtering**:Add filter
|
||||||
|
- **Splitting**:Apply splitting,数量限制 1-50
|
||||||
|
- **Aggregation**:Sum/Count/Average/Min/Max
|
||||||
|
- **Pan/Zoom with time brush**:平移/缩放
|
||||||
|
- **Multi-metric / multi-chart**:多指标/多图
|
||||||
|
- **Custom line colors**:自定义线条颜色
|
||||||
|
- **Y-axis range lock**:Y 轴范围锁定
|
||||||
|
- **Drill into Logs**:
|
||||||
|
- Activity log
|
||||||
|
- Resource logs
|
||||||
|
- Recommended log
|
||||||
|
- **Share**:
|
||||||
|
- Pin to dashboard
|
||||||
|
- Save to workbook
|
||||||
|
- Download to Excel
|
||||||
|
- Copy link
|
||||||
|
- Send to Workbook
|
||||||
|
- Pin to Grafana
|
||||||
|
- **PromQL support**:Azure Monitor workspace metrics 支持 PromQL
|
||||||
|
|
||||||
|
#### 设计亮点
|
||||||
|
|
||||||
|
1. **Time Brush 平移/缩放**:边缘按钮按半时间跨度移动
|
||||||
|
2. **Drill into Logs**:从指标异常跳到日志,闭环根因
|
||||||
|
3. **Y 轴锁定**:避免动态缩放干扰对比
|
||||||
|
|
||||||
|
### 6.7 Dynatrace Data Explorer
|
||||||
|
|
||||||
|
**来源**:[https://docs.dynatrace.com/docs/observe-and-explore/explorer](https://docs.dynatrace.com/docs/observe-and-explore/explorer)
|
||||||
|
|
||||||
|
#### 核心特性
|
||||||
|
|
||||||
|
- **Query Components**:
|
||||||
|
- Metric name
|
||||||
|
- Space aggregation
|
||||||
|
- Split by
|
||||||
|
- Sort
|
||||||
|
- Rate
|
||||||
|
- Filter by
|
||||||
|
- Limit
|
||||||
|
- Timeshift
|
||||||
|
- **Visualization**:Graph/Stacked column/area/Pie/Single value/Table/Top list/Heatmap/Honeycomb
|
||||||
|
- **Timeshift**:时间对比
|
||||||
|
- **Baselines**:季节性 baseline 模型
|
||||||
|
- **Correlated Metrics**:Davis AI 自动推荐相关指标
|
||||||
|
- **Focus**:隐藏其他 series
|
||||||
|
- **Pin to dashboard / Share link / Export CSV / Copy request**
|
||||||
|
- **限制**:10 metrics per viz, 100 series per metric
|
||||||
|
|
||||||
|
#### 设计亮点
|
||||||
|
|
||||||
|
1. **Davis AI Correlated Metrics**:AI 自动推荐相关指标
|
||||||
|
2. **Seasonal Baseline**:根据分辨率自动选 14 天或 5 年基线
|
||||||
|
3. **Honeycomb 可视化**:六边形图适合高基数分组
|
||||||
|
|
||||||
|
### 6.8 Chronosphere Control Plane
|
||||||
|
|
||||||
|
**来源**:[https://chronosphere.io/platform/control-plane/](https://chronosphere.io/platform/control-plane/)
|
||||||
|
|
||||||
|
#### 核心特性
|
||||||
|
|
||||||
|
- **Observability Data Optimization Cycle**:
|
||||||
|
- Centralized Governance(Quotas)
|
||||||
|
- Analyze(utility score)
|
||||||
|
- Refine(降采样/聚合/采样)
|
||||||
|
- Operate(Query Accelerator、Query Scheduler)
|
||||||
|
- **PromQL 兼容**
|
||||||
|
- **89% 体积减少**
|
||||||
|
- **50% 故障排查时间减少**
|
||||||
|
|
||||||
|
#### 设计亮点
|
||||||
|
|
||||||
|
1. **数据成本显式化**:让用户看到"这次查询要花多少钱"
|
||||||
|
2. **Query Accelerator 自动预聚合**:提升查询性能
|
||||||
|
3. **Utility Score**:评估数据价值
|
||||||
|
|
||||||
|
### 6.9 阿里云 ARMS
|
||||||
|
|
||||||
|
**来源**:[https://www.aliyun.com/product/arms](https://www.aliyun.com/product/arms)
|
||||||
|
|
||||||
|
#### 核心特性
|
||||||
|
|
||||||
|
- **全栈覆盖**:Web/小程序/Mobile + Java/Golang/Python
|
||||||
|
- **链路打通**:端到端调用链
|
||||||
|
- **智能加持**:LLM 告警收敛、故障洞察
|
||||||
|
- **开源兼容**:OpenTelemetry、Prometheus
|
||||||
|
- **应用监控 / 前端监控 / 可观测链路 OpenTelemetry 版**
|
||||||
|
|
||||||
|
#### 设计亮点
|
||||||
|
|
||||||
|
1. **端到端调用链联动**:从指标到 trace 无缝
|
||||||
|
2. **LLM 告警收敛**:新一代智能体
|
||||||
|
3. **OpenTelemetry 兼容**:开放生态
|
||||||
|
|
||||||
|
### 6.10 夜莺 Nightingale
|
||||||
|
|
||||||
|
**来源**:[https://flashcat.cloud/docs/](https://flashcat.cloud/docs/)
|
||||||
|
|
||||||
|
#### 核心特性
|
||||||
|
|
||||||
|
- **PromQL 兼容**
|
||||||
|
- **开源**
|
||||||
|
- **VictoriaMetrics/TDengine 兼容**
|
||||||
|
- **生态丰富**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、设计模式深度分析
|
||||||
|
|
||||||
|
### 7.1 三种入口与导航模式对比
|
||||||
|
|
||||||
|
| 模式 | 优点 | 缺点 | 代表 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| **独立一级入口** | 探索感强、鼓励 ad-hoc | 与 Dashboard 关系弱 | Grafana Explore、Prometheus /graph |
|
||||||
|
| **嵌入主菜单** | 入口轻量、不混淆 | 用户可能找不到 | Datadog、Honeycomb、Dynatrace |
|
||||||
|
| **隐藏在 Widget 中** | 与 Dashboard 紧密 | 探索结果难持久 | Kibana、Azure Monitor |
|
||||||
|
| **URL 即结果** | 无持久化也分享 | 学习成本高 | Honeycomb |
|
||||||
|
|
||||||
|
### 7.2 查询构建的"门槛-表达力"光谱
|
||||||
|
|
||||||
|
```
|
||||||
|
高门槛
|
||||||
|
│
|
||||||
|
│ 纯文本 DSL(PromQL、CloudWatch Insights)
|
||||||
|
│
|
||||||
|
│ Query Builder(Honeycomb、New Relic)
|
||||||
|
│
|
||||||
|
│ 分步表单(Dynatrace、Azure Monitor)
|
||||||
|
│
|
||||||
|
│ 拖拽字段(Kibana Lens)
|
||||||
|
│
|
||||||
|
│ 自然语言 NLQ(Datadog Ask、ARMS Copilot)
|
||||||
|
│
|
||||||
|
低门槛
|
||||||
|
└─────────────────────────────────→ 高表达力
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键观察**:最佳实践是**多种方式并存**,让用户在不同场景选择合适的工具。
|
||||||
|
|
||||||
|
### 7.3 时间选择的"复杂度阶梯"
|
||||||
|
|
||||||
|
| 阶梯 | 能力 | 代表 |
|
||||||
|
|------|------|------|
|
||||||
|
| L1 | 相对/绝对 | 几乎所有平台 |
|
||||||
|
| L2 | 时区/粒度切换 | Grafana、Datadog |
|
||||||
|
| L3 | 自动刷新联动 | Datadog、Grafana |
|
||||||
|
| L4 | Pan/Zoom/Brush | Azure Monitor、Grafana |
|
||||||
|
| L5 | Time Shift 对比 | Datadog、Kibana、Dynatrace、CloudWatch |
|
||||||
|
| L6 | Split View 同步 | Grafana |
|
||||||
|
|
||||||
|
### 7.4 可视化的"默认 vs 高级"
|
||||||
|
|
||||||
|
| 平台 | 默认 | 高级 |
|
||||||
|
|------|------|------|
|
||||||
|
| Grafana | 折线 | Time Series/Table/Logs/Trace 切换 |
|
||||||
|
| Kibana Lens | 自动选择 | Suggestions 切换备选 |
|
||||||
|
| Datadog | Timeseries | Timeseries/Query Value/Top List/Heatmap/Pie/Geomap |
|
||||||
|
| Dynatrace | Graph | Graph/Stacked/Honeycomb/Heatmap/Top list |
|
||||||
|
|
||||||
|
### 7.5 下钻链路的"5 个层级"
|
||||||
|
|
||||||
|
```
|
||||||
|
L1 图表点 → Trace (Exemplar)
|
||||||
|
L2 指标点 → Log (Drill into Logs)
|
||||||
|
L3 当前查询 → 相关指标 (Correlated Metrics)
|
||||||
|
L4 选区 vs 基线 → 字段差异 (BubbleUp)
|
||||||
|
L5 异常点 → 根因服务 (Davis AI RCA)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、关键 UX 设计原则提炼
|
||||||
|
|
||||||
|
### 8.1 探索的核心价值是"从数据中问出问题"
|
||||||
|
|
||||||
|
- **Grafana Explore** 的 Outline 帮助用户理解查询
|
||||||
|
- **Kibana Lens** 的 Suggestions 帮用户"不知道问什么"
|
||||||
|
- **Honeycomb BubbleUp** 帮用户"问出对的问题"
|
||||||
|
- **Dynatrace Davis** 帮用户"找到根因"
|
||||||
|
|
||||||
|
### 8.2 探索结果必须可分享
|
||||||
|
|
||||||
|
- URL 化(所有平台都支持)
|
||||||
|
- 短链接(Grafana `/goto/:uid`)
|
||||||
|
- Embed / iFrame
|
||||||
|
- 协作(Notebook 模式)
|
||||||
|
|
||||||
|
### 8.3 对比是探索的核心动作
|
||||||
|
|
||||||
|
- **时间对比**:Time Shift
|
||||||
|
- **空间对比**:Split by、Split Graph
|
||||||
|
- **基线对比**:Seasonal Baseline
|
||||||
|
- **分屏对比**:Grafana Split View
|
||||||
|
- **选区对比**:BubbleUp
|
||||||
|
|
||||||
|
### 8.4 降低第一张图的门槛
|
||||||
|
|
||||||
|
- **Datadog NLQ**:自然语言直达
|
||||||
|
- **Kibana Suggestions**:拖字段直达
|
||||||
|
- **Watchdog 自动告警**:零配置开箱即用
|
||||||
|
- **Change Overlay**:自动叠加变更线
|
||||||
|
|
||||||
|
### 8.5 可解释性是工业落地的关键
|
||||||
|
|
||||||
|
- **GDN 的图结构**:可解释变量关系
|
||||||
|
- **Anomaly Transformer 的关联差异**:可解释异常归因
|
||||||
|
- **Dynatrace Davis 的因果 AI**:可解释根因
|
||||||
|
- **BubbleUp 的字段对比**:可解释差异来源
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、参考来源
|
||||||
|
|
||||||
|
### 9.1 官方文档
|
||||||
|
|
||||||
|
1. Grafana Explore: https://grafana.com/docs/grafana/latest/explore/get-started-with-explore/
|
||||||
|
2. Grafana Explore 概览: https://grafana.com/docs/grafana/latest/explore/
|
||||||
|
3. Prometheus Querying basics: https://prometheus.io/docs/prometheus/latest/querying/basics/
|
||||||
|
4. Prometheus Expression browser: https://prometheus.io/docs/visualization/browser/
|
||||||
|
5. Prometheus Feature flags: https://prometheus.io/docs/prometheus/latest/feature_flags/
|
||||||
|
6. Datadog Dashboards 概览: https://docs.datadoghq.com/dashboards/
|
||||||
|
7. Datadog Metrics Explorer: https://docs.datadoghq.com/metrics/explorer/
|
||||||
|
8. Datadog Functions 概览: https://docs.datadoghq.com/dashboards/functions/
|
||||||
|
9. Honeycomb BubbleUp: https://docs.honeycomb.io/working-with-data/bubbleup/
|
||||||
|
10. Kibana Lens: https://www.elastic.co/guide/en/kibana/current/lens.html
|
||||||
|
11. Azure Monitor Metrics Explorer: https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/metrics-getting-started
|
||||||
|
12. Dynatrace Data Explorer: https://docs.dynatrace.com/docs/observe-and-explore/explorer
|
||||||
|
13. Chronosphere Control Plane: https://chronosphere.io/platform/control-plane/
|
||||||
|
14. 阿里云 ARMS 产品总览: https://www.aliyun.com/product/arms
|
||||||
|
15. 夜莺 Nightingale 文档: https://flashcat.cloud/docs/
|
||||||
|
|
||||||
|
### 9.2 第三方资料
|
||||||
|
|
||||||
|
- Honeycomb BubbleUp 设计哲学起源于工业过程控制(气泡判断发酵状态),强调"无预设维度的自动归因",推动 wide events 范式
|
||||||
|
- Prometheus 3.0 UI 重写:可通过 `--enable-feature=old-ui` 回退到 2.x 风格 Web UI
|
||||||
|
|
||||||
|
### 9.3 未能成功抓取的页面(需进一步验证)
|
||||||
|
|
||||||
|
- AWS CloudWatch Metrics Insights 详细查询语法页(页面抓取仅返回标题)
|
||||||
|
- Splunk SPL 文档(403 Forbidden)
|
||||||
|
- Lightstep / ServiceNow Cloud Observability 文档(已迁移至新域)
|
||||||
|
- 华为云 APM / AOM 详细产品页(404)
|
||||||
|
- New Relic NRQL / Data Explorer 详细子页(仅抓到总览页)
|
||||||
|
|
||||||
|
> **说明**:本报告基于 2026 年 6 月的实际页面抓取与公开产品资料整理,标注 `*` 的部分基于通用知识/产品总览推演,建议在产品决策前对存疑条目进行官方文档二次核实。腾讯云监控、华为云 APM、夜莺 Nightingale 的核心功能主要通过产品总览/索引页确认,详细交互细节未完全覆盖。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 附录 A:术语表
|
||||||
|
|
||||||
|
| 术语 | 含义 |
|
||||||
|
|------|------|
|
||||||
|
| **Explore** | 时序探索,强调查询驱动 + 即时反馈 |
|
||||||
|
| **BubbleUp** | Honeycomb 提出的"选区 vs 基线"对比交互 |
|
||||||
|
| **Split View** | 分屏对比视图(Grafana) |
|
||||||
|
| **Exemplar** | Prometheus 概念,指标点附加的 trace_id 等样本 |
|
||||||
|
| **Change Overlay** | Datadog 独有,图表自动叠加变更线 |
|
||||||
|
| **Time Shift** | 时间偏移对比 |
|
||||||
|
| **Annotations** | 图表上的事件标记线 |
|
||||||
|
| **Query Builder** | 图形化查询构建器(Honeycomb、New Relic) |
|
||||||
|
| **NLQ** | Natural Language Query,自然语言查询 |
|
||||||
|
| **TSFM** | Time Series Foundation Model,时序基础模型 |
|
||||||
|
| **Wide Events** | Honeycomb 范式,每个事件携带 50+ 字段 |
|
||||||
|
| **Faceted Search** | 字段 facet 侧栏搜索 |
|
||||||
|
| **Apprentice** | 阿里清华联合的异常检测框架(KDD 2015) |
|
||||||
|
| **SR-CNN** | Spectral Residual + CNN(KDD 2019) |
|
||||||
|
| **Davis AI** | Dynatrace 因果 AI 引擎 |
|
||||||
|
|
||||||
|
## 附录 B:报告使用建议
|
||||||
|
|
||||||
|
### B.1 给产品经理
|
||||||
|
|
||||||
|
- **入口与导航**:参考 Grafana Explore 独立入口设计
|
||||||
|
- **查询构建**:提供"DSL + GUI + NLQ"三层并存能力
|
||||||
|
- **时间选择**:必须支持 Time Shift 与 Split View 同步
|
||||||
|
- **可视化**:默认折线 + 5+ 图表类型可切换
|
||||||
|
- **下钻联动**:指标 → Trace / Log 跳转是硬需求
|
||||||
|
- **对比叠加**:Change Overlay 与 BubbleUp 风格值得借鉴
|
||||||
|
- **异常检测**:与 AI 副驾结合是趋势
|
||||||
|
|
||||||
|
### B.2 给 UX 设计师
|
||||||
|
|
||||||
|
- **三栏式布局**:左侧导航 + 中间查询 + 右侧 Outline
|
||||||
|
- **Split View**:做对比分析的核心交互
|
||||||
|
- **BubbleUp 风格**:黄/蓝对比 + 字段柱状图
|
||||||
|
- **Suggestions 模式**:拖字段即出图
|
||||||
|
- **键盘可访问性**:Tab + Space 拖拽
|
||||||
|
- **错误状态友好**:语法高亮 + 自动修复建议
|
||||||
|
- **URL 可分享**:每个查询状态可复制为 URL
|
||||||
|
|
||||||
|
### B.3 给前端工程师
|
||||||
|
|
||||||
|
- **图表库选择**:uPlot、ECharts、D3.js、Plotly
|
||||||
|
- **采样与降采样**:百万级时序的渲染策略
|
||||||
|
- **虚拟滚动**:长列表性能优化
|
||||||
|
- **WebWorker 处理**:避免阻塞主线程
|
||||||
|
- **IndexedDB 缓存**:查询历史与设置
|
||||||
|
- **WebSocket 实时推送**:自动刷新机制
|
||||||
|
- **iFrame Embed**:支持嵌入其他平台
|
||||||
|
|
||||||
|
### B.4 给架构师
|
||||||
|
|
||||||
|
- **服务端预聚合**:Query Accelerator 模式
|
||||||
|
- **下推计算**:把 aggregation 下推到存储
|
||||||
|
- **成本-质量权衡**:Chronosphere Control Plane 思路
|
||||||
|
- **多数据源联邦**:Grafana Mixed Data Source
|
||||||
|
- **AI 副驾集成**:LLM 翻译自然语言 → DSL
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> **报告结束**
|
||||||
|
>
|
||||||
|
> 如需深入某一平台的交互设计或具体模式的实现细节,欢迎告知,我可以进一步展开调研或产出原型设计建议。
|
||||||
Reference in New Issue
Block a user