
使用 Session Log 定位与优化 Dart Analysis Server 的 LSP 请求延迟从日志回放到 SDK 重编译的完整工作流【免费下载链接】sdkThe Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.项目地址: https://gitcode.com/gh_mirrors/sdk1/sdkDart Analysis ServerDAS是支撑 VS Code、IntelliJ / Android Studio 等编辑器完成补全、诊断、颜色预览等 LSP 能力的核心进程其请求延迟直接决定开发者的编辑体验。本文基于 Dart SDK 仓库中的官方技能文档pkg/analysis_server/.agents/skills/improve-performance-with-session-log/SKILL.md完整讲解一套记录会话日志 → 归一化 → 回放复现 → 定位优化 → 重编 SDK → 回放验证的性能诊断闭环。读完本文你将掌握如何用analyze_session_log.dart分析日志中的慢请求、用normalize.dart制作可移植日志、用measure_request_latency.dart与server_driver_main.dart精确复现并测量基线延迟并能在修改pkg/analysis_server/pkg/analyzer后重新编译 SDK 验证优化效果。一、整体工作流从一条慢日志到一次被验证的优化会话通信日志session communications log记录了 DAS 与客户端之间全部的请求、响应与通知JSON-RPC 消息。它既可以用来上报和复现 bug也可以用来量化每一次 LSP 交互的延迟。围绕这份日志SKILL.md 定义了如下 7 步闭环Inspect Log定位目标请求如textDocument/documentColor、textDocument/completion按response.time - request.time计算请求到响应的耗时Normalize Log把机器相关的绝对路径替换为可移植占位符{{workspaceFolder-0}}、{{dartSdkRoot}}Reproduce Baseline用server_driver_main.dart或自动化回放脚本把归一化日志对着基线服务器回放将热请求延迟与冷启动排队区分开Optimize Code在pkg/analysis_server或pkg/analyzer中实施性能优化Recompile SDK在 SDK 根目录执行python3 tools/build.py -mrelease create_sdkReplay with the freshly built SDK用新编译出的dart二进制重新回放验证延迟降低与结果正确性Run unit and integration tests运行既有测试并为修改的逻辑补充回归测试。日志的采集方式Analyzer Insights 网页捕获或--session-log启动参数不是本技能的主角但它是整个流程的数据来源详见 Recording a Session Communications Log。二、Step 1检查会话通信日志会话日志是行分隔的 JSONJSON Lines每一行都是一个独立的 JSON 对象包含time与kind字段对于message类条目还带有sender、receiver和message部分条目可能省略这些字段。其中message即 JSON-RPC 载荷time为事件发生的毫秒级时间戳kind的取值示例为commandLine或messagesender/receiver的取值包括ide、server、watcher、dtd。2.1 用analyze_session_log.dart自动分析逐行翻日志找慢请求效率太低技能目录直接提供了解析与汇总脚本scripts/analyze_session_log.dartdart sdk-root/pkg/analysis_server/.agents/skills/improve-performance-with-session-log/scripts/analyze_session_log.dart \ [--methodname] [--min-msms] [--warm-only] [--summary] path_to_session_log例如只查看某次会话中的textDocument/documentColor请求dart sdk-root/pkg/analysis_server/.agents/skills/improve-performance-with-session-log/scripts/analyze_session_log.dart \ -m documentColor session-log-2.txt对照脚本源码analyze_session_log.dart的ArgParser配置它实际支持以下参数参数缩写默认值说明--methodname-m无按方法名过滤请求子串或精确匹配不区分大小写--min-msms无0只显示耗时 ≥ 该值毫秒的请求--topn-n25只显示最慢的 N 条请求0表示全部--warm-only-w关闭只显示热请求排除排队发生在分析期间的请求--summary-s关闭打印按方法分组的延迟统计表--errors-e关闭只显示返回了错误响应的请求--help-h关闭打印用法2.2 脚本能告诉你什么脚本的核心能力均可在源码SessionLogAnalyzer类中找到对应实现同时度量客户端与服务器端耗时客户端感知耗时response.time - request.clientRequestTime服务器处理耗时response.time - request.time。日志中请求消息内若带clientRequestTime客户端发出时刻两条指标都会在报告中展示。检测冷启动排队脚本跟踪$/progress通知中 token 为ANALYZING的begin/end事件据此判断每个请求发出时服务器是否正处于后台分析期。一个请求若在分析进行中发出、或初始分析从未完成会被标记为COLD否则为WARM对应源码中_CompletedRequest.isCold的判定wasAnalyzingAtStart || !hadInitialAnalysisCompleted。自动提取错误与崩溃脚本扫描window/logMessage中的错误文本如包含An error occurred、Exception、Bad state并在报告末尾单列LOGGED SERVER EXCEPTIONS / ERRORS区块。示例异常如Bad state: SimpleIdentifierImpl is not in the V2 AST view。输出按方法的延迟统计表--summary或默认首屏对每个方法给出 Total / Warm 请求数、Min / Avg / Max 耗时、WarmAvg / WarmMax 与错误数按最大耗时降序排列方便一眼锁定最慢的 LSP 方法。请求明细表每行输出ID、Method、ClientDur、ServerDur、StateCOLD/WARM、StatusERROR/OK与目标文件从params.textDocument.uri或params.uri中提取的文件名。错误请求下方会追加└─ Error: message一行。2.3 手工估算延迟不借助脚本如果只想快速目测可以直接按如下规则在日志中配对找到客户端请求行sender: ide、receiver: server记下其id与时间戳time或message内的clientRequestTime找到sender: server、receiver: ide且id相同的那一行往返耗时 response.time - request.time或response.time - request.message.clientRequestTime。[!NOTE]冷启动 vs 热服务器编辑器首次打开工作区时服务器会在后台索引并分析文件由$/progress的title: Analyzing...跟踪。此时发出的早期请求会在任务队列中排在后台分析之后response.time - request.time反映的是排队等待而非处理器计算耗时。要评估真实处理器耗时应只看初始分析完成$/progressend 通知之后发出的请求。三、Step 2归一化日志Normalize原始日志包含录制机器的绝对路径换一台机器回放必然失败。归一化就是把它们替换成可移植占位符dart sdk-root/pkg/analysis_server/tool/log_player/normalize.dart \ -i path/to/raw_log.json \ -o path/to/normalized_log.json \ -r path/to/project/root \ [-p path/to/.dart_tool/package_config.json]参数说明与normalize.dart源码中ArgParser定义一致参数缩写必填说明--input-i是原始记录日志路径--output-o是归一化日志的输出路径--root-dir-r是录制时打开的项目根目录--package-config-p否显式指定package_config.json缺省时从root-dir下的工作区推断getContextRoots--help-h否打印用法归一化替换规则见normalizeLog实现工作区目录 →{{workspaceFolder-[i]}}从日志中的initialize请求消息提取工作区与 root 路径即addLspWorkspaceReplacementsDart SDK 根 →{{dartSdkRoot}}源码中通过sdkPath推断addReplacementsForPath(path, dartSdkRoot)包根 URI →{{context-i:package-root:[package-name]}}遍历各ContextRoot的package_config逐包注册回放时由LogNormalizer.denormalize反向替换为本地路径。此外normalize.dart在写出归一化日志后还会扫描其中残留的file:///绝对路径并打印出来最多前 5 条帮助你确认没有遗漏的机器相关路径。这一步骤同时具有隐私价值公开发布日志前做归一化可避免泄漏本地文件路径详见 session_log.md 的隐私章节。四、Step 3复现并测量基线延迟归一化之后回放工具会把{{workspaceFolder-0}}、{{rootPath}}、{{rootUri}}映射回本地工作区启动一个真实的 DAS 进程逐条喂入消息。有两种回放方式。4.1 自动化回放与测量measure_request_latency.dart技能目录提供了scripts/measure_request_latency.dart自动把目标请求之前的设置消息全部喂给服务器等待后台分析结束然后对目标请求重复测量墙钟延迟dart sdk-root/pkg/analysis_server/.agents/skills/improve-performance-with-session-log/scripts/measure_request_latency.dart \ -i request_id -w path/to/workspace -r 3 path/to/normalized_log.json参数说明参数缩写默认值说明--request-id-i无必填目标请求 IDStep 1 分析得到的如153--method-m无目标方法名如textDocument/documentColor仅按方法匹配会命中日志中第一次出现该方法的位置通常是一条很快的早期请求而非你要优化的慢交互--workspace-w当前工作目录用于映射{{workspaceFolder-0}}的本地项目目录--repeat-r1重复迭代次数用于观察测量一致性[!IMPORTANT]务必使用--request-id-i传入 Step 1 中分析得到的特定请求 ID。不要只依赖--method——它只匹配该方法的第一次出现往往是一条快速的初始请求而不是你真正想优化的慢交互。从脚本源码可以看到它的完整执行管线加载并反归一化日志用LogNormalizer把工作区目录注册为workspaceFolder-0、rootPath、rootUri然后Log.fromFile(logFile, normalizer.denormalize)读取全部条目定位目标请求遍历日志条目按 ID 精确匹配-i或按方法名子串匹配-m找到目标消息启动服务器通过ServerDriver以--protocollsp--${Driver.serverProtocolOption}解析为ServerProtocol.lsp拉起独立的 analysis server 进程启动所用解释器即Platform.resolvedExecutable这一点对 Step 6 至关重要喂入设置消息把目标请求之前所有发往服务器的消息逐条发送并在initialized之后短暂延迟 100ms 以等待分析器 isolate 启动同时自动应答服务器发起的workspace/configuration请求返回[{}]等待后台分析完成监听$/progress中 token 为ANALYZING的end事件最长等待 30 秒超时则警告并继续循环测量对目标请求执行repeat次每次用Stopwatch计时并输出单次耗时迭代间隔 50ms任一迭代超过 30 秒记为TIMEOUT ( 30s)汇总结果输出Method、Target Request ID、Iterations、Min / Avg / Max Duration并报告最后一次响应的状态OK (size bytes)或ERROR: error关闭服务器调用driver.shutdown()后driver.exit()。4.2 交互式回放server_driver_main.dart需要手工逐步检查、逐条观察消息时使用server_driver_main.dartcd /path/to/project/root dart sdk-root/pkg/analysis_server/tool/log_player/server_driver_main.dart /path/to/normalized_log.json按Enter逐条步进每条发往服务器的消息以打印服务器返回的消息以打印步进到目标请求之前先等后台分析结束$/progress的{kind: end}再按 Enter 发出目标请求这样测到的是热处理器延迟而不是冷启动排队。无参数运行即手工 stdin 模式server_driver_main.dart不带参数启动后可以直接在终端逐行输入/粘贴 JSON-RPC 消息来测试服务器对任意消息的反应源码中sendManualMessages分支。注意交互式终端的单行输入存在字节数限制Linux 约 4096 字节macOS 约 1028 字节超大消息无法通过手动粘贴发送。[!WARNING] 回放工具会绝对改变日志中操作的时间节奏而时序变化可能进一步改变服务器的行为例如竞态、异步任务调度顺序回放结果不能 100% 等同于线上复现。4.3 冷启动排队 vs 热请求延迟务必区分DAS 在收到initialize/initialized后会立即在后台索引并分析工作区此过程由$/progresstitle: Analyzing...跟踪。早期请求会排在后台分析之后因此在记录日志中应检查初始分析完成$/progressend之后的请求来评估真实处理器耗时在交互式回放中应等待后台分析通知结束再发送目标请求在自动化回放中measure_request_latency.dart已经替你等待分析完成并把请求标记为热状态。五、Step 4定位代码并实施优化找出慢方法后按方法名映射到对应的 handler / computerLSP 方法实现位置textDocument/documentColorpkg/analysis_server/lib/src/lsp/handlers/handler_document_color.dart与pkg/analysis_server/lib/src/computer/computer_color.darttextDocument/completionpkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart与pkg/analysis_server/lib/src/services/completion/textDocument/codeActionpkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart以documentColor为例handler_document_color.dart中的DocumentColorHandler继承自SharedMessageHandler其handle流程是校验文档是否为 Dart 文档非 Dart 文档直接返回空列表→ 通过requireResolvedUnit取得已解析的语法单元 → 委托ColorComputercomputer_color.dart计算颜色引用并转成 LSP 的ColorInformation范围 0~1 十进制的 RGBA。优化点往往集中在 computer 层。定位瓶颈时重点排查以下三类共性问题SKILL.md 给出的检查清单不必要的 AST 遍历visitor 是否走过了不可能包含相关节点的 AST 子树过度的元素解析或类型求值是否可以在解析类型、求值常量之前先用轻量级语法检查过滤掉候选重复分配或重复计算map / set 是否可以复用是否可以提前退出循环优化实施位置为pkg/analysis_server或pkg/analyzer例如在 completion 的 candidate 过滤、documentColor 的 color 收集等处。可以借助 profiling 数据或 trace 执行路径来确认热点。六、Step 5重新编译 Dart SDK对pkg/analysis_server/pkg/analyzer的修改要生效需要重新编译 analysis server 快照与 SDK 二进制(cd sdk-root python3 tools/build.py -mrelease create_sdk)编译产物位置macOSxcodebuild/ReleaseARM64/dart-sdk/bin/dart或xcodebuild/ReleaseX64/...Linux / Windowsout/ReleaseX64/dart-sdk/bin/dart七、Step 6用新编译的 SDK 回放并验证[!IMPORTANT]ServerDriver启动服务器进程时使用Platform.resolvedExecutable即运行脚本的那个dart二进制见server_driver.dart与Process.start(_dartExecutable, [...])。因此要测试你刚编译的改动必须用编译出的dart来运行server_driver_main.dart或你的回放脚本而不是系统自带的dart——否则服务器仍然跑在旧实现上。7.1 用新 SDK 运行自动化回放built-sdk-dart \ sdk-root/pkg/analysis_server/.agents/skills/improve-performance-with-session-log/scripts/measure_request_latency.dart \ -i request_id -w path/to/workspace -r 3 /path/to/normalized_log.json7.2 用新 SDK 交互式步进built-sdk-dart \ sdk-root/pkg/analysis_server/tool/log_player/server_driver_main.dart \ /path/to/normalized_log.json如果在外部项目根目录运行且包解析失败显式指定 SDK 内的 package config--packagessdk-root/.dart_tool/package_config.json7.3 验证清单目标请求返回与基线完全一致的有效响应载荷颜色、补全项等请求执行延迟相比基线显著下降服务器输出流中没有未处理异常或错误响应。如果响应内容与基线不一致说明优化改变了行为需要回到 Step 4 重新审视逻辑。八、Step 7运行单元与集成测试用编译出的 Dart 二进制运行相关既有测试built-sdk-dart test sdk-root/pkg/analysis_server/test/src/...同时在合适的测试套件中新增单元测试例如pkg/analysis_server/test/src/computer/...把这次性能优化锁死防止后续回归。性能类改动建议配套两类测试正确性测试验证优化前后对同一输入产生相同结果如 documentColor 的颜色列表、completion 的候选集合回归测试针对被修改的过滤/遍历逻辑覆盖原本会触发慢路径或错误路径的输入。九、相关资源技能文档原文pkg/analysis_server/.agents/skills/improve-performance-with-session-log/SKILL.md日志分析脚本scripts/analyze_session_log.dart自动延迟测量脚本scripts/measure_request_latency.dart回放工具族pkg/analysis_server/tool/log_player/README.md含normalize.dart、server_driver_main.dart、server_driver.dart、log.dart日志采集教程pkg/analysis_server/doc/tutorial/session_log.mdAnalyzer Insights 网页捕获与--session-log启动参数两种方式自动化性能场景pkg/analysis_server/tool/performance/README.md基于LogPlayer的可复现基准【免费下载链接】sdkThe Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.项目地址: https://gitcode.com/gh_mirrors/sdk1/sdk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考