@swmansion/argent 0.10.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 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
 
@@ -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;
@@ -0,0 +1,25 @@
1
+ -- Argent — RSS growth weak signal.
2
+ --
3
+ -- NOT real leak detection: heap-dump-based leak detection lands in a later
4
+ -- phase. The analyze step tags this row as YELLOW and renders it under its
5
+ -- own "RSS Growth — Weak Signal" header with a "manual confirmation needed"
6
+ -- caveat (see render.ts).
7
+ --
8
+ -- Placeholder (declared in the _argent_args view below): target_process —
9
+ -- package / cmdline.
10
+ -- See README.md for the shared _argent_args / template-token conventions.
11
+
12
+ INCLUDE PERFETTO MODULE android.memory.process;
13
+
14
+ DROP VIEW IF EXISTS _argent_args;
15
+ CREATE PERFETTO VIEW _argent_args AS
16
+ SELECT '{{TARGET_PROCESS}}' AS target_process;
17
+
18
+ SELECT
19
+ process_name,
20
+ MIN(anon_rss + file_rss) / 1048576.0 AS start_rss_mb,
21
+ MAX(anon_rss + file_rss) / 1048576.0 AS peak_rss_mb,
22
+ (MAX(anon_rss + file_rss) - MIN(anon_rss + file_rss)) / 1048576.0 AS growth_mb
23
+ FROM memory_oom_score_with_rss_and_swap_per_process
24
+ WHERE process_name = (SELECT target_process FROM _argent_args)
25
+ GROUP BY process_name;
@@ -0,0 +1,34 @@
1
+ -- Argent — per-thread CPU breakdown.
2
+ --
3
+ -- Powers profiler-stack-query mode=thread_breakdown for Android sessions.
4
+ -- Returns sample_count + share of total per thread.
5
+ --
6
+ -- Placeholder (declared in the _argent_args view below): target_process —
7
+ -- package / cmdline.
8
+ -- See README.md for the shared _argent_args / template-token conventions.
9
+
10
+ DROP VIEW IF EXISTS _argent_args;
11
+ CREATE PERFETTO VIEW _argent_args AS
12
+ SELECT '{{TARGET_PROCESS}}' AS target_process;
13
+
14
+ WITH per_thread AS (
15
+ SELECT
16
+ t.name AS thread_name,
17
+ t.is_main_thread AS is_main_thread,
18
+ COUNT(ps.id) AS sample_count
19
+ FROM perf_sample ps
20
+ JOIN thread t USING (utid)
21
+ JOIN process p USING (upid)
22
+ WHERE p.name = (SELECT target_process FROM _argent_args)
23
+ GROUP BY t.name, t.is_main_thread
24
+ ),
25
+ total AS (
26
+ SELECT SUM(sample_count) AS total_samples FROM per_thread
27
+ )
28
+ SELECT
29
+ thread_name,
30
+ is_main_thread,
31
+ sample_count,
32
+ ROUND(100.0 * sample_count / (SELECT total_samples FROM total), 2) AS pct_of_app
33
+ FROM per_thread
34
+ ORDER BY sample_count DESC;
@@ -0,0 +1,6 @@
1
+ -- Argent — trace timestamp anchor.
2
+ --
3
+ -- Returns the trace's earliest ts so the JS side can normalise Perfetto's
4
+ -- CLOCK_MONOTONIC timestamps to trace-relative ns.
5
+ -- See README.md ("Timestamps are CLOCK_MONOTONIC nanoseconds").
6
+ SELECT start_ts FROM trace_bounds;