普贤教程 › 生态包
可观测与测试
日志、指标、统计、基准、测试。这一组的价值不在功能多,而在让「猜」变成「量」——本页所有输出都是本机编译产物跑出来的。
分级日志
px
import log var lg = log_new("demo", {"level": "info"}) log_info(lg, "服务启动", {"port": 8080}) log_debug(lg, "这条不该出现") # 被 level=info 过滤
text
2026-11-05 17:52:16 [INFO] demo: 服务启动 port=8080
在 Ma 的 px_routes 里不能用
print 打日志Ma 把脚本的 stdout 原样当 HTTP 响应,中间输出一行日志,客户端收到的是坏响应(Status: 行前面多了一行)。Web 脚本里要写 stderr:var f = open("/dev/stderr", "w") → write(f, msg + "\n") → close(f)。没有 eprint 内建。Prometheus 指标
px
import metrics var reg = m_registry() m_counter(reg, "http_requests_total", "HTTP 请求数") m_inc(reg, "http_requests_total", 3, {"path": "/api"}) m_gauge(reg, "queue_depth", "队列深度") m_set(reg, "queue_depth", 7) print(m_render(reg))
text
# HELP http_requests_total HTTP 请求数 # TYPE http_requests_total counter http_requests_total{path=/api} 3.0 # HELP queue_depth 队列深度 # TYPE queue_depth gauge queue_depth 7.0
发现一处缺陷:标签值没有加引号Prometheus 的文本格式要求标签值是带双引号的字符串(
{path="/api"})。上面渲染出来的 {path=/api} 会被 Prometheus 抓取端判为非法样本(text format parsing error)。在 0.2.0 里 m_label_key 直接拼 k=v,没走引号包装。用它对接真 Prometheus 前先自己 m_render 一次看清格式,或者只在内部看板上用。描述统计
px
import statx print(st_mean([1, 2, 3, 4]).ok()) # 2.5 print(st_median([1, 2, 3, 4]).ok()) # 2.5 print(st_stddev([1, 2, 3, 4], false).ok()) # 1.118033988749895 print(st_linreg([1, 2, 3], [2, 4, 6]).ok())# [2.0, 0.0, 1.0] ← [斜率, 截距, r]
还有 st_quantile / st_pearson / st_mode / st_range。分析线上指标分布时,这些函数够用;做假设检验要另写。
行级 diff
px
import diff print(df_lines("a\nb\nc", "a\nx\nc")) # [ a, -b, +x, c] ← 空格=共有,-=仅左,+=仅右 print(df_ratio("hello world", "hello there")) # 0.0
df_ratio 是「按行」的,不是按字符两句话只差一个词,但作为「一行」比较是 0% 相同。要字符级相似度用 edist 的 ed_ratio。这类「看起来该给 0.8、结果给了 0」的地方,先想清楚比较单位是行还是字符。基准与测试
| 包 | 用途 | 典型写法 |
|---|---|---|
bench | 微基准:跑 N 次并统计 | bn_time(f, n) / bn_compare(fa, fb, n) |
testkit | 断言与用例组织 | 断言失败即计数,最后统一报 |
quickcheck | 属性测试:随机输入找反例 | 「反转两次等于原串」这类性质 |
tdtest | 表驱动测试 | 输入/期望表格批量跑 |
mock | HTTP 桩服务 | 见 Web 页 |
基准要跑编译轨
px
import bench def work(): var s = 0 for i in range(0, 100): s += i return s def main(): print(str(bn_time(work, 1000))) # Ok({total_ns, ns_per_op, ops_per_sec}) print(bn_format(bn_time_ms(work, 1000))) # 跑 1000 次,格式化成 ms / μs / s print(str(bn_compare(work, work, 1000, "a", "b"))) # 两函数对比,ratio 是倍数
bn_time 返 Ok(统计),不是毫秒数要单个数字取 bn_time(work, 1000).ok()["ns_per_op"](纳秒/次);要能直接格式化的毫秒值用 bn_time_ms,它返 float,正好喂给 bn_format(自动选 μs / ms / s)。把 bn_time 的返回值直接交给 bn_format 会报 R1002: 无法比较: result vs float。解释轨的基准数字没有意义同一段循环解释轨 5182–5928 ms、编译轨 10–12 ms(差约 500 倍)。基准要
px build 之后跑产物;解释轨测出来的只能是「解释器有多慢」。观测的三层
| 层 | 回答什么问题 | 用什么 |
|---|---|---|
| 日志 | 「这一件事发生了什么」 | log(或 stderr) |
| 指标 | 「整体现在什么水位」 | metrics + statx |
| 追踪 | 「这一请求慢在哪一段」 | 打点时间戳手算(无内建 tracing 包) |