Netdata v1.44.3源码解析:实时监控系统的采集、存储与插件开发 简介Netdata v1.44.3 性能实时监测工具源码包面向系统管理员、运维工程师及计算机专业学生用于学习实时监控系统设计与实现也适合作为毕业设计或论文研究素材。该压缩包共 2000 个文件大小约 22.17MB整体轻量且结构清晰其中包含 C 语言核心源码、Python 辅助脚本、JavaScript 前端展示、YAML 配置文件以及 Markdown/HTML 说明文档覆盖从系统数据采集、SQLite 存储、插件调度到可视化界面和告警通知的完整链路。目前已有 254 人学习下载。阅读者可借此了解毫秒级实时监控的实现细节、分布式集群部署方式及自定义插件扩展机制也可以结合安装说明文档在服务器上快速部署用于实际性能监测或做二次开发。对深入掌握 Netdata 内部原理、提升运维实践能力或撰写论文都有实用价值。1. Netdata 性能实时监测工具一分钟看懂这套源码包的真正价值Netdata 性能实时监测工具 v1.44.3我最早是在排查一台老服务器 CPU 毛刺时接触到的。那时候我在 top 和 htop 之间来回切始终找不到哪个进程把负载拉起来后来发现真正缺的不是瞬时值而是一秒内多次采样的变化曲线。Netdata 做的就是这件事毫秒级采集、自动发现指标、网页上直接出图而且默认端口 19999 打开就能看。这个压缩包里的东西不是装完就完事的二进制它把sqlite3.c、apps_plugin.c、ebpf.c、pluginsd_parser.c这些核心源文件一起打包了适合不想只装工具、更想弄清楚“采集到存储再到展示”这条链路怎么工作的人。对做毕业设计、论文源码分析或者系统软件研究的读者这套代码比任何黑盒监控工具都值得读。运维上用它能少很多“这个数据到底怎么算出来的”式的扯皮。2. 拆包先看链路从 main 到 query.cNetdata 的数据到底怎么流拿到源码包第一件事我通常不是找 README而是先把文件摊开看它属于哪一层。Netdata 的代码可以粗略分成四层采集插件、解析器、存储引擎、查询与 Web API。apps_plugin.c、proc_net_netstat.c、freebsd_sysctl.c、ebpf.c是采集层pluginsd_parser.c是插件协议解析dictionary.c是数据字典sqlite3.c和query.c负责元数据与查询。先把这条链路装进脑子里再编译后面排错会快很多。$ unzip Netdata性能实时监测工具 v1.44.3.zip $ cd netdata-1.44.3 $ ls -la | grep -E (sqlite3.c|apps_plugin.c|global_statistics.c|ebpf.c|pluginsd_parser.c) -rw-r--r-- 1 root root 3211574 sqlite3.c -rw-r--r-- 1 root root 343218 apps_plugin.c -rw-r--r-- 1 root root 89102 global_statistics.c -rw-r--r-- 1 root root 129401 ebpf.c -rw-r--r-- 1 root root 61234 pluginsd_parser.c命令里的grep只是帮你快速定位文件实际目录结构里还会有一堆头文件和构建脚本。我一般先看global_statistics.c因为它名字最像“入口”实际上它记录的是 Netdata 自己每秒处理了多少样本、API 被调了多少次而不是被监控机器的指标。很多人第一次读源码时把这两个概念搞混导致论文里写出来的架构图是错的。2.1 从 main 到 global_statistics启动时先初始化什么Netdata 的入口在daemon目录下压缩包里的构建树可以直接用./configure生成 Makefile。启动顺序大致是读配置、创建 dictionary、初始化存储引擎、启动插件扫描、拉起 Web 服务器。global_statistics.c在这条链路的偏后位置它维护的是 Netdata 自身的运行时指标。读这个文件时重点看它暴露的接口而不是全局变量。比如它会有类似global_statistics_lock()、global_statistics_add_point()这样的操作作用是让多线程在更新计数器时不互相踩。毕业设计里讲“监控系统自身可观测性”时这一段是非常好的素材Netdata 连自己都监控这跟只监控业务指标的系统不是一个设计层次。压缩包里还有个ilove.c文件名确实会让人多看两眼。我读代码的习惯是先跳过这种边缘文件把主流程走通之后再回来。它在当前构建配置里不是关键路径写论文时别把文件名当功能名写进结论要看它所在目录的Makefile.am确认它到底什么时候参与编译。2.2 采集层三份核心代码不同平台、不同数据来源的采集方式apps_plugin.c是经典进程采集插件负责按进程统计 CPU、内存、磁盘和网络。它不直接读内核而是从/proc目录、cgroup 以及系统调用三个口子拿数据然后汇总成每个进程一行。读它的要点是看“如何把一次采样组织成一个 chart”而不是背里面的数值。proc_net_netstat.c管的是网络协议栈统计它读/proc/net/netstat和/proc/net/snmp能拿到 TCP 重传、SYN 丢弃、UDP 丢包这一类和连接状态相关的指标。注意它和网卡流量是两回事网卡流量主要看/proc/net/dev如果你在源码里找网络带宽应该去找proc_net_dev.c或proc.plugin相关代码而不是proc_net_netstat.c。freebsd_sysctl.c的存在说明 Netdata 的采集层不是把 Linux 的/proc写死在代码里的而是抽象出了插件接口。同一套监控面板在 FreeBSD 上通过sysctl拿数据在 Linux 上读/proc两边的图表长得很像这种“平台适配”正是它跨平台的关键。ebpf.c则是另一个量级的东西它通过内核 BPF 探针采集 TCP 连接、文件系统延迟等更底层的数据依赖 DEBUGFS 和内核权限后文避坑部分会专门讲它。2.3 存储与查询sqlite3.c、dictionary.c、query.c 的分工这三个文件经常被混为一谈。dictionary.c是 Netdata 自研的哈希字典所有指标名、上下文名、标签都会先在这里做字符串驻留作用是减少重复内存分配。读它的时候重点看键值冲突和扩容策略很多源码分析论文会把这段拿出来讲“如何做高频状态管理”。sqlite3.c是嵌入式 SQLite 的 amalgamation 版本整个文件三百万字符量级不要从头读。在 v1.44.3 里它主要承担元数据存储比如图表上下文、告警配置、Agent 状态信息。为什么实时指标不直接写 SQLite因为时间序列是追加写模式每秒几千个点SQLite 的事务开销扛不住。所以 Netdata 的样本数据默认走 dbengine 内存加磁盘文件SQLite 只存“图谱叫什么名字、有哪些 label”这类低频信息。query.c是查询引擎负责把存储层原始点按时间窗口、采样间隔、聚合方式组织成 JSON。你平时在页面上看到的每个图表最后都会落到一次 query 调用。读这三个文件的顺序我建议先 dictionary再 query最后才碰 sqlite这样不会被 SQLite 的实现细节拖住。3. 从源码编译到出图依赖、插件开关和 /api/v1 验证很多人拿到源码包以后第一反应是找编译脚本结果被一堆 autotools 的东西吓住。其实 Netdata 对开发者很友好它自带netdata-installer.sh你只要把依赖装齐脚本会帮你完成 configure、make、install、写 systemd unit 这一整套动作。下面这套步骤在 Ubuntu/Debian 上验证过CentOS/Rocky 的包名略有差异但思路一样。3.1 依赖清单与编译参数没有 root 也能跑先装依赖这是整个过程中最容易翻车的地方。Debian 系常见的包如下sudo apt-get update sudo apt-get install -y autoconf automake pkg-config libjson-c-dev \ libssl-dev libuv1-dev liblz4-dev libjudy-dev libmnl-dev \ libuuid1 libcurl4-openssl-dev zlib1g-dev这些包名里libjson-c-dev和libuv1-dev是最容易被漏掉的。Netdata 的 Web API 返回 JSON底层内存池和事件循环依赖 libuv缺了它们configure 会在很靠前的位置报错。CentOS 上对应的包名是json-c-devel、libuv-devel、libuuid-devel别照着 Debian 的名字硬敲。依赖装完以后进入解压出来的目录执行./netdata-installer.sh --dont-wait --dont-start-it--dont-wait的意思是跳过所有交互确认--dont-start-it是安装完不立刻启动方便你先改配置。如果不想用安装脚本也可以手动走./configure --prefix/opt/netdata make -j$(nproc) sudo make install。区别在于安装脚本会额外帮你处理插件权限和 systemd unit手动方式更适合想搞明白每条文件去哪了的人。如果你在开发机上没有 rootNetdata 并不是完全跑不了。你可以把--prefix指到自己的目录比如$HOME/netdata然后运行$HOME/netdata/sbin/netdata -D前面加一个-D是为了前台运行看日志。后面的 Web 端口还是 19999只是少了 systemd 管理进程一关监控就停。3.2 启动并接入 Nginx插件配置里的关键开关安装完成后直接启动sudo systemctl start netdata sudo systemctl enable netdata curl -s http://127.0.0.1:19999/api/v1/info | head最后一条 curl 是为了确认 Web API 起来了。如果返回了 JSON说明 Web 服务正常如果 curl 失败去翻/var/log/netdata/error.log最常见的错误是端口被占或者安装脚本没把netdata用户创建成功。接入 Nginx 是 Nginx 用户最关心的场景。Netdata 不会自己去解析 nginx 日志它靠的是 nginx 自带的stub_status模块。先让 nginx 暴露一个内部状态端点server { listen 127.0.0.1:8088; location /nginx_status { stub_status on; allow 127.0.0.1; deny all; } }然后在 Netdata 的 go.d 插件配置里加一个 job。配置一般在/etc/netdata/go.d/nginx.confjobs: - name: local url: http://127.0.0.1:8088/nginx_statusname是图表里显示的名字url必须和 nginx 实际监听地址一致。改完以后不用重启 Netdata它会在几秒内自动加载 go.d 配置。如果图表没出来先 curl 一下那个nginx_status地址确认返回的是当前活跃连接数而不是 nginx 的默认欢迎页。3.3 验证实时性通过 /api/v1 拿毫秒级数据Netdata 真正值钱的地方是它把所有图表都暴露成了可编程的 API。你不一定用页面直接请求数据接口就能验证它“到底是不是实时”curl -s http://127.0.0.1:19999/api/v1/data?chartsystem.cpuafter-5before0points5groupaverageformatjson | python3 -m json.tool这里chartsystem.cpu指定图表after-5before0表示最近 5 秒points5表示返回 5 个点groupaverage表示每点做平均。返回结果里的result数组就是你要的时间序列。这套参数几乎是通用的把chart换成net.eth0、apps.cpu都能拿到对应数据。我通常会再跑一个watch -n 1去看同一个接口返回的数字是否会随时间变如果数字一直在动说明采集链路是通的。这一步在源码分析里特别有用你可以用 API 数据对比自己写的采集插件输出判断到底是采集错了还是展示错了。4. 写一个自定义采集插件照着 pluginsd 协议半小时出图Netdata 能火除了实时性强还因为插件机制足够简单。pluginsd_parser.c这个文件就是在给所有外部插件当翻译官插件往标准输出按协议写几行文本parser 读完以后把数据送进存储引擎。你不需要改 Netdata 内核代码只需要会写脚本。4.1 为什么首选 pluginsd外部插件协议的核心指令老式监控工具的自定义采集通常要写共享库、注册回调函数编译失败一次就劝退一半人。Netdata 的 pluginsd 协议把这件事退化成“按行打印文本”先声明图表再声明维度然后不断上报数值。核心指令是CHART、DIMENSION、BEGIN、SET、END。一个最小的协议片段长这样CHART custom_test.counter Test Counter requests custom_test.counter line 1000 1 DIMENSION count incremental 1 1 BEGIN custom_test.counter SET count 42 ENDCHART里第二个字段是图表 id空字符串表示用第一个字段自动生成custom_test.counter是完整的上下文名页面上会显示成一层菜单一个图。DIMENSION里的incremental告诉 Netdata 这个值是计数器需要算增量如果你上报的是 CPU 使用率这种绝对值这里应该写absolute。最后的BEGIN/SET/END组成一次采样一个循环里可以 SET 多个维度。4.2 用 Python 写一个实际可用的 Nginx 请求数插件我一般用 Python 写这类插件因为调试成本低。下面这个例子模拟的是“每 2 秒读一次 nginx_status把请求总数上报给 Netdata”#!/usr/bin/env python3 import sys import time import urllib.request # 启动时先声明图表和维度这一步非常重要 print(CHART custom_nginx.requests Nginx Requests requests custom_nginx.requests line 1000 1) print(DIMENSION handled incremental 1 1) sys.stdout.flush() while True: try: with urllib.request.urlopen(http://127.0.0.1:8088/nginx_status, timeout3) as resp: body resp.read().decode() value 0 for line in body.splitlines(): if line.strip().startswith(server accepts): # 这一行格式类似: # server accepts handled requests # 10 10 12345 parts line.rsplit(None, 1) if parts: value int(parts[-1]) print(BEGIN custom_nginx.requests) print(fSET handled {value}) print(END) sys.stdout.flush() except Exception as exc: sys.stderr.write(fcustom_nginx: {exc}\n) sys.stderr.flush() time.sleep(2)这个脚本的逻辑很直白第一轮先把CHART和DIMENSION打好让 Netdata 知道接下来要接收什么。主循环里每次请求 nginx_status从返回文本最后一行取到累计请求数然后按协议输出。flushTrue或sys.stdout.flush()一定要有否则数据积在管道缓冲区里Netdata 干等到超时。用法是把这个文件放到插件目录并加上执行权限sudo cp custom_nginx.chart.py /usr/libexec/netdata/plugins.d/ sudo chmod x /usr/libexec/netdata/plugins.d/custom_nginx.chart.py之后刷新 Netdata 页面左侧会多出一个custom_nginx菜单。如果没出现先手动执行一遍这个脚本直接在终端里看它打印出来的文本是否符合协议这比看日志快十倍。4.3 把自定义图表挂到独立页面Netdata 的自定义仪表板不强制你写 HTML。每个图表都有一个独立 URL可以直接嵌 iframe 或者存到书签iframe srchttp://127.0.0.1:19999/custom_nginx.requests?after-300before0points300 width800 height240/iframecustom_nginx.requests是你在CHART里定义的 contextafter和before是时间窗口。这个 URL 在 Netdata 内部会走一遍 query 引擎返回已经聚合好的数据。注意iframe 只适合内网别把它直接发到公网因为默认不带鉴权别人拿到 URL 就能看到你全部图表。如果你想把多个自定义图表拼成一页常见做法是写一个静态 HTML里面并列摆几个这种 iframe。Netdata 官方也有自己的动态 dashboards但那个更适合团队协作场景单人调试时一个带 iframe 的本地文件够用了。5. 避坑指南Netdata 源码部署与二次开发里的 5 个典型翻车点这套源码包我在编译和改代码的过程中踩过的坑集中在权限、依赖和插件协议三个方向。下面五条每条都是“现象 - 原因 - 解决”的结构直接对着日志看就行。5.1 configure 报错找不到 json-c 头文件现象./configure跑到一半报fatal error: json-c/json.h: No such file or directory。原因发行版包名不统一。Ubuntu 里 json-c 的开发包叫libjson-c-devCentOS 里叫json-c-devel而且即使装了旧版pkg-config也可能找不到新版本。解决先sudo apt-get install libjson-c-dev装完以后用pkg-config --cflags --libs json-c验证输出。如果输出为空说明还有别的库名比如json-c改成json手动到configure.ac里搜一下就知道它到底查的哪个包名。不要一报错就往代码里加头文件路径那不是根治办法。5.2 页面起来了apps 菜单却是空的现象CPU、磁盘曲线都有但apps子菜单没有任何进程名。原因apps_plugin.c需要 root 权限或 CAP_SYS_PTRACE 才能读其他进程的/proc信息。如果启动 Netdata 的用户不是 root或者你用了普通用户自定义安装它能看到的进程只有自己那一组。解决用 systemd 管理时确认/etc/systemd/system/netdata.service里的Userroot。如果你不想给 root 权限至少给进程加上CAP_SYS_PTRACE并且保证/proc没有被 hidepid 锁住。临时测试可以直接sudo -u root ./netdata -D前台跑看 apps 菜单是否恢复。5.3 ebpf 图表一直不出来日志里全是 debugfs 错误现象error.log里出现Cannot access /sys/kernel/debug/tracingebpf插件显示 disabled。原因eBPF 采集需要内核挂载 debugfs并且当前内核要开启 BPF 相关选项。容器环境里更严格需要特权模式否则连探针都加载不了。解决在宿主机执行sudo mount -t debugfs none /sys/kernel/debug然后确认/proc/sys/kernel/unprivileged_bpf_disabled的值。如果是容器把 NETDATA 的 systemd unit 加AmbientCapabilitiesCAP_BPF CAP_SYS_ADMIN或者直接让插件在非特权模式下跑放弃部分高级探针。读ebpf.c里那段检测代码它会明确告诉你哪一步不满足。5.4 本机 curl 正常别的机器访问不了 19999现象开发机上curl http://127.0.0.1:19999/api/v1/info有返回换到局域网另一台机器就超时。原因Netdata 默认 bind 到 127.0.0.1只接受本机连接。这是安全设计不是 bug。解决在netdata.conf的[web]段里改bind to 0.0.0.0:19999然后重启。改之前先想清楚19999 没有鉴权暴露到公网等于把你的监控数据公开给全世界。我一般只 bind 到内网网卡 IP比如bind to 192.168.1.10:19999再让防火墙只放行运维网段。5.5 自定义插件目录里放了脚本页面就是不出现现象脚本手动执行能打印CHART但 Netdata 页面没有任何新菜单日志里也没有插件报错。原因三个常见原因一是脚本没有执行权限二是文件名不以.chart.py结尾Netdata 的插件扫描器会跳过未识别的文件三是你把CHART写进了主循环每次上报都重新声明图表协议解析器只认最后一次。解决前两个问题分别用chmod x和改文件名解决。第三个问题按照开头的顺序来启动时先打印CHART和DIMENSION进入 while 循环后再只打BEGIN/SET/END。如果还不行在终端里手动执行脚本观察它是否阻塞在stdout.flush()上管道堵塞往往是因为忘了 flush。6. 上生产前的三个细节看门狗、告警阈值与最小化开销源码编译的 Netdata 和发行版装出来的最大区别是没人替你处理进程守护和默认告警这三个细节最好在上线前手动过一遍。第一个是进程守护。如果安装脚本没帮你生成 systemd unit或者你用了--prefix自定义安装手工写一个netdata.service[Unit] DescriptionNetdata Afternetwork.target [Service] ExecStart/opt/netdata/usr/sbin/netdata -P /run/netdata.pid Restartalways RestartSec5 WatchdogSec60 PIDFile/run/netdata.pid [Install] WantedBymulti-user.targetWatchdogSec是 systemd 的看门狗Netdata 如果卡死超过 60 秒systemd 会直接把它重启。写完后sudo systemctl daemon-reload sudo systemctl enable --now netdata。从那以后我每次编译完 Netdata 都会先确认systemctl status netdata里能看到WatchdogSec60生效。第二个细节是告警阈值。默认配置适合“演示”并不适合生产。以 CPU iowait 为例在/etc/netdata/health.d/cpu.conf里写alarm: cpu_iowait on: system.cpu lookup: average -10s of iowait every: 10s warn: $this 20 crit: $this 40lookup表示对这 10 秒内的 iowait 做平均warn和crit是触发线。改完不用重启Netdata 会在几秒内自动加载健康文件。我踩过的最小坑是忘了写on:告警无法匹配任何图表页面上的告警区一直是空的注意检查。第三个细节是资源开销。如果你只关心告警和长期趋势不需要每秒刷新把全局采样间隔拉长[global] update every 5 memory mode dbengine page cache size 32 dbengine disk space 256 [web] mode static-threadedupdate every 5会让 CPU 开销直接降一个量级代价是曲线不再那么细。memory mode dbengine表示数据写盘而不是只放内存这样重启后历史数据还在。v1.44.3 的默认 1 秒采样适合现场抓故障生产长期跑我会优先保底性能。最后给你一个验证顺序先裸跑十分钟把 CPU、内存、网络基线记下来再开告警人工压一个高负载确认告警能触发最后才把节点挂到集中视图。从那以后我每次上线新节点都强制走一遍这个流程宁可慢十分钟也不要半夜被同一个误报叫醒。希望帮到你。本文还有配套的精品资源点击获取