@swmansion/argent 0.9.0 → 0.11.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.
Files changed (37) hide show
  1. package/README.md +53 -10
  2. package/assets/argent.tracecfg.pbtxt +133 -0
  3. package/assets/queries/README.md +93 -0
  4. package/assets/queries/cpu-hotspots.sql +106 -0
  5. package/assets/queries/function-callers.sql +62 -0
  6. package/assets/queries/hang-folds-batched.sql +88 -0
  7. package/assets/queries/hang-main-thread-samples.sql +32 -0
  8. package/assets/queries/hang-state-breakdown.sql +44 -0
  9. package/assets/queries/memory-rss.sql +25 -0
  10. package/assets/queries/thread-breakdown.sql +34 -0
  11. package/assets/queries/trace-bounds.sql +6 -0
  12. package/assets/queries/ui-hangs.sql +59 -0
  13. package/assets/trace-processor/LICENSE +235 -0
  14. package/assets/trace-processor/SHA256SUMS +4 -0
  15. package/assets/trace-processor/engine.mjs +42423 -0
  16. package/assets/trace-processor/engine_bundle.node.js +11130 -0
  17. package/assets/trace-processor/trace_processor.wasm +0 -0
  18. package/{dist → bin}/argent-android-devtools-0.1.0.apk +0 -0
  19. package/bin/argent-simulator-server.cjs +56 -0
  20. package/bin/{ax-service → darwin/ax-service} +0 -0
  21. package/bin/{simulator-server → darwin/simulator-server} +0 -0
  22. package/bin/linux/simulator-server +0 -0
  23. package/dist/cli-cmds.mjs +2540 -85
  24. package/dist/cli.d.ts +6 -0
  25. package/dist/cli.js +23 -2
  26. package/dist/cli.js.map +1 -1
  27. package/dist/installer.mjs +241 -107
  28. package/dist/mcp-server.mjs +440 -110
  29. package/dist/tool-server.cjs +4297 -1895
  30. package/dylibs/libArgentInjectionBootstrap.dylib +0 -0
  31. package/dylibs/libKeyboardPatch.dylib +0 -0
  32. package/dylibs/libNativeDevtoolsIos.dylib +0 -0
  33. package/package.json +3 -3
  34. package/skills/argent-android-emulator-setup/SKILL.md +1 -1
  35. package/skills/argent-native-profiler/SKILL.md +5 -5
  36. /package/{dist → assets}/Argent.tracetemplate +0 -0
  37. /package/{manifest.json → assets/manifest.json} +0 -0
package/README.md CHANGED
@@ -4,7 +4,10 @@
4
4
  <img width="1100" height="382" alt="argent-header" src="https://github.com/user-attachments/assets/6cec01d5-da3c-4b6c-97c3-0374a63c213c" />
5
5
  </a>
6
6
  </p>
