@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.
- package/README.md +53 -10
- package/assets/argent.tracecfg.pbtxt +133 -0
- package/assets/queries/README.md +93 -0
- package/assets/queries/cpu-hotspots.sql +106 -0
- package/assets/queries/function-callers.sql +62 -0
- package/assets/queries/hang-folds-batched.sql +88 -0
- package/assets/queries/hang-main-thread-samples.sql +32 -0
- package/assets/queries/hang-state-breakdown.sql +44 -0
- package/assets/queries/memory-rss.sql +25 -0
- package/assets/queries/thread-breakdown.sql +34 -0
- package/assets/queries/trace-bounds.sql +6 -0
- package/assets/queries/ui-hangs.sql +59 -0
- package/assets/trace-processor/LICENSE +235 -0
- package/assets/trace-processor/SHA256SUMS +4 -0
- package/assets/trace-processor/engine.mjs +42423 -0
- package/assets/trace-processor/engine_bundle.node.js +11130 -0
- package/assets/trace-processor/trace_processor.wasm +0 -0
- package/{dist → bin}/argent-android-devtools-0.1.0.apk +0 -0
- package/bin/argent-simulator-server.cjs +56 -0
- package/bin/{ax-service → darwin/ax-service} +0 -0
- package/bin/{simulator-server → darwin/simulator-server} +0 -0
- package/bin/linux/simulator-server +0 -0
- package/dist/cli-cmds.mjs +2540 -85
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +23 -2
- package/dist/cli.js.map +1 -1
- package/dist/installer.mjs +241 -107
- package/dist/mcp-server.mjs +440 -110
- package/dist/tool-server.cjs +4297 -1895
- package/dylibs/libArgentInjectionBootstrap.dylib +0 -0
- package/dylibs/libKeyboardPatch.dylib +0 -0
- package/dylibs/libNativeDevtoolsIos.dylib +0 -0
- package/package.json +3 -3
- package/skills/argent-android-emulator-setup/SKILL.md +1 -1
- package/skills/argent-native-profiler/SKILL.md +5 -5
- /package/{dist → assets}/Argent.tracetemplate +0 -0
- /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
|
-
|
|
7
|
+
|
|
8
|
+
[](https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-argent-1&n=1)
|
|
9
|
+
[](https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-argent-2&n=1)
|
|
10
|
+
[](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
|
|
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;
|