-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript_help.txt
More file actions
206 lines (173 loc) · 9.36 KB
/
Copy pathscript_help.txt
File metadata and controls
206 lines (173 loc) · 9.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
Usage: ap-query script [-c CODE | FILE] [--timeout DURATION] [-- ARGS...]
-c CODE Execute inline Starlark code
--timeout DUR Script timeout (default: 30s)
-- ARGS... Arguments available as ARGS list
Predeclared:
ARGS list[string] — script arguments passed after --
print(value) Print to stdout (built-in)
Starlark built-ins available: len, sorted, enumerate, range, str, int, float,
list, dict, type, hasattr, min, max, abs, zip, any, all, repr, reversed.
Functions:
open(path, event="cpu", start="", end="", thread="") → Profile
Load a profile (JFR, pprof, or collapsed text). stdin: open("-").
event: cpu, wall, alloc, lock.
start/end: time window, Go duration syntax (e.g. "5s", "1m30s"). JFR only.
thread: substring match on thread name.
diff(a, b, min_delta=0.5, top=0, fqn=False) → Diff
Compare two Profiles by self%. Returns categorized changes.
top: limit entries per category (0 = unlimited).
fqn: use fully-qualified names (changes aggregation granularity).
round(x, decimals=0) → float
Round a float. Workaround for Starlark lacking %.Nf format specifiers.
ljust(value, width) → string
Left-justify (pad right with spaces). ljust("hi", 8) → "hi ".
rjust(value, width) → string
Right-justify (pad left with spaces). rjust(42, 6) → " 42".
Both stringify any type and return unchanged if already wider.
match(string, pattern) → bool
RE2 regex match on any string. has() is substring, match() is regex.
emit(stack) Write one stack in collapsed format: [thread];frame1;frame2 count
emit_all(profile) Write all stacks to stdout in collapsed format.
Both pipeable to ap-query hot -.
fail(msg) Print to stderr and exit 1.
warn(msg) Print to stderr, continue.
Types:
Profile — a loaded profile scoped to one event type
.stacks list[Stack]
.samples int — total sample count
.duration float — seconds (0 when unavailable: collapsed text, some pprof producers)
.start float — seconds from recording start (0 for root profiles)
.end float — seconds from recording start (= duration for root profiles)
.event string — selected event type
.events list[string] — all event types in the file
.path string
.hot(n=all, fqn=False, sort="self") → list[Method] Top methods. sort="total" ranks by total time.
.threads(n?) → list[Thread] Thread sample distribution.
.filter(fn(Stack)→bool) → Profile Keep matching stacks.
.group_by(fn(Stack)→string|None) → dict[str,Profile] Partition by key; None excludes.
.timeline(resolution?, buckets?) → list[Bucket] Time buckets. JFR only.
resolution: duration string ("1s") or keyword-only numeric seconds (resolution=30).
First call re-parses JFR with timestamps (expensive on large files).
.split(times list[float|string]) → list[Profile] Split at time boundaries. JFR only.
times: floats (seconds) or duration strings ("5s", "1m30s"), or mixed.
Times are relative to the profile's scope, not recording start.
start/end report absolute position; split takes scope-relative offsets.
Example: after parts = p.split([4.0]), parts[1].start is 4.0 but
parts[1].split([3.0]) splits 3s into that segment, not at absolute 3s.
.tree(method="", depth=4, min_pct=1.0) → string Call tree from method (or root).
.trace(method, min_pct=0.5, fqn=False) → string Hottest path from method.
.callers(method, depth=4, min_pct=1.0) → string Callers tree toward root.
.no_idle() → Profile Remove idle leaf frames (same as CLI --no-idle).
.summary() → string One-line summary: "event: N samples, Xs, M stacks".
Stack — frames[0] is root, frames[-1] is leaf
.frames list[Frame]
.thread string — filter by thread: use s.thread, not s.has() (has() searches frames only)
.samples int
.leaf Frame (or None)
.root Frame (or None)
.depth int
.has(pattern) → bool Substring on short name and FQN (not regex).
.has_seq(p1, p2, ...) → bool Patterns match in order (not necessarily adjacent).
.above(pattern) → list[Frame] Callees (toward leaf). above[0] = direct callee.
.below(pattern) → list[Frame] Callers (toward root). below[-1] = direct caller.
.thread_has(pattern) → bool Substring match on thread name (not frames).
Frame
.name string — short name, e.g. "HashMap.resize"
.fqn string — e.g. "java.util.HashMap.resize"
.pkg string — e.g. "java.util"
.cls string — e.g. "HashMap"
.method string — e.g. "resize"
.line int — source line (0 if unavailable)
Method — returned by hot()
.name string .self int .self_pct float
.fqn string .total int .total_pct float
Thread — returned by threads()
.name string .samples int .pct float
Bucket — returned by timeline()
.start float — seconds .end float — seconds
.samples int .stacks list[Stack]
.label string — formatted time range (e.g. "4m20.0s-4m30.0s")
.hot(n=5, sort="self") → list[Method]
.profile → Profile Full Profile wrapping bucket data.
Diff — returned by diff()
.regressions list[DiffEntry] — got worse, sorted by delta desc
.improvements list[DiffEntry] — got better, sorted by delta asc
.added list[DiffEntry] — only in second profile
.removed list[DiffEntry] — only in first profile
.all list[DiffEntry] — all above, sorted by |delta| desc
DiffEntry
.name string .fqn string
.before float .after float .delta float
Starlark notes:
- Python dialect. No try/except, no classes, no import, no for/else.
- %s, %d, %f work. No width, padding, alignment, or precision modifiers
(%-22s, %8d, %.1f all fail). Use ljust/rjust for alignment, round() for precision.
%% is only needed inside % formatting; in concatenation use plain %.
- "from" is reserved — use "start"/"end" kwargs in open().
- Sets, while, top-level if/for, recursion, global reassignment enabled.
- Dict: .get(k, default), .items(), .keys(), .values().
String: .split(), .startswith(), .join(), .replace(), .strip().
Examples:
# Hot methods (JFR)
p = open("profile.jfr")
for m in p.hot(5):
print(rjust(round(m.self_pct, 1), 6) + "% " + m.name)
# Hot methods (pprof — same API, different format)
p = open("cpu.pb.gz")
for m in p.hot(10):
print(rjust(round(m.self_pct, 1), 6) + "% " + m.name)
# Filter + emit pipeline: ap-query script -c '...' | ap-query hot -
p = open("profile.jfr")
for s in p.stacks:
if s.has("HashMap"):
emit(s)
# CI budget check
p = open("profile.jfr")
ser = p.filter(lambda s: s.has("Serialization"))
if 100.0 * ser.samples / p.samples > 10.0:
fail("serialization too high")
# Callers of a leaf method
p = open("profile.jfr")
callers = {}
for s in p.stacks:
if s.leaf.name == "__sched_yield":
below = s.below("__sched_yield")
if len(below) > 0:
c = below[-1].name
callers[c] = callers.get(c, 0) + s.samples
for name, count in sorted(callers.items(), key=lambda x: x[1], reverse=True):
print(str(count) + " " + name)
# Split at time boundaries (floats = seconds, strings = Go durations, or mixed)
parts = open("profile.jfr").split(["5s", "10s"]) # 3 profiles: [0-5s), [5-10s), [10s+)
for i, part in enumerate(parts):
print("part " + str(i) + ": " + str(part.start) + "s-" + str(part.end) + "s " + str(part.samples) + " samples")
# Group by thread pool (aggregate by name prefix)
p = open("profile.jfr")
groups = p.group_by(lambda s: s.thread.split("-")[0] if s.thread else None)
for name in sorted(groups.keys()):
print(name + ": " + str(groups[name].samples))
# Call tree and callers
p = open("profile.jfr")
print(p.tree("HashMap.get", depth=3))
print(p.callers("HashMap.get"))
# Filter by thread name (thread_has, not has() — has() searches frames only)
workers = open("profile.jfr").filter(lambda s: s.thread_has("worker"))
Caching: filter()/no_idle() profiles reuse the parent's parsed timeline data —
no re-parse. timeline()/split() on a filtered profile is cheap.
Windowing: compare time windows within a single recording.
Two approaches:
1. timeline() → bucket.profile → diff() — regular intervals.
2. split() → diff() — ad-hoc boundaries.
# Window diff via timeline: compare first vs last bucket
p = open("profile.jfr")
buckets = p.timeline(resolution="5s")
if len(buckets) >= 2:
d = diff(buckets[0].profile, buckets[-1].profile)
for e in d.regressions:
print(e.name + " +" + str(round(e.delta, 1)) + "%")
# Window diff via split: compare first half vs second half
p = open("profile.jfr")
parts = p.split([p.duration / 2])
d = diff(parts[0], parts[1])
for e in d.regressions:
print(e.name + " +" + str(round(e.delta, 1)) + "%")