7
- <br/>
7
+
8
+ [![Ad](https://swm-delivery.com/www/images/zone-gh-argent-1?n=1)](https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-argent-1&n=1)
9
+ [![Ad](https://swm-delivery.com/www/images/zone-gh-argent-2?n=1)](https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-argent-2&n=1)
10
+ [![Ad](https://swm-delivery.com/www/images/zone-gh-argent-3?n=1)](https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-argent-3&n=1)
8
11
 
9
12
  **[Argent](https://argent.swmansion.com)** is an **agentic toolkit** that gives your AI assistant direct access to iOS Simulators and Android Emulators. Ask it to tap a button, run a profiler or reproduce an issue manually - all from within your CLI, without switching context.
10
13
 
@@ -37,6 +40,43 @@ npx @swmansion/argent init
37
40
  - For iOS: macOS with **Xcode** installed
38
41
  - For Android: **Android SDK Platform Tools** (`adb`) on `PATH`, and the **Android Emulator** package if you want to boot AVDs from Argent. Create AVDs via Android Studio or `avdmanager`.
39
42
 
43
+ ##### Linux host: extra prerequisites for Android emulators
44
+
45
+ Argent runs Android emulators on Linux but the default install can be slow if a few host-side knobs aren't right. Cover these once and the experience matches macOS:
46
+
47
+ - **KVM access.** The emulator falls back to slow software emulation (TCG) without `/dev/kvm`. Make sure virtualization is enabled in BIOS/UEFI (`vmx` for Intel, `svm` for AMD in `/proc/cpuinfo`) and that your user can read/write `/dev/kvm` — on most distros that means joining the `kvm` group:
48
+
49
+ ```bash
50
+ sudo usermod -aG kvm "$USER"
51
+ # log out and back in so the new group takes effect
52
+ ```
53
+
54
+ - **GPU mode (`-gpu swiftshader` on Linux, override available).** The Android emulator's Linux GPU story is messy: `-gpu auto` frequently resolves to lavapipe (slow software Vulkan via host libvulkan, ~10× cold-boot regression on flagship hardware), and `-gpu host` silently produces a corrupted or black emulator window on hosts with non-trivial GL stacks — dual-GPU / Optimus laptops, NVIDIA + Mesa coexistence via libglvnd, Wayland sessions on hybrid graphics, headless / containerized hosts. The failure mode is invisible to argent's framebuffer-based screenshot tool, so an agent reports success while the developer sees a black window.
55
+
56
+ Argent picks `-gpu swiftshader` on Linux for universal compatibility: it sidesteps the host GL stack entirely and renders via the emulator's bundled SwiftShader. On modern multi-core machines this is indistinguishably smooth from hardware-accelerated `-gpu host` (and far faster than lavapipe).
57
+
58
+ Override with the `ARGENT_EMULATOR_GPU_MODE` env var if you've verified `-gpu host` works on your machine (typical single-GPU Mesa box with a healthy X session):
59
+
60
+ ```bash
61
+ ARGENT_EMULATOR_GPU_MODE=host argent ...
62
+ ```
63
+
64
+ Argent's boot-device preflight prints a warning if `/dev/kvm` isn't usable — the condition that causes a 10–50× TCG-vs-KVM slowdown.
65
+
66
+ - **System image.** Prefer the `default` or `google_apis` variants of `x86_64` system images for headless agent workflows; `google_apis_playstore` adds noticeable boot-time CPU churn from Play services. Always pick `x86_64` on Intel/AMD hosts — ARM images run via QEMU translation and are dramatically slower.
67
+
68
+ - **AVD config.** AVDs created via `avdmanager create avd` default to `hw.gpu.enabled=no`. Argent overrides this with an explicit `-gpu` arg at launch (so the on-disk config doesn't need editing). For the smoothest experience under heavy native builds (gradle compilations alongside the AVD), bump the AVD's RAM and CPU count — edit `~/.android/avd/<name>.avd/config.ini`:
69
+
70
+ ```
71
+ hw.ramSize = 8192
72
+ hw.cpu.ncore = 6
73
+ vm.heapSize = 512
74
+ ```
75
+
76
+ Stock 2 GB / 4 vCPU AVDs can be CPU-starved into wedged-system_server states by a concurrent gradle/Kotlin compile.
77
+
78
+ - **Headless / CI mode (`ARGENT_EMULATOR_NO_WINDOW=1`).** Argent shows the emulator window by default so a local developer can see the AVD UI. In a headless context — CI runner, container, or a Wayland-only session where the emulator's bundled Qt has no `wayland` platform plugin and SIGABRTs on the crash-consent dialog — opt out by exporting `ARGENT_EMULATOR_NO_WINDOW=1` before starting the tool-server. This appends `-no-window` to the spawn args, selecting `qemu-system-x86_64-headless` which doesn't need a Qt window. Argent's screencap-based screenshot tool reads the in-memory framebuffer correctly without a visible window.
79
+
40
80
  #### Run `init` in your project
41
81
 
42
82
  From your project root:
@@ -60,14 +100,17 @@ argent init
60
100
 
61
101
  ## CLI Reference
62
102
 
63
- | Command | Description |
64
- | ------------------ | ----------------------------------------------------------- |
65
- | `argent init` | Install globally and configure MCP in the current workspace |
66
- | `argent install` | Alias for `init` command |
67
- | `argent update` | Pull the latest version and refresh workspace configuration |
68
- | `argent remove` | Unregister the MCP server and uninstall the package |
69
- | `argent uninstall` | Alias for `remove` command |
70
- | `argent mcp` | Start MCP server instance, used internally by agent |
103
+ | Command | Description |
104
+ | ------------------ | ---------------------------------------------------------------------- |
105
+ | `argent init` | Install globally and configure MCP in the current workspace |
106
+ | `argent install` | Alias for `init` command |
107
+ | `argent update` | Pull the latest version and refresh workspace configuration |
108
+ | `argent remove` | Unregister the MCP server and uninstall the package |
109
+ | `argent uninstall` | Alias for `remove` command |
110
+ | `argent mcp` | Start MCP server instance, used internally by agent |
111
+ | `argent enable` | Enable a predefined feature flag (`--scope project` for project-local) |
112
+ | `argent disable` | Disable a feature flag (`--scope project` for project-local) |
113
+ | `argent flags` | List available feature flags and their state |
71
114
 
72
115
  ## Supported Editors
73
116
 
@@ -99,7 +142,7 @@ Argent uses a mixed licensing model.
99
142
 
100
143
  **Source code** is released under the [Apache License 2.0](LICENSE.txt).
101
144
 
102
- **Proprietary binaries** (the `bin/simulator-server` and `bin/ax-service` executables and the `.dylib` files in `native-devtools-ios`) are the intellectual property of Software Mansion S.A. and are licensed solely for use within this project. Decompiling, reverse-engineering, or redistributing them without explicit written permission is prohibited.
145
+ **Proprietary binaries** (the per-platform `bin/<platform>/simulator-server` and `bin/darwin/ax-service` executables and the `.dylib` files in `native-devtools-ios`) are the intellectual property of Software Mansion S.A. and are licensed solely for use within this project. Decompiling, reverse-engineering, or redistributing them without explicit written permission is prohibited.
103
146
 
104
147
  By using Argent, you acknowledge and agree to this structure. See [LICENSE](https://github.com/software-mansion/argent/blob/main/LICENSE.txt) for full details.
105
148
 
@@ -0,0 +1,133 @@
1
+ # Argent — Perfetto TraceConfig (text protobuf)
2
+ #
3
+ # The Android twin of packages/tool-server/src/utils/ios-profiler/Argent.tracetemplate.
4
+ # Pushed to the device and consumed by `perfetto --txt -c <this-file>`.
5
+ #
6
+ # In production this is template-substituted: TARGET_CMDLINE_PLACEHOLDER and
7
+ # TARGET_PACKAGE_PLACEHOLDER are replaced with the auto-detected foreground app
8
+ # (mirrors detectRunningApp in native-profiler-start.ts:46-117).
9
+ #
10
+ # Buffers sized for ~5 minutes of capture on a typical RN app. Adjust if you
11
+ # extend RECORDING_CAP_MS beyond the iOS 10-min default.
12
+
13
+ # duration_ms is intentionally omitted — the recording is stopped explicitly via
14
+ # `kill -TERM <perfetto-pid>` from native-profiler-stop, mirroring xctrace SIGINT.
15
+ # If you want a hard cap, set duration_ms here AND keep the stop signal as a
16
+ # safety net (the perfetto daemon will exit either way).
17
+
18
+ buffers: {
19
+ size_kb: 65536 # 64 MB — CPU samples + atrace slices
20
+ fill_policy: DISCARD # ring-buffer once full; we want recency over completeness
21
+ }
22
+ buffers: {
23
+ size_kb: 16384 # 16 MB — frame timeline + ANRs (sparser, smaller buffer)
24
+ fill_policy: DISCARD
25
+ }
26
+
27
+ # ----------------------------------------------------------------------------
28
+ # Data source 1: CPU callstack sampling (the Time Profiler equivalent)
29
+ # ----------------------------------------------------------------------------
30
+ # Outputs perf_sample rows we GROUP BY callsite + thread in queries/cpu-hotspots.sql.
31
+ # target_cmdline restricts sampling to the user's app — system-wide noise is
32
+ # excluded at capture time, not analysis time.
33
+ data_sources: {
34
+ config: {
35
+ name: "linux.perf"
36
+ target_buffer: 0
37
+ perf_event_config: {
38
+ timebase: {
39
+ # 100 Hz software CPU clock — matches the iOS coresampler2 default.
40
+ # Adjust higher (e.g. 1000) for tight CPU-bound investigations.
41
+ counter: SW_CPU_CLOCK
42
+ frequency: 100
43
+ timestamp_clock: PERF_CLOCK_MONOTONIC
44
+ }
45
+ callstack_sampling: {
46
+ scope: {
47
+ target_cmdline: "TARGET_CMDLINE_PLACEHOLDER"
48
+ # Drop the next two if Perfetto warns about missing /proc access:
49
+ # target_installed_by: "PLAY_STORE" # only for production-builds w/ trusted source
50
+ }
51
+ kernel_frames: true # include kernel callsites; great for "stuck in syscall" diagnosis
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ # ----------------------------------------------------------------------------
58
+ # Data source 2: kernel scheduler events + atrace app slices
59
+ # ----------------------------------------------------------------------------
60
+ # Feeds thread_state table; used in queries/ui-hangs.sql to attribute main-thread
61
+ # stalls to "blocked on I/O", "blocked on lock", etc.
62
+ #
63
+ # atrace categories / atrace_apps are nested in ftrace_config (NOT as a separate
64
+ # `android.atrace` data source with `atrace_config` — that field was removed
65
+ # from DataSourceConfig and is rejected by perfetto >= v45). See
66
+ # https://perfetto.dev/docs/data-sources/atrace.
67
+ data_sources: {
68
+ config: {
69
+ name: "linux.ftrace"
70
+ target_buffer: 0
71
+ ftrace_config: {
72
+ ftrace_events: "sched/sched_switch"
73
+ ftrace_events: "sched/sched_wakeup"
74
+ ftrace_events: "sched/sched_wakeup_new"
75
+ ftrace_events: "sched/sched_process_exit"
76
+ # sched_blocked_reason gives us the "main thread blocked on what?" answer.
77
+ ftrace_events: "sched/sched_blocked_reason"
78
+ # binder events — surfaces IPC contention on the main thread.
79
+ ftrace_events: "binder/binder_transaction"
80
+ ftrace_events: "binder/binder_transaction_received"
81
+ # System atrace categories — see `adb shell atrace --list_categories` for the full set.
82
+ atrace_categories: "view" # View system: measure/layout/draw
83
+ atrace_categories: "gfx" # SurfaceFlinger/HWUI
84
+ atrace_categories: "wm" # WindowManager
85
+ atrace_categories: "am" # ActivityManager
86
+ atrace_categories: "input" # input dispatch (key/touch)
87
+ atrace_categories: "binder_driver"
88
+ atrace_categories: "dalvik" # GC, JIT, class loading
89
+ atrace_categories: "ss" # SystemServer
90
+ atrace_categories: "aidl" # AIDL call slices
91
+ # Per-app categories — capture slices that the app itself emits via Trace.beginSection.
92
+ atrace_apps: "TARGET_PACKAGE_PLACEHOLDER"
93
+ }
94
+ }
95
+ }
96
+
97
+ # ----------------------------------------------------------------------------
98
+ # Data source 4: SurfaceFlinger frame timeline (the Hangs equivalent + reason codes)
99
+ # ----------------------------------------------------------------------------
100
+ # Produces expected_frame_timeline_slice and actual_frame_timeline_slice rows.
101
+ # A frame whose actual end > expected end is a jank; the jank_type enum tells us
102
+ # whose fault (AppDeadlineMissed / BufferStuffing / SfCpu / SfGpu / ...).
103
+ data_sources: {
104
+ config: {
105
+ name: "android.surfaceflinger.frametimeline"
106
+ target_buffer: 1
107
+ }
108
+ }
109
+
110
+ # ----------------------------------------------------------------------------
111
+ # Data source 5: process stats — for the weak RSS-growth signal until phase-2 leak detection lands
112
+ # ----------------------------------------------------------------------------
113
+ data_sources: {
114
+ config: {
115
+ name: "linux.process_stats"
116
+ target_buffer: 1
117
+ process_stats_config: {
118
+ proc_stats_poll_ms: 1000 # 1 s cadence is enough for RSS-trend detection
119
+ scan_all_processes_on_start: true
120
+ }
121
+ }
122
+ }
123
+
124
+ # Drop incremental_state every 5 s so we recover symbol tables even if perfetto crashes
125
+ # mid-recording — partial trace is still useful (mirrors iOS handleXctraceExit recovery).
126
+ incremental_state_config: {
127
+ clear_period_ms: 5000
128
+ }
129
+
130
+ # When the on-device file is large, write it directly instead of going through the
131
+ # 32 MB ring buffer before flushing. Avoids data loss on long recordings.
132
+ write_into_file: true
133
+ file_write_period_ms: 2500
@@ -0,0 +1,93 @@
1
+ # Argent Android profiler — PerfettoSQL queries
2
+
3
+ These `*.sql` files are the source of truth for every query the Argent Android
4
+ native profiler runs against a captured `.pftrace`. They run through the
5
+ in-process Perfetto WASM trace-processor from the tool-server pipeline
6
+ (`runTpQuery` / `runTpInline` in
7
+ `tool-server/src/utils/android-profiler/pipeline/run-tp.ts`).
8
+
9
+ The directory lives in `native-devtools-android`; the bundler copies it next to
10
+ the bundled tool-server at publish time, so `traceProcessorQueriesDir()` resolves
11
+ the same path in dev and packaged builds.
12
+
13
+ Each `.sql` header documents only what's specific to that query. The shared
14
+ conventions live here so they aren't repeated nine times.
15
+
16
+ ## What each file is for
17
+
18
+ | File | Consumed by | Purpose |
19
+ | ------------------------------ | -------------------------------------------- | ---------------------------------------------------------------- |
20
+ | `trace-bounds.sql` | every analyze run | Trace start timestamp anchor (see _Timestamps_). |
21
+ | `ui-hangs.sql` | analyze | ANRs + app-jank frames → one hang per frame. |
22
+ | `cpu-hotspots.sql` | analyze | Per-thread hottest leaf functions + burst windows. |
23
+ | `thread-breakdown.sql` | profiler-stack-query `mode=thread_breakdown` | Per-thread sample share. |
24
+ | `hang-folds-batched.sql` | batched analyze | State breakdown + GC overlap for ALL hangs in one batched query. |
25
+ | `hang-state-breakdown.sql` | drill-down (single hang) | Main-thread state breakdown for one hang window. |
26
+ | `hang-main-thread-samples.sql` | profiler-stack-query `mode=hang_stacks` | Main-thread CPU samples inside one hang window. |
27
+ | `function-callers.sql` | profiler-stack-query `mode=function_callers` | Callsites that hit one hot function. |
28
+ | `memory-rss.sql` | analyze | RSS-growth weak signal (not leak detection). |
29
+
30
+ ## Conventions
31
+
32
+ ### Parameters via the `_argent_args` view
33
+
34
+ Each query declares its runtime parameters once in a small `_argent_args`
35
+ PERFETTO VIEW at the top, then references them by name:
36
+
37
+ ```sql
38
+ DROP VIEW IF EXISTS _argent_args;
39
+ CREATE PERFETTO VIEW _argent_args AS
40
+ SELECT '{{TARGET_PROCESS}}' AS target_process;
41
+ ...
42
+ WHERE p.name = (SELECT target_process FROM _argent_args)
43
+ ```
44
+
45
+ This keeps each value at one self-documenting site, and the body reads like
46
+ normal SQL instead of scattering bare tokens through it.
47
+
48
+ ### `{{NAME}}` template tokens
49
+
50
+ `{{NAME}}` placeholders are resolved by `renderSqlTemplate` (`run-tp.ts`) before
51
+ the query runs. It throws on a mismatch either way: a `{{NAME}}` with no
52
+ substitution, or a substitution the template never uses — catching forgotten or
53
+ stale tokens early.
54
+
55
+ Values are **not** escaped for SQL injection — they're interpolated into the
56
+ query string passed to the in-process engine, so callers must validate them
57
+ (numeric for `*_ns`; identifier-shaped for process/thread/function names) — see
58
+ `hang-folds-batched.ts` for the strictest example.
59
+
60
+ Most queries render through `runTpQuery`. `hang-folds-batched.sql` is the
61
+ exception: `pipeline/hang-folds-batched.ts` loads it directly, builds the
62
+ `{{HANG_WINDOWS_VALUES}}` tuple list, and resolves it through the same renderer.
63
+
64
+ ### Timestamps are CLOCK_MONOTONIC nanoseconds
65
+
66
+ Perfetto's `ts` columns are CLOCK_MONOTONIC nanoseconds since device boot — not
67
+ trace-relative. `trace-bounds.sql` returns the earliest `ts`; the JS side
68
+ subtracts it (`traceStartMs`) to normalise every emitted timestamp to
69
+ trace-relative ns. Any native ms/ns a query emits (burst windows, hang bounds)
70
+ stays native until JS does that subtraction.
71
+
72
+ ### One trace parse per warm engine → batch
73
+
74
+ Re-parsing the whole trace on every query is expensive (~1.3 s for 76 MB), so
75
+ one query per item is quadratic — the per-hang loop this replaced took ~47 min
76
+ for 1013 hangs. Instead, fold many per-item queries into a single script with
77
+ `CREATE PERFETTO VIEW`/`TABLE` + a terminal `UNION ALL SELECT`, joining over a
78
+ runtime-built table. See `hang-folds-batched.sql`: one ~1.7 s run regardless of
79
+ hang count. Only the final SELECT reaches stdout.
80
+
81
+ ### Two copies of the hang state breakdown — keep them in sync
82
+
83
+ The main-thread state-breakdown logic lives in two places:
84
+
85
+ - `hang-state-breakdown.sql` — single window, drill-down path;
86
+ - the `argent_hang_state` view in `hang-folds-batched.sql` — all windows.
87
+
88
+ Keep the window-clipping math in sync: each `thread_state` slice is clipped to
89
+ the hang window (`MIN(ts.ts + ts.dur, end) - MAX(ts.ts, start)`) so a slice
90
+ straddling a boundary only counts its overlap. A plain `SUM(dur)` over slices
91
+ that merely start inside the window can overshoot its length.
92
+
93
+ There's no standalone GC query — GC overlap lives only in the batched file.
@@ -0,0 +1,106 @@
1
+ -- Argent — CPU hotspots.
2
+ --
3
+ -- One output row per (thread_name, leaf_function) — the leaf frame name IS the
4
+ -- dominant function, so one SQL row maps 1:1 to one aggregateCpuHotspots group
5
+ -- (we drop leaf_mapping, which was unused and only fragmented the grouping).
6
+ -- The aggregator normalises the thread name and applies severity bands.
7
+ --
8
+ -- Burst windows are computed here in SQL rather than shipping every sample
9
+ -- timestamp (the old `GROUP_CONCAT(ts_ns)` shipped ~54 KB of timestamps that
10
+ -- JS re-parsed). A "burst" is a run of samples for the same (thread, function)
11
+ -- with no gap larger than the burst threshold. LAG() finds the gaps, a running
12
+ -- SUM() assigns burst ids, and we emit one `start_ms:end_ms:count` triple per
13
+ -- burst. start_ms/end_ms are native ms (README.md, "Timestamps"); the JS side
14
+ -- subtracts traceStartMs to make them trace-relative.
15
+ --
16
+ -- The total_samples column is repeated on every output row so the JS side can
17
+ -- compute weight % without a second round-trip.
18
+ --
19
+ -- Placeholders (declared in the _argent_args view below): target_process —
20
+ -- package / cmdline; burst_gap_ns — burst gap threshold in ns (BURST_GAP_MS ×
21
+ -- 1e6 from aggregate.ts, so the SQL and iOS-JS burst paths share one constant).
22
+ -- See README.md for the shared _argent_args / template-token conventions.
23
+
24
+ DROP VIEW IF EXISTS _argent_args;
25
+ CREATE PERFETTO VIEW _argent_args AS
26
+ SELECT
27
+ '{{TARGET_PROCESS}}' AS target_process,
28
+ {{BURST_GAP_NS}} AS burst_gap_ns;
29
+
30
+ DROP VIEW IF EXISTS argent_app_total_samples;
31
+ CREATE PERFETTO VIEW argent_app_total_samples AS
32
+ SELECT COUNT(*) AS total_samples
33
+ FROM perf_sample ps
34
+ JOIN thread t USING (utid)
35
+ JOIN process p USING (upid)
36
+ WHERE p.name = (SELECT target_process FROM _argent_args);
37
+
38
+ WITH samples AS (
39
+ SELECT
40
+ ps.ts AS ts_ns,
41
+ t.name AS thread_name,
42
+ t.is_main_thread AS is_main_thread,
43
+ spf.name AS leaf_function
44
+ FROM perf_sample ps
45
+ JOIN thread t USING (utid)
46
+ JOIN process p USING (upid)
47
+ LEFT JOIN stack_profile_callsite spc ON ps.callsite_id = spc.id
48
+ LEFT JOIN stack_profile_frame spf ON spc.frame_id = spf.id
49
+ WHERE p.name = (SELECT target_process FROM _argent_args)
50
+ ),
51
+ -- Flag each sample whose gap to the previous sample of the same
52
+ -- thread+function exceeds the burst threshold. LAG over the first sample is
53
+ -- NULL, so its CASE yields 0 — the opening sample never counts as a gap.
54
+ flagged AS (
55
+ SELECT
56
+ thread_name, is_main_thread, leaf_function, ts_ns,
57
+ CASE
58
+ WHEN ts_ns - LAG(ts_ns) OVER w > (SELECT burst_gap_ns FROM _argent_args) THEN 1
59
+ ELSE 0
60
+ END AS is_new_burst
61
+ FROM samples
62
+ WINDOW w AS (PARTITION BY thread_name, leaf_function ORDER BY ts_ns)
63
+ ),
64
+ -- Running sum of the gap flags == a monotonically increasing burst id within
65
+ -- each thread+function partition.
66
+ ided AS (
67
+ SELECT
68
+ thread_name, is_main_thread, leaf_function, ts_ns,
69
+ SUM(is_new_burst) OVER (
70
+ PARTITION BY thread_name, leaf_function
71
+ ORDER BY ts_ns
72
+ ROWS UNBOUNDED PRECEDING
73
+ ) AS burst_id
74
+ FROM flagged
75
+ ),
76
+ -- Collapse each burst to [start_ns, end_ns, sample_count]. Every sample lands
77
+ -- in exactly one burst, so summing burst counts == total samples and
78
+ -- MIN/MAX of burst bounds == first/last sample of the (thread, function).
79
+ per_burst AS (
80
+ SELECT
81
+ thread_name, leaf_function,
82
+ MAX(is_main_thread) AS is_main_thread,
83
+ MIN(ts_ns) AS burst_start_ns,
84
+ MAX(ts_ns) AS burst_end_ns,
85
+ COUNT(*) AS burst_count
86
+ FROM ided
87
+ GROUP BY thread_name, leaf_function, burst_id
88
+ )
89
+ SELECT
90
+ thread_name,
91
+ MAX(is_main_thread) AS is_main_thread,
92
+ leaf_function,
93
+ SUM(burst_count) AS sample_count,
94
+ MIN(burst_start_ns) AS first_ts_ns,
95
+ MAX(burst_end_ns) AS last_ts_ns,
96
+ (SELECT total_samples FROM argent_app_total_samples) AS total_samples,
97
+ -- Compact `start_ms:end_ms:count` triples, comma-separated. JS sorts them
98
+ -- by start before display, so GROUP_CONCAT order is irrelevant.
99
+ GROUP_CONCAT(
100
+ (burst_start_ns / 1000000) || ':' || (burst_end_ns / 1000000) || ':' || burst_count,
101
+ ','
102
+ ) AS burst_windows
103
+ FROM per_burst
104
+ GROUP BY thread_name, leaf_function
105
+ ORDER BY sample_count DESC
106
+ LIMIT 200;
@@ -0,0 +1,62 @@
1
+ -- Argent — callers/callees for a single hot function.
2
+ --
3
+ -- Drill-down for profiler-stack-query mode=function_callers. Returns one row
4
+ -- per unique callsite (+ owning thread) whose LEAF frame matches the requested
5
+ -- function, callstack text unwound via experimental_annotated_callstack.
6
+ --
7
+ -- Function matching is a literal, case-sensitive SUBSTRING test (INSTR), not
8
+ -- exact equality: perf frame names are stored MANGLED (e.g. the source symbol
9
+ -- "uncompressLZW" lives inside "_Z13uncompressLZWP7_JNIEnv..."), so an exact
10
+ -- match on a demangled name would miss. The Itanium length prefix means the
11
+ -- bare symbol still appears verbatim, so a substring catches it. matched_function
12
+ -- exposes the real leaf name and is_exact flags the rows that matched verbatim
13
+ -- (ordered first) so a precise query isn't drowned by incidental substrings.
14
+ --
15
+ -- Thread filter (the thread_name placeholder), resolved caller-side:
16
+ -- '__ALL__' → all threads; each row is labelled with its thread so the
17
+ -- caller can see where the function runs without knowing names.
18
+ -- '__MAIN__' → the UI/main thread, matched via thread.is_main_thread. The
19
+ -- main thread's raw perf `comm` is the truncated package
20
+ -- (e.g. ".blueskyweb.app"), never the literal "main", so a
21
+ -- name match would silently miss it.
22
+ -- <name> → exact thread name match (raw perf `comm`).
23
+ -- Sentinels are upper-snake so they can't collide with a real comm name.
24
+ --
25
+ -- Placeholders (declared in the _argent_args view below): target_process —
26
+ -- package / cmdline; thread_name — see above; function_name — leaf function.
27
+ -- See README.md for the shared _argent_args / template-token conventions.
28
+
29
+ DROP VIEW IF EXISTS _argent_args;
30
+ CREATE PERFETTO VIEW _argent_args AS
31
+ SELECT
32
+ '{{TARGET_PROCESS}}' AS target_process,
33
+ '{{THREAD_NAME}}' AS thread_name,
34
+ '{{FUNCTION_NAME}}' AS function_name;
35
+
36
+ SELECT
37
+ t.name AS thread_name,
38
+ MAX(t.is_main_thread) AS is_main_thread,
39
+ spf.name AS matched_function,
40
+ (spf.name = (SELECT function_name FROM _argent_args)) AS is_exact,
41
+ (
42
+ SELECT GROUP_CONCAT(inner_spf.name, ' <- ' ORDER BY eac.depth DESC)
43
+ FROM experimental_annotated_callstack(ps.callsite_id) eac
44
+ LEFT JOIN stack_profile_frame inner_spf ON eac.frame_id = inner_spf.id
45
+ ) AS callstack_text,
46
+ COUNT(*) AS occurrences
47
+ FROM perf_sample ps
48
+ JOIN thread t USING (utid)
49
+ JOIN process p USING (upid)
50
+ LEFT JOIN stack_profile_callsite spc ON ps.callsite_id = spc.id
51
+ LEFT JOIN stack_profile_frame spf ON spc.frame_id = spf.id
52
+ WHERE p.name = (SELECT target_process FROM _argent_args)
53
+ AND spf.name IS NOT NULL
54
+ AND INSTR(spf.name, (SELECT function_name FROM _argent_args)) > 0
55
+ AND (
56
+ (SELECT thread_name FROM _argent_args) = '__ALL__'
57
+ OR ((SELECT thread_name FROM _argent_args) = '__MAIN__' AND t.is_main_thread = 1)
58
+ OR t.name = (SELECT thread_name FROM _argent_args)
59
+ )
60
+ GROUP BY ps.callsite_id, t.name, spf.name
61
+ ORDER BY is_exact DESC, occurrences DESC
62
+ LIMIT 50;
@@ -0,0 +1,88 @@
1
+ -- Argent — batched per-hang annotation (state breakdown + GC overlap).
2
+ --
3
+ -- Computes the main-thread state breakdown AND ART GC overlap for EVERY hang
4
+ -- window in ONE batched trace-processor query, via a JOIN over the
5
+ -- runtime-built `argent_hang_windows` table instead of looping one query
6
+ -- per hang. See README.md, "One trace parse per warm engine → batch".
7
+ --
8
+ -- Single source of truth for the batched analyze path. The `argent_hang_state`
9
+ -- view below mirrors the standalone, single-window `hang-state-breakdown.sql`
10
+ -- (drill-down) — keep the two consistent (README.md, "Two copies of the hang
11
+ -- state breakdown"). There is no standalone GC query; GC overlap lives here
12
+ -- only (drill-down never surfaced GC).
13
+ --
14
+ -- Loaded directly by pipeline/hang-folds-batched.ts, not the generic runTpQuery
15
+ -- path.
16
+ --
17
+ -- Placeholders: target_process (declared in the _argent_args view below); and a
18
+ -- hang-windows token in the `FROM (VALUES ...)` below, which the TS replaces
19
+ -- with one `(hang_index, start_ns, end_ns)` tuple per hang. That windows token
20
+ -- must NOT appear anywhere else (e.g. this header): its replacement spans
21
+ -- multiple lines and would break a comment, so it is referenced only obliquely.
22
+ -- See README.md for the shared _argent_args / template-token conventions.
23
+
24
+ DROP VIEW IF EXISTS _argent_args;
25
+ CREATE PERFETTO VIEW _argent_args AS
26
+ SELECT '{{TARGET_PROCESS}}' AS target_process;
27
+
28
+ DROP TABLE IF EXISTS argent_hang_windows;
29
+ CREATE PERFETTO TABLE argent_hang_windows AS
30
+ SELECT
31
+ column1 AS hang_index,
32
+ column2 AS start_ns,
33
+ column3 AS end_ns
34
+ FROM (VALUES
35
+ {{HANG_WINDOWS_VALUES}}
36
+ );
37
+
38
+ DROP VIEW IF EXISTS argent_hang_state;
39
+ CREATE PERFETTO VIEW argent_hang_state AS
40
+ SELECT
41
+ hw.hang_index AS hang_index,
42
+ 'state' AS row_kind,
43
+ ts.state AS state_v,
44
+ ts.blocked_function AS blocked_function_v,
45
+ -- Clip each thread_state slice to the hang window so a state that begins
46
+ -- before the window or extends past its end contributes only the overlapping
47
+ -- duration. Plain SUM(dur) over states whose START falls in the window can
48
+ -- otherwise exceed the window length (SmartPerfetto time-interval JOIN).
49
+ CAST(SUM(MIN(ts.ts + ts.dur, hw.end_ns) - MAX(ts.ts, hw.start_ns)) AS TEXT) AS total_dur_ns_v,
50
+ CAST(COUNT(*) AS TEXT) AS occurrences_v,
51
+ NULL AS gc_reason_v,
52
+ NULL AS gc_ts_ns_v,
53
+ NULL AS gc_dur_ns_v
54
+ FROM argent_hang_windows hw
55
+ JOIN thread_state ts
56
+ ON ts.ts < hw.end_ns AND ts.ts + ts.dur > hw.start_ns
57
+ JOIN thread t USING (utid)
58
+ JOIN process p USING (upid)
59
+ WHERE p.name = (SELECT target_process FROM _argent_args)
60
+ AND t.is_main_thread
61
+ GROUP BY hw.hang_index, ts.state, ts.blocked_function;
62
+
63
+ DROP VIEW IF EXISTS argent_hang_gc;
64
+ CREATE PERFETTO VIEW argent_hang_gc AS
65
+ SELECT
66
+ hw.hang_index AS hang_index,
67
+ 'gc' AS row_kind,
68
+ NULL AS state_v,
69
+ NULL AS blocked_function_v,
70
+ NULL AS total_dur_ns_v,
71
+ NULL AS occurrences_v,
72
+ s.name AS gc_reason_v,
73
+ CAST(s.ts AS TEXT) AS gc_ts_ns_v,
74
+ CAST(s.dur AS TEXT) AS gc_dur_ns_v
75
+ FROM argent_hang_windows hw
76
+ JOIN slice s
77
+ ON s.ts < hw.end_ns AND s.ts + s.dur > hw.start_ns
78
+ JOIN thread_track tt ON s.track_id = tt.id
79
+ JOIN thread t USING (utid)
80
+ JOIN process p USING (upid)
81
+ WHERE p.name = (SELECT target_process FROM _argent_args)
82
+ AND t.is_main_thread
83
+ AND s.name GLOB 'GC*';
84
+
85
+ SELECT * FROM argent_hang_state
86
+ UNION ALL
87
+ SELECT * FROM argent_hang_gc
88
+ ORDER BY hang_index, row_kind;
@@ -0,0 +1,32 @@
1
+ -- Argent — main-thread CPU samples during a hang window.
2
+ --
3
+ -- Drill-down for profiler-stack-query mode=hang_stacks. Returns one row per
4
+ -- perf_sample on the main thread inside the hang window, with the full
5
+ -- callstack text unwound via experimental_slice_callstack.
6
+ --
7
+ -- Placeholders (declared in the _argent_args view below): target_process —
8
+ -- package / cmdline; hang_start_ns / hang_end_ns — hang window bounds, native ns.
9
+ -- See README.md for the shared _argent_args / template-token conventions.
10
+
11
+ DROP VIEW IF EXISTS _argent_args;
12
+ CREATE PERFETTO VIEW _argent_args AS
13
+ SELECT
14
+ '{{TARGET_PROCESS}}' AS target_process,
15
+ {{HANG_START_NS}} AS hang_start_ns,
16
+ {{HANG_END_NS}} AS hang_end_ns;
17
+
18
+ SELECT
19
+ ps.ts AS ts_ns,
20
+ (
21
+ SELECT GROUP_CONCAT(inner_spf.name, ' <- ' ORDER BY eac.depth DESC)
22
+ FROM experimental_annotated_callstack(ps.callsite_id) eac
23
+ LEFT JOIN stack_profile_frame inner_spf ON eac.frame_id = inner_spf.id
24
+ ) AS callstack_text
25
+ FROM perf_sample ps
26
+ JOIN thread t USING (utid)
27
+ JOIN process p USING (upid)
28
+ WHERE p.name = (SELECT target_process FROM _argent_args)
29
+ AND t.is_main_thread
30
+ AND ps.ts BETWEEN (SELECT hang_start_ns FROM _argent_args)
31
+ AND (SELECT hang_end_ns FROM _argent_args)
32
+ ORDER BY ts_ns;
@@ -0,0 +1,44 @@
1
+ -- Argent — main-thread state breakdown during a single hang window.
2
+ --
3
+ -- Called once per UiHang from the drill-down path (pipeline/index.ts
4
+ -- renderHangStacksAndroid). The aggregator folds the rows back into the hang
5
+ -- object as `stateBreakdown`.
6
+ --
7
+ -- iOS literally cannot produce this — Time Profiler only samples while a
8
+ -- thread is *running*, so a 500ms hang spent blocked on a futex shows up as
9
+ -- an empty sample window. ftrace gives us the partition for free.
10
+ --
11
+ -- Single-window twin of the `argent_hang_state` view in hang-folds-batched.sql;
12
+ -- keep the two consistent (see README.md, "Two copies of the hang state
13
+ -- breakdown").
14
+ --
15
+ -- Placeholders (declared in the _argent_args view below): target_process —
16
+ -- package / cmdline; hang_start_ns / hang_end_ns — hang window bounds, native ns.
17
+ -- See README.md for the shared _argent_args / template-token conventions.
18
+
19
+ DROP VIEW IF EXISTS _argent_args;
20
+ CREATE PERFETTO VIEW _argent_args AS
21
+ SELECT
22
+ '{{TARGET_PROCESS}}' AS target_process,
23
+ {{HANG_START_NS}} AS hang_start_ns,
24
+ {{HANG_END_NS}} AS hang_end_ns;
25
+
26
+ SELECT
27
+ state,
28
+ blocked_function,
29
+ -- Clip each thread_state slice to the hang window so a state that begins
30
+ -- before the window or extends past its end contributes only the overlapping
31
+ -- duration. Plain SUM(dur) over states whose START falls in the window can
32
+ -- otherwise exceed the window length (SmartPerfetto time-interval JOIN).
33
+ SUM(MIN(ts.ts + ts.dur, (SELECT hang_end_ns FROM _argent_args))
34
+ - MAX(ts.ts, (SELECT hang_start_ns FROM _argent_args))) AS total_dur_ns,
35
+ COUNT(*) AS occurrences
36
+ FROM thread_state ts
37
+ JOIN thread t USING (utid)
38
+ JOIN process p USING (upid)
39
+ WHERE p.name = (SELECT target_process FROM _argent_args)
40
+ AND t.is_main_thread
41
+ AND ts.ts < (SELECT hang_end_ns FROM _argent_args)
42
+ AND ts.ts + ts.dur > (SELECT hang_start_ns FROM _argent_args)
43
+ GROUP BY state, blocked_function
44
+ ORDER BY total_dur_ns DESC;