MCPInstall

investigating-performance

Use when something is too slow, uses too much memory or CPU, degrades under load, or times out — and before making any change intended to speed something up. Triggers include "this is slow", "optimize this", "high latency", "p99", "memory leak", "it's fine locally but not in production", "the query takes forever", "reduce bundle size", and any request to make code faster.

Installs
112
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/osmontero/opencode-skills --skill investigating-performance
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads investigating-performance automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

Investigating Performance

Overview

Performance work fails in a specific way: the engineer reads the code, forms an intuition about what must be slow, optimizes that, and ships a change that measurably does nothing. The bottleneck is somewhere else, because the bottleneck is almost always somewhere else.

Core principle: Measure, localize, change one thing, measure again. Intuition selects what to measure; it never selects what to fix.

Companion skills: systematic-debugging — same discipline applied to wrongness rather than slowness. Both refuse to act before the cause is located.

The Iron Law

NO OPTIMIZATION WITHOUT A MEASUREMENT THAT NAMES THE BOTTLENECK

You must be able to say "X accounts for N% of the time" before you change X. Not "X looks expensive." Not "X is O(n²)." A measurement, with a number.

No exceptions:

  • Not for "this is obviously the slow part" — obvious is what makes it worth measuring
  • Not for "the fix is cheap anyway" — a cheap fix to a non-bottleneck is pure risk with zero payoff
  • Not for changes you would make on style grounds regardless. Make them; just don't call it performance work

The corollary: a change without an after-measurement is not an optimization, it is a guess that shipped.

Step 0: Define the Target

"Slow" is not a bug report. Write down all four before touching anything:

Example
MetricEnd-to-end latency of POST /documents
Statisticp99, not mean — means hide the problem users complain about
Load200 concurrent clients, production-shaped data volume
TargetUnder 300ms, from 2.1s today

Without a target you cannot tell when you are done, and performance work has no natural stopping point. Without a statistic you will optimize the average and never touch the tail that generated the complaint.

Step 1: Reproduce It

You need a harness that produces the slow behavior on demand, repeatably.

  • Real data shape. A thousand dev rows will not reproduce a missing index; ten million will.
  • Warm up first, then measure. JIT compilation, connection pools, and page cache all make the first run a different program.
  • Repeat and take the distribution. A single timing is noise. Report min/p50/p99, not one number.
  • Pin the environment. Same machine, same build flags, no other load. Especially: measure a production build — a dev-mode framework build can be 10× slower and is not the thing you are shipping.

If you cannot reproduce it locally, measure in the environment where it happens. Production profiling at low sample rates is safe and is better than a faithful reproduction you do not have.

Step 2: Characterize Before Profiling

Two minutes here saves an hour of profiling the wrong dimension.

ObservationWhat it implicates
Slow on the first call, fast afterCold cache, lazy init, connection setup, JIT
Slow always, uniformlyAn algorithm or a per-call fixed cost
Fast at low load, cliff at high loadContention, pool exhaustion, or a queue — not the code path
Time scales with input size superlinearlyComplexity bug — find the nested loop before you profile
p50 fine, p99 terribleGC pauses, lock convoy, retries, a rare slow path
CPU at 100%Compute-bound → CPU profile
CPU low, latency highWaiting — I/O, network, lock, or a sleep
Memory grows and never returnsRetention, not allocation rate → heap profile with two snapshots
Slow only in productionData volume, concurrency, network topology, or config

A quick sanity check: concurrency ≈ throughput × latency (Little's Law). If you serve 100 req/s at 2s each, roughly 200 requests are in flight. If the pool holds 20 connections, the pool is the bottleneck and no amount of query tuning will help.

Step 3: Profile the Right Dimension

Profile the dimension the characterization implicated. A CPU profile of an I/O-bound program shows an idle process and teaches you nothing.

DimensionWhenWhat it shows
CPUCPU pegged, compute-boundWhich functions burn cycles
AllocationHigh GC time, memory churnWhere garbage comes from
Heap (in-use)Memory grows without boundWhat is retained, and by whom
Block / waitLow CPU, high latencyWhat the program waits on
Mutex / lockDegrades with concurrencyContended locks
Execution traceLatency spikes with no obvious ownerGC pauses, scheduler stalls, serialization
Query planThe database is involved at allScans, bad join order, missing indexes

Exact invocations per stack — Go, Node, Python, browser, SQL, system-level: references/profiling-tools.md.

Read the profile top-down by cumulative time, and be suspicious of flat profiles. A profile with no peak usually means the cost is spread across a call made far too many times — count the calls, don't just time them.

Step 4: The Usual Suspects

Once localized, the cause is usually on this list.

LayerCauseTell
DatabaseN+1 queriesQuery count scales with result count
DatabaseMissing indexSeq Scan on a large table in the plan
DatabaseIndex exists but is unusedType mismatch, function on the column, or a leading-wildcard LIKE
DatabaseSELECT * over wide rowsBytes transferred dwarf the rows needed
ApplicationWork inside a loop that could be hoistedSame call, same arguments, n times
ApplicationSerialization/deserializationJSON encode/decode dominates a CPU profile
ApplicationUnbounded concurrencyGoroutine/promise count grows with load; memory follows
ApplicationLock held across I/OMutex profile shows a long hold, not many acquisitions
ApplicationAllocation in a hot pathAlloc profile dominated by one call site
NetworkChatty protocolMany small round trips where one batch would do
NetworkNo connection reuseTLS handshakes in the trace
FrontendRender-blocking waterfallSequential dependent requests in the network panel
FrontendWork on the main threadLong tasks in the performance trace
FrontendShipping the whole bundleLarge initial JS payload, most of it unused

Before optimizing, ask whether the work can be avoided entirely. Caching, batching, pagination, doing it asynchronously, or not doing it are all bigger wins than making it faster, and usually simpler.

Step 5: Change One Thing, Then Measure Again

one change → re-measure → keep it or revert it → repeat
  • One variable at a time. Two changes at once and you cannot attribute the result — including the case where one helped and the other hurt by the same amount.
  • Revert what does not measurably help. Every optimization costs readability; unpaid-for complexity is a permanent tax.
  • Confirm the improvement is real, not noise. Compare distributions across repeated runs (benchstat and equivalents do this properly), not two single numbers.
  • Commit the benchmark with the fix. It is the only thing that will notice when the regression comes back.
  • Re-profile after each win. The bottleneck moves. The second-biggest cost is now the biggest, and it is often somewhere entirely new.

Stop when you hit the target from Step 0. Continuing past it trades real maintainability for a number nobody asked for.

Tail Latency

If the complaint is about p99, the mean is actively misleading — a fix that improves the mean often makes the tail worse.

Common causes, in rough order of frequency:

  • Queueing. Utilization above ~70% makes wait time rise sharply. The fix is capacity or backpressure, not faster code.
  • GC pauses. Look at pause distribution, not total GC time. Reduce allocation rate or retained set.
  • Lock convoy. One slow holder makes every waiter slow; latency spikes correlate with contention, not load.
  • Retries. A timeout plus a retry turns one slow request into three, exactly when the system is least able to serve them.
  • Cold paths. Cache misses, first-request-after-idle, connection re-establishment.
  • Head-of-line blocking. One slow item stalls everything behind it in the same connection, batch, or partition.

Watch for coordinated omission in your load generator. A harness that waits for each response before sending the next request stops sending load exactly when the system is slowest — which erases the tail you are trying to measure. Use a generator that sends at a fixed rate, or one that explicitly corrects for it.

Red Flags

  • "This is obviously the slow part" — with no profile
  • Optimizing a function that a profile shows at 0.4% of runtime
  • Benchmarking a dev build, or with an empty database
  • A single timing used as evidence
  • Two changes in one commit, both labeled performance
  • A micro-benchmark improving while the end-to-end metric does not move
  • Caching added before the cost of the uncached path was measured
  • "It should be faster now"

All of these mean: go back and measure.

Common Mistakes

Optimizing the part you understand. Familiarity is not evidence. The bottleneck is disproportionately in code you did not write.

Micro-benchmarks that do not reflect real use. Amdahl's law is unforgiving: making 5% of the runtime twice as fast buys 2.5%.

Confusing allocation with retention. High allocation rate is a GC-pressure problem. Growing in-use heap is a leak. They need different profiles and different fixes.

Caching to hide a bug. A cache in front of an accidentally-quadratic function makes the problem invisible until the cache misses under exactly the conditions where you need it most.

Ignoring variance. A change that improves p50 and degrades p99 is usually a regression, because the tail is what users report.

No after-measurement. Without it you have shipped a diff, not a fix — and you have no idea which it was.

FAQ

What does the investigating-performance skill do?

Use when something is too slow, uses too much memory or CPU, degrades under load, or times out — and before making any change intended to speed something up. Triggers include "this is slow", "optimize this", "high latency", "p99", "memory leak", "it's fine locally but not in production", "the query takes forever", "reduce bundle size", and any request to make code faster.

How do I install investigating-performance?

Run npx skills add https://github.com/osmontero/opencode-skills --skill investigating-performance in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/investigating-performance/ (personal) or .claude/skills/investigating-performance/ (project).

Which AI agents can use investigating-performance?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is investigating-performance safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains investigating-performance?

It is published by osmontero in osmontero/opencode-skills. It has 112 recorded installs.

More from osmontero/opencode-skills

View all →

designing-frontend-interfaces

osmontero/opencode-skills

Use when building or restyling any web interface — landing pages, dashboards, marketing sites, web apps, React/Vue components, HTML/CSS layouts, artifacts, posters — or when asked to make a UI look better, more polished, more distinctive, less generic, or less "AI-generated". Also use when a design brief, art direction, visual identity, type scale, or color system is needed before implementation.

Frontend & Design650 installs

designing-user-experience

osmontero/opencode-skills

Open the skill to read its SKILL.md instructions.

460 installs

reviewing-interface-quality

osmontero/opencode-skills

Open the skill to read its SKILL.md instructions.

363 installs

building-accessible-interfaces

osmontero/opencode-skills

Use when building or auditing any web UI for accessibility — keyboard navigation, screen readers, focus management, ARIA, color contrast, form labels and error announcements, reduced motion, or touch target size. Triggers include "is this accessible", "WCAG", "a11y", "screen reader", "keyboard navigation", "focus trap", "contrast ratio", "aria-label", or building modals, menus, tabs, comboboxes, toasts, and custom controls that must work without a mouse.

Frontend & Design350 installs

systematic-debugging

osmontero/opencode-skills

Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes

verifying-before-completion

osmontero/opencode-skills

Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always

157 installs

Popular code quality & workflow skills

View category →

grill-me

mattpocock/skills

A relentless interview to sharpen a plan or design.

Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.

handoff

mattpocock/skills

Compact the current conversation into a handoff document for another agent to pick up.

domain-modeling

mattpocock/skills

Build and sharpen a project's domain model. Use when discussing codebase terminology, writing or editing a GLOSSARY.md, or recording or editing an ADR.

codebase-design

mattpocock/skills

Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.

diagnosing-bugs

mattpocock/skills

Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.