Skip to main content
Glama

inferwatch

ci python license

本地服务的 LLM 的实时和历史指标——OllamavLLM——带有浏览器仪表盘、设置界面和一个 MCP 服务器,以便代理可以查询相同的数据。

一个 Python 进程,一个 SQLite 文件。无需 Docker、Node、Prometheus 或外部服务。它从不位于请求路径中,因此不会减慢或破坏推理。

┌── Ollama ──────────────┐        ┌── vLLM ────────────────┐
│ journald / file /      │        │ GET /metrics           │
│ docker logs            │        │ (native Prometheus)    │
└──────────┬─────────────┘        └──────────┬─────────────┘
           │ per-request rows                │ pre-aggregated
           ▼                                 ▼
        ┌──────────────── SQLite (WAL) ────────────────┐
        │  requests · rollups · vllm_samples/hist      │
        └───────┬──────────────────────────┬───────────┘
                ▼                          ▼
         dashboard :7070            MCP server (stdio)

两个引擎并不对称,工具也不假装对称

这是核心设计事实,因此值得直说。

Ollama

vLLM

来源

其日志

/metrics

每请求行

— 没有可收集的行

TTFT / 延迟

精确,每请求

仅直方图

令牌

每请求

累计计数器

错误

每请求的 HTTP 状态

request_success_total{finished_reason}

客户端地址

百分位数

保留期内精确

桶上界;均值精确

独特附加项

提示缓存重用、草稿接受、冷加载时间

KV 缓存占用、抢占、批处理占用、按原因等待

两个标签页都显示 GPU 利用率、显存、温度和功耗,因为这些是由 nvidia-smi 测量的,而不是由任一引擎测量的。温度和功耗有单独的图表,而不是共享一个轴,并且每个都按其单位要求聚合:利用率跨卡平均,显存和瓦特求和,温度报告最热的卡。温度是唯一不从零绘制的序列——从 0 开始的 33–68 °C 范围会浪费大部分绘图。

因此它们有单独的仪表盘标签页、单独的表和单独的 MCP 工具。不尝试通过差分计数器为 vLLM 重建每请求行:你无法恢复哪个 TTFT 属于哪个请求,伪造它会把虚构的行放在真实的行旁边。

Ollama:数字来自哪里

Ollama 不暴露 /metrics 端点(已验证——该路由不在二进制中)。使用 OLLAMA_DEBUG=1,嵌入的 llama.cpp 会为每个请求打印一个计时块,与访问行和调度行结合:

slot print_timing: id 0 | task 6763 | prompt eval time = 1254.52 ms /  55 tokens
slot print_timing: id 0 | task 6763 |        eval time = 14591.31 ms / 416 tokens
[GIN] ... | 200 | 16.862061865s | 192.0.2.10 | POST "/v1/chat/completions"
time=... msg="context for request finished" runner.name=.../llama3.2:3b

这产生了 TTFT、预填充/解码拆分、令牌计数、解码速率、状态、客户端、端点和模型——对于每个请求,来自每个客户端,而不触及请求路径。两个数字从组合中得出,单独来源都没有:

  • 队列等待 = 墙钟延迟 − 运行器时间:等待而不是生成的时间。代理无法区分这些。

  • 提示缓存重用 = 完整提示长度 − 实际评估的令牌。

需要 OLLAMA_DEBUG=1 没有它,llama.cpp 不会打印计时行:请求速率、状态和 GPU 指标仍然有效,但 TTFT 和令牌计数保持为空。仪表盘会在横幅中说明这一点,而不是显示零。

# /etc/systemd/system/ollama.service.d/override.conf
[Service]
Environment="OLLAMA_DEBUG=1"

vLLM:数字来自哪里

vLLM 的原生 Prometheus 端点每 collection.scrape_interval_s(默认 10 秒)抓取一次。累计计数器被差分;直方图桶按桶差分;两者都按分钟聚合写入,因为存储每次抓取会在一个月内增加数百万行,而图表不会使用这些分辨率。

