使用 time-grunt 输出每个任务耗时并生成火焰图

解读

在国内前端面试中,构建性能是高频考点。面试官不仅想知道“能不能跑”,更关注“跑得有多快、瓶颈在哪”。time-grunt 是官方推荐的性能分析插件,默认只能输出文本耗时,而火焰图(Flamegraph)是可视化 CPU 占用分布的黄金标准。题目要求“输出耗时并生成火焰图”,本质考察两点:

  1. 正确接入 time-grunt 并拿到毫秒级精确数据
  2. 把数据转成火焰图,在无 GUI 的 CI 环境也能落地
    很多候选人只回答“装插件、看日志”,会被追问“如何持续追踪”“如何定位慢任务”,因此必须给出可落地的完整链路

知识点

  • time-grunt 原理:通过 hook Grunt.event.on('task_start'/'task_end') 记录时间戳,支持自定义 logger 输出 JSON。
  • grunt --profile:Grunt 1.5+ 内置 --profile 开关,可生成 tmp/grunt-profile-*.json,含每个 task 的 cpu 采样栈。
  • 0x / flamebearer:Node 端火焰图生成工具,0x 基于 --prof + tick-processor,flamebearer 直接消费 --prof 文件,均可在 Linux CI 无头运行
  • source-map 对齐:若任务内调用 webpack、babel、uglify,需保留 source-map,否则火焰图栈会指向压缩后代码,无法定位真实慢函数
  • 国内镜像加速:0x 依赖原生模块 node-dtrace-provider,在阿里源、华为源下需提前预编译,避免 CI 超时。
  • 性能基线:美团、字节等厂内规范要求“单个 Grunt 任务 < 2 s,总构建 < 15 s”,火焰图红框函数占用 > 30 % 必须优化。

答案

  1. 安装依赖
    npm i -D time-grunt 0x

    若 CI 无 Python2,再加 --python=python3

  2. 在 Gruntfile.js 顶部注入 time-grunt
    module.exports = function (grunt) { require('time-grunt')(grunt, (stats, table) => { // 把 stats 写到文件供后续持续追踪 require('fs').writeFileSync('.grunt-timings.json', JSON.stringify(stats, null, 2)); console.log(table); // 保持控制台输出 }); ... };

  3. 生成火焰图

    方式 A:利用 Grunt 内置 --profile(推荐,零侵入)

    grunt build --profile 0x -o tmp/grunt-profile-*.json --name grunt flamegraph.html

    方式 B:全链路采样(含插件内部)

    node --prof $(which grunt) build 0x --kernel-tracing -- node --prof-process isolate-*.log > flamegraph.html

  4. 在 CI 中固化
    在 GitLab-CI / GitHub Actions 中增加 step:

    • 任务失败阈值:若 .grunt-timings.json 中任一任务耗时较上次主分支上涨 > 20 %,则 exit 1;
    • 火焰图产物作为 artifact,MR 阶段自动评论“性能报告”链接,实现性能回归可追踪
  5. 优化示例
    火焰图发现 uglify 任务红框集中在 AST_Node.DEFMETHOD,说明单线程压缩大文件;改用 grunt-parallelize 把文件哈希分片后并行跑 4 个子进程,总耗时从 9.3 s 降到 2.1 s,满足字节内部基线

拓展思考

  • 双引擎对比:若团队已迁移到 Vite/Rollup,可同样用 vite --profile + 0x 生成火焰图,与 Grunt 版本并排对比,量化迁移收益,用数据说服管理层。
  • 非 CPU 瓶颈:火焰图只能看 CPU,若任务是 IO 密集(如大量 copy、图片压缩),需结合 clinic.js doctor 看事件循环延迟,避免误判
  • 安全合规:0x 默认会采样完整栈,可能带出代码路径;在金融、政务项目里需加 --exclude-env 过滤敏感路径,满足等保测评要求