> For the complete documentation index, see [llms.txt](https://freebsd-journal-cn.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://freebsd-journal-cn.bsdcn.org/20221112-ke-guan-ce-xing-he-heng-liang-biao-zhun/act.md).

# 活动监控脚本（activitymonitor.sh）

* 原文：[Tom Once Again, Does Stupid Things with a Computer: activitymonitor.sh](https://freebsdfoundation.org/wp-content/uploads/2023/01/jones_activitymonitor.pdf)
* 作者：**TOM JONES**

在今年的维也纳 EuroBSDCon 上，我讲述了我在 FreeBSD 和 Linux 上研究 QUIC 性能的工作。

我的测量方法的核心部分是网络传输期间 CPU 饱和，发送方利用所有可用的 CPU 周期进行发送。我通过两种方式使用 CPU 饱和度，第一种是检测 CPU 是否是系统中的瓶颈。如果它不是瓶颈，那么我需要确认网络就是瓶颈，而不是我没有尝试测量的其他组件。其次，待我控制了 CPU 饱和度并确保没有其他瓶颈在起作用，我就可以利用实际的 CPU 使用率估算系统如果能够用 UDP 饱和网络卡，发送速度能有多快。这非常有用，因为在所有测量中，我的网络接口在任何测试的操作系统上对于任何形式的 UDP 流量都只能处理大约 6Gbit/s。但这并不会使 CPU 饱和，CPU 的利用率大约为 70%。通过良好的 CPU 测量，我可以发明一个指标，乐观地预测如果网络接口不成为障碍，处理器能达到的速度。

我的 EuroBSDCon 演讲基于尚未发布的学术工作，寻找一种经过深思熟虑的方法来查看网络测试期间的 CPU 性能，似乎是个好主意。我的一些测试灵感来自 Fastly 的工作，Fastly 比较了 QUIC 和 TCP 的计算效率。尽管 Fastly 似乎建立了很好的测试平台，但我从他们的文章中能理解到的唯一机制是“凭眼睛看” top。

这对于发布结果或准确评估数千个测试结果来说并不够好。如果仅仅看 top 不够，那我是否可以去了解 top 是如何查看的，并重新实现它呢？

## top 如何估算 CPU 利用率？

top 可能是我们在想知道系统上发生了什么时，首先使用的工具。尽管它用途广泛，top 其实是个非常简单的程序，它能够在屏幕上漂亮地显示信息，并且在机器层面做了一些抽象，使得代码更具可移植性。CPU 利用率接口由 FreeBSD 提供，以两个 sysctl 节点的形式呈现：

![FreeBSD CPU 利用率 sysctl 节点示意图](/files/cebtVSFAAGPoQz2v8Lay)

```sh
$ sysctl hw.ncpu
4
$ sysctl kern.cp_time
kern.cp_time: 3832370 11408 3650627 44926 2061043745
$ sysctl kern.cp_times
kern.cp_times: 1080959 3035 954019 4354 515101995 1062132 2823 815176 1143
515263088 960320 3419 980090 37760 515162773 728956 2131 901334 1668 515510273
```

第一个节点 `kern.cp_time` 返回处理器的五个值，报告自启动以来在用户、优先级、系统、中断和空闲状态下花费的总时间。`kern.cp_times` 报告系统中每个处理器的这五个值。通过使用 `kern.cp_times` 和 `hw.ncpu`，我们可以细分这个列表。通过这两个 sysctl，我们可以获得自启动以来的系统总时间使用情况以及每个处理器的时间使用情况。自启动以来的使用情况对于理解机器的工作状态很有帮助，并且对于观察系统使用情况在短时间内如何变化也很有用。top 启动时显示系统的总时间分解，但刷新后（默认每秒刷新一次），它将显示这些字段在这一秒内的变化。

## 绘图

既然能非常容易地复现 top 的功能，我想知道是否可以定期抓取系统 CPU 使用率并将其绘制出来。我想我可以将这些绘图与吞吐量图并排显示，以展示在测试之间，主机几乎完全空闲，而在测试运行时，饱和了预期的单核。

多年来，我在绘图时使用过不同的工具，但在这项工作中，我对定制工具感到厌倦，想要简洁些。我本可以使用最简单的方法——电子表格来绘制利用率，但我觉得能给我更多控制的东西会更好。在其他近期的工作中，我使用了基于 Web 的绘图工具。c3js 库提供了很好的交互式图表，但当数据点数量过多时（超过几万）会有些力不从心。考虑到我还要查看网络使用情况，数据量在一分钟的录制中会非常大。当我在考虑工具时，我想起了我为 Klara Systems 撰写的关于 `inetd` 的文章。在写这篇文章时，我创建了自己的简单 `inetd` 服务，通过 shell 脚本实现了日期时间服务。我能否通过 `inetd` 从主机实时提供 CPU 使用信息？

## activitymonitor.sh

这将我们引向 `activitymonitor.sh`，不按常规出牌的创作，违背了所有良好的规范，却能在网页浏览器中提供简单的图表。`activitymonitor.sh` 是单一的 shell 脚本，包含三个部分： • 由 `inetd` 运行的脚本。\
• 一个基本的 HTML 页面。\
• 一些 JavaScript 用于更新实时页面。

`inetd` 是互联网服务运行器。`inetd` 的完整历史和功能略微超出了本文的范围，但它用于在主机太小、无法在内存中保持服务等待时按需启动应用程序。`inetd` 负责监听流量。当接收到连接或数据报（对于基于 UDP 的协议）时，`inetd` 启动内建处理程序或指定的程序。该程序从网络连接的标准输入读取。所有写入标准输出的内容将通过网络发送。这是非常简单的接口，但足够强大，可以实现纯文本的服务器协议。

## 一个小脚本

这个小脚本非常简单。它有两个主要组件，然后是附加的网页和 JavaScript 数据。首先，shell 脚本处理作为 `inetd` 的客人并解析 HTTP 头部。脚本的输入将是客户端在发出请求时发送的 HTTP 头部。

```sh
# 读取客户端头信息，我们只关心其中是否有 data.json。
h=""
while read -t 1 h
do
 log $h

 if echo $h | grep -q "data.json";
 then
 page="data.json"
 contenttype="application/json"
 else
 fi
done
echo "HTTP/1.0 200 OK"
echo "Content-Type: $contenttype"
echo
```

该脚本使用带有超时的 `read` 内置命令，这意味着脚本会消耗套接字上的所有输入，直到输入行之间出现 1 秒的间隔才会继续执行。这个 `read` 超时是限速机制。接着检查 HTTP 头部，查看是否正在请求数据 URL，如果不是，则返回基本的 HTML 页面。

脚本的 `data url` 路径是我们收集关于主机的有趣数据的地方。`activitymonitor.sh` 实现了 `top` 的大部分默认接口。为了做到这一点，它使用 FreeBSD 的基本系统命令收集所需的信息，并将其编码为 JSON 格式的 blob 返回给请求者。

```sh
if["$contenttype" == "text/html" ]
then
 indexstart=$((cat -n $0 | grep -e 'INDEX START'\
 | awk '{print $1}' | tail -n 1+1))
 sed -n"$indexstart"',$p' $0
elif["$contenttype" == "application/json" ]
then
 psout=$(ps -ax -o \
 "user,pid,%cpu,cpu %mem,vsz,rss,state,command"\
 --libxo json)
 vmstatout=$(vmstat --libxo json)
 netstatout=$(netstat -bi --libxo json)
 # kern.cp_time(s) 为系统提供 5 个数值：
 # user nice system interrupt idle
 # kern.cp_times 为这些 5 个数值提供 hw.ncpu 个条目
 totalcputime=$(sysctl -n kern.cp_time)
 percputime=$(sysctl -n kern.cp_times)
 ncpu=$(sysctl -n hw.ncpu)
 loadavg=$(sysctl -n vm.loadavg)
 lastpid=$(sysctl -n kern.lastpid)
 hostname=$(sysctl -n kern.hostname)
 system=$(printf '{"hostname":"%s",
 "cp_time":"%s", "cp_times":"%s", "ncpu":"%s",
 "loadavg":"%s", "lastpid":"%s"}' "$hostname"
 "$totalcputime" "$percputime" "$ncpu"
 "$loadavg" "$lastpid")
 log $system
 physmem=$(sysctl -n hw.physmem)
 pagesize=$(sysctl -n hw.pagesize)
pagecount=$(sysctl -n vm.stats.vm.v_page_count)
 wirecount=$(sysctl -n vm.stats.vm.v_wire_count)
 activecout=$(sysctl -n vm.stats.vm.v_active_count)
 inactivecount=$(sysctl -n vm.stats.vm.v_inactive_count)
 cachecount=$(sysctl -n vm.stats.vm.v_cache_count)
 freecount=$(sysctl -n vm.stats.vm.v_free_count)
 memory=$(printf '{"physmem":"%s", "pagesize":"%s",
 "pagecount":"%s", "wirecount":"%s",
 "activecout":"%s", "inactivecount":"%s",
 "cachecount":"%s", "freecount":"%s" }'
 "$physmem" "$pagesize" "$pagecount"
 "$wirecount" "$activecout" "$inactivecount"
 "$cachecount" "$freecount")
 log $totalcputime
 # 输出 data json
 printf '{"system":%s, "memory":%s, "ps":%s,
 "vmstat":%s, "netstat":%s}'"$system"
 "$memory" "$psout" "$vmstatout" "$netstatout"
fi
exit # 不要继续进入网页
```

脚本收集的第一组信息来自支持 libxo 的 FreeBSD 工具。Libxo 是 FreeBSD 非常强大的特性——支持的基本系统工具可以原生输出 JSON 格式的数据。我们获取了 `ps`、`vmstat` 和 `netstat` 的输出。这使我们能够显示进程、虚拟内存系统信息以及网络统计信息，如接口速率。脚本中的第二组信息直接来自 sysctl 接口。目前，我们获取了 `kern.cp_time(s)`、CPU 数量、主机名、负载平均值和关于内存的基本统计信息。每项信息都必须通过 `printf` 手动打包成 JSON 对象。所有这些信息最终构建为 JSON 对象，并在简单的响应头之后由脚本打印出来。`inetd` 然后将其反馈到连接的套接字，并作为 HTTP 响应的主体返回给客户端。

### 基本网页

`activitymonitor.sh` 脚本在内部嵌入了小型网页，当没有请求数据 URL 时，它会返回这个页面。页面包含头部、一些画布供 JavaScript 绘制图表，以及用于显示 `top` 风格进程列表的 `pre` 块。它还嵌入了让所有功能得以实现的 JavaScript。HTML 页面（和 JavaScript）被追加到脚本的末尾，并用 ‘INDEX START’ 标签标记。`activitymonitor.sh` 在自身中搜索该标签，并将标签后的所有内容作为要交付的内容，使用 `sed` 截取。

## 一些 JavaScript

JavaScript 执行所有繁重的任务，包括从数据中解析信息并提供用户界面。它提取并处理 `kern.cp_times` 的值，并计算绘制图表所需的增量。除了绘制之外，主要的功能是从 `activitymonitor.sh` 的数据端请求数据。当基本网页加载完成后，脚本会启动任务来获取 `/data.json` 链接。当数据成功到达时，它会从 JSON 结果中提取所需字段，并合并新数据以计算应该显示的内容。最后，它再次调用 `getdata` 启动此任务。因为 `activitymonitor.sh` 中的读取超时，结果之间会有 1 秒的间隔。

## 这是个坏主意

`activitymonitor.sh` 是有趣的小项目，渐渐超出我的预期，几乎成为了可用的工具。CPU 绘图帮助我理解了 FreeBSD 调度器非常积极地在 CPU 之间移动进程，并帮助我制定了测量策略来考虑这一点。尽管是有趣的项目，但它并不适合在现实世界中使用。相反，它展示了 FreeBSD 基本系统中工具的组合能力。除了 `sysctl`，我们使用的所有工具都原生输出 JSON 格式，并可供强大的用户界面语言调用。这种对 JSON 的原生支持使得使用标准工具的输出变得容易，并且让自动化能够轻松处理人类使用的数据。它让我们能够用机器可读的方式构建系统，使用我们在屏幕上读取的同样数据。这是超越传统 UNIX 接口的小增强，但极其强大。

可在此获取 `activitymonitor.sh`。需要类似以下的 `inetd` 配置：

```sh
http-alt stream tcp nowait tj /home/tj/code/activitymonitor.sh
activitymonitor.sh
```

***

**TOM JONES** 希望基于 FreeBSD 的项目能够获得应有的关注。他住在苏格兰东北部，并提供 FreeBSD 咨询服务。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://freebsd-journal-cn.bsdcn.org/20221112-ke-guan-ce-xing-he-heng-liang-biao-zhun/act.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