三个值得了解的细节:

  • vLLM 自己的桶边界与计数一起存储。 其边界步进为 1ms/20ms/250ms/2.5s/40s/640s;Ollama 的步进为 25ms/200ms/1.5s/15s/60s。两者都不是对方的细化,因此将一个重新分桶到另一个需要在边界之间插值——发明数字。百分位数根据每个来源自己的边界计算,并报告为桶上界

  • _sum_count 是精确的,因此均值是精确的。由于 vLLM 的桶在秒范围内较粗,仪表盘和 MCP 工具以均值为主,并将百分位数标记为“至多”。

  • 通过 process_start_time_seconds 检测重启(以及计数器倒退)。跨越重启的间隔被丢弃,而不是作为虚假的增量发出。

跨 vLLM 版本,令牌间延迟已拼写为 time_per_output_token_secondsinter_token_latency_secondsrequest_time_per_output_token_seconds。所有都被收集,并且使用有数据的那个,因此这适用于旧的和新的服务器,无需配置。


Related MCP server: System Monitor MCP Server

安装

需要 Python 3.10 或更新版本——不是因为此代码(它是 3.9 干净的),而是因为 fastapi、uvicorn、starlette 和 mcp 都需要它。

pip install git+https://github.com/floatsmyboat/inferwatch     # or:
git clone https://github.com/floatsmyboat/inferwatch && cd inferwatch
python3 -m venv --upgrade-deps .venv && .venv/bin/pip install -e ".[dev]"

安装后你会得到两个命令:

命令

它是什么

inferwatch

