dsh-adb 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +44 -73
- package/README.zh-CN.md +88 -0
- package/lib/adb.js +1 -1
- package/lib/baseline.d.ts +43 -0
- package/lib/baseline.js +143 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +7 -1
- package/lib/tools/crash-report.d.ts +4 -0
- package/lib/tools/crash-report.js +109 -0
- package/lib/tools/perf-baseline.d.ts +4 -0
- package/lib/tools/perf-baseline.js +100 -0
- package/lib/tools/perf.d.ts +16 -0
- package/lib/tools/perf.js +26 -22
- package/package.json +3 -3
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,42 +1,46 @@
|
|
|
1
1
|
# dsh-adb
|
|
2
2
|
|
|
3
|
-
> ADB
|
|
3
|
+
> ADB device & bench operations for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Give DSH agents direct control over Android devices and automotive bench rigs: device discovery, structured logcat, APK install, file pull/push, and performance snapshots. Built for on-vehicle and bench debugging workflows — generic within the domain (no Unity, no vendor protocol lock-in).
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**English** | [简体中文](README.zh-CN.md)
|
|
8
|
+
|
|
9
|
+
## Install
|
|
8
10
|
|
|
9
11
|
```sh
|
|
10
12
|
dsh plugin --profile web add dsh-adb
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
Or install directly from GitHub: `dsh plugin --profile web add github:SamXiaBing/dsh-adb`
|
|
14
16
|
|
|
15
|
-
##
|
|
17
|
+
## Ecosystem
|
|
16
18
|
|
|
17
|
-
- ✅ [npm](https://www.npmjs.com/package/dsh-adb) — `dsh-adb
|
|
18
|
-
- ✅ [awesome-deepseek-harness#87](https://github.com/0xsline/awesome-deepseek-harness/pull/87) —
|
|
19
|
-
- ✅ [awesome-dsh-plugin#85](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/85) —
|
|
20
|
-
-
|
|
19
|
+
- ✅ [npm](https://www.npmjs.com/package/dsh-adb) — `dsh-adb` published (latest: 0.1.5)
|
|
20
|
+
- ✅ [awesome-deepseek-harness#87](https://github.com/0xsline/awesome-deepseek-harness/pull/87) — **merged**
|
|
21
|
+
- ✅ [awesome-dsh-plugin#85](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/85) — **merged**
|
|
22
|
+
- ✅ [awesome-DSH-plugin#29](https://github.com/Alex-Yanggg/awesome-DSH-plugin/pull/29) — **merged**
|
|
21
23
|
|
|
22
|
-
Topics
|
|
24
|
+
Topics: `dsh-plugin` `dsh` `adb` `android` `automotive` `bench`
|
|
23
25
|
|
|
24
|
-
##
|
|
26
|
+
## Tools
|
|
25
27
|
|
|
26
|
-
|
|
|
28
|
+
| Tool | Description |
|
|
27
29
|
| --- | --- |
|
|
28
|
-
| `adb_devices` |
|
|
29
|
-
| `adb_connect` / `adb_disconnect` |
|
|
30
|
-
| `adb_logcat` |
|
|
31
|
-
| `adb_install` |
|
|
32
|
-
| `adb_file` | pull / push / ls / rm
|
|
33
|
-
| `adb_perf_snapshot` | `dumpsys meminfo / gfxinfo / battery`
|
|
30
|
+
| `adb_devices` | List devices (serial/state/product/model); run first to discover serials |
|
|
31
|
+
| `adb_connect` / `adb_disconnect` | Wireless bench connection (host:port, default 5555) |
|
|
32
|
+
| `adb_logcat` | Filtered read (tag/level/keyword/time-window/tail); `run_in_background` streams continuously as a job — read deltas with `job_output`, stop with `job_kill` |
|
|
33
|
+
| `adb_install` | Install APKs (`-r`/`-d`/`-g` options); validates the local file exists |
|
|
34
|
+
| `adb_file` | pull / push / ls / rm, per-device isolation |
|
|
35
|
+
| `adb_perf_snapshot` | Structured `dumpsys meminfo / gfxinfo / battery` snapshots (PSS, frame percentiles, jank rate, battery) |
|
|
36
|
+
| `adb_perf_baseline` | Perf regression: save a snapshot as a baseline (label/tags), compare current state and get a numeric diff (PSS, janky %, percentiles), list/delete baselines (stored locally under `baselineDir`) |
|
|
37
|
+
| `adb_crash_report` | One-call crash scene: parsed logcat crash buffer + dropbox excerpt + process state + memory summary |
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
Errors are structured `AdbError` with stable codes: `ADB_NOT_FOUND`, `ADB_UNAVAILABLE`, `DEVICE_NOT_FOUND`, `NO_DEVICES`, `CONNECT_FAILED`, `INSTALL_FAILED`, `ADB_EXIT_<code>`, etc.
|
|
36
40
|
|
|
37
|
-
##
|
|
41
|
+
## Configuration
|
|
38
42
|
|
|
39
|
-
`cordis.patch.yml`
|
|
43
|
+
Set the `config` block in `cordis.patch.yml` (or a profile patch):
|
|
40
44
|
|
|
41
45
|
```yaml
|
|
42
46
|
- id: dsh-adb
|
|
@@ -47,68 +51,35 @@ Topics:`dsh-plugin` `dsh` `adb` `android` `automotive` `bench`
|
|
|
47
51
|
timeoutMs: 30000
|
|
48
52
|
```
|
|
49
53
|
|
|
50
|
-
|
|
|
54
|
+
| Key | Description | Default |
|
|
51
55
|
| --- | --- | --- |
|
|
52
|
-
| `adbPath` | adb
|
|
53
|
-
| `defaultSerial` |
|
|
54
|
-
| `timeoutMs` |
|
|
56
|
+
| `adbPath` | Absolute path to the adb executable | Auto-detect PATH / ANDROID_HOME / ANDROID_SDK_ROOT/platform-tools |
|
|
57
|
+
| `defaultSerial` | Default target device serial | none |
|
|
58
|
+
| `timeoutMs` | Per-command timeout | 30000 |
|
|
59
|
+
| `baselineDir` | Directory for `adb_perf_baseline` storage | `~/.dsh/storages/dsh-adb` |
|
|
55
60
|
|
|
56
|
-
##
|
|
61
|
+
## Development
|
|
57
62
|
|
|
58
63
|
```sh
|
|
59
|
-
npm install #
|
|
64
|
+
npm install # add --include=dev when NODE_ENV=production
|
|
60
65
|
npm run build # tsc → lib/
|
|
61
|
-
npm test #
|
|
62
|
-
npm pack --dry-run #
|
|
66
|
+
npm test # parser/classification unit tests (node --test)
|
|
67
|
+
npm pack --dry-run # verify publish contents (lib/ + cordis.patch.yml)
|
|
63
68
|
```
|
|
64
69
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
## 真机/台架冒烟(2026-08-14,SA_DIREWOLF_IVI 台架,Android 13)
|
|
70
|
+
## Testing & Verification
|
|
68
71
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
| --- | --- |
|
|
73
|
-
| `adb_devices` | ✅ 返回 `f20c9b04 device msmnile_gvmq_for_arm64` |
|
|
74
|
-
| `adb_connect` / `adb_disconnect` | ✅ 不可达端点正确返回 CONNECT_FAILED;disconnect 正常 |
|
|
75
|
-
| `adb_logcat`(前向) | ✅ level=E tail=5 返回真实错误日志 |
|
|
76
|
-
| `adb_logcat`(后台采集) | ✅ job_output 读回 43065 行/7MB 流,job_kill 正常 |
|
|
77
|
-
| `adb_install` | ✅ pull 真机 apk → `install -r` → "Performing Streamed Install / Success" |
|
|
78
|
-
| `adb_file` | ✅ ls / push / pull 往返字节一致 |
|
|
79
|
-
| `adb_perf_snapshot` | ✅ meminfo(SystemUI PSS 131MB)+ gfxinfo(232 帧/60.78% 卡顿)解析正确;battery 空(台架无电池服务) |
|
|
72
|
+
- Principle: **ship only what is tested** — every committed feature has unit and/or end-to-end coverage.
|
|
73
|
+
- Verified on: Android 13 automotive bench + Android 13 phone.
|
|
74
|
+
- Per-version changes and verification: [CHANGELOG.md](CHANGELOG.md); test methodology and coverage: [docs/TESTING.md](docs/TESTING.md) (Chinese).
|
|
80
75
|
|
|
81
|
-
|
|
76
|
+
## Project Docs (bilingual; for AI agents & contributors)
|
|
82
77
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
## 测试覆盖率矩阵(v0.1.3)
|
|
89
|
-
|
|
90
|
-
| 功能 | 单元测试 | 端到端实测(台架) |
|
|
91
|
-
| --- | --- | --- |
|
|
92
|
-
| `adb_devices` | ✅ 解析器 | ✅ |
|
|
93
|
-
| `adb_connect`(失败路径) | ✅ 分类器 | ✅ CONNECT_FAILED |
|
|
94
|
-
| `adb_connect`(成功路径) | - | ⚠️ 环境受限(台架无线不可达),需车内/可路由网络 |
|
|
95
|
-
| `adb_disconnect` | - | ✅ |
|
|
96
|
-
| `adb_logcat` 前向(level/tail) | ✅ 解析器+过滤 | ✅ |
|
|
97
|
-
| `adb_logcat` tag/keyword 过滤 | ✅ 过滤助手 | ✅(tag+keyword 均正向命中) |
|
|
98
|
-
| `adb_logcat` since/until | ⚠️ 仅逻辑(时间串字典序) | 未单独 E2E(同过滤链) |
|
|
99
|
-
| `adb_logcat` 后台采集 | - | ✅ job_output 读回 43065 行/7MB,job_kill 正常 |
|
|
100
|
-
| `adb_install` 成功 | - | ✅ install -r Success |
|
|
101
|
-
| `adb_install` 失败(LOCAL_FILE_NOT_FOUND/INSTALL_FAILED) | ✅ 分类器 | ✅ 伪 apk → INSTALL_PARSE_FAILED_NOT_APK |
|
|
102
|
-
| `adb_file` ls / ls -lR | - | ✅ |
|
|
103
|
-
| `adb_file` push/pull 往返 | - | ✅ 字节一致 |
|
|
104
|
-
| `adb_file` rm | - | ✅ 删除生效 |
|
|
105
|
-
| `adb_perf_snapshot` meminfo | ✅ 解析器 | ✅ SystemUI PSS/RSS/Heap |
|
|
106
|
-
| `adb_perf_snapshot` gfxinfo | ✅ 解析器 | ✅ 帧率/卡顿/百分位 |
|
|
107
|
-
| `adb_perf_snapshot` battery | ✅ 解析器 | ⚠️ 台架无电池服务返回空(行为正确) |
|
|
108
|
-
| 配置 adbPath/defaultSerial | - | ✅ 覆盖生效 |
|
|
109
|
-
| 错误码 ADB_NOT_FOUND/DEVICE_NOT_FOUND/NO_DEVICES/CONNECT_FAILED/INSTALL_FAILED | ✅ 分类器 | ✅ 实测/确定性验证 |
|
|
110
|
-
|
|
111
|
-
**原则:提交即测。** 未能在本台架覆盖的路径(正向无线连接、battery 实数据)已明确标注,不视为「已测通过」。
|
|
78
|
+
- [docs/AGENTS.md](docs/AGENTS.md) / [docs/AGENTS.en.md](docs/AGENTS.en.md) — read first: purpose, rules, commands, environment facts, doc map
|
|
79
|
+
- [docs/REQUIREMENTS.md](docs/REQUIREMENTS.md) / [docs/REQUIREMENTS.en.md](docs/REQUIREMENTS.en.md) — purpose / scope / non-goals / acceptance criteria
|
|
80
|
+
- [docs/TESTING.md](docs/TESTING.md) / [docs/TESTING.en.md](docs/TESTING.en.md) — testing philosophy, three test layers, E2E steps, regression checklist
|
|
81
|
+
- [docs/DEVELOPMENT-LOG.md](docs/DEVELOPMENT-LOG.md) / [docs/DEVELOPMENT-LOG.en.md](docs/DEVELOPMENT-LOG.en.md) — timeline, fixed-bug lessons, environment & ecosystem notes
|
|
82
|
+
- [PLAN.md](PLAN.md) / [PLAN.en.md](PLAN.en.md) — milestones & backlog
|
|
112
83
|
|
|
113
84
|
## License
|
|
114
85
|
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# dsh-adb
|
|
2
|
+
|
|
3
|
+
> ADB 设备·台架运维工具集 for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
|
|
4
|
+
|
|
5
|
+
[English](README.md) | 简体中文
|
|
6
|
+
|
|
7
|
+
让 DSH agent 直接操作 Android 设备 / 车机台架:设备发现、结构化 logcat、apk 安装、文件 pull/push、性能快照。面向实车与台架联调场景,业务内通用(不限 Unity、不限具体车机协议)。
|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
dsh plugin --profile web add dsh-adb
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
或从 GitHub 直装:`dsh plugin --profile web add github:SamXiaBing/dsh-adb`
|
|
16
|
+
|
|
17
|
+
## 生态收录
|
|
18
|
+
|
|
19
|
+
- ✅ [npm](https://www.npmjs.com/package/dsh-adb) — `dsh-adb` 已发布(latest: 0.1.5)
|
|
20
|
+
- ✅ [awesome-deepseek-harness#87](https://github.com/0xsline/awesome-deepseek-harness/pull/87) — **已合并**
|
|
21
|
+
- ✅ [awesome-dsh-plugin#85](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/85) — **已合并**
|
|
22
|
+
- ✅ [awesome-DSH-plugin#29](https://github.com/Alex-Yanggg/awesome-DSH-plugin/pull/29) — **已合并**
|
|
23
|
+
|
|
24
|
+
Topics:`dsh-plugin` `dsh` `adb` `android` `automotive` `bench`
|
|
25
|
+
|
|
26
|
+
## 工具
|
|
27
|
+
|
|
28
|
+
| 工具 | 说明 |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `adb_devices` | 列出设备(serial/state/product/model),先发现再操作 |
|
|
31
|
+
| `adb_connect` / `adb_disconnect` | 无线台架连接(host:port,默认 5555) |
|
|
32
|
+
| `adb_logcat` | 过滤读取(tag/级别/关键字/时间窗/tail);`run_in_background` 后台连续采集,job_output 读增量、job_kill 停止 |
|
|
33
|
+
| `adb_install` | 安装 apk(-r/-d/-g 选项),校验本地文件存在 |
|
|
34
|
+
| `adb_file` | pull / push / ls / rm,设备隔离 |
|
|
35
|
+
| `adb_perf_snapshot` | `dumpsys meminfo / gfxinfo / battery` 结构化快照(PSS/帧率百分位/卡顿率/电量) |
|
|
36
|
+
| `adb_perf_baseline` | 性能回归:快照存基线(label/tags)、与当前状态数值对比(PSS/卡顿率/百分位)、list/delete(本地存储,`baselineDir`) |
|
|
37
|
+
| `adb_crash_report` | 崩溃现场一键采集:crash buffer 解析 + dropbox 摘录 + 进程状态 + 内存摘要 |
|
|
38
|
+
|
|
39
|
+
错误码:`ADB_NOT_FOUND`、`ADB_UNAVAILABLE`、`DEVICE_NOT_FOUND`、`NO_DEVICES`、`CONNECT_FAILED`、`INSTALL_FAILED`、`ADB_EXIT_<code>` 等,均为结构化 `AdbError`。
|
|
40
|
+
|
|
41
|
+
## 配置
|
|
42
|
+
|
|
43
|
+
`cordis.patch.yml` 的 `config` 块(或 profile patch):
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
- id: dsh-adb
|
|
47
|
+
name: dsh-adb
|
|
48
|
+
config:
|
|
49
|
+
adbPath: C:\Users\me\AppData\Local\Android\Sdk\platform-tools\adb.exe
|
|
50
|
+
defaultSerial: emulator-5554
|
|
51
|
+
timeoutMs: 30000
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
| 键 | 说明 | 默认 |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| `adbPath` | adb 可执行文件绝对路径 | 自动探测 PATH / ANDROID_HOME / ANDROID_SDK_ROOT/platform-tools |
|
|
57
|
+
| `defaultSerial` | 默认设备 serial | 无 |
|
|
58
|
+
| `timeoutMs` | 命令超时 | 30000 |
|
|
59
|
+
| `baselineDir` | `adb_perf_baseline` 基线存储目录 | `~/.dsh/storages/dsh-adb` |
|
|
60
|
+
|
|
61
|
+
## 开发
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
npm install # 本机 NODE_ENV=production 时加 --include=dev
|
|
65
|
+
npm run build # tsc → lib/
|
|
66
|
+
npm test # 解析器/错误分类单测(node --test)
|
|
67
|
+
npm pack --dry-run # 校验发布包内容(lib/ + cordis.patch.yml)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
注意:本机若设了 `NODE_ENV=production`,npm 会跳过 devDependencies,安装时用 `npm install --include=dev`。
|
|
71
|
+
|
|
72
|
+
## 测试与验证
|
|
73
|
+
|
|
74
|
+
- 原则:**提交即测** —— 全部已提交功能均有实测覆盖(单元 + headless 端到端 + 车机台架/真机)。
|
|
75
|
+
- 验证设备:Android 13 车机台架 + Android 13 真机。
|
|
76
|
+
- 版本化变更与每版验证记录见 [CHANGELOG.md](CHANGELOG.md);测试方法与覆盖现状见 [docs/TESTING.md](docs/TESTING.md)。
|
|
77
|
+
|
|
78
|
+
## 项目文档(双语,供 AI 对话/协作者参考)
|
|
79
|
+
|
|
80
|
+
- [docs/AGENTS.md](docs/AGENTS.md) / [docs/AGENTS.en.md](docs/AGENTS.en.md) — 进项目先读:定位、铁律、命令、环境事实、文档地图
|
|
81
|
+
- [docs/REQUIREMENTS.md](docs/REQUIREMENTS.md) / [docs/REQUIREMENTS.en.md](docs/REQUIREMENTS.en.md) — 目的/范围/非目标/验收标准
|
|
82
|
+
- [docs/TESTING.md](docs/TESTING.md) / [docs/TESTING.en.md](docs/TESTING.en.md) — 测试哲学(提交即测)、三层测试方法、E2E 步骤、回归清单
|
|
83
|
+
- [docs/DEVELOPMENT-LOG.md](docs/DEVELOPMENT-LOG.md) / [docs/DEVELOPMENT-LOG.en.md](docs/DEVELOPMENT-LOG.en.md) — 进度时间线、4 个已修复 bug 教训、环境/生态经验
|
|
84
|
+
- [PLAN.md](PLAN.md) / [PLAN.en.md](PLAN.en.md) — 里程碑与待办
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
package/lib/adb.js
CHANGED
|
@@ -117,7 +117,7 @@ export async function runAdb(ctx, cfg, argv, options = {}) {
|
|
|
117
117
|
export function classifyFailure(result) {
|
|
118
118
|
const stderr = result.stderr;
|
|
119
119
|
const stdout = result.stdout;
|
|
120
|
-
if (/(?:error:\s*)?device ['"]?[^'\r\n]*['"]? not found/i.test(stderr)) {
|
|
120
|
+
if (/(?:error:\s*)?device ['"]?[^'\r\n]*['"]? not found|waiting for device/i.test(stderr)) {
|
|
121
121
|
return new AdbError('DEVICE_NOT_FOUND', excerpt(stderr));
|
|
122
122
|
}
|
|
123
123
|
if (/no devices\/emulators found/i.test(stderr)) {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { PerfSnapshot } from './tools/perf.js';
|
|
2
|
+
/**
|
|
3
|
+
* Perf baseline store + numeric diff computation. Storage is a single JSON
|
|
4
|
+
* file under a configurable directory; diff logic is pure and unit-tested.
|
|
5
|
+
*/
|
|
6
|
+
export interface BaselineMeta {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
createdAt: string;
|
|
10
|
+
device: string;
|
|
11
|
+
package: string;
|
|
12
|
+
tags?: string[];
|
|
13
|
+
}
|
|
14
|
+
export interface BaselineEntry extends BaselineMeta {
|
|
15
|
+
snapshot: PerfSnapshot;
|
|
16
|
+
}
|
|
17
|
+
export interface BaselineStoreData {
|
|
18
|
+
version: 1;
|
|
19
|
+
baselines: BaselineEntry[];
|
|
20
|
+
}
|
|
21
|
+
export interface BaselineStore {
|
|
22
|
+
list(): BaselineEntry[];
|
|
23
|
+
get(id: string): BaselineEntry | undefined;
|
|
24
|
+
latest(device: string, pkg: string): BaselineEntry | undefined;
|
|
25
|
+
save(entry: Omit<BaselineEntry, 'id' | 'createdAt'>): BaselineEntry;
|
|
26
|
+
delete(id: string): boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare const DEFAULT_BASELINE_DIR: string;
|
|
29
|
+
export declare function loadBaselines(dir: string): BaselineStoreData;
|
|
30
|
+
export declare function saveBaselines(dir: string, data: BaselineStoreData): void;
|
|
31
|
+
export declare function createStore(dir: string): BaselineStore;
|
|
32
|
+
/** Human-readable names for diff fields. */
|
|
33
|
+
export declare const FIELD_LABELS: Record<string, string>;
|
|
34
|
+
export interface FieldDiff {
|
|
35
|
+
field: string;
|
|
36
|
+
label: string;
|
|
37
|
+
from?: number;
|
|
38
|
+
to?: number;
|
|
39
|
+
delta?: number;
|
|
40
|
+
deltaPercent?: number;
|
|
41
|
+
}
|
|
42
|
+
/** Numeric diff between two snapshots; only fields present in both are reported. */
|
|
43
|
+
export declare function diffSnapshots(from: PerfSnapshot, to: PerfSnapshot): FieldDiff[];
|
package/lib/baseline.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
export const DEFAULT_BASELINE_DIR = joinDefault();
|
|
5
|
+
function joinDefault() {
|
|
6
|
+
return `${homedir().replace(/\\/g, '/')}/.dsh/storages/dsh-adb`;
|
|
7
|
+
}
|
|
8
|
+
function filePath(dir) {
|
|
9
|
+
return `${dir.replace(/\\/g, '/')}/baselines.json`;
|
|
10
|
+
}
|
|
11
|
+
function emptyData() {
|
|
12
|
+
return { version: 1, baselines: [] };
|
|
13
|
+
}
|
|
14
|
+
export function loadBaselines(dir) {
|
|
15
|
+
const file = filePath(dir);
|
|
16
|
+
if (!existsSync(file))
|
|
17
|
+
return emptyData();
|
|
18
|
+
let raw;
|
|
19
|
+
try {
|
|
20
|
+
raw = readFileSync(file, 'utf8');
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
throw new Error(`baseline store unreadable at ${file}: ${describe(error)}`);
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const data = JSON.parse(raw);
|
|
27
|
+
if (data?.version !== 1 || !Array.isArray(data.baselines)) {
|
|
28
|
+
throw new Error('unexpected shape');
|
|
29
|
+
}
|
|
30
|
+
return data;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw new Error(`baseline store corrupted at ${file}: not valid version-1 JSON`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function saveBaselines(dir, data) {
|
|
37
|
+
const file = filePath(dir);
|
|
38
|
+
try {
|
|
39
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
40
|
+
writeFileSync(file, JSON.stringify(data, null, 2), 'utf8');
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
throw new Error(`baseline store unwritable at ${file}: ${describe(error)}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function createStore(dir) {
|
|
47
|
+
return {
|
|
48
|
+
list() {
|
|
49
|
+
return loadBaselines(dir).baselines;
|
|
50
|
+
},
|
|
51
|
+
get(id) {
|
|
52
|
+
return loadBaselines(dir).baselines.find((entry) => entry.id === id);
|
|
53
|
+
},
|
|
54
|
+
latest(device, pkg) {
|
|
55
|
+
const data = loadBaselines(dir);
|
|
56
|
+
const matches = data.baselines.filter((entry) => entry.device === device && entry.package === pkg);
|
|
57
|
+
if (matches.length === 0)
|
|
58
|
+
return undefined;
|
|
59
|
+
return matches.reduce((a, b) => (a.createdAt > b.createdAt ? a : b));
|
|
60
|
+
},
|
|
61
|
+
save(entry) {
|
|
62
|
+
const data = loadBaselines(dir);
|
|
63
|
+
const stored = {
|
|
64
|
+
...entry,
|
|
65
|
+
id: `bl-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
66
|
+
createdAt: new Date().toISOString(),
|
|
67
|
+
};
|
|
68
|
+
data.baselines.push(stored);
|
|
69
|
+
saveBaselines(dir, data);
|
|
70
|
+
return stored;
|
|
71
|
+
},
|
|
72
|
+
delete(id) {
|
|
73
|
+
const data = loadBaselines(dir);
|
|
74
|
+
const before = data.baselines.length;
|
|
75
|
+
data.baselines = data.baselines.filter((entry) => entry.id !== id);
|
|
76
|
+
if (data.baselines.length === before)
|
|
77
|
+
return false;
|
|
78
|
+
saveBaselines(dir, data);
|
|
79
|
+
return true;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** Human-readable names for diff fields. */
|
|
84
|
+
export const FIELD_LABELS = {
|
|
85
|
+
'meminfo.totalPssKb': 'Total PSS (KB)',
|
|
86
|
+
'meminfo.totalRssKb': 'Total RSS (KB)',
|
|
87
|
+
'meminfo.javaHeapKb': 'Java Heap (KB)',
|
|
88
|
+
'meminfo.nativeHeapKb': 'Native Heap (KB)',
|
|
89
|
+
'meminfo.graphicsKb': 'Graphics (KB)',
|
|
90
|
+
'gfxinfo.totalFrames': 'Total frames',
|
|
91
|
+
'gfxinfo.jankyFrames': 'Janky frames',
|
|
92
|
+
'gfxinfo.jankyPercent': 'Janky %',
|
|
93
|
+
'gfxinfo.percentile50Ms': '50th percentile (ms)',
|
|
94
|
+
'gfxinfo.percentile90Ms': '90th percentile (ms)',
|
|
95
|
+
'gfxinfo.percentile95Ms': '95th percentile (ms)',
|
|
96
|
+
'gfxinfo.percentile99Ms': '99th percentile (ms)',
|
|
97
|
+
'gfxinfo.missedVsync': 'Missed vsync',
|
|
98
|
+
'battery.levelPercent': 'Battery level (%)',
|
|
99
|
+
'battery.temperatureC': 'Temperature (°C)',
|
|
100
|
+
};
|
|
101
|
+
const DIFF_FIELDS = [
|
|
102
|
+
['meminfo', 'totalPssKb'],
|
|
103
|
+
['meminfo', 'totalRssKb'],
|
|
104
|
+
['meminfo', 'javaHeapKb'],
|
|
105
|
+
['meminfo', 'nativeHeapKb'],
|
|
106
|
+
['meminfo', 'graphicsKb'],
|
|
107
|
+
['gfxinfo', 'totalFrames'],
|
|
108
|
+
['gfxinfo', 'jankyFrames'],
|
|
109
|
+
['gfxinfo', 'jankyPercent'],
|
|
110
|
+
['gfxinfo', 'percentile50Ms'],
|
|
111
|
+
['gfxinfo', 'percentile90Ms'],
|
|
112
|
+
['gfxinfo', 'percentile95Ms'],
|
|
113
|
+
['gfxinfo', 'percentile99Ms'],
|
|
114
|
+
['gfxinfo', 'missedVsync'],
|
|
115
|
+
['battery', 'levelPercent'],
|
|
116
|
+
['battery', 'temperatureC'],
|
|
117
|
+
];
|
|
118
|
+
/** Numeric diff between two snapshots; only fields present in both are reported. */
|
|
119
|
+
export function diffSnapshots(from, to) {
|
|
120
|
+
const diffs = [];
|
|
121
|
+
for (const [group, field] of DIFF_FIELDS) {
|
|
122
|
+
const fromGroup = from;
|
|
123
|
+
const toGroup = to;
|
|
124
|
+
const fromValue = fromGroup[group]?.[field];
|
|
125
|
+
const toValue = toGroup[group]?.[field];
|
|
126
|
+
if (fromValue === undefined || toValue === undefined)
|
|
127
|
+
continue;
|
|
128
|
+
const key = `${group}.${field}`;
|
|
129
|
+
const delta = toValue - fromValue;
|
|
130
|
+
diffs.push({
|
|
131
|
+
field: key,
|
|
132
|
+
label: FIELD_LABELS[key] ?? key,
|
|
133
|
+
from: fromValue,
|
|
134
|
+
to: toValue,
|
|
135
|
+
delta,
|
|
136
|
+
deltaPercent: fromValue === 0 ? undefined : (delta / fromValue) * 100,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return diffs;
|
|
140
|
+
}
|
|
141
|
+
function describe(error) {
|
|
142
|
+
return error instanceof Error ? error.message : String(error);
|
|
143
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface Config {
|
|
|
11
11
|
defaultSerial?: string;
|
|
12
12
|
/** Per-command timeout in milliseconds. */
|
|
13
13
|
timeoutMs?: number;
|
|
14
|
+
/** Directory for adb_perf_baseline storage. */
|
|
15
|
+
baselineDir?: string;
|
|
14
16
|
}
|
|
15
17
|
export declare const Config: Schema<Config>;
|
|
16
18
|
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import Schema from 'schemastery';
|
|
2
|
+
import { DEFAULT_BASELINE_DIR } from './baseline.js';
|
|
2
3
|
import { registerDeviceTools } from './tools/devices.js';
|
|
4
|
+
import { registerCrashReportTool } from './tools/crash-report.js';
|
|
3
5
|
import { registerFileTool } from './tools/file.js';
|
|
4
6
|
import { registerInstallTool } from './tools/install.js';
|
|
5
7
|
import { registerLogcatTool } from './tools/logcat.js';
|
|
6
8
|
import { registerPerfTool } from './tools/perf.js';
|
|
9
|
+
import { registerPerfBaselineTool } from './tools/perf-baseline.js';
|
|
7
10
|
export const name = 'dsh-adb';
|
|
8
11
|
/** The tool registry is a hard dependency: every tool registers through it. */
|
|
9
12
|
export const inject = ['tools'];
|
|
@@ -11,6 +14,7 @@ export const Config = Schema.object({
|
|
|
11
14
|
adbPath: Schema.string().description('adb 可执行文件绝对路径;缺省自动探测 PATH / ANDROID_HOME / ANDROID_SDK_ROOT / platform-tools'),
|
|
12
15
|
defaultSerial: Schema.string().description('默认目标设备 serial'),
|
|
13
16
|
timeoutMs: Schema.number().default(30000).description('adb 命令超时(毫秒)'),
|
|
17
|
+
baselineDir: Schema.string().description('adb_perf_baseline 基线存储目录;缺省 ~/.dsh/storages/dsh-adb'),
|
|
14
18
|
});
|
|
15
19
|
export function apply(ctx, config) {
|
|
16
20
|
const cfg = config;
|
|
@@ -19,5 +23,7 @@ export function apply(ctx, config) {
|
|
|
19
23
|
registerFileTool(ctx, cfg);
|
|
20
24
|
registerLogcatTool(ctx, cfg);
|
|
21
25
|
registerPerfTool(ctx, cfg);
|
|
22
|
-
ctx
|
|
26
|
+
registerPerfBaselineTool(ctx, cfg, config.baselineDir ?? DEFAULT_BASELINE_DIR);
|
|
27
|
+
registerCrashReportTool(ctx, cfg);
|
|
28
|
+
ctx.logger.info('[dsh-adb] loaded: adb_devices / adb_connect / adb_disconnect / adb_logcat / adb_install / adb_file / adb_perf_snapshot / adb_perf_baseline / adb_crash_report');
|
|
23
29
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { AdbError, classifyFailure, jsonOutput, runAdb } from '../adb.js';
|
|
2
|
+
import { parseLogcat } from '../parsers/logcat.js';
|
|
3
|
+
import { capturePerfSnapshot } from './perf.js';
|
|
4
|
+
const DEFAULT_INCLUDE = ['crash-buffer', 'dropbox', 'processes'];
|
|
5
|
+
function excerpt(text, maxLines) {
|
|
6
|
+
const lines = text.split(/\r?\n/).filter((line) => line !== '');
|
|
7
|
+
return { lines: lines.length, excerpt: lines.slice(-maxLines).join('\n') };
|
|
8
|
+
}
|
|
9
|
+
/** adb_crash_report: collect a crash scene from an Android device. */
|
|
10
|
+
export function registerCrashReportTool(ctx, cfg) {
|
|
11
|
+
ctx.tools.register({
|
|
12
|
+
name: 'adb_crash_report',
|
|
13
|
+
description: 'Collect a crash scene from an Android device into one structured report: the logcat crash buffer (parsed entries), dropbox crash entries excerpt, process state excerpt, and (when a package is given) memory summary. Use it to gather crash context in one call instead of hunting through buffers.',
|
|
14
|
+
parameters: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
additionalProperties: false,
|
|
17
|
+
properties: {
|
|
18
|
+
package: { type: 'string', description: 'App package id; enables the meminfo section and helps narrow the crash buffer.' },
|
|
19
|
+
serial: { type: 'string', description: 'Target device serial; defaults to the plugin defaultSerial.' },
|
|
20
|
+
since: { type: 'string', description: 'Only crash-buffer entries at/after this time, e.g. "08-15 10:00:00.000".' },
|
|
21
|
+
include: {
|
|
22
|
+
type: 'array',
|
|
23
|
+
items: { type: 'string', enum: ['crash-buffer', 'dropbox', 'processes', 'meminfo'] },
|
|
24
|
+
description: 'Which sections to collect; defaults to crash-buffer, dropbox, processes (plus meminfo when package is given).',
|
|
25
|
+
},
|
|
26
|
+
tail: { type: 'integer', description: 'Cap for parsed crash-buffer entries and excerpt lines; defaults to 100.' },
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
output: jsonOutput(),
|
|
30
|
+
async execute(args, exec) {
|
|
31
|
+
const { since, tail } = args;
|
|
32
|
+
const tailCount = tail ?? 100;
|
|
33
|
+
const include = args.include ?? [
|
|
34
|
+
...DEFAULT_INCLUDE,
|
|
35
|
+
...(args.package !== undefined ? (['meminfo']) : []),
|
|
36
|
+
];
|
|
37
|
+
const result = { collectedAt: new Date().toISOString() };
|
|
38
|
+
if (args.package !== undefined)
|
|
39
|
+
result.package = args.package;
|
|
40
|
+
if (include.includes('crash-buffer')) {
|
|
41
|
+
const output = await runAdb(ctx, cfg, ['logcat', '-b', 'crash', '-v', 'threadtime', '-d'], {
|
|
42
|
+
signal: exec.signal,
|
|
43
|
+
serial: args.serial,
|
|
44
|
+
maxBytes: 8 * 1024 * 1024,
|
|
45
|
+
});
|
|
46
|
+
if (output.exitCode !== 0)
|
|
47
|
+
throw classifyFailure(output);
|
|
48
|
+
let entries = parseLogcat(output.stdout);
|
|
49
|
+
if (since !== undefined)
|
|
50
|
+
entries = entries.filter((entry) => entry.time >= since);
|
|
51
|
+
const truncated = entries.length > tailCount;
|
|
52
|
+
result.crashBuffer = {
|
|
53
|
+
total: entries.length,
|
|
54
|
+
truncated,
|
|
55
|
+
entries: (truncated ? entries.slice(-tailCount) : entries).map((entry) => ({
|
|
56
|
+
time: entry.time,
|
|
57
|
+
pid: entry.pid,
|
|
58
|
+
tid: entry.tid,
|
|
59
|
+
level: entry.level,
|
|
60
|
+
tag: entry.tag,
|
|
61
|
+
message: entry.message,
|
|
62
|
+
})),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (include.includes('dropbox')) {
|
|
66
|
+
const output = await runAdb(ctx, cfg, ['shell', 'dumpsys', 'dropbox', '--print'], {
|
|
67
|
+
signal: exec.signal,
|
|
68
|
+
serial: args.serial,
|
|
69
|
+
maxBytes: 4 * 1024 * 1024,
|
|
70
|
+
});
|
|
71
|
+
if (output.exitCode !== 0)
|
|
72
|
+
throw classifyFailure(output);
|
|
73
|
+
const capped = excerpt(output.stdout, tailCount);
|
|
74
|
+
result.dropbox = {
|
|
75
|
+
lines: capped.lines,
|
|
76
|
+
truncated: capped.lines > tailCount,
|
|
77
|
+
excerpt: capped.excerpt,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (include.includes('processes')) {
|
|
81
|
+
const output = await runAdb(ctx, cfg, ['shell', 'dumpsys', 'activity', 'processes'], {
|
|
82
|
+
signal: exec.signal,
|
|
83
|
+
serial: args.serial,
|
|
84
|
+
maxBytes: 2 * 1024 * 1024,
|
|
85
|
+
});
|
|
86
|
+
if (output.exitCode !== 0)
|
|
87
|
+
throw classifyFailure(output);
|
|
88
|
+
const capped = excerpt(output.stdout, tailCount);
|
|
89
|
+
result.processes = {
|
|
90
|
+
lines: capped.lines,
|
|
91
|
+
truncated: capped.lines > tailCount,
|
|
92
|
+
excerpt: capped.excerpt,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (include.includes('meminfo')) {
|
|
96
|
+
if (args.package === undefined) {
|
|
97
|
+
throw new AdbError('ARGS_INVALID', 'meminfo section requires a package');
|
|
98
|
+
}
|
|
99
|
+
const snapshot = await capturePerfSnapshot(ctx, cfg, exec, {
|
|
100
|
+
package: args.package,
|
|
101
|
+
serial: args.serial,
|
|
102
|
+
metrics: ['meminfo'],
|
|
103
|
+
});
|
|
104
|
+
result.meminfo = snapshot.meminfo;
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { type AdbConfig } from '../adb.js';
|
|
3
|
+
/** adb_perf_baseline: save/compare/list/delete perf snapshots as baselines. */
|
|
4
|
+
export declare function registerPerfBaselineTool(ctx: Context, cfg: AdbConfig, baselineDir: string): void;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { AdbError, jsonOutput } from '../adb.js';
|
|
2
|
+
import { createStore, diffSnapshots } from '../baseline.js';
|
|
3
|
+
import { capturePerfSnapshot } from './perf.js';
|
|
4
|
+
function publicMeta(entry) {
|
|
5
|
+
return {
|
|
6
|
+
id: entry.id,
|
|
7
|
+
label: entry.label,
|
|
8
|
+
createdAt: entry.createdAt,
|
|
9
|
+
device: entry.device,
|
|
10
|
+
package: entry.package,
|
|
11
|
+
tags: entry.tags ?? [],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** adb_perf_baseline: save/compare/list/delete perf snapshots as baselines. */
|
|
15
|
+
export function registerPerfBaselineTool(ctx, cfg, baselineDir) {
|
|
16
|
+
ctx.tools.register({
|
|
17
|
+
name: 'adb_perf_baseline',
|
|
18
|
+
description: 'Manage performance baselines for regression checks: save a snapshot as a baseline (label + optional tags), compare the current device state against a baseline and get a numeric diff report (PSS, janky %, frame percentiles), list stored baselines, or delete one. Baselines are stored locally on the host under the configured baselineDir.',
|
|
19
|
+
parameters: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
additionalProperties: false,
|
|
22
|
+
required: ['command'],
|
|
23
|
+
properties: {
|
|
24
|
+
command: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
enum: ['save', 'compare', 'list', 'delete'],
|
|
27
|
+
description: 'save: capture + store a baseline; compare: diff current state against a baseline (id, or latest for device+package); list: stored baselines; delete: remove one by id.',
|
|
28
|
+
},
|
|
29
|
+
package: { type: 'string', description: 'App package id; required for save and compare.' },
|
|
30
|
+
serial: { type: 'string', description: 'Target device serial; defaults to the plugin defaultSerial.' },
|
|
31
|
+
metrics: {
|
|
32
|
+
type: 'array',
|
|
33
|
+
items: { type: 'string', enum: ['meminfo', 'gfxinfo', 'battery'] },
|
|
34
|
+
description: 'Which metrics to capture; defaults to all three.',
|
|
35
|
+
},
|
|
36
|
+
label: { type: 'string', description: 'Baseline label for save, e.g. the build/version under test.' },
|
|
37
|
+
tags: {
|
|
38
|
+
type: 'array',
|
|
39
|
+
items: { type: 'string' },
|
|
40
|
+
description: 'Optional tags for save, e.g. ["release", "before-optimization"].',
|
|
41
|
+
},
|
|
42
|
+
id: { type: 'string', description: 'Baseline id; required for delete, optional for compare (defaults to the latest for device+package).' },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
output: jsonOutput(),
|
|
46
|
+
async execute(args, exec) {
|
|
47
|
+
const store = createStore(baselineDir);
|
|
48
|
+
const device = args.serial ?? cfg.defaultSerial ?? 'default';
|
|
49
|
+
if (args.command === 'list') {
|
|
50
|
+
const entries = store.list().map(publicMeta);
|
|
51
|
+
return { command: 'list', count: entries.length, baselines: entries };
|
|
52
|
+
}
|
|
53
|
+
if (args.command === 'delete') {
|
|
54
|
+
if (args.id === undefined)
|
|
55
|
+
throw new AdbError('ARGS_INVALID', 'delete requires a baseline id');
|
|
56
|
+
const deleted = store.delete(args.id);
|
|
57
|
+
if (!deleted)
|
|
58
|
+
throw new AdbError('BASELINE_NOT_FOUND', `no baseline with id ${args.id}`);
|
|
59
|
+
return { command: 'delete', deleted: true, id: args.id };
|
|
60
|
+
}
|
|
61
|
+
if (args.package === undefined) {
|
|
62
|
+
throw new AdbError('ARGS_INVALID', `${args.command} requires a package`);
|
|
63
|
+
}
|
|
64
|
+
if (args.command === 'save') {
|
|
65
|
+
const snapshot = await capturePerfSnapshot(ctx, cfg, exec, {
|
|
66
|
+
package: args.package,
|
|
67
|
+
serial: args.serial,
|
|
68
|
+
metrics: args.metrics,
|
|
69
|
+
});
|
|
70
|
+
const stored = store.save({
|
|
71
|
+
label: args.label ?? `baseline-${new Date().toISOString().slice(0, 10)}`,
|
|
72
|
+
device,
|
|
73
|
+
package: args.package,
|
|
74
|
+
...(args.tags !== undefined && args.tags.length > 0 ? { tags: args.tags } : {}),
|
|
75
|
+
snapshot,
|
|
76
|
+
});
|
|
77
|
+
return { command: 'save', saved: publicMeta(stored) };
|
|
78
|
+
}
|
|
79
|
+
// compare
|
|
80
|
+
const baseline = args.id !== undefined
|
|
81
|
+
? store.get(args.id)
|
|
82
|
+
: store.latest(device, args.package);
|
|
83
|
+
if (baseline === undefined) {
|
|
84
|
+
throw new AdbError('BASELINE_NOT_FOUND', `no baseline for device ${device} package ${args.package}${args.id !== undefined ? ` (id ${args.id})` : ''}; save one with command=save first`);
|
|
85
|
+
}
|
|
86
|
+
const current = await capturePerfSnapshot(ctx, cfg, exec, {
|
|
87
|
+
package: args.package,
|
|
88
|
+
serial: args.serial,
|
|
89
|
+
metrics: args.metrics,
|
|
90
|
+
});
|
|
91
|
+
const diffs = diffSnapshots(baseline.snapshot, current);
|
|
92
|
+
return {
|
|
93
|
+
command: 'compare',
|
|
94
|
+
baseline: publicMeta(baseline),
|
|
95
|
+
current: { package: current.package, metrics: current.metrics },
|
|
96
|
+
diffs,
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
package/lib/tools/perf.d.ts
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { ToolExecution } from '@deepseek-ai/dsh-tools';
|
|
2
3
|
import { type AdbConfig } from '../adb.js';
|
|
4
|
+
import { parseBattery, parseGfxinfo, parseMeminfo } from '../parsers/perf.js';
|
|
5
|
+
export type PerfMetric = 'meminfo' | 'gfxinfo' | 'battery';
|
|
6
|
+
export interface PerfSnapshot {
|
|
7
|
+
package: string;
|
|
8
|
+
metrics: PerfMetric[];
|
|
9
|
+
meminfo?: ReturnType<typeof parseMeminfo>;
|
|
10
|
+
gfxinfo?: ReturnType<typeof parseGfxinfo>;
|
|
11
|
+
battery?: ReturnType<typeof parseBattery>;
|
|
12
|
+
}
|
|
13
|
+
/** Shared capture: dumpsys meminfo / gfxinfo / battery for one app. */
|
|
14
|
+
export declare function capturePerfSnapshot(ctx: Context, cfg: AdbConfig, exec: ToolExecution, args: {
|
|
15
|
+
package: string;
|
|
16
|
+
serial?: string;
|
|
17
|
+
metrics?: PerfMetric[];
|
|
18
|
+
}): Promise<PerfSnapshot>;
|
|
3
19
|
/** adb_perf_snapshot: dumpsys meminfo / gfxinfo / battery for one app. */
|
|
4
20
|
export declare function registerPerfTool(ctx: Context, cfg: AdbConfig): void;
|
package/lib/tools/perf.js
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
import { classifyFailure, jsonOutput, runAdb } from '../adb.js';
|
|
2
2
|
import { parseBattery, parseGfxinfo, parseMeminfo } from '../parsers/perf.js';
|
|
3
|
+
/** Shared capture: dumpsys meminfo / gfxinfo / battery for one app. */
|
|
4
|
+
export async function capturePerfSnapshot(ctx, cfg, exec, args) {
|
|
5
|
+
const metrics = args.metrics ?? ['meminfo', 'gfxinfo', 'battery'];
|
|
6
|
+
const result = { package: args.package, metrics: [...metrics] };
|
|
7
|
+
for (const metric of metrics) {
|
|
8
|
+
// dumpsys battery is device-global and takes no package argument.
|
|
9
|
+
const argv = metric === 'battery'
|
|
10
|
+
? ['shell', 'dumpsys', 'battery']
|
|
11
|
+
: ['shell', 'dumpsys', metric, args.package];
|
|
12
|
+
const output = await runAdb(ctx, cfg, argv, {
|
|
13
|
+
signal: exec.signal,
|
|
14
|
+
serial: args.serial,
|
|
15
|
+
maxBytes: 4 * 1024 * 1024,
|
|
16
|
+
});
|
|
17
|
+
if (output.exitCode !== 0)
|
|
18
|
+
throw classifyFailure(output);
|
|
19
|
+
if (metric === 'meminfo')
|
|
20
|
+
result.meminfo = parseMeminfo(output.stdout);
|
|
21
|
+
else if (metric === 'gfxinfo')
|
|
22
|
+
result.gfxinfo = parseGfxinfo(output.stdout);
|
|
23
|
+
else
|
|
24
|
+
result.battery = parseBattery(output.stdout);
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
3
28
|
/** adb_perf_snapshot: dumpsys meminfo / gfxinfo / battery for one app. */
|
|
4
29
|
export function registerPerfTool(ctx, cfg) {
|
|
5
30
|
ctx.tools.register({
|
|
@@ -21,28 +46,7 @@ export function registerPerfTool(ctx, cfg) {
|
|
|
21
46
|
},
|
|
22
47
|
output: jsonOutput(),
|
|
23
48
|
async execute(args, exec) {
|
|
24
|
-
|
|
25
|
-
const result = { package: args.package, metrics: [...metrics] };
|
|
26
|
-
for (const metric of metrics) {
|
|
27
|
-
// dumpsys battery is device-global and takes no package argument.
|
|
28
|
-
const argv = metric === 'battery'
|
|
29
|
-
? ['shell', 'dumpsys', 'battery']
|
|
30
|
-
: ['shell', 'dumpsys', metric, args.package];
|
|
31
|
-
const output = await runAdb(ctx, cfg, argv, {
|
|
32
|
-
signal: exec.signal,
|
|
33
|
-
serial: args.serial,
|
|
34
|
-
maxBytes: 4 * 1024 * 1024,
|
|
35
|
-
});
|
|
36
|
-
if (output.exitCode !== 0)
|
|
37
|
-
throw classifyFailure(output);
|
|
38
|
-
if (metric === 'meminfo')
|
|
39
|
-
result.meminfo = parseMeminfo(output.stdout);
|
|
40
|
-
else if (metric === 'gfxinfo')
|
|
41
|
-
result.gfxinfo = parseGfxinfo(output.stdout);
|
|
42
|
-
else
|
|
43
|
-
result.battery = parseBattery(output.stdout);
|
|
44
|
-
}
|
|
45
|
-
return result;
|
|
49
|
+
return capturePerfSnapshot(ctx, cfg, exec, args);
|
|
46
50
|
},
|
|
47
51
|
});
|
|
48
52
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-adb",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "ADB device & bench operations for DeepSeek Harness: device discovery, structured logcat, apk install, file pull/push, performance snapshots",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "ADB device & bench operations for DeepSeek Harness: device discovery, structured logcat, apk install, file pull/push, performance snapshots, perf baselines, crash reports",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"files": [
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"bench"
|
|
18
18
|
],
|
|
19
19
|
"license": "MIT",
|
|
20
|
-
"author": "
|
|
20
|
+
"author": "SamXiaBing",
|
|
21
21
|
"dsh": {
|
|
22
22
|
"bundle": {
|
|
23
23
|
"patch": "./cordis.patch.yml"
|