
WeKan 磁盘用量监控设计:Admin Panel → Problems 中的 statfs 采样与 start/end 报告机制【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan本文围绕 WeKan 的设计文档docs/Features/Admin-Panel/Problems/Disk-usage.md展开,讲解 WeKan 如何监控磁盘用量并在管理面板 Problems 区域生成报告:从监控哪些文件系统、fs.statfs采样与使用率计算,到带滞回(hysteresis)的高低状态机,再到写入eventLog事件的disk流的 start/end 两行式报告。读完你可以完整掌握该子系统的架构、全部环境变量及其默认值,并能对照已实现的 CPU/RAM 监控兄弟模块与已有的上传前磁盘空间检查,理解其实现边界与落地方式。1. 背景:磁盘写满会让 WeKan 整体瘫痪WeKan 通常与 FerretDB 部署在同一台机器上,数据库后端可以是 FerretDB(SQLite)或 MongoDB。原文档开宗明义:磁盘写满会硬性击穿WeKan(以及 FerretDB/SQLite)——上传失败、数据库无法写入,同机其他软件也会一起停摆。因此该子系统的定位是只观察、只报告(observe-and-report):监控 WeKan 实际使用的文件系统的剩余空间,并在管理员能够提前干预之前,给出早期、自描述(能直接看懂、无需再翻日志)的警告。它是 CPU-usage 与 RAM-usage 两个监控的兄弟模块:三个 Problems 监控共享同一种结构、同一套事件流机制、同一个报告模板。管理面板侧的入口在Admin Panel → Problems → Disk usage,该区域的总览、REST API(GET /api/admin/problems/:stream)与snap run wekan.problems命令行出口见 Problems 目录 README。需要如实说明当前状态:文档标注为 **Status: Design (proposed),即监控本体的实现文件(server/lib/diskMonitor.js、server/lib/diskLog.js、models/lib/diskHighTracker.js)在设计文档中列为待添加。当前仓库中已存在并可直接阅读的是它所镜像的 CPU 侧实现**(server/lib/cpuMonitor.js、models/lib/cpuHighTracker.js)与已有的上传前磁盘空间检查(models/lib/diskSpace.js)。本文按文档设计展开,并用这些已落地源码逐条印证设计中的每个环节;涉及尚未落地的部分会明确标注。2. 设计要求(完整继承原文档)设计文档提出五条需求:监控对象:在 WeKan(及并行的 FerretDB)运行期间,监视真正重要的文件系统上的磁盘使用量——数据/状态文件系统(FerretDB SQLite / Mongo 数据)、附件存储路径(WRITABLE_PATH/ 已配置的存储)、以及上传消毒(sanitizing)过程中使用的临时路径。报告入口:新增Admin Panel → Problems → Disk usage报告页。只记 start/end:只记录每次持续高使用期什么时候开始、什么时候结束——每个事件(episode)只产生两行,报告永远不会被刷爆。每行的信息量:每行记录每个被监视文件系统的已用/总量与百分比,该事件期间的峰值使用率、持续时长,以及事件发生时 WeKan/FerretDB 在做什么。尽力而为、绝不抛异常:在无法获取剩余空间信息的环境(例如不允许statfs的沙箱)中静默降级为 no-op。这一点与 Filename 文档中已有的上传前磁盘空间检查保持一致——后者在读取不到剩余空间时,回退到小 RAM 分块流式写入。3. 测量层:WeKan 实际使用的文件系统3.1 采样与 statfs 计算公式server/lib/diskMonitor.js(设计中的实现文件)按固定间隔采样:间隔由WEKAN_DISK_SAMPLE_INTERVAL_MS控制,默认 30000ms(30s)。磁盘的变化远比 CPU/RAM 缓慢(CPU 监控默认 5s 采样,见server/lib/cpuMonitor.js中WEKAN_CPU_SAMPLE_INTERVAL_MS默认 5000),更长的间隔可以避免无谓的statfs系统调用。对每个被监视路径调用fs.statfs(path)(Node ≥ 18),并做如下换算:bsize × blocks 该文件系统的总字节数;bsize × bavail非 root 用户可用的字节数(这正是 WeKan 进程真正能写到的空间,比 root 视角的 free 更贴近实际);使用率:Used% (total − available) / total × 100。这个statfs用法可以直接对照仓库中已经落地的models/lib/diskSpace.js。它在写上传文件前探测剩余空间,核心逻辑如下(见 diskSpace.js):// 模型:返回 dirPath 所在文件系统的剩余可用字节,拿不到则返回 null function getFreeDiskBytes(dirPath) { try { if (typeof fs.statfsSync ! function) return null; // 平台不支持 → 未知 const stats fs.statfsSync(dirPath); if (!stats || typeof stats.bavail ! number || typeof stats.bsize ! number) { return null; } const free stats.bavail * stats.bsize; // 与磁盘监控同一公式 return Number.isFinite(free) free 0 ? free : null; } catch (e) { return null; // 路径缺失/平台不支持/statfs 失败 —— 视为未知,而非已满 } } // 剩余 需要 安全余量(默认 16MB)才放行;未知(null)时也放行, // 但调用方必须改用小分块流式写入、出错即清理半成品 function hasEnoughDiskSpace(dirPath, neededBytes, marginBytes 16 * 1024 * 1024) { const free getFreeDiskBytes(dirPath); if (free null) return true; // unknown - proceed with safe streaming const need Math.max(0, Number(neededBytes) || 0) marginBytes; return free need; }注意其中的语义细节:读取不到被区分于磁盘已满。statfs失败返回null(未知),而不是 0(满);调用方在未知情形下仍会写,但切换到小分块流式 出错清理的安全路径。这正是设计文档第 5 条best-effort and never throws的已实现版本,磁盘监控沿用同一哲学。3.2 被监视的路径与去重设计中的默认监视清单:路径角色数据/状态目录FerretDB SQLite / Mongo 数据库文件所在WRITABLE_PATH附件存储WRITABLE_PATH/files/temp上传文件消毒过程的临时路径三个关键设计点:按解析到的文件系统去重:三个路径可能落在同一块物理盘上,报告时一块物理盘只出现一次,避免同一块盘产生三条几乎相同的行。可配置覆盖:通过WEKAN_DISK_WATCH_PATHS(冒号:分隔的路径列表)可以为非标准部署布局指定自定义监视路径,覆盖默认清单。每块盘一个独立事件:如第 4 节所述,状态机是按被监视文件系统分别跟踪的——数据盘写满与附件盘写满是两个互不干扰的 episode,各自的 start/end 行独立记录。4. 状态机:只记 start 与 end(diskHighTracker)4.1 滞回(双阈值 连续采样)防抖models/lib/diskHighTracker.js(设计中)是纯函数、可单测的状态机,形状与已实现的models/lib/cpuHighTracker.js完全一致。已落地的 CPU 版实现可以作为它的确切参照,核心update()逻辑(见 cpuHighTracker.js):update(pct, now) { const value Number(pct) || 0; if (!this.high) { // 未处于高状态:只有连续 enterSamples 次 highPct才进入 this.aboveCount value this.highPct ? this.aboveCount 1 : 0; if (this.aboveCount this.enterSamples) { this.high true; this.belowCount 0; this.startedAt now; this.peak value; return { event: start, at: now, pct: value }; } return { event: null }; } if (value this.peak) this.peak value; // 事件期间持续追踪峰值 this.belowCount value this.lowPct ? this.belowCount 1 : 0; if (this.belowCount this.exitSamples) { // 连续 exitSamples 次 lowPct 才退出,返回持续时长与峰值 ... return { event: end, at: now, startedAt, durationMs: now - startedAt, peak }; } return { event: null }; }设计文档为磁盘监控规定的参数:进入高状态:连续WEKAN_DISK_HIGH_SAMPLES(默认2)次采样达到或超过WEKAN_DISK_HIGH_PERCENT(默认90% used);离开高状态:连续WEKAN_DISK_LOW_SAMPLES(默认2)次采样低于WEKAN_DISK_LOW_PERCENT(默认85% used);峰值追踪:记录事件期间见过的最高 used%,写入 end 行。进入阈值(90%)与退出阈值(85%)不同,加上连续 N 次的双重滞回,保证使用率在 85%~90% 之间来回抖动的磁盘不会触发 start/end/start/end 的行风暴。对照 CPU 侧的默认值(进入 85%/退出 70%,各需 3 次连续采样,见 CPU-usage.md 与cpuMonitor.js),磁盘侧2 次连续即可配合 30s 的采样间隔,意味着一次真实的高使用期最迟约 1 分钟后被记录开始——对缓慢膨胀的磁盘而言是合理的灵敏度/噪音比。4.2 纯函数与单测边界设计文档明确要求:tracker 与statfs → 百分比的辅助函数都是纯函数,在无 Meteor 环境下单测,覆盖正向与负向场景:正向:连续采样跨越进入/退出阈值,产生正确的 start/end、时长、峰值;负向:读不到剩余空间信息(沙箱禁用statfs)、总量为零的异常文件系统、按文件系统独立成事件(两块盘各自开各自的 episode)。这与cpuHighTracker.js文件头注释(Pure and unit-testable (no timers, no os calls))声明的方式一致。5. 报告层:Admin Panel → Problems → Disk usage5.1 写入 eventLog 的disk流每个 episode 写入eventLog集合的disk流(与cpu、ram并列),由既有的eventStreamReport模板展示。事件集合的落地实现见 eventLog.js:集合名eventlog,每条事件一个文档;stream字段区分报告流,severity取info|low|medium|high|critical,action取blocked|remediated|sanitized|rate-limited|detected|failed,detail承载人类可读描述(见 eventLog.js 的 schema)。服务端在 startup 时创建复合索引{ stream: 1, at: -1 },使按流过滤 按时间倒序的分页报告在事件量增长后仍是有界索引扫描(见 eventLog.js);管理端通过eventLogPage/eventLogCount等仅管理员可读的方法取数,并支持按username、ip、detail等文本列搜索。每个流有独立的确认(Acknowledge)机制(eventlogAcks):管理员点确认后,该流新事件计数归零,Problems 按钮与 Summary 页只反映尚未处理的问题(见 eventLog.js)。需要如实指出一个实现边界:当前仓库的 eventLog.js 中,EVENT_STREAMS白名单为[security, speed, tests, cpu, database, integrity]——disk流尚未加入,与文档Design (proposed)的状态一致;ram流同样未在其中,说明 Problems 各监控流是在逐个模块中注册进该清单的。落地时disk会按同一模式追加进EVENT_STREAMS,报告页与 REST 出口(GET /api/admin/problems/disk)随之可用。5.2 每个 episode 的两行报告每个事件、每块盘固定两行:start(action: detected),示例:high disk usage started ( 90%) on /var/lib/wekan (attachments): 92% used, 6.1 GB free of 80 GB, WeKan: activityend(action: remediated),示例:high disk usage ended after 12m on /var/lib/wekan (peak 97%, back under 85%): 71% used, 23 GB free of 80 GB行内信息覆盖:哪块盘(路径 用途标注)、当前 used/总量/百分比、进入阈值、事件期间峰值、持续时长、回落后的状态,以及WeKan: activity当前活动标签——与 CPU 侧 start 行的activitySnapshot(system CPU、load、cores、WeKan: activity,见 cpuMonitor.js)是同一套自描述风格。严重度规则:used≥ 95% 记high(即将失败的先兆),否则medium。这与 CPU 监控中severity: pct 95 ? high : medium的既有写法(cpuMonitor.js)一致;end 行则按 CPU 侧惯例记info。5.3 与 CPU 监控同构的调用链从已实现的 CPU 监控可以推断磁盘监控落地后的采样主循环形态(见 cpuMonitor.js):// cpuMonitor.js 的实际结构(磁盘监控镜像此结构,采样换为 statfs) Meteor.startup(() { const { record } require(/server/lib/cpuLog); Meteor.setInterval(() { try { const pct sampleCpuPercent(); // 磁盘侧:fs.statfs → used% const res tracker.update(pct, Date.now()); if (res.event start) { record({ action: detected, severity: pct 95 ? high : medium, detail: high CPU usage started ( ${HIGH_PCT}%): ... }); } else if (res.event end) { record({ action: remediated, severity: info, detail: high CPU usage ended after ${secs}s (peak ${res.peak}%, ...) }); } } catch (e) { /* best effort, never throws */ } }, INTERVAL_MS); });整体 try/catch 注释 best effort 的写法对应设计文档never throws的要求:任何一次采样失败(路径消失、statfs被拒)都只丢失该次样本,监控不中断、不向应用抛错。6. 环境变量一览(完整继承原文档)变量默认值含义WEKAN_DISK_MONITORtrue主开关(on/off)WEKAN_DISK_SAMPLE_INTERVAL_MS30000采样间隔(毫秒)WEKAN_DISK_HIGH_PERCENT90used% 达到该值(连续足够采样)时进入高状态WEKAN_DISK_LOW_PERCENT85used% 低于该值(连续足够采样)时离开高状态WEKAN_DISK_HIGH_SAMPLES2进入高状态所需的连续高采样次数WEKAN_DISK_LOW_SAMPLES2离开高状态所需的连续低采样次数WEKAN_DISK_WATCH_PATHS(自动)冒号:分隔的监视路径列表,覆盖默认清单配置建议(由默认值语义推导,供调参参考):想更早收到警告,可下调WEKAN_DISK_HIGH_PERCENT(如 85);想减少误报,可上调WEKAN_DISK_HIGH_SAMPLES(如 3,配合 30s 间隔即约 90s 确认窗口);数据盘与附件盘分离部署时,务必确认两者都在监视清单中——默认清单靠路径推导,非标准布局请用WEKAN_DISK_WATCH_PATHS显式列出;主开关WEKAN_DISK_MONITORfalse时整个子系统静默关闭(对照cpuMonitor.js中ENABLED的解析方式:字符串false才关闭,其余视为开启,见 cpuMonitor.js)。7. 与上传前磁盘空间检查的分工设计文档 Notes 一节强调,磁盘监控与 Filename 文档描述的上传前检查是互补关系,二者构成事前 事后两道防线:维度上传前检查(已实现)磁盘监控(设计)时机每次上传写入之前后台按 30s 间隔持续采样覆盖仅附件写入路径数据/状态盘、附件盘、临时盘全部能力能干预:空间不足时拒绝写入或改用小分块安全流式只观察报告:WeKan 无法自行释放磁盘,价值在于早期自描述警告实现models/lib/diskSpace.jsdiskMonitor.jsdiskHighTracker.js(镜像 CPU 侧)也就是说:上传前检查防止某一次大文件把盘写爆,磁盘监控回答盘正在被什么东西持续吃满、从什么时候开始、峰值多高、是否已回落——后者在上传检查无法感知数据库文件膨胀、日志增长等场景下不可替代。8. 落地状态小结与验证方式结合仓库现状,可核对的事实如下:设计文档 Disk-usage.md 状态为Design (proposed),列出的四个相关文件(server/lib/diskMonitor.js、server/lib/diskLog.js、models/lib/diskHighTracker.js及models/eventLog.js)中,仅eventLog.js已存在,其余三个按设计待添加;其镜像模块 CPU 侧已完整实现:采样(server/lib/cpuMonitor.js)、状态机(models/lib/cpuHighTracker.js)、日志(server/lib/cpuLog.js)三件套均在仓库中可直接阅读,磁盘监控落地时将复用同一形状与eventStreamReport报告模板;eventLog的 schema、索引、管理端方法与 ack 机制已就绪,disk流接入时只需将流名加入EVENT_STREAMS白名单(当前为 6 个流,见 eventLog.js)并按streamSelector的搜索列扩展;已有的models/lib/diskSpace.js证明本仓库statfs采样 未知 ≠ 满的降级语义在现有 Node 运行时上已被验证,磁盘监控对 Node ≥ 18 的前提与之一致(fs.statfsSync自 Node 18.15 可用,见 diskSpace.js 的注释)。落地后,管理员的验证路径为:在Admin Panel → Problems → Disk usage查看 start/end 行;或在服务器上用snap run wekan.problems命令行查看同一数据(Snap.md);或对外部监控暴露GET /api/admin/problems/diskREST 端点(Problems README)。当 start 行出现且 severity 为high(≥ 95%)时,应优先清理附件盘或扩展数据盘容量,并在处理完毕后通过 end 行确认回落(used 回到 85% 以下且持续 2 个采样周期)。【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考