收集器、仪表盘和 API(serveingeststatssources

inferwatch-mcp

MCP 服务器,通过 stdio

尚未在 PyPI 上;目前从 git 安装。

从你已有的日志回填,然后查看数据库:

inferwatch ingest --since 2d      # or: python -m inferwatch.main ingest
inferwatch stats

运行它:

inferwatch serve                  # http://127.0.0.1:7070

作为服务——单元从 systemd/inferwatch.service.in 为当前用户、检出路径和解释器渲染,因此没有硬编码:

./scripts/install-systemd.sh          # system service (uses sudo)
sudo systemctl enable --now inferwatch

./scripts/install-systemd.sh --user   # or per-user, no sudo
systemctl --user enable --now inferwatch

使用 HOST=0.0.0.0 PORT=7070 DATADIR=... ./scripts/install-systemd.sh 覆盖。

在网络上暴露它

没有身份验证。 仪表盘是只读的(仅 GET),不存储提示或响应文本——只存储计数、计时、模型名称和客户端地址。在防火墙处限制它:

sudo ufw allow from 192.168.1.0/24 to any port 7070 proto tcp comment "inferwatch"

配置监控内容

一切都可以从仪表盘的设置标签页或 CLI 编辑:

python -m inferwatch.main sources                       # list
python -m inferwatch.main sources add --kind vllm --name qwen \
    --set url=http://127.0.0.1:8000
python -m inferwatch.main sources add --kind ollama --name box \
    --set reader=file --set path=~/.ollama/logs/server.log
python -m inferwatch.main sources disable qwen

Ollama 日志读取器。 并非每个人都在 systemd 下运行 Ollama:

读取器

用于

时间戳保真度

journald

ollama.service

微秒,来自 journald

file

在终端中 ollama serve,或任何记录到文件的安装

派生;见下文

docker

容器中的 Ollama

每行,来自 docker logs -t

file 读取器像 tail -F 一样跟随,在轮换(inode 更改)和截断时存活,并持久化偏移量,以便重启不会重放。Ollama 的 Go 行带有 time=,但 llama.cpp 的 slot 行——持有令牌计数的行——没有时间戳,因此最近看到的一个被向前携带。连接所依赖的顺序始终成立;绝对精度低于 journald 的,并且秒分辨率的 [GIN] 行被向前钳制,因此时间永远不会看起来倒退。

设置优先级

spec default  <  database (Settings tab)  <  environment  <  command line

由环境或标志提供的键在设置标签页中显示为只读,并带有其来源,因为进程被告知使用它,浏览器不能静默覆盖它。保存是全有或全无,因此一个字段中的拼写错误不能留下半应用的配置。对来源、间隔和保留的更改无需重启即可应用;server.hostserver.port 标记为需要重启,API 在保存后说明这一点。

配置参考

以下每个设置都可以在设置标签页中编辑,可以设置为环境变量,有些可以用标志固定。表格是从代码生成的(scripts/gen-config-docs.py),因此它们不能偏离程序实际接受的内容。

scripts/gen-config-docs.py 生成 — 不要手动编辑。

收集

设置

默认

接受

环境变量

注释

collection.poll_interval_s

5.0

1–300

INFERWATCH_COLLECTION_POLL_INTERVAL_S

nvidia-smi 和引擎自身状态端点被采样的频率。

collection.scrape_interval_s

10.0

1–300

INFERWATCH_COLLECTION_SCRAPE_INTERVAL_S

每个 vLLM 实例的 /metrics 端点被读取的频率。vLLM 计数器是累计的,因此这设置了从中派生的每个速率和直方图的分辨率。

collection.backfill

2d

7d,或 -2 days / @epoch

INFERWATCH_COLLECTION_BACKFILL

需要重启 — 在首次运行、任何恢复状态存在之前,读取多远的过去。接受 7d / 6h,或 journalctl 形式如 '-2 days'。

collection.rollup_interval_s

60.0

10–3600

INFERWATCH_COLLECTION_ROLLUP_INTERVAL_S

1 分钟和 1 小时聚合被重新计算的频率。

保留

设置

默认

接受

环境变量

注释

retention.raw_days

7.0

0.5–3650

INFERWATCH_RETENTION_RAW_DAYS

早于此的每请求细节被删除。无论保留期如何,汇总都无限期保留,因此长期图表得以存活。

retention.sample_days

30.0

0.5–3650

INFERWATCH_RETENTION_SAMPLE_DAYS

GPU 样本、引擎样本和事件日志被修剪到此。

仪表盘

设置

默认值

接受的取值

环境变量

说明

dashboard.default_window

1h

15m, 1h, 6h, 24h, 7d, 30d

INFERWATCH_DASHBOARD_DEFAULT_WINDOW

打开仪表盘时默认选中的时间范围。

dashboard.include_health

false

INFERWATCH_DASHBOARD_INCLUDE_HEALTH

将 HEAD / 请求和状态轮询计入请求速率。默认关闭,因为在被轮询的实例上,它可能占命中数的 90% 以上。

dashboard.refresh_s

13.0

2–600

INFERWATCH_DASHBOARD_REFRESH_S

打开的仪表盘每隔多长时间重新抓取一次。实时请求推送是单独推送的,不受此设置影响。

服务器

设置

默认值

接受值

环境变量

说明

server.host

127.0.0.1

INFERWATCH_SERVER_HOST

需重启生效 — 0.0.0.0 会将仪表盘暴露到网络上。由于没有身份认证,请在防火墙上限制访问。

server.port

7070

1–65535

INFERWATCH_SERVER_PORT

需重启生效 — 仪表盘和 API 监听的端口。

源字段

sources add 上使用 --set key=value 设置这些字段,或在“设置”标签页中设置。

Ollama--kind ollama

字段

默认值

所需时机

说明

reader

journald

设置从哪里读取 ollama 的日志。每个请求的指标来自 llama.cpp 的调试输出行,因此这几种方式必选其一。可选 journaldfiledocker

unit

ollama

reader=journald

path

reader=file

以类似 tail -F 的方式跟踪文件,因此会处理日志轮转和截断。

container

独立容器名 需要?

reader=docker

url

http://127.0.0.1:11434

用于轮询 /api/v1/objects.翻译来了

用于轮询 /api/ps 获取常驻模型。

models_dir

可选。在加载事件时将 blob 摘要解析为模型名称。默认使用 $OLLAMA\_MODELS~/.ollama/models

vLLM--kind vllm

字段

默认单位

类型

说明

url

http://127.0.0.1:8000

OpenAI 兼容服务器的根地址。从这里读取 /metrics

unit

如果设置,还会读取 journal 以获取 HTTP 状态码、客户端地址和引擎错误,这些是 /metrics 不暴露的。

api_key

如果服务器要求,则以 bearer token 形式发送。

命令行标志

标志

用途

固定的设置

--db

--unit

设置源 / ingest 的 systemd 单元,用于初始化的 Ollama /

--ollama-url

--models-dir

ollama 模型目录(将 blob 摘要解析为模型名称)

--log-file

ingest:读取此日志文件而不是 journal

--since

日志回填窗口,例如 -2 days

collection.backfill

--retention-days

原始请求保留时间;汇总数据永久保留

retention.raw_days

--poll-interval

collection.poll_interval_s

--scrape-interval

collection.scrape_interval_s

--host

server.host

--port

server.port

-v, -verbose

固定设置的标志优先级高于环境变量和“设置”标签页;标签页会以只读方式显示这些键及来源。

范围:一个 Ollama 源,多个 vLLM 源

vLLM 行的整个过程中都以 source 为键,因此可以并排监控任意数量的 vLLM 实例。Ollama 表(requestseventsps_samples不是按 source 分区的,因此同时只能运行一个 Ollama 源;启用第二个会记录一条警告并忽略,而不会静默地把两个实例混合为一组数字。对这些表进行分区是一个值得慎重做的 schema 变更。


仪表盘

http://127.0.0.1:7070 —— 三个标签页:OllamavLLM设置

哪个 GPU 属于哪个引擎

一台主机常常运行多种引擎,因此如果在一个实例的面板中绘制所有显卡,会让人以为该实例使用了全部 GPU。每个 vLLM 实例的 GPU 是通过追踪进程解析得到的:它所提供服务的端口 → 监听该端口的 pid → 该 pid 的子进程 → 与 nvidia-smi 的 compute processes 取交集 → 得到这些进程持有的 GPU。实例自身的 GPU 使用系列颜色标识,其 VRAM 磁贴只统计这些 GPU;主机上的其他 GPU 仍以灰色显示,并标记为 other engine.

归属推理需要 ss、本地实例以及 nvidia-smi 能看到进程(容器内部通常缺少)。如果任一缺失,面板都会说明情况,并显示所有 GPU 而不做强调,而不是去猜测。

一行筛选条件会限定其下方的所有数据。每个图表都有一个 “Table” (表格)开关,以数字显示相同的序列,因此任何数值都不会只能通过悬停才可看到。实时 SSE 推送驱动请求滚动条和当前速率数字。

URL 参数:?tab=vllm?window=6h?model=llama3.2:3b?source=name?nostream=1(禁用实时推送,适合 kiosk 显示器和截图工具使用,否则它们会在打开的流上一直等待)。

API

端点

返回内容

/api/dashboard?window=1h&model=

Ollama 标签页需要的所有数据,一个时间片

/api/vllm/dashboard?window=1h&source=

相同数据,对应一个 vLLM 实例

/api/summary/api/timeseries/api/models/api/slowest?by=queue_ms

Ollama 的细分数据

/api/vllm/summary/api/vllm/timeseries/api/vllm/instances

vLLM 的细分数据

/api/requests/api/errors/api/model_events/api/gpu/api/ps

原始行和时间线

/api/ps/rest

没有

/api/v1/

需要修正:/api/sources/probe保存前检查定义,因此拼写错误会在那里出现,而不是表现在图表中一直没有数据。


MCP 服务器

./scripts/install-mcp.sh      # writes .mcp.json for this checkout (gitignored)

或执行 claude mcp add inferwatch -- /path/to/.venv/bin/python -m inferwatch.mcp_server

与仪表盘使用同一个只读 SQLite 文件(mode=ro 加上 PRAGMA query_only),并通过同一个查询层回答,因此它报告的任何一个数字都会与屏幕上显示的完全一致。

工具

用途

get_summaryget_timeseries

Ollama 的端到栏指标与时间序列

compare_modelslist_models

按模型细分;哪些模型常驻

recent_requestsslowest_requestsrecent_errors

Ollama 每个请求的详细信息

get_events

冷加载、逐出、截断、告警

vllm_summaryvllm_timeseriesvllm_instances

vLLM 指标、可达性、GPU 归属

gpu_status

每个 GPU 的占用率/VRAM/温度/功耗

list_sourcesget_settings

监控了哪些实例(源),它们如何配置

health

收集是否正常工作,调试日志是否开启

run_sqldescribe_schema

只读 SELECT 逃生通道,并附单位说明


保留策略

  • 原始每请求行(Ollama):7 天retention.raw_days)。

  • GPU 采样、事件、vLLM 行:30 天retention.sample_days)。

  • rollup_1mrollup_1h永久保留

