SmartPerfetto Architecture Q&A
This article collects technical questions received after publishing From Trace to Insight: Harness Engineering in SmartPerfetto AI Agent and discusses them in Q&A format.
Version note (reviewed October 2026): These answers explain the April 2026 design, pinned to f3206864. Q8 lists 164 analysis definitions; the additional _base.skill.yaml template makes 165 files. Interfaces, thresholds, cache design, and timings here are historical, not current defaults or new test results. The project is now open source; consult the v1.16.1 documentation for that version and the updated companion article for historical limits.
Q1: Why build a custom YAML Skill system instead of using Claude Code’s standard Skills?
Question context: Claude Code’s Skill system supports placing deterministic scripts in a scripts/ directory to avoid LLM generalization. Since you can use scripts/ to execute fixed SQL, why build a separate YAML Skill system? Isn’t a YAML Skill essentially a tool that lets performance engineers execute SQL according to predefined rules?
Key distinction: The two Skill systems operate at different layers
Claude Code Skills and SmartPerfetto YAML Skills solve problems at different stages:
1 | Development stage (when I write code): |
This was the project’s division of responsibilities, not a limitation of standard Skills. Users or models can invoke Claude Code Skills, and the Agent SDK can load project Skills when configured (see SDK Skills documentation). Scripts can accept parameters and return JSON. SmartPerfetto chose YAML to centralize query, output-schema, and orchestration contracts in its backend engine, not because standard Skills only work during development.
Even focusing only on “deterministic execution,” YAML Skills have several targeted designs
1. Parameterized SQL, not fixed scripts
Performance analysis SQL isn’t hardcoded – the same Skill needs to accept different parameters (process names, time ranges, frame ID lists):
1 | steps: |
This teaching fragment omits input declarations. thread_state has direct utid, not track_id; clipping to a half-open window includes states starting earlier without counting time outside the window. Unfinished/invalid dur <= 0 intervals are excluded, and missing state is not zero. Resolve and validate the UTID and nanosecond bounds before engine substitution; YAML itself does not eliminate injection risk. Python/Node scripts can offer the same parameter and validation contract.
2. Self-describing output format (DataEnvelope)
1 | display: |
Each step declares column names, types, and units for DataEnvelope formatting. A duration is not necessarily always displayed in ms. Timeline navigation also needs valid identity, time units, and the relevant clickAction. Scripts returning the same structured JSON can reuse rendering; the choice concerns where that contract is maintained.
3. Composable (composite + iterator)
A composite Skill can reference multiple atomic Skills, and iterators can traverse data rows for per-frame analysis. This composition is declarative in YAML, with the Skill Engine handling orchestration. The scripts/ approach would require writing your own orchestration logic for the same composition.
4. Designed for performance engineers, not developers
The questioner got it right: YAML Skills are essentially a tool that lets performance engineers contribute analysis logic through predefined rules. Performance engineers know which SQL to query and which metrics to examine, but they don’t necessarily know TypeScript. The YAML format lets them directly define SQL queries and output formats without touching backend code. Development changes require confirming server-side reloading. New steps, rendering, or execution semantics may still require code changes.
Comparison summary
| Dimension | Standard Skill + scripts/ | SmartPerfetto YAML Skill |
|---|---|---|
| Use in this project | Mainly development then; can also integrate with Agent SDK | Express backend Skill Engine |
| Invocation | User/model invokes a Skill and its script | Agent through invoke_skill |
| Parameters | Scripts can define typed arguments and validation | Engine template substitution and input contract |
| Output | JSON, tables, or text | DataEnvelope |
| Frontend | Renderable when integrated with a structured contract | Reuses existing renderer |
| Composition | Script/tool orchestration | Engine support such as composite and iterator |
| Contribution | Understand script and contract | YAML + SQL when reusing supported capabilities |
Engine templates support ${param|default}; the pipe separates the parameter from its default. These approaches can coexist. YAML is this project’s choice of execution contract and maintenance cost.
Q2: How exactly is “deterministic + flexible” implemented?
Question context: The article says “known scenarios use Strategy files to constrain mandatory checks, but within each phase, the specific queries and deep drill directions are autonomously decided by Claude.” Where is the boundary between constraint and autonomy? How exactly is this achieved?
Three-layer mechanism working together
This hybrid design relies on three layers working in concert: Strategy files define “what must be done,” the enabled Planning Gate requires plan submission before protected calls, and Verifier examines textual/state risk signals afterward.
Layer 1: Strategy files – hard constraints alongside soft guidance
Taking the scrolling analysis scrolling.strategy.md as an example, it defines multiple analysis phases, but the constraint strength differs across phases:
Strong prompt requirements (not execution-layer enforcement):
Phase 1.9 root cause deep drill is the most strictly constrained phase, with the strategy file using red circle markers and “prohibited” language:
1 | **Phase 1.9 -- Root Cause Deep Drill (Red circle mandatory, cannot skip):** |
Soft guidance (suggested but skippable):
Phase 1.5 (architecture-aware branching) and Phase 1.7 (root cause branching) use suggestive language like “switch to” and “note,” allowing Claude to decide whether to execute based on actual data:
1 | **Phase 1.5 -- Architecture-Aware Branching:** |
The entire content of the Strategy file is injected verbatim into the System Prompt, with a hard constraint statement added at injection time:
1 | Scene Strategy (must be strictly followed) |
Claude sees these phase definitions, red circle markers, and “prohibited” language directly in the System Prompt.
“Must,” “prohibited,” and red markers express policy but do not themselves block tools or prove execution. Later text heuristics can miss omissions. Preconditions that must be enforced need explicit runtime checks. Phases and thresholds such as 15%/20%/5ms are historical strategy choices, not universal causal rules.
Layer 2: Planning Gate – forces planning first, but doesn’t limit plan content
When an analysisPlan reference is enabled, the execute_sql and invoke_skill MCP entry points require submit_plan to submit an analysis plan. Calling execute_sql or invoke_skill without submitting a plan is directly rejected:
1 | function requirePlan(toolName: string): string | null { |
The key point is: the Gate only requires a plan to exist, not that it precisely matches the Strategy’s phases. Plans still need to satisfy submit_plan’s parameter schema; within it Claude can restructure phases – it can merge Phase 1 and 1.5, add extra steps not mentioned in the Strategy, or adjust deep drill directions based on preliminary data.
When submitting a plan, the system performs scene-aware keyword checking (e.g., for scrolling scenarios, checking whether the plan mentions “frames,” “jank,” etc.), but this is only at the warning level – the plan is accepted even without these keywords.
The purpose of this design is: require a plan submission before protected tool calls (planning discipline), without restricting how it thinks (planning freedom).
This is not a global lock on every internal query: preprocessing, informational tools, and nested queries are different entry points. Quick mode without the reference disables the gate. A plan’s presence proves structured submission, not that the model thought correctly or executed the steps.
Layer 3: Verifier – multi-dimensional post-hoc checking
There can be gaps between planning and execution – Claude might submit a plan but actually skip a critical step. The Verifier performs multi-dimensional post-hoc checks after analysis completes, primarily using heuristic behavioral checks while supplementing with plan/hypothesis/scene completeness validation:
a) Scene completeness check – whether the analysis output covers the scene’s core content:
1 | // Scrolling scenario: check if significant jank exists but Phase 1.9 deep drill was skipped |
b) Hypothesis state check – whether recorded hypotheses remain formed; a status change does not establish sufficient evidence.
c) Causal chain depth check – whether CRITICAL/HIGH severity findings contain sufficient causal connectors and mechanistic terminology (heuristic text matching).
d) Optional LLM review – using an independent Haiku model for evidence support verification (can be disabled).
If the check finds ERROR-level issues, it triggers a Correction Prompt for Claude to fill in the gaps.
Note that the Verifier does not check whether Claude’s plan phases match the Strategy’s phase numbers – it checks whether “critical analysis actions are reflected in the output,” not whether “the plan format is correct.”
At this snapshot, verifySceneCompleteness reads allText built from findings and conclusion. hasDeepDrill is hasAnalysisTool || hasKnowledgeDrill; mentioning lookup_knowledge can satisfy it. Comments about real calls do not change that implementation. Causal words, model-written evidence, and resolved state do not establish actual evidence collection or causality. Model review can be skipped or time out, and at most two corrections do not guarantee coverage.
The complete constraint spectrum
Layering all three mechanisms together, different phases form a spectrum of constraint strength:
| Phase | Strategy tone | Planning Gate | Verifier check | Constraint strength |
|---|---|---|---|---|
| Phase 1 (overview) | Suggestive | Plan required | Not individually checked | Medium |
| Phase 1.5 (architecture branch) | Suggestive | – | Not checked | Low |
| Phase 1.7 (root cause branch) | Suggestive + conditional | – | Not checked | Low |
| Phase 1.9 (root cause deep drill) | Must/Prohibited | – | Matches deep-drill-related output text | High |
| Phase 2 (supplementary deep drill) | Optional | – | Not checked | None |
| Phase 3 (comprehensive conclusion) | Must cover distribution | – | Checks conclusion completeness | Medium |
Meanwhile, general.strategy.md (the fallback when no scene matches) is entirely soft guidance: it only provides a routing decision tree based on the user’s focus direction (CPU -> cpu_analysis, memory -> memory_analysis), with no mandatory phases. General still has tool, permission, budget, and enabled gate/verifier constraints.
One-sentence summary
Strategy expresses requirements, the enabled Planning Gate checks for a plan, and Verifier provides post-hoc risk signals. The model can choose queries, but describing an action is different from logged execution returning valid data.
Q3: What’s the biggest difference between Agent and Workflow? Where are the Agent’s capability boundaries and what determines them?
Question context: In building our Agent, we went from initially assuming the Agent could understand and make decisions about every Skill given to it, to now having essentially hardcoded a decision tree in the Skills. The stepping stones along the way were always: “we assumed the Agent had capability X, but it didn’t,” causing its output to deviate from our expectations, so we kept adding boundaries to the Skills until it became a hardcoded Workflow.
The essential difference: Who holds decision-making authority
Agent and Workflow aren’t two tools or two frameworks – they’re two ends of the same spectrum:
1 | Hardcoded Workflow <------------------------------------> Fully Autonomous Agent |
| Dimension | Workflow | Agent |
|---|---|---|
| Control flow | Developer hardcodes if/else in code |
LLM autonomously selects next step |
| Tool selection | Predefined execution order | LLM selects on-demand based on data |
| Branch conditions | Conditional logic in code | LLM reasoning |
| Failure handling | try/catch + retry logic |
LLM self-reflection + direction change |
| Predictability | Highly deterministic | Highly uncertain |
| Adapting to new scenarios | Developer must add branches | Can explore autonomously |
But in engineering practice, almost no one operates at either extreme of the spectrum. Workflows can include generic queries, dynamic routing, and abstention/escalation branches; Agent reliability varies by task and constraints. Real-world production systems sit somewhere in the middle.
The root cause of your pitfalls: Making global assumptions about Agent capabilities
“Defaulting to ‘Agent can understand everything’ -> discovering it can’t -> constantly adding constraints -> becoming a hardcoded Workflow” – the fundamental problem with this path is: making a one-size-fits-all judgment about Agent capabilities.
But Agent capabilities vary enormously across different tasks:
| Capability dimension | Project allocation consideration | Who should handle it |
|---|---|---|
| Intent understanding (what the user wants) | Useful with ambiguity handling | Agent (though simple scenarios can use keyword matching instead) |
| Plan formulation (how many steps, what order) | Requires task-level validation | Needs a constraint framework: Strategy files provide structure, LLM fills in details |
| Data collection (what to query) | Requires task-level validation | Semi-autonomous: Skills define what to query, Agent decides order and parameters |
| Data reasoning (attribution after seeing data) | Model self-assessment cannot prove correctness | Agent proposes explanations; evidence checks constrain attribution |
| Precise computation (numerical statistics) | Use reproducible computation | Tool system (SQL / Skill Engine) |
| Self-evaluation (knowing if it’s right) | Insufficient as acceptance evidence | External Verifier; don’t trust Agent self-assessment |
The correct approach isn’t “choose Agent globally or choose Workflow globally,” but assign by task:
1 | Scene recognition -> Workflow (deterministic logic, no LLM needed) |
This table recommends responsibilities; it is not a measured model-reliability ranking. The question alone cannot establish the other system’s sole root cause without failure cases, tool contracts, and data coverage.
SmartPerfetto’s approach: A constraint strength spectrum
SmartPerfetto doesn’t choose between Agent and Workflow; instead, it sets different constraint strengths for different analysis phases (detailed in Q2). Here we re-examine this design from the “capability boundary” perspective:
Strong prompt requirement (Phase 1.9) – addressing observed skipped deep drills:
1 | # Phase 1.9 in scrolling.strategy.md |
Why strengthen the prompt? Because we found the Agent has a systematic bias: it tends to jump straight to conclusions after getting overview data, skipping the deep drill. Overview reason_code labels can be mistaken for conclusions. Without controlled evidence, this does not establish an internal cognitive-cost or path-dependency explanation.
Less prompt constraint (Phase 1.5) – using architecture information to guide selection:
1 | # Phase 1.5 in scrolling.strategy.md |
This uses suggestive language like “switch to” and “note” without enforcement. Because the architecture detection result (Flutter/WebView/Standard) has already been placed in the system prompt by deterministic code, the Agent has routing information, but detection errors, missing signals, and wrong selections still need checks.
No fixed scene steps (general) – generic exploration within runtime boundaries:
1 | # general.strategy.md -- only a routing decision tree, no mandatory steps |
General means no specific strategy matched at entry. It does not mean the problem cannot be programmed or that all constraints vanish. Generic exploration, clarification, and an unknown outcome are available; unconditional trust in the model is not the only option.
What determines Agent capability boundaries
Model capability, task distribution, and budget affect results. The following are three engineering factors this project can directly improve, not an exhaustive capability formula:
1. Observation capability – what data can the Agent “see”
The same model, given structured L2 per-frame data from the scrolling_analysis Skill vs. writing SQL to query raw tables itself, produces significantly different analysis quality. Available data tools constrain what the Agent can analyze. SmartPerfetto uses 164 YAML Skills to encapsulate domain experts’ query logic; the Agent gets processed, structured analysis data through invoke_skill, not raw millions of trace events.
2. Constraint framework – within what bounds does the Agent make decisions
An unconstrained Agent is like an intern without a task checklist – knowledgeable enough but unsure what to do first. Strategy files, Planning Gate, and Verifier together define the Agent’s decision boundaries: Strategy tells it “at minimum, what must be done,” Planning Gate forces it to “think before acting,” and Verifier post-checks “whether the analysis is sufficient” (heuristic checks + hypothesis closure + scene completeness + optional LLM review).
3. Feedback quality – can the Agent be corrected when wrong
A significant proportion of Agent findings have issues (shallow attribution, false positives, missing critical steps). Relying solely on model self-correction has limited effectiveness. SmartPerfetto uses multi-layer verification + external correction prompts to close the loop:
1 | Verifier finds ERROR -> Generates Correction Prompt -> Triggers SDK retry |
Addendum: Strategy files are SOPs, and that’s fine
Some might point out that scrolling.strategy.md reads like an SOP (Standard Operating Procedure) – with numbered Phases, condition tables, mandatory items, and even explicit invoke_skill("scrolling_analysis", {...}). How is this different from “hardcoding a decision tree in Skills”?
Data collection borrows a Workflow-style organization. Strategy files express expert step requirements; model-driven calls remain distinct from program-controlled query/branch execution. This is intentional.
The key is understanding what the SOP covers and what it doesn’t:
What the SOP can cover (data collection) – what Strategy files do:
1 | scrolling.strategy.md: |
What the SOP does not fully cover (reasoning/attribution) – where the Agent’s value lies:
1 | - Among 47 jank frames, which share the same root cause? (Data clustering) |
Different scenarios have different SOP levels:
| Strategy file | SOP level | Reason |
|---|---|---|
scrolling.strategy.md |
High – Phase numbers + condition tables + mandatory items | Scrolling analysis methodology is most mature; common collection paths are relatively clear |
startup.strategy.md |
Medium-high – Has Phase structure, but deep drill directions are more open | Startup scenarios are more diverse (cold/warm/hot, different bottlenecks) |
anr.strategy.md |
Medium – 2-skill pipeline, but combines rules and further attribution | ANR root causes are highly diverse |
general.strategy.md |
Low – Only a routing decision tree, no mandatory items | Unmatched scenarios; use generic investigation rules |
scrolling.strategy.md has the highest SOP level because scrolling analysis methodology is the most mature. general.strategy.md has almost no SOP because user questions are completely unpredictable.
So the correct understanding is: SmartPerfetto = “SOP-driven data collection + Agent-driven reasoning/attribution.”
The SOP addresses “what data to look at, at minimum, when analyzing scrolling issues” – this question has a deterministic answer, and using an SOP is correct. The Agent addresses “how to reason about causality and organize conclusions after getting the data” – the instance varies by trace and can combine rules, statistics, and model reasoning.
Coming back to your concern that the Skills have become a Workflow: check each rule’s scope and evidence before treating that change as a step backward. Keep reliable collection and diagnostic rules fixed, and allow exploration where coverage is incomplete.
This allocation does not prevent SOPs from supporting reasoning. Clustering, severity ranking, rule-based diagnosis, and formatting can be programmed. Validated deterministic diagnoses can be useful outputs. Distinguish observations, rule judgments, and hypotheses with provenance instead of forbidding tool conclusions merely to preserve the Agent label.
One-sentence summary
Agent and Workflow mainly differ in how decision authority is allocated. Measure reliability on the target task, use failure types to decide what to program and what to delegate to a model, and retain an exit for insufficient evidence.
Q4: Does the Agent architecture need improvement from a business perspective?
Question context: Agent architecture has gone through different designs and evolutions – from the initial ReAct architecture to LangGraph’s node-based architecture. How do different Agent architecture designs affect it? When building your own business Agent, should you consider the architecture’s impact on Agent performance? For example, SmartPerfetto has added different Skill loading modes on top of the Claude Agent SDK based on business understanding.
Essential differences of three mainstream architectures
These names describe different layers rather than mutually exclusive approaches. ReAct is a reasoning/action pattern, LangGraph is a stateful graph orchestration framework, and Claude Agent SDK is a runtime library providing agent loops and tool integration.
| Name | Control/composition | Application decisions |
|---|---|---|
| ReAct | Interleaves reasoning/actions and observations; implementable in different frameworks | Tools, stopping conditions, evidence checks |
| LangGraph | Supports cycles, conditional edges, state, and dynamic routing; nodes can call models or ordinary code | State/execution boundaries, persistence, retries, human intervention |
| Claude Agent SDK | Query streams, tool/permission configuration, and session support; the application retains lifecycle/business contracts | Tool integration, permissions, result validation, cancellation |
LangGraph is not restricted to DAGs. Its official Agent examples loop between model and tool nodes, letting the model choose tools from intermediate results. Declaring nodes does not require enumerating every possible root cause.
Why SmartPerfetto chose Native SDK + custom constraint layers
SmartPerfetto chose Claude Agent SDK to reuse its agent loop, tool dispatch, and sessions, then add Strategy, Planning Gate, ArtifactStore, and Verifier. This was an engineering tradeoff, not a limitation preventing LangGraph from expressing performance analysis.
1 | SDK query stream -> application message/cancellation/timeout handling -> SSE -> frontend |
A model can choose Binder, GPU, GC, thermal, or lock investigations from data. LangGraph can implement the same model-node/tool-node loop without a new node for every combination of 21 reason codes. The original consecutive add_edge calls represented fixed edges, not a mandatory conditional-routing design, so they do not establish combinatorial path explosion.
The SDK yields iterable messages; SmartPerfetto converts them into frontend SSE. That website connection is not entirely provided automatically by the SDK. Selection should also consider persistence, recovery, tool permissions, deployment, cost, and observability.
Key business-driven architectural designs
The following are architectural improvements SmartPerfetto made based on business understanding, each directly corresponding to a business problem:
1. Conditional tool loading – reduce the Agent’s decision space
SmartPerfetto has up to 20 MCP tools total (9 always-on + 11 conditionally injected), but only a subset is injected per analysis:
1 | // claudeMcpServer.ts -- switch tool sets by mode |
Business reason: Removing irrelevant tools can reduce context and unnecessary calls, but tool count versus selection accuracy needs task-specific evaluation rather than a universal rule.
2. Sub-Agent scene gating – avoid unnecessary parallel overhead
1 | // claudeAgentDefinitions.ts -- only enable sub-agents in complex scenarios |
| Scenario | Sub-Agent configuration | Reason |
|---|---|---|
| scrolling | frame-expert + system-expert | Frame analysis and system analysis are suitable for splitting, coordinated by orchestrator |
| startup | startup-expert + system-expert | Startup phase analysis and resource contention analysis are suitable for splitting |
| anr | No sub-agent | ANR is a 2-skill pipeline; extra sub-agents would only add overhead |
Note: The actual parallelism of sub-agents depends on the SDK’s internal scheduling strategy. We design prompts for parallel evidence collection, but actual execution may be sequential.
3. Lightweight vs Full dual mode – quick Q&A doesn’t go through the full pipeline
For queries classified quick without a forced-full condition, the runtime can skip the full Planning -> Skill -> Verification pipeline. SmartPerfetto’s ClaudeRuntime performs complexity classification at the entry point:
1 | analyze(query) |
Business reason: A significant proportion of user questions are factual queries (“what’s the frame rate,” “is there an ANR”), and running the full pipeline would unnecessarily increase latency.
These are configuration sketches, not executable TypeScript. classifyQueryComplexity first forces full for selection, reference traces, existing findings, prior full analysis, and scrolling/startup/anr/interaction/scroll-response scenes. Remaining queries use Haiku classification, falling back to full on failure/timeout. “What is the frame rate?” or “Is there an ANR?” may match a forced-full scene; short wording does not guarantee quick mode. Quick lacking the full verifier does not validate its answer.
Answering “whether architecture needs business-driven improvement”
Locate bottlenecks on real tasks before adding constraints or replacing orchestration:
- Separate strategy text from mandatory execution conditions. Keep parameter validation, identity scope, budgets, and required dependencies in code; do not move all safety/correctness branches into Markdown.
- A Planning Gate can require a plan. Its effect needs failure cases and comparative evaluation; short code does not establish high reliability.
- Use tool records and returned data to establish execution. Runtime checks should also validate numbers, sources, entities, time windows, and causal evidence. Textual hints are supplementary.
- Choose tool sets and depth by complexity, while keeping missing-data, failure, and uncertainty outcomes in the fast path.
One-sentence summary
Architecture selection involves both business needs and runtime constraints. ReAct can be implemented in different frameworks, and LangGraph supports cycles and dynamic routing. This records SmartPerfetto’s SDK choice and application constraints, not a claim that another framework only handles fixed workflows.
Q5: How should a performance AI agent handle scene recognition?
Question context: We want to do scene recognition and route to the correct Skill. When building this, should we rely more on “user utterance” or “logs,” or is there a better approach? Several paths for scene recognition each have issues: code matching (keyword matching leads to results that are too broad or too narrow), LLM understanding (LLM understanding isn’t necessarily accurate), log reconstruction (can filter for scrolling presence, but that may not be what the user cares about).
SmartPerfetto’s approach: Three signal layers with clear division of labor
SmartPerfetto’s scene recognition doesn’t rely on a single signal source but uses three signal layers working together, each solving a different problem:
1 | Layer 1: User utterance -- keyword matching -> scene type (scrolling / startup / anr / ...) |
Layer 1: User utterance (keyword matching, <1ms)
1 | // sceneClassifier.ts -- 46 lines of code, handles all scene classification |
Keywords are defined in each Strategy file’s YAML frontmatter, not hardcoded in TypeScript:
1 | # scrolling.strategy.md frontmatter |
Why was keyword matching chosen then?
- Cost: This step makes no model call.
<1msis a historical target/observation; LLM latency and tokens vary with model, network, and prompt, not a fixed 500ms/500 tokens. - Inspectability: Fixed inputs/rules make routing reproducible, not necessarily correct.
- Scope: Explicit scene words are useful; negation, multiple intents, and follow-ups can still misroute and need correction paths.
What about cases where keyword matching falls short?
Keyword matching does have boundaries – when a user says “why is this app slow,” keywords can’t determine whether it’s slow startup or slow scrolling. SmartPerfetto handles this by: falling back to the general scene when no match is found, letting the Agent autonomously choose a direction within the general strategy’s routing decision tree.
1 | # general.strategy.md -- no hard constraints, only routing suggestions |
The core idea of this design is: don’t try to achieve 100% accurate classification at the entry point; instead, let accurate cases take the fast path (keywords -> Strategy), and uncertain cases take the exploration path (general -> Agent autonomous routing).
Layer 2: Trace data – architecture detection (deterministic code)
Scene classification only resolves “what the user wants to analyze,” but the same scenario (e.g., scrolling) requires completely different analysis paths under different rendering architectures:
| Architecture | Rendering pipeline | Analysis differences |
|---|---|---|
| Standard Android | UI Thread -> RenderThread -> SurfaceFlinger | Dual-track analysis of main thread + RenderThread |
| Flutter TextureView | 1.ui -> 1.raster -> JNISurfaceTexture -> RenderThread updateTexImage | Dual-pipeline; need to analyze Flutter engine threads + texture bridging |
| Flutter SurfaceView | 1.ui -> 1.raster -> BufferQueue -> SurfaceFlinger | Single pipeline; doesn’t go through RenderThread |
| WebView | CrRendererMain -> Viz Compositor | Chromium rendering pipeline; different thread names |
| Compose | UI Thread (Composition) -> RenderThread | Similar to Standard but with Composition phase |
Architecture detection calls rendering_pipeline_detection to collect and score signals. TypeScript still contains if/else mappings from pipeline IDs to broad types plus fallback handling. architectureDetector.ts can fall back to STANDARD with 0.5 confidence on failure/missing results; that is neither successful identification nor a calibrated 50% probability. Pipeline labels are heuristic classifications, not independent architecture ground truth.
1 | rendering_pipeline_detection skill (SQL) |
Detection results are injected into the system prompt (via templates like arch-flutter.template.md), and the Agent selects the corresponding analysis tools after seeing the architecture information.
Layer 3: Data completeness – capability register
Different trace capture configurations yield different available data dimensions. Some traces lack GPU frequency data; others lack thermal zone data. SmartPerfetto probes the availability of 18 data dimensions before analysis begins:
1 | available: primary table has at least 3 rows |
This information is likewise injected into the system prompt, telling the Agent which dimensions can be analyzed and which lack data. These hints can reduce some blind queries, but do not prove global completeness.
The prober counts a subquery limited to three rows; rowEstimate=3 means at least three, not totals such as 456/12000. GPU probes gpu_slice; the thermal capability uses android_dvfs_counters, not a count of four thermal zones. Unloaded modules, absent scenes, query failures, and missing capture can all appear empty. The old fallback records individual probe failures as zero; interpretation must retain unknown. Probes also lack target-process/selection scope, and architecture exclusions do not prove Flutter/WebView lacks FrameTimeline.
The rendering table is a historical sketch omitting stages such as GPU/HWC. Flutter thread names vary by engine; TextureView/SurfaceView is a separate dimension from Skia/Impeller. Compose is not a separate display pipeline, and WebView rendering is not confined to CrRendererMain.
Evaluating the three paths from the question
1. Code matching (keywords): Viable, but needs well-designed fallback
The questioner said “keyword matching leads to results that are too broad or too narrow.” SmartPerfetto’s experience:
- Priority ordering chooses among multiple matches: ANR(1) > startup(2) > scrolling(3); when multiple keywords match simultaneously, take the highest priority
- Compound patterns improve precision:
/startup.*slow/is more precise than matching “startup” alone generalfallback handles no-match cases: when nothing matches, don’t guess – hand it to Agent for autonomous exploration
2. LLM understanding: Not recommended for the classification entry point; can be used in the fallback path
LLM classification in SmartPerfetto isn’t Layer 1 but rather the Agent’s autonomous routing within the general scenario – at that point, the Agent already has trace data and can make more accurate judgments combined with the data.
3. Log reconstruction: Suitable as a supplementary signal for Layer 2
Logs can tell you “what’s in the trace” (whether scrolling events exist, whether ANR exists), but can’t tell you “what the user cares about.” SmartPerfetto’s data completeness probing plays exactly this role – it doesn’t participate in scene classification but provides the Agent with data availability information.
Priority resolves which match wins, not whether it is correct. Compound patterns run first within each scene; a broad keyword in a higher-priority scene can still win. General handles no-match, not false-positive matches. Distinguish user intent from recorded events and evaluate LLM routing by accuracy, latency, and cost. A non-LLM scene classifier does not mean the adjacent quick/full classifier also avoids models.
One-sentence summary
Intent routing, architecture identification, and data availability are different problems. Keyword routing is inspectable but can misroute; architecture scores and three-row probes are only clues. Keep correction paths and confirm analysis preconditions using the target process, time window, and actual data.
Q6: How to better leverage AI autonomous exploration in “deterministic steps + AI exploration”?
Question context: We’ve found in production that when AI explores autonomously and drills into root causes, it tends to go off track and give incorrect results. For example, in the SmartPerfetto blog post example – after identifying RenderThread being blocked by Binder in the earlier steps (based on deterministic steps), are the subsequent hypothesis formations and validations pure AI, or do we give the AI some common causes as guidance for Binder blocking and let it investigate on its own?
First, answering the core question: It’s neither pure AI nor hardcoded guidance
SmartPerfetto’s approach is structured reasoning framework + on-demand knowledge injection:
1 | Deterministic steps produce data |
Three key mechanisms make AI autonomous exploration more reliable:
Mechanism 1: Hypothesis management tools – adding structure to the reasoning process
Hypothesis tools record inspectable analytical claims; they do not expose or force the model’s private reasoning. The f3206864 parameters differ from the original examples:
1 | { |
The tool/arguments wrapper is for readability; the MCP client sends the actual protocol request. The tool returns hypothesisId. A resolution after contradictory evidence could be:
1 | { |
Replace h1 with the returned ID. Historical status permits only confirmed/rejected, not outcome: refined; submission uses statement/basis rather than description/expected_evidence. Do not force either outcome when evidence is insufficient: preserve unresolved state, explain limits, and form a new hypothesis where appropriate. Resolution stores model-supplied evidence text; it does not run SQL to prove it.
Mechanism 2: Knowledge injection – not hardcoded guidance, but on-demand domain knowledge loading
“Could we give the AI some common causes as guidance for Binder blocking?”
Yes, but not hardcoded in Skills. Instead, it’s loaded on demand through the lookup_knowledge MCP tool. After discovering Binder blocking, the Agent can call:
1 | invoke lookup_knowledge("binder-ipc") |
This returns a Binder IPC knowledge template (knowledge-binder-ipc.template.md) containing:
- Classification of typical Binder transaction blocking causes (server-side busy, process frozen, CPU scheduling delay, oneway queue full)
- Investigation paths and key metrics for each cause
- Common misdiagnosis scenarios (e.g., remote oneway does not await server completion, but submission/serialization still has costs and failure modes)
Key design: Knowledge is actively pulled by the Agent, not force-injected by the system. There are currently 8 knowledge templates (rendering-pipeline, binder-ipc, gc-dynamics, cpu-scheduler, thermal-throttling, lock-contention, startup-root-causes, data-sources). If all templates were pre-injected into the system prompt, it would consume a large number of tokens and most would be irrelevant. Through on-demand loading via MCP tools, the Agent only retrieves the relevant domain background knowledge when needed.
SmartPerfetto also provides conditional deep drill suggestion tables in Strategy files, which is another form of guidance:
1 | # scrolling.strategy.md Phase 1.9 |
This table isn’t a hardcoded decision tree – it’s a lookup table for the Agent. The Agent decides which row to follow based on metric values in the data. If the data doesn’t match any row, the Agent can explore autonomously.
Mechanism 3: ReAct Reasoning Nudge – triggering reflection when tools return results
During the first few successful returns from data tools (execute_sql / invoke_skill), SmartPerfetto appends a reasoning prompt at the end of the result:
1 | // claudeMcpServer.ts |
The appended prompt can remind the model to record intermediate judgments; it does not guarantee reflection or prevent drift. The original ~20 tokens/call and ~80 total lack tokenizer/usage evidence, and Chinese/English text need not have equal token counts. Four is the code’s append limit, not measured effectiveness. Use comparative evaluation.
Walking through the complete flow with the Binder example from the article
This is a teaching hypothesis, not a trace result reproduced by the article’s images. The original “47 frames, Frame #234, 23ms, frequency limited to 50%” lacks accompanying raw data and cannot establish the causal chain.
1 | 1. Query the target process/window, defining frame counts and missing data. |
Scheduling delay can be part of a slow Binder response, not evidence against that response being slow. Low frequency plus high temperature does not establish that thermal control caused the jank. Use the actual hypothesis schema above; an already rejected hypothesis cannot be changed to a nonexistent refined status.
Four practical recommendations for making AI autonomous exploration more reliable
- Separate observations, rule judgments, and hypotheses: Retain structured data and provenance. Validated rule diagnoses are useful but are not raw observations.
- Keep deterministic boundaries: Enforce budgets, identity scope, arithmetic, and required query dependencies in code; allow model routing for open questions.
- Use knowledge as investigative guidance: Versioned background knowledge is not evidence from the current trace; query the corresponding data.
- Validate conclusions at runtime too: Check numbers, entities, references, time windows, and causal chains. Tool logs establish execution; text heuristics add signals. Labeled datasets and LLM Judges can aid offline evaluation, but a Judge is not independent ground truth.
One-sentence summary
Hypothesis records, retrieval, and reminders help organize exploration without guaranteeing correctness. Reliability still requires actual tool results, traceable evidence, and appropriate stopping conditions; keep insufficiently supported claims unresolved.
Q7: How is the Prompt assembled for each turn?
Question context: LLM Agent output quality depends heavily on system prompt design. How does SmartPerfetto construct the prompt for each analysis? How does the prompt change across different scenarios and different turns? How is the token budget controlled?
Overall design: Four-tier layered assembly + cache optimization
buildSystemPrompt() organizes context at analysis start and in some correction paths; it does not rebuild the entire system prompt after every tool call. Relatively stable content goes first to increase reusable prefixes.
The old source comment “above 1024 tokens it is automatically cached” is an oversimplification, not an API contract. Official Prompt caching documentation distinguishes automatic caching configuration from explicit breakpoints. Request/SDK behavior, model-specific minimum length, matching prefixes, and expiry all matter. Current documentation provides a default five-minute and optional one-hour TTL, separate from SDK session lifetime.
The original 4000/800/400 cached-token and ~80%/18%/~8% savings figures lack usage records and mix cache proportion with price savings. Caching does not remove logical context. Inspect cache_creation_input_tokens, cache_read_input_tokens, and ordinary input/output separately, then calculate cost using the applicable model prices. Token figures in the structural diagram below are estimates.
Four-tier assembly structure
1 | +-------------------------------------------------------+ |
Template loading and variable substitution
Most fixed rules come from Markdown templates; TypeScript still constructs selection, findings, notes, and budget trimming. The following shows template loading:
1 | // strategyLoader.ts -- template system |
Template file inventory:
| Category | File | Purpose |
|---|---|---|
| Static templates | prompt-role.template.md |
Role definition |
prompt-output-format.template.md |
Output format rules (91 lines) | |
prompt-quick.template.md |
Quick mode streamlined prompt | |
| Methodology | prompt-methodology.template.md |
Analysis methodology (contains {{sceneStrategy}} placeholder) |
| Architecture guides | arch-standard.template.md |
Standard Android rendering guidance |
arch-flutter.template.md |
Flutter engine guidance | |
arch-compose.template.md |
Jetpack Compose guidance | |
arch-webview.template.md |
WebView guidance | |
| Selection templates | selection-area.template.md |
Time range selection ({{startNs}}, {{endNs}}…) |
selection-slice.template.md |
Slice selection ({{eventId}}, {{ts}}…) |
|
| Comparison mode | comparison-methodology.template.md |
Dual-trace comparison methodology |
| Scene strategies | 12 *.strategy.md files |
scrolling/startup/anr/memory/… |
| Knowledge templates | 8 knowledge-*.template.md files |
On-demand domain knowledge (not injected into prompt) |
| Auxiliary templates | prompt-complexity-classifier.template.md |
Quick/Full routing decision (not injected into prompt, but determines which path to take) |
Token budget management
Soft budget target: 4500 tokens (MAX_PROMPT_TOKENS). During correction retries, if SDK auto-compact is detected (conversation history automatically compressed), the budget is reduced to 3000 tokens to leave room; otherwise the original prompt is reused.
Token estimation method: Mixed Chinese-English estimation – Chinese characters at 1.5 tokens/character, ASCII at 0.3 tokens/character. This project approximation is not measured tokenizer usage or billing data; estimation error can affect budgets.
Progressive dropping strategy when over budget:
When the assembled prompt’s token count exceeds the budget, entire sections are dropped in order from lowest to highest priority:
1 | Drop order (dropped first -> dropped last): |
Content that is never dropped:
- Role definition, output format (Tier 1 static)
- Architecture info, focus application (Tier 2 per-trace)
- Methodology + scene strategy (Tier 3 core)
- User selection context (user’s explicit intent)
- Conversation context (previous findings and analysis notes)
If optional-section removal still leaves the prompt over budget, f3206864 logs a warning and returns it. Thus 4500/3000 are not hard caps and do not represent all history, tool definitions, or output budget. Tool results must retain missing/unknown data even when completeness guidance is dropped; absent warnings do not prove completeness.
Complete context construction flow
In ClaudeRuntime.analyze(), prompt assembly is preceded by over twenty preparation phases to collect all context:
1 | Phase 0: Selection context logging |
All Phase results feed into the ClaudeAnalysisContext object, passed to buildSystemPrompt() for final assembly.
These Phase numbers identify preparation steps in this source snapshot, not an external protocol requiring serial completion. LRU eviction, restoration, and failures can trigger new probes; restored state also means the first visible turn need not start with empty context.
Quick vs Full dual mode
Not all queries need the full 4500-token prompt. When entry routing selects quick, SmartPerfetto uses a streamlined quick prompt:
1 | // buildQuickSystemPrompt() -- ~1500 tokens |
| Dimension | Quick Mode | Full Mode |
|---|---|---|
| Target tokens | ~1500 | ~4500 |
| Scene strategy | None | One of 12 |
| Methodology | None | prompt-methodology.template.md |
| Conversation context | None | findings + notes + entity + summary |
| Planning Gate | None | Yes |
| Verifier | None | Yes |
| Use case | Simple queries classified quick after forced-full checks | “Analyze scrolling stuttering” “Analyze startup performance” |
This compares explicitly assembled system prompts. Quick runtime may still pass SDK resume, so no injected conversation context does not mean the SDK sees no history. Q4 describes the actual routing checks; not every factual question takes the quick path.
How the prompt changes across multi-turn conversations
During context preparation, the project checks an SDK ID and timestamp in its local sessionMap. SDK_SESSION_FRESHNESS_MS = 4 * 60 * 60 * 1000 is an application freshness heuristic, not proof that Anthropic sessions universally expire after four hours.
- Locally considered resumable: Skip manually injected previousFindings/conversationSummary while retaining available notes, entities, and plans. This decision is not confirmation that SDK resume succeeded.
- Locally old/unavailable: Inject partial findings and summaries. Summaries and item caps can lose details.
- Correction: Detected auto-compaction can lower the system-prompt soft budget to 3000; otherwise reuse the original prompt. Compacted history is not the original complete message set.
The SDK session documentation describes disk persistence and ID-based restoration. Availability also depends on session files, environment, and SDK configuration; prefix-cache hits do not verify successful session restoration. Follow-ups can change scene, selection, or focus, so Tiers 1–3 need not stay unchanged and have no fixed 80% hit rate.
A concrete example: Prompt assembly for scrolling analysis
User inputs "Analyze scrolling stuttering", Flutter TextureView architecture, Turn 1:
1 | [Tier 1] prompt-role.template.md -> "You are an Android performance analysis expert..." |
User follows up with "Deep dive into frame 3", Turn 2 (SDK session resume hit):
1 | [Tier 1-3] Same as Turn 1 (unchanged content may be reusable; not measured) |
This is a teaching assembly example: 92% is an illustrative score, and 3200/3400 are estimates, not calibrated probabilities or billing records from a user session. Recheck tokens and output contracts when placeholders, strategy content, or model configuration change.
One-sentence summary
Stable prefixes can help reuse, but caching depends on model/API conditions and measured usage; session restoration is separate. The 4500/3000 targets are soft budgets. Trimming and summaries can lose information, so preserve user scope and missing-evidence semantics.
Q8: What Skills does SmartPerfetto have?
Question context: SmartPerfetto’s analysis capabilities are carried by YAML Skills. A complete Skill inventory helps understand the system’s analysis coverage.
Overview
This inventory checks definition names/directories at f3206864 and restores five omitted atomic entries. A definition’s existence does not mean every trace provides data or executes successfully: modules, recording, platform, and parameters still matter. Labels such as v3.0 identify Skill versions, not Android versions. Installed versions can have different inventories.
| Category | Count | Description |
|---|---|---|
| Atomic | 87 | Single-step detection/statistics, completed with one or a few SQL statements |
| Composite | 29 | Combines multiple atomic skills, supports iterator/conditional |
| Deep | 2 | Deep profiling (callstack, CPU profiling) |
| Pipeline | 28 | Rendering pipeline detection + teaching (24+ architectures) |
| Module | 18 | Modular configuration: app/framework/hardware/kernel |
| Total | 164 |
Atomic Skills (87)
Single-step data extraction and detection – the building blocks for all higher-level Skills.
Frame rendering and jank:
| Skill ID | One-line description |
|---|---|
| consumer_jank_detection | Historical frame-interval heuristic, not independent perceptual-jank ground truth |
| frame_blocking_calls | Identify blocking calls during each jank frame (GC, Binder, locks, IO) |
| frame_production_gap | Detect frame production gaps: gaps between consecutive frames exceeding 1.5x VSync |
| frame_pipeline_variance | Detect frame duration jitter and high-variance intervals |
| render_pipeline_latency | Break down latency across all stages of the frame rendering pipeline |
| render_thread_slices | Analyze RenderThread time slice distribution |
| app_frame_production | Analyze application main thread frame production |
| sf_frame_consumption | Analyze SurfaceFlinger frame consumption |
| sf_composition_in_range | Analyze SurfaceFlinger composition latency |
| sf_layer_count_in_range | Count active SF layers within a time range |
| present_fence_timing | Correlate recorded present-fence timing with verified endpoint semantics |
| game_fps_analysis | Game-specific frame rate analysis, supporting fixed frame rate modes |
VSync and refresh rate:
| Skill ID | One-line description |
|---|---|
| vsync_period_detection | Detect VSync period, return refresh rate and confidence |
| vsync_config | Read or estimate periods; distinguish fallback defaults from measurement |
| vsync_alignment_in_range | Analyze frame-to-VSync signal alignment |
| vsync_phase_alignment | Analyze input event to VSync phase relationship, locating touch-to-display latency bottlenecks |
| vrr_detection | Inspect cadence-change clues, not proof of LTPO hardware or complete VRR support |
CPU and scheduling:
| Skill ID | One-line description |
|---|---|
| cpu_topology_detection | Infer core groups from recorded frequency information; verify device topology |
| cpu_topology_view | Create reusable SQL VIEW _cpu_topology |
| cpu_slice_analysis | Analyze CPU time slice distribution (with dynamic topology detection) |
| cpu_load_in_range | Analyze per-CPU core load within a specified time range |
| cpu_cluster_load_in_range | Calculate overall CPU load percentage for big and little core clusters |
| cpu_freq_timeline | Analyze per-CPU core frequency change timeline |
| cpu_idle_analysis | Inspect recorded CPU-idle states/intervals; assess impact with wakeup timing |
| cpu_throttling_in_range | Inspect frequency-limit candidates; low frequency alone does not prove thermal control |
| sched_latency_in_range | Analyze thread scheduling wait time distribution, detecting CPU contention |
| scheduling_analysis | Analyze thread scheduling latency (Runnability) |
| task_migration_in_range | Analyze thread migration frequency between big and little cores |
| thread_affinity_violation | Inspect main/RenderThread migration; migration alone is not an affinity violation |
| thermal_predictor | Frequency-trend heuristic, not a calibrated thermal-throttling forecast |
| util_tracking_analysis | Correlate early main-thread running/frequency windows; not proof of WALT/PELT lag |
| cache_miss_impact | Count cache-miss counters and evaluate fluctuation |
GPU:
| Skill ID | One-line description |
|---|---|
| gpu_render_in_range | Analyze GPU rendering duration and Fence wait |
| gpu_freq_in_range | Analyze GPU frequency changes |
| gpu_metrics | Analyze GPU frequency, utilization, and rendering performance |
| gpu_power_state_analysis | Analyze GPU frequency state transitions, identifying frequency reduction pressure and jitter |
| gpu_frequency_analysis | Summarize GPU-frequency samples; verify units, weighting, and thermal inference |
Main thread analysis:
| Skill ID | One-line description |
|---|---|
| main_thread_states_in_range | Count main thread states, blocking functions, and percentages within a range |
| main_thread_slices_in_range | Count main thread slice duration distribution within a range |
| main_thread_sched_latency_in_range | Count main thread Runnable wait time distribution |
| main_thread_file_io_in_range | Count main thread file IO related slice durations within a range |
Binder IPC:
| Skill ID | One-line description |
|---|---|
| binder_in_range | Analyze Binder transactions within a specified time range |
| binder_blocking_in_range | Analyze counterpart process response delays in synchronous Binder calls |
| binder_root_cause | Perform server/client-side blocking cause attribution for slow Binder transactions |
| binder_storm_detection | Detect Binder transaction storms: too many IPC calls in a short period |
Locks and synchronization:
| Skill ID | One-line description |
|---|---|
| lock_contention_in_range | Analyze lock contention within a specified time range |
| futex_wait_distribution | Inspect futex-related waits; not every futex wait is mutex contention |
Startup-specific (19):
| Skill ID | One-line description |
|---|---|
| startup_events_in_range | Query startup events and TTID/TTFD metrics |
| startup_slow_reasons | Startup slow reasons (Google official classification + self-check) v3.0 |
| startup_critical_tasks | Query recorded active threads in the startup window, sorted by CPU time |
| startup_thread_blocking_graph | Build waker associations; not automatic proof of lock ownership |
| startup_jit_analysis | Analyze JIT compilation thread impact on startup speed |
| startup_cpu_placement_timeline | Analyze main thread core type changes by time bucket, detecting stuck-on-little-core during startup |
| startup_freq_rampup | Analyze CPU frequency ramp-up speed during cold start, detecting frequency scaling delays |
| startup_binder_pool_analysis | Inspect observed Binder threads/transactions; saturation needs queuing/wait evidence |
| startup_hot_slice_states | Analyze thread state distribution of Top N hot slices during startup interval |
| startup_main_thread_states_in_range | Count main thread Running/Runnable/Blocked percentages during startup |
| startup_main_thread_slices_in_range | Count main thread slice hotspots during startup |
| startup_binder_in_range | Count Binder call distribution during startup |
| startup_main_thread_file_io_in_range | Count main thread file IO during startup |
| startup_sched_latency_in_range | Count main thread Runnable wait latency during startup |
| startup_main_thread_sync_binder_in_range | Count main thread synchronous Binder duration during startup |
| startup_main_thread_binder_blocking_in_range | Analyze main thread synchronous Binder blocking details during startup |
| startup_breakdown_in_range | Count attribution reason time percentages during startup |
| startup_gc_in_range | Count GC slices and main thread percentage during startup |
| startup_class_loading_in_range | Count class loading slice durations during startup |
Memory and GC:
| Skill ID | One-line description |
|---|---|
| gc_events_in_range | Query GC events for a given process and optional time range |
| memory_pressure_in_range | Analyze memory pressure metrics within a specified time range |
| page_fault_in_range | Analyze Page Fault and memory reclaim impact on performance |
Input and touch:
| Skill ID | One-line description |
|---|---|
| input_events_in_range | Extract raw input events within a range, analyzing dispatch latency |
| input_to_frame_latency | Estimate latency from recorded input/frame associations; verify mapping and missing events |
| touch_to_display_latency | Estimate input-to-presentation latency from recorded endpoints, not optical touch latency |
| scroll_response_latency | Measure response latency from scroll gesture input to first frame rendering |
System and device:
| Skill ID | One-line description |
|---|---|
| system_load_in_range | Analyze overall system CPU utilization and process activity |
| device_state_snapshot | Capture device environment info during trace (screen, battery, temperature, etc.) |
| device_state_timeline | Track device state changes over time |
| wakelock_tracking | Inspect recorded Wake Lock holding; duration alone does not measure battery drain |
Others:
| Skill ID | One-line description |
|---|---|
| blocking_chain_analysis | Analyze main thread blocking chain: what blocked the main thread? What was the waker doing? |
| anr_main_thread_blocking | Deep analysis of main thread blocking cause during ANR |
| anr_context_in_range | Extract first ANR event data as time window anchor |
| app_lifecycle_in_range | Track Activity/Fragment lifecycle events |
| compose_recomposition_hotspot | Detect Jetpack Compose recomposition hotspots |
| webview_v8_analysis | Analyze WebView V8 engine: GC, script compilation, execution time |
| rendering_pipeline_detection | Identify application rendering pipeline type (24 fine-grained detection types) |
| pipeline_key_slices_overlay | Query pipeline key Slice ts/dur for timeline overlay |
| fpsgo_analysis | Summarize FPSGO/FSTB/FBT-like slice names; name matches can be false positives |
| logcat_analysis | Filter captured Logcat for ANR/GC/Binder/StrictMode warning clues |
Composite Skills (29)
Combine multiple atomic skills, supporting iterator (per-frame/per-event deep drill) and conditional (data-driven branching).
| Skill ID | One-line description |
|---|---|
| scrolling_analysis | Scrolling analysis main entry: overview -> frame list -> root cause classification -> per-frame diagnosis |
| flutter_scrolling_analysis | Flutter-specific frame analysis, using Flutter thread model |
| jank_frame_detail | Analyze a specific jank frame in detail: deep drill into jank cause and root cause classification |
| startup_analysis | Startup analysis main entry: Iterator mode, big/little core analysis, four-quadrant |
| startup_detail | Analyze a single startup event: main thread duration, Binder, CPU big/little core ratio |
| anr_analysis | ANR v3.0 analysis: system issue vs. app issue, categorized handling |
| anr_detail | Associate ANR thread states/Binder dependencies; deadlock needs additional evidence |
| cpu_analysis | CPU analysis: time distribution, big/little core analysis, scheduling chain |
| gpu_analysis | GPU analysis: frequency distribution, memory usage, frame rendering correlation |
| memory_analysis | Memory analysis: GC events, GC-to-frame correlation, thread states |
| gc_analysis | GC analysis: based on stdlib android_garbage_collection_events |
| binder_analysis | Binder deep analysis: transaction basics, thread states |
| binder_detail | Single Binder transaction detail: CPU big/little core, four-quadrant, blocking cause |
| thermal_throttling | Temperature monitoring, thermal throttling detection, CPU frequency correlation |
| lock_contention_analysis | Lock contention multi-dimensional analysis: based on android.monitor_contention |
| surfaceflinger_analysis | SF frame composition performance: GPU/HWC composition ratio, slow composition detection |
| click_response_analysis | Click response analysis: based on stdlib android_input_events |
| click_response_detail | Single slow input event detail: latency breakdown, four-quadrant, main thread blocking |
| scroll_session_analysis | Single complete scroll session: Touch phase vs Fling phase FPS |
| navigation_analysis | Activity/Fragment navigation performance: lifecycle, transition animations |
| lmk_analysis | LMK analysis: cause distribution, timeline, frequency |
| dmabuf_analysis | Inspect DMA-buffer events and unmatched candidates, not proof of a leak |
| block_io_analysis | Block IO analysis: device-level statistics, queue depth, long-duration IO |
| io_pressure | IO blocking data detection, IO Wait time, severity assessment |
| suspend_wakeup_analysis | Suspend/wakeup analysis: time distribution, wakeup source ranking |
| network_analysis | Network analysis: traffic overview, per-app traffic, protocol distribution |
| irq_analysis | Hard interrupt and soft interrupt frequency, duration, nesting |
| scene_reconstruction | Reconstruct user operation scenarios through user input and screen state |
| state_timeline | Four-lane continuous state timeline: device/user/app/system |
The engine supports conditional steps, but this snapshot’s definitions do not use that step type. Separate supported types from present execution paths. Output classifications can still be heuristic; a root_cause name does not establish causality.
Deep Skills (2)
Deep profiling, typically requiring longer execution time.
| Skill ID | One-line description |
|---|---|
| cpu_profiling | CPU performance profiling: usage hotspots and scheduling efficiency deep analysis |
| callstack_analysis | Call stack hotspot analysis in Running state |
Pipeline Skills (28)
Rendering pipeline detection + teaching. Each pipeline skill corresponds to a rendering architecture, including pipeline description, key threads, performance metrics, and optimization recommendations.
| Skill ID | Rendering architecture |
|---|---|
| pipeline_android_view_standard_blast | Common Android 12+ HWUI/BLAST path; verify version/device evidence |
| pipeline_android_view_standard_legacy | Legacy HWUI/BufferQueue path; Android version alone is insufficient |
| pipeline_android_view_software | CPU Skia software rendering, no RenderThread |
| pipeline_android_view_mixed | View + SurfaceView mixed rendering |
| pipeline_android_view_multi_window | Same-process multi-window (Dialog/PopupWindow) |
| pipeline_android_pip_freeform | Picture-in-Picture and freeform window mode |
| pipeline_compose_standard | Jetpack Compose + HWUI RenderThread |
| pipeline_flutter_textureview | Flutter hosted through TextureView, not necessarily a PlatformView fallback |
| pipeline_flutter_surfaceview_skia | Flutter + Skia engine (JIT Shader) |
| pipeline_flutter_surfaceview_impeller | Flutter SurfaceView + Impeller; runtime pipeline/cache work still needs inspection |
| pipeline_webview_gl_functor | Traditional WebView, App RenderThread synchronous wait |
| pipeline_webview_surface_control | Modern WebView + Viz/OOP-R independent composition |
| pipeline_webview_textureview_custom | X5/UC and other custom WebView engines |
| pipeline_webview_surfaceview_wrapper | WebView fullscreen video wrapper mode |
| pipeline_chrome_browser_viz | Chrome Viz compositor, multi-process architecture |
| pipeline_opengl_es | Direct OpenGL ES / EGL rendering |
| pipeline_vulkan_native | Native Vulkan rendering |
| pipeline_angle_gles_vulkan | ANGLE: OpenGL ES -> Vulkan translation layer |
| pipeline_game_engine | Unity/Unreal/Godot and other game engines |
| pipeline_surfaceview_blast | Standalone SurfaceView + BLAST sync |
| pipeline_textureview_standard | SurfaceTexture texture sampling/composition mode |
| pipeline_camera_pipeline | Camera2/HAL3 multi-stream camera rendering |
| pipeline_video_overlay_hwc | HWC video layer hardware-accelerated overlay |
| pipeline_hardware_buffer_renderer | Android 14+ HBR API direct Buffer rendering |
| pipeline_surface_control_api | NDK SurfaceControl direct transaction submission |
| pipeline_variable_refresh_rate | VRR/ARR + FrameTimeline dynamic refresh rate |
| pipeline_imagereader_pipeline | ImageReader API: ML inference, screen recording, custom camera |
| pipeline_software_compositing | Teaching definition for software-composition candidates, not proof of a universal SF CPU fallback |
Note:
_base.skill.yamlis the base template file for Pipeline Skills, not registered as an available Skill, and not counted in the total.
Module Skills (18)
Modular analysis configuration, organized by layer. The Agent discovers them via list_skills and invokes on demand.
Hardware layer (5):
| Skill ID | One-line description |
|---|---|
| cpu_module | CPU frequency, thermal throttling, and power states |
| gpu_module | GPU rendering, frequency, and VRAM usage |
| memory_module | Memory bandwidth, LMK, dmabuf, PSI, page faults |
| thermal_module | Temperature sensors, thermal throttling detection, cooling policy |
| power_module | Wake Lock, CPU idle, power mode, suspend/wakeup |
Framework layer (6):
| Skill ID | One-line description |
|---|---|
| surfaceflinger_module | Frame rendering timing, jank causes, GPU composition |
| choreographer_module | VSync signal, doFrame callbacks, frame production pipeline |
| ams_module | Application lifecycle, process management, startup timing |
| wms_module | Window animations, Activity transitions, multi-window |
| art_module | GC, JIT compilation, and memory allocation |
| input_module | Touch latency, input dispatch, and click response |
Kernel layer (4):
| Skill ID | One-line description |
|---|---|
| scheduler_module | Thread scheduling latency, CPU utilization, big/little core assignment |
| binder_module | Cross-process calls, blocking transactions, call latency |
| lock_contention_module | Mutex/Futex, Java monitor, deadlock detection |
| filesystem_module | Block IO, file operations, database, SharedPreferences |
Application layer (3):
| Skill ID | One-line description |
|---|---|
| launcher_module | Home screen performance, app launch, widget updates |
| systemui_module | Status bar, notification shade, quick settings, navigation bar |
| third_party_module | Third-party app performance, stuttering, and resource usage |
Relationships between Skills
1 | Module Skills (configuration layer) |
Scrolling path in this snapshot:
1 | invoke_skill("scrolling_analysis") |
This snapshot uses batch SQL classification, not the original “consumer_jank_detection -> iterator -> jank_frame_detail for every frame” chain. Reusing computation is not invoking a same-named Skill. Distinguish inline SQL, explicit Skill references, and later Agent tool calls.
SmartPerfetto Discussion Group
The historical QR code is retained without a guarantee that it remains valid. Current bug reports can use the project issue tracker.

For contact details, see About the author.