{"id":48999939,"url":"https://github.com/piti6/spiketrap","last_synced_at":"2026-04-18T18:03:40.500Z","repository":{"id":352125839,"uuid":"642447570","full_name":"piti6/SpikeTrap","owner":"piti6","description":"Set traps, catch bad frames. Enhanced Unity CPU profiler with spike detection, selective frame capture, and pluggable filters.","archived":false,"fork":false,"pushed_at":"2026-04-18T01:35:33.000Z","size":533,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-18T02:40:01.598Z","etag":null,"topics":["csharp","gc-allocation","performance","profiler","profiling","spike-detection","unity","unity-editor","unity-package","unity-plugin","unity-profiler","upm"],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/piti6.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-05-18T15:30:38.000Z","updated_at":"2026-04-18T01:35:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/piti6/SpikeTrap","commit_stats":null,"previous_names":["piti6/spiketrap"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/piti6/SpikeTrap","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piti6%2FSpikeTrap","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piti6%2FSpikeTrap/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piti6%2FSpikeTrap/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piti6%2FSpikeTrap/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/piti6","download_url":"https://codeload.github.com/piti6/SpikeTrap/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piti6%2FSpikeTrap/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31978810,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T17:30:12.329Z","status":"ssl_error","status_checked_at":"2026-04-18T17:29:59.069Z","response_time":103,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","gc-allocation","performance","profiler","profiling","spike-detection","unity","unity-editor","unity-package","unity-plugin","unity-profiler","upm"],"created_at":"2026-04-18T18:03:37.511Z","updated_at":"2026-04-18T18:03:40.490Z","avatar_url":"https://github.com/piti6.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SpikeTrap\n\n### Set traps, catch bad frames.\n\n**[日本語版はこちら](README_JP.md)**\n\nAn enhanced CPU profiler module for Unity Editor with pluggable frame filters, visual highlight strips, AI-driven profiling automation, and marked-frame collection.\n\n![SpikeTrap — spike detection with filter strips and hierarchy view](Documentation~/spike-detection-overview.png)\n\n## Why SpikeTrap?\n\nUnity's built-in CPU module is a **rolling buffer** — it keeps the last 300-2000 frames and silently discards everything older. At 60fps that's 5-33 seconds of history.\n\nSpikeTrap adds two layers on top:\n\n- **Recording mode** — the standard profiler keeps running, but SpikeTrap adds filter highlight strips, prev/next navigation, pause on match, and log on match. You can spot spikes at a glance without changing how recording works.\n- **Collect mode** — set your filter thresholds, press **Collect**, and just play your game. SpikeTrap captures only the frames that matter in the background. No need to stare at the profiler — when you're done, press **Save** to merge matched frames into a single `.data` file.\n\n### Comparison\n\n| | Native CPU Module | SpikeTrap (Recording) | SpikeTrap (Collect) |\n|---|---|---|---|\n| **Frame retention** | ❌ Rolling buffer (300-2000) | ❌ Same rolling buffer | ✅ Only matched frames, no limit |\n| **10 min @ 60fps** (36K frames) | ❌ Loses 95%+ | ❌ Same, but spikes highlighted | ✅ Keeps only ~20 spikes |\n| **Rare spike (1/10,000)** | ❌ Overwritten before you see it | ⚠️ Caught if still in buffer | ✅ Captured automatically |\n| **Filter types** | ❌ None | ✅ Spike, GC, Search, custom | ✅ Same filters drive capture |\n| **Visual indicators** | ❌ None | ✅ Color-coded strips + nav | ✅ Same + \"Collecting...\" overlay |\n| **Filter composition** | ❌ N/A | ✅ Match Any / Match All | ✅ Same logic drives capture |\n| **Pause \u0026 log on match** | ❌ None | ✅ Auto-pause and/or log | ✅ Available |\n| **Automation API** | ❌ Low-level `ProfilerDriver` | — | ✅ `SpikeTrapApi` fire-and-forget |\n| **Output** | ⚠️ `.data` (all frames, rolling) | ⚠️ Same | ✅ `.data` (matched only, mergeable) |\n| **Hierarchy view** | ✅ Built-in | ✅ Same | ✅ Same |\n\n### How Collect Mode Works\n\nSet your filter thresholds, press **Collect**, and play your game normally. SpikeTrap evaluates each frame against your active filters in real-time — only frames that match (spike threshold exceeded, GC allocation too high, specific marker found) are saved to temporary `.data` files. When you're done, press **Save** to merge them into a single file.\n\nA 100-minute session producing 360,000 frames might save only 50 spike frames — each with full call-stack detail, ready for analysis. You don't lose data to the rolling buffer, and you don't need to watch the profiler while it runs.\n\nThis also makes Collect mode ideal for AI-driven profiling. The entire workflow — start collecting, wait, stop, save, analyze — maps to simple `SpikeTrapApi` calls with no frame-by-frame polling or buffer management. An AI agent can kick off a profiling session, let the game run, and retrieve pre-sorted results with marker names already resolved.\n\n## Requirements\n\n- Unity 2022.3+\n\n## Installation\n\nAdd via Unity Package Manager using the git URL:\n\n```\nhttps://github.com/piti6/SpikeTrap.git?path=Packages/com.piti6.spike-trap\n```\n\nOr clone the repository and copy `Packages/com.piti6.spike-trap` into your project's `Packages/` directory.\n\n## Quick Start\n\n1. Open the Profiler window (**Window \u003e Analysis \u003e Profiler**)\n2. Select the **SpikeTrap CPU Usage** module from the module dropdown\n3. Set filter thresholds in each strip row (Spike ms, GC KB, search term)\n4. Choose **Match any** or **Match all** from the dropdown\n5. Toggle **Pause on match** / **Log on match** as needed\n6. Use the **Collect** button to record only matched frames\n\n## Features\n\n### Frame Filters\n\nThree built-in filters, each with a visual highlight strip and prev/next navigation buttons:\n\n| Filter | What it detects | Unit selector | Strip color |\n|---|---|---|---|\n| **Spike** | Frames exceeding a CPU time threshold | ms / s | Green |\n| **GC** | Frames exceeding a GC allocation threshold | KB / MB | Red |\n| **Search** | Frames containing a named profiler sample (case-insensitive) | — | Orange |\n\n![Filter strips with live spike and GC detection](Documentation~/filter-strips-live.png)\n\n### Match Any / Match All\n\nControl how filters combine with a dropdown in the toolbar:\n\n- **Match any** (OR) — a frame matches if any active filter matches. Default behavior.\n- **Match all** (AND) — a frame matches only if all active filters match. A **Result** strip appears showing the intersection with its own prev/next navigation.\n\nInactive filters (threshold = 0 or empty search) are skipped — they don't affect the result.\n\n### Pause \u0026 Log on Match\n\n- **Pause on match** — automatically pauses play mode when filters match a frame\n- **Log on match** — dumps the full sample call-stack hierarchy to the console for matched frames\n\n### Collect Marked Frames\n\nRecord only frames that match active filters into a `.data` file:\n\n1. Set up filters (spike threshold, GC threshold, search term)\n2. Press **Collect** — filter controls stay visible so you can adjust thresholds while collecting\n3. Matched frames are saved to temp files as they occur\n4. Press **Save (N)** to merge collected frames into a single `.data` file, or **Stop** to exit without saving\n\n![Collect mode overlay](Documentation~/collect-mode.png)\n\n### Screenshot Preview\n\nDisplays per-frame screenshots captured by the [ScreenshotToUnityProfiler](https://github.com/wotakuro/ScreenshotToUnityProfiler) runtime package inline in the profiler details view. Screenshots persist while scrubbing through frames without screenshot data, and clear on new recording or file load.\n\nEnable screenshot capture at runtime:\n\n```csharp\nusing SpikeTrap.Runtime;\n\n// Start capturing (default: quarter resolution)\nSpikeTrapApi.InitializeScreenshotCapture();\n\n// Or specify scale (0.5 = half resolution)\nSpikeTrapApi.InitializeScreenshotCapture(0.5f);\n\n// Custom compression\nSpikeTrapApi.InitializeScreenshotCapture(0.25f, TextureCompress.JPG_BufferRGB565);\n\n// Custom capture routine (e.g., capture a specific camera instead of screen)\nSpikeTrapApi.InitializeScreenshotCapture(captureBehaviour: target =\u003e\n{\n    Camera.main.targetTexture = target;\n    Camera.main.Render();\n    Camera.main.targetTexture = null;\n});\n\n// Stop capturing and release resources\nSpikeTrapApi.DestroyScreenshotCapture();\n```\n\n### Custom Filters\n\nCreate your own filters by extending `FrameFilterBase` and registering via `SpikeTrapApi`:\n\n```csharp\nusing SpikeTrap.Editor;\nusing UnityEngine;\n\npublic class MyFilter : FrameFilterBase\n{\n    public override Color HighlightColor =\u003e Color.cyan;\n    public override bool IsActive =\u003e true;\n\n    public override bool Matches(in CachedFrameData frameData)\n    {\n        // Must be thread-safe — called from Parallel.For\n        return frameData.EffectiveTimeMs \u003e 16f \u0026\u0026 frameData.GcAllocBytes \u003e 1024;\n    }\n}\n\n// Register (e.g., from an editor script or [InitializeOnLoad] constructor)\nSpikeTrapApi.RegisterCustomFilterFactory(() =\u003e new MyFilter());\n```\n\n### Scripting API\n\n`SpikeTrap.Editor.SpikeTrapApi` and `SpikeTrap.Runtime.SpikeTrapApi` provide the API for profiling automation, custom filters, and screenshot capture:\n\n```csharp\nusing SpikeTrap.Editor;\n\nSpikeTrapApi.StartCollecting(spikeThresholdMs: 33f);\n// ... game runs, spikes are captured ...\nawait SpikeTrapApi.StopCollectingAndSaveAsync(\"/path/to/spikes.data\");\n\nFrameSummary[] spikes = SpikeTrapApi.GetSpikeFrames(33f);\nforeach (var s in spikes)\n    Debug.Log(s); // \"Frame 296: 2038.40ms, GC 5.7KB | NavMeshManager=1953.89ms, ...\"\n```\n\n### AI Agents \u0026 Claude Skills\n\nSpikeTrap is designed code-first: every UI action has an `SpikeTrapApi` equivalent, results come back pre-sorted with marker names already resolved, and analysis works on the static cache without driving the UI. This makes it straightforward to drive from AI agents (Claude Code, uLoop MCP, editor scripts).\n\nThe package ships a Claude Code skill under `Packages/com.piti6.spike-trap/.claude/skills/spike-trap/` that wraps the common workflow as a slash command:\n\n- **`/spike-trap`** — runs an end-to-end profiling session: enters play mode, collects matched frames, stops, saves to `.data`, and summarizes top bottlenecks by marker name. Also has an `analyze` mode that skips profiling and reports against already-cached data.\n\n```\n/spike-trap profile 33ms 10 seconds\n/spike-trap analyze 16ms\n```\n\nWhy the API works well for agents:\n\n- **Session-scoped, not frame-scoped** — `StartCollecting` / `StopCollectingAndSaveAsync` wrap a full capture. The agent starts, waits, stops — no frame-by-frame iteration or buffer management.\n- **Pre-sorted, pre-resolved results** — `GetSpikeFrames` returns frames worst-first with `TopMarkerNames` already resolved. No `ProfilerDriver` plumbing on the agent side.\n- **Analysis without UI interaction** — `GetSpikeFrames` and `GetCachedFrameSummaries` read the static cache, so agents can inspect a loaded `.data` file without driving the Profiler window.\n- **Session-aware cache** — reloading the same `.data` reuses its cache, so repeated `load → analyze` cycles don't re-extract.\n\nThe full agent-facing API reference lives at `Packages/com.piti6.spike-trap/CLAUDE.md`.\n\n## Architecture\n\n### Pluggable Filter System\n\nFilters implement `IFrameFilter` (or extend `FrameFilterBase`). The controller handles all native API access, caching, and matched-frame tracking.\n\n```\nNative API (main thread)          Managed cache              Filters (thread-safe)\nProfilerDriver.GetRawFrameDataView  --\u003e  CachedFrameData  --\u003e  filter.Matches()\n  one call per frame per session         { EffectiveTimeMs,     pure managed,\n  extracts all data in one pass            GcAllocBytes,        parallelized for\n                                           UniqueMarkerIds }    large frame ranges\n```\n\n**`CachedFrameData`** is a readonly struct containing all filter-relevant data extracted from a single frame. Filters receive this pre-extracted data and never touch native APIs.\n\n**`IFrameFilter`** interface:\n\n```csharp\npublic interface IFrameFilter : IDisposable\n{\n    Color HighlightColor { get; }\n    bool IsActive { get; }\n    bool DrawToolbarControls();\n    bool Matches(in CachedFrameData frameData);\n    void InvalidateCache();\n}\n```\n\n### Performance\n\n- **One native call per frame per session**: `GetRawFrameDataView` is called once per frame. All filter data (CPU time, GC bytes, marker IDs) is extracted in a single sample iteration loop.\n- **Cached frame data**: Extracted data is stored in a `ConcurrentDictionary`. Threshold/search changes re-evaluate cached managed data without native API calls.\n- **Parallel matching**: Full rescans above 500 frames use `Parallel.For`. `OnMarkerDiscovered` and `Matches` are thread-safe.\n- **Marker ID caching**: Search filter resolves marker names once per unique ID. Subsequent checks are integer comparisons via `ConcurrentDictionary`.\n- **Amortized extraction**: Loading large `.data` files extracts 50 frames per editor frame to avoid blocking.\n- **Session-aware caching**: Caches use `frameStartTimeNs` as session fingerprint. Same `.data` file loaded multiple times shares the same cache (A→B→A reuses A's cached data).\n\n### Thread Safety\n\n| Component | Thread-safe | Mechanism |\n|---|---|---|\n| `SearchFrameFilter.Matches` | Yes | Single volatile `SearchState` reference, local capture |\n| `SearchFrameFilter.OnMarkerDiscovered` | Yes | `ConcurrentDictionary.TryAdd`, local state capture (internal to search filter) |\n| `SpikeFrameFilter.Matches` | Yes | Reads only immutable `CachedFrameData` fields |\n| `GcFrameFilter.Matches` | Yes | Reads only immutable `CachedFrameData` fields |\n| `s_FrameDataCache` | Yes | `ConcurrentDictionary` |\n| `s_MarkerNames` | Yes | `ConcurrentDictionary` |\n| `CollectMatchingFrames` | Yes | `Parallel.For` with `ConcurrentBag` result collection |\n| Native API extraction | Main thread only | `GetRawFrameDataView`, `ProfilerFrameDataIterator` |\n\n## Development\n\nThis repository is a Unity project. `Assets/ProfilerStressTest.cs` generates random CPU spikes, GC pressure, and named profiler samples (`HeavyComputation`, `GarbageBlast`, `NetworkSync`, `SaveCheckpoint`) for testing.\n\n### Tests\n\n34 EditMode tests (assembly `SpikeTrap.Tests`) covering:\n- Spike/GC/Search filter matching logic and edge cases\n- Thread safety: concurrent `OnMarkerDiscovered`, concurrent `Matches`, `SetSearchString` race\n- Multi-filter OR composition semantics\n- Custom filter API usability\n- `CachedFrameData` struct correctness\n\nRun via Unity Test Runner (EditMode).\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiti6%2Fspiketrap","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpiti6%2Fspiketrap","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiti6%2Fspiketrap/lists"}