Rollup 保存的是固定桶的 TTFT 和延迟直方图,而不是预先计算的分位数。直方图是可以相加的,因此对任意范围的分位数都可以通过桶的和相加后逐步移到目标位置来计算。百分位数的百分位数没有意义;而这里的做法并非如此。

原始窗口内的查询返回精确百分位数;超出该窗口的查询来自直方图,并报告为所在桶的上界。每个响应都带有 exact: true|false

重启与重新引导

恢复状态按来源区分——journald 游标、文件 inode+offset 或 docker 时间戳——并在 SIGTERM 时刷新。有两件事使其安全而非仅仅可能:

**写入是幂等的。**每个请求和事件行都在 UNIQUE 索引下携带 dedupe_key,插入使用 INSERT OR IGNORE。重新读取已存储的行是空操作,因此 ingest 可以重复运行,恢复可以安全重叠。

**无法使用的游标不被信任。**如果游标指向的 journal 已被轮转,journalctl 会使用游标中嵌入的时间戳静默重新定位并正确恢复。但标记在未来的游标(时钟偏差、恢复的数据库)会使 journalctl 等待不会到达的条目,静默停滞采集;此类游标在启动时被拒绝。连续两次跟随尝试无结果也会触发同样处理。

systemctl stop 在一秒内即可完成。systemd 记录 ExecMainStatus=15 以及 Result=success:uvicorn 在关闭后有意重新抛出信号,因此 SIGTERM 退出是预期行为,而非崩溃。


坦诚的局限

**并行下的归因(Ollama)。**llama.cpp 计时行中的任务 id 与访问行的状态从不一起出现,因此它们按到达顺序连接。当只有一个请求在途时这是精确的。当两个请求在任一访问行打印前完成时,日志中没有任何信息可以区分它们——这些行存储为 attribution='ambiguous' 而非猜测。取值:exactambiguousnone(在到达 runner 之前失败——也不猜测任何模型)、orphan(有计时但无访问行)。开发主机上为期 2 天的回填在 OLLAMA_NUM_PARALLEL=1 生效下得到 174 个 exact、9 个 ambiguous、4 个 orphan;并发请求越多,ambiguous 占比预计越高。

vLLM 的 token 和请求计数器并非按请求对齐。generation_tokens_total 随 token 流式输出而递增;request_success_total 仅在请求完成时递增。因此在短时间窗口内,它们描述的是重叠但不同的请求集合,将两者相除并不得到每请求的 token 数。API 以 counters_aligned: false 标记此情况,仪表板在 vLLM 标签页上也如此说明。

**vLLM 没有任何按请求的指标。**上文已述。如果你需要 vLLM 的按请求细节,其请求级日志是唯一来源,而它会记录提示文本——本工具刻意从不存储这些。

**日志是输入源,而非存档。**journal 可能只保留一两天,具体取决于 journald.conf;SQLite 文件才是历史记录者。如果日志轮转速度快于 inferwatch 的运行频率,该间隙无法恢复。

**同一微秒内两个字节相同的事件合并为一个。**事件的去重键由其值构建,因此同一微秒内记录两次的相同警告只保留一行。这是为保证幂等性而有意做出的取舍——丢弃重复警告胜过复制历史。

健康检查流量被分离,不计入统计。HEAD /GET /api/ps 占开发主机请求的 96%。它们以 class='health' 存储,除非开启 dashboard.include_health,否则从推理速率中排除;requests_all 始终包含它们。

**依赖日志格式和指标名称。**Ollama 的计时行是调试输出,而非契约,vLLM 会在版本之间重命名指标。tests/test_parse.py 保存逐字逐句的 fixture 行,tests/test_vllm.py 保存真实的 /metrics 摘录;如果升级破坏了解析,这些测试会失败并显示变化内容。


测试

python -m unittest discover -s tests -t .

CI 在 Python 3.10 至 3.14 上运行,外加一个打包任务:构建 wheel、断言仪表板 HTML 包含在其中,并从空目录安装到干净环境中,使源码树无法掩盖打包错误。无需网络、GPU 或引擎。解析器 fixture 是逐字逐句的真实日志行和真实的 /metrics 摘录。覆盖范围包括关联器的连接及其 ambiguous/orphan/failed 情况、直方图百分位数和汇总幂等性、schema 迁移、信号安全提交、游标验证、文件轮转和截断、时间戳单调性、计数器重置检测,以及配置优先级和锁定。本文件中的配置参考由 spec 生成,如果发生漂移,测试会失败。仪表板的 JavaScript 使用纯 Python 解析器进行语法检查,其格式化器在真实 JS 引擎中执行(两者均为可选——无需 Node)。

.venv/bin/python scripts/gen-config-docs.py --check   # docs match the code?

布局

inferwatch/parse.py        ollama log line parsers (pure, fixture-tested)
inferwatch/readers.py      journald / file / docker log readers
inferwatch/collect.py      correlator, GPU + model pollers, maintainer
inferwatch/vllm.py         Prometheus scraper, delta and reset handling
inferwatch/gpuproc.py      maps GPUs to the process tree holding them
inferwatch/vllm_metrics.py vLLM query layer
inferwatch/metrics.py      ollama query layer (shared by API and MCP)
inferwatch/store.py        SQLite schema, rollups, histograms, retention
inferwatch/config.py       typed settings spec, precedence, source validation
inferwatch/supervisor.py   builds and rebuilds collectors from the sources table
inferwatch/api.py          FastAPI endpoints + SSE
inferwatch/web/index.html  dashboard (single file, no CDN, no build step)
inferwatch/mcp_server.py   MCP server (read-only)
inferwatch/main.py         serve / ingest / stats / sources

贡献

欢迎提交 issue 和 pull request。两件事能让改动容易被接受:

  • python -m unittest discover -s tests -t . 通过。

  • 如果你修改了 inferwatch/config.py,请运行 python scripts/gen-config-docs.py,使 README 的配置参考与代码一致——测试会强制执行这一点。

解析器改动应附带从真实引擎输出中逐字复制的 fixture 行,就像现有测试那样。日志格式和指标名称不是契约,真实的 fixture 才能让未来的破坏显而易见。

许可证

Apache License 2.0 — 参见 LICENSENOTICE

A
license - permissive license
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • F
    license
    A
    quality
    D
    maintenance
    Enables AI agents to query Prometheus metrics and Loki logs for intelligent alert investigation and troubleshooting. Provides service discovery, metric querying, log searching, and correlation tools to help identify root causes of issues.
    9
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables AI assistants to monitor real-time CPU, RAM, and disk usage on the local machine.

View all related MCP servers

Related MCP Connectors

  • Provide real-time data querying and visualization by integrating Tako with your agents. Generate o…

  • Gateway between LLM agents and world data through eight tools and a bundled endpoint catalog.

  • See, price, and control every tool call your AI agents make: policy checks, cost, and audit tools.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/floatsmyboat/inferwatch'

If you have feedback or need assistance with the MCP directory API, please join our Discord server