Overview
A Stack represents a single call stack with its frame list, thread name, and sample count. Stacks are ordered from root (index 0) to leaf (index -1).
Access stacks via profile.stacks:
p = open("profile.jfr")
for s in p.stacks:
print(s.depth, s.samples)
Properties
List of frames from root to leaf. frames[0] is the root, frames[-1] is the leaf.for f in s.frames:
print(f.name)
Thread name for this stack. Empty string if unavailable.if s.thread:
print(s.thread) # "worker-pool-3"
Sample count for this stack.
Leaf frame (top of stack). None for empty stacks.if s.leaf:
print(s.leaf.name) # "HashMap.resize"
Root frame (bottom of stack). None for empty stacks.if s.root:
print(s.root.name) # "Thread.run"
Stack depth (number of frames).
Methods
has()
Check if any frame matches a pattern (substring match on both short name and FQN).
Substring to search for. Not a regex - use match() for regex.
if s.has("HashMap"):
print("Found HashMap")
# Matches both short name and FQN
s.has("resize") # Matches HashMap.resize
s.has("HashMap.resize") # Also matches
s.has("java.util") # Matches java.util.HashMap
has() only searches frame names, not thread names. Use thread_has() to filter by thread.
has_seq()
Check if patterns match in order (not necessarily adjacent).
has_seq(pattern1, pattern2, ...) → bool
Patterns that must appear in order from root to leaf.
# Check for Server.handle → Service.process → HashMap.put
if s.has_seq("Server.handle", "Service.process", "HashMap.put"):
print("Request path identified")
# Patterns don't need to be adjacent
s.has_seq("Thread.run", "HashMap") # Matches with frames in between
above()
Return frames above (toward leaf) from a matching frame.
above(pattern) → list[Frame]
Pattern to match. First matching frame is used.
Returns: List of frames after the match, or empty list if no match. above[0] is the direct callee.
# Stack: Thread.run → Server.handle → Service.process → HashMap.put
above = s.above("Server.handle")
# Returns: [Service.process, HashMap.put]
if len(above) > 0:
print("Direct callee: " + above[0].name) # Service.process
print("Leaf: " + above[-1].name) # HashMap.put
below()
Return frames below (toward root) from a matching frame.
below(pattern) → list[Frame]
Pattern to match. First matching frame is used.
Returns: List of frames before the match, or empty list if no match. below[-1] is the direct caller.
# Stack: Thread.run → Server.handle → Service.process → HashMap.put
below = s.below("HashMap.put")
# Returns: [Thread.run, Server.handle, Service.process]
if len(below) > 0:
print("Direct caller: " + below[-1].name) # Service.process
print("Root: " + below[0].name) # Thread.run
thread_has()
Check if thread name contains a pattern (substring match).
thread_has(pattern) → bool
Substring to search in thread name.
if s.thread_has("worker"):
print("Worker thread: " + s.thread)
# Filter to specific thread pool
workers = p.filter(lambda s: s.thread_has("worker-pool-1"))
Use thread_has() instead of has() when filtering by thread name. has() only searches frame names.
Frame Type
Each frame in stack.frames is a Frame object.
Properties
Short name (e.g. "HashMap.resize").
Fully-qualified name (e.g. "java.util.HashMap.resize").
Package name (e.g. "java.util"). Empty for native/kernel frames.
Class name (e.g. "HashMap"). Empty for native/kernel frames.
Method name (e.g. "resize"). For kernel/native frames without dots, equals the full name.
Source line number. 0 if unavailable.
Examples
f = s.leaf
print(f.name) # HashMap.resize
print(f.fqn) # java.util.HashMap.resize
print(f.pkg) # java.util
print(f.cls) # HashMap
print(f.method) # resize
print(f.line) # 742
Native/shared library frames:
# Frame: libc.so.6.__sched_yield
f = s.leaf
print(f.name) # __sched_yield
print(f.method) # __sched_yield
print(f.pkg) # (empty)
print(f.cls) # (empty)
Kernel frames:
# Frame: __do_softirq
f = s.leaf
print(f.name) # __do_softirq
print(f.method) # __do_softirq
print(f.pkg) # (empty)
Examples
Filter stacks by pattern
p = open("profile.jfr")
filtered = p.filter(lambda s: s.has("HashMap"))
print(filtered.samples)
Find request paths
# Find stacks: Server.handle → Service.process → Database.query
for s in p.stacks:
if s.has_seq("Server.handle", "Service.process", "Database.query"):
print(s.samples, s.thread)
Analyze callers
# Find what calls HashMap.resize
callers = {}
for s in p.stacks:
if s.has("HashMap.resize"):
below = s.below("HashMap.resize")
if len(below) > 0:
caller = below[-1].name
callers[caller] = callers.get(caller, 0) + s.samples
for name, count in sorted(callers.items(), key=lambda x: x[1], reverse=True)[:10]:
print(rjust(str(count), 6) + " " + name)
Filter by thread and depth
# Long stacks in worker threads
deep = p.filter(lambda s: s.thread_has("worker") and s.depth > 20)
print(deep.samples)
Emit filtered stacks
# Pipe HashMap stacks to ap-query hot
for s in p.stacks:
if s.has("HashMap"):
emit(s)
ap-query script -c '...' | ap-query hot -