Hotpath Init logo

Hotpath Init

CommunityPopular
pawurb
hotpath_init

Configure hotpath profiling in a Rust project. Adds the hotpath dependency with feature-gated setup, instruments main with hotpath::main, functions with measure/measure_all, and wraps channels, mutexes, rw_locks, streams, futures, reqwest clients, axum routers and byte-level I/O with hotpath macros. Use when the user wants to add or set up hotpath profiling in a crate.

Overview

Publisherpawurb
Repositoryhotpath-rs
Skill namehotpath_init
Stars
1.7K
Forks
51
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by pawurb on GitHub. Read the source before you install it.

Installation

Install the Hotpath Init AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/pawurb/hotpath-rs.git /tmp/hotpath-rs
mkdir -p .claude/skills
cp -r /tmp/hotpath-rs/skills/hotpath_init .claude/skills/hotpath_init
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Hotpath Init in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Hotpath Init on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Hotpath Init is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Initialize hotpath Profiling

Set up hotpath profiling in the current Rust project. The setup is fully feature-gated: zero compile-time and runtime overhead unless the hotpath feature is explicitly enabled. All macros are noops when the feature is off, so no cfg_attr wrapping is needed.

Steps

1. Inspect the project

  • Find the binary crate(s) and the main function. If there is no main you control (e.g. a library or a test harness), use the HotpathGuardBuilder API instead of #[hotpath::main] (see step 3).
  • Detect the async runtime (tokio, smol, none) and which instrumentable primitives the code uses: channels (tokio::sync::mpsc/oneshot, std::sync::mpsc, crossbeam_channel, flume, async-channel, futures_channel), Mutex and RwLock (std/parking_lot/tokio/async-lock), futures streams, sqlx, diesel, reqwest clients (async only; note which reqwest major - 0.12 or 0.13), axum 0.8 routers (find where the Router is finished and passed to axum::serve), byte-level I/O values implementing std::io::Read/Write or tokio::io::AsyncRead/AsyncWrite (files, sockets, compression codecs).

2. Add the dependency and feature passthrough

In the target crate's Cargo.toml:

toml
[dependencies]
hotpath = "0.26"

[features]
hotpath = ["hotpath/hotpath"]
hotpath-alloc = ["hotpath/hotpath-alloc"]
hotpath-prometheus = ["hotpath/hotpath-prometheus"]

Enable extra hotpath cargo features on the dependency based on what the project uses:

  • tokio - for tokio::sync channel instrumentation, async io! traits (AsyncRead/AsyncWrite), and hotpath::tokio_runtime!() metrics: hotpath = { version = "0.26", features = ["tokio"] }
  • crossbeam - for crossbeam_channel instrumentation
  • futures - for futures_channel instrumentation
  • flume - for flume channel instrumentation
  • async-channel - for async-channel instrumentation
  • parking_lot - for parking_lot RwLock/Mutex instrumentation
  • async-lock - for async-lock RwLock/Mutex instrumentation
  • sqlx - for SQL query profiling via hotpath::sqlx_tracing_layer()
  • diesel - for SQL query profiling via hotpath::instrument_diesel_sql()
  • reqwest-0-12 / reqwest-0-13 - for HTTP request profiling via hotpath::http!(client); pick the feature matching the project's reqwest major version
  • axum-0-8 - for server-side response time profiling per axum 0.8 route via hotpath::axum!(router)

If the crate already has a [features] section, merge the entries.

3. Instrument main

#[hotpath::main] initializes the profiler and prints the report when main exits. With tokio, #[tokio::main] must come FIRST (above):

rust
#[tokio::main]
#[hotpath::main]
async fn main() {
    // ...
}

Optional parameters: #[hotpath::main(percentiles = [50, 95, 99.9], format = "json", limit = 20)]. Defaults are fine for a first setup; don't add parameters unless asked.

If attribute placement on main is not possible, build a guard programmatically (report prints when the guard drops):

rust
let _hotpath = hotpath::HotpathGuardBuilder::new("main")
    .build();

Unlike #[hotpath::main], the builder does not install the allocation-tracking allocator, so with the guard builder also declare it as a static (required for hotpath-alloc to report anything):

rust
#[global_allocator]
static GLOBAL: hotpath::CountingAllocator = hotpath::CountingAllocator::new();

No feature gating needed: CountingAllocator is a no-op pass-through when hotpath-alloc (or hotpath) is disabled. Never combine this static with #[hotpath::main] - the macro emits its own #[global_allocator] under hotpath-alloc and the build fails with a duplicate; with the macro, pass allocator = ... instead.

If the project already declares a custom global allocator (jemalloc, mimalloc, ...):

  • With the guard builder: replace the existing static's type with the wrapper, e.g. static GLOBAL: hotpath::CountingAllocator<tikv_jemallocator::Jemalloc> = hotpath::CountingAllocator::with(tikv_jemallocator::Jemalloc);. The program keeps running on the custom allocator in all builds; tracking activates only under hotpath-alloc.
  • With #[hotpath::main]: add the allocator = tikv_jemallocator::Jemalloc parameter and gate the project's own #[global_allocator] static behind #[cfg(not(feature = "hotpath-alloc"))] so the two never coexist.

4. Instrument functions

  • Prefer #[hotpath::measure_all] on inline modules and impl blocks - it instruments every function inside. Exclude noisy or trivial functions with #[hotpath::skip].
  • Use #[hotpath::measure] on individual functions, both sync and async.
  • Useful parameters: log = true (log return values, requires Debug), label = "name" (custom identifier, duplicates panic at runtime).
  • hotpath::measure_block!("label", { ... }) for ad-hoc code blocks.

Start with hot paths: request handlers, worker loops, parsing/serialization, IO-heavy functions. Don't instrument one-line getters.

Async functions are measured runtime-agnostically, and under hotpath-alloc their allocations are tracked too (per-poll attribution via an async bridge), so no special handling is needed.

Don't try to instrument everything, use up to ~5 hotpath::measure_all annotations and up to 30 hotpath::measure. Goal of the initial setup is not to measure all functions, but to get the initial working instrumentation in place.

5. Wrap data-flow primitives

Wrap at the creation site; all wrappers accept optional label = "name" and (where noted) log = true:

rust
// Channels (tokio mpsc/oneshot, std mpsc, crossbeam, flume, async-channel, futures_channel)
let (tx, rx) = hotpath::channel!(mpsc::channel::<String>(100), label = "jobs", log = true);
// bounded std sync_channel and futures_channel mpsc need capacity = N (must match):
let (tx, rx) = hotpath::channel!(futures_channel::mpsc::channel::<String>(10), capacity = 10);

// Locks (wait time + held time)
let mutex = hotpath::mutex!(std::sync::Mutex::new(state), label = "state");
let lock = hotpath::rw_lock!(tokio::sync::RwLock::new(config), label = "config");

// Streams and futures
let s = hotpath::stream!(stream::iter(1..=10), label = "events");
let result = hotpath::future!(some_async_operation(), label = "fetch").await;

// Byte-level I/O (std Read/Write; AsyncRead/AsyncWrite require the `tokio` feature) -
// per-operation counts, bytes, transfer rate, durations, and errors
let mut file = hotpath::io!(std::fs::File::open("data.bin")?, label = "data-file");
let stream = hotpath::io!(tokio::net::TcpStream::connect(addr).await?, label = "conn");

Call-site aggregation (channel!, stream!, io!):

  • By default all instances created at one call site (with the same message/item type) aggregate into a single report entry: counts, rates, and histograms are summed across instances, and an Inst column reports how many instances the entry aggregates. Profiler state stays bounded by the number of call sites, so this is safe for unbounded instance churn (a channel or stream per handled request, an io! wrapper per accepted connection).
  • Aggregated channel/stream entries show - for state (instances open and close independently); single-instance entries keep their exact state.
  • Disable aggregation with iter = true (e.g. hotpath::channel!(mpsc::channel::<u32>(8), iter = true)): every instance gets its own row (label, label-2, label-3, ...) with individual counts and rates - useful for one row per spawned worker. State then grows with the number of instances ever created, so avoid it for unbounded churn.

io! notes:

  • Wrapping the underlying resource (file, socket) measures actual resource I/O; wrapping a BufReader/BufWriter measures application-facing buffered operations.
  • The wrapper derefs to the wrapped value, so call sites don't change. For consuming methods (e.g. a codec's finish(self)), unwrap first with hotpath::io_unwrap(x) - identity when profiling is off, so call sites compile identically in both modes.
  • Wrap the side where the work happens, or the reported rate is meaningless. Deferring writers (e.g. brotli::CompressorWriter) accept cheap buffered write calls and compress at finalization, outside any measured op; prefer read-side codec adapters (flate2::read::GzEncoder, brotli::CompressorReader, zstd::stream::read::Encoder), which compress inside instrumented read calls and report compressed bytes out.

Wrapped locks/channels are drop-in: the wrappers expose the same API, so call sites don't change. If passing them across function boundaries requires type-signature changes, note that to the user rather than rewriting half the codebase silently.

Wrapper macros return types prefixed with hotpath::wrap, make sure to update type signatures where needed. Explain to user that these types are no-op unless hotpath feature is enabled.

Apply log = true only if Debug is already implemented.

6. Optional extras (only when relevant)

  • Tokio runtime metrics: call hotpath::tokio_runtime!(); once at startup (requires tokio feature).
  • SQL profiling (sqlx 0.8/0.9): add the layer to the tracing subscriber once - tracing_subscriber::registry().with(hotpath::sqlx_tracing_layer()).init(); (requires sqlx feature). Don't filter out the sqlx::query target.
  • SQL profiling (diesel): call hotpath::instrument_diesel_sql(); once at startup (requires diesel feature).
  • HTTP profiling (reqwest, async client only): wrap the client once at creation - let client = hotpath::http!(reqwest::Client::new()); (requires reqwest-0-12 or reqwest-0-13 feature). Common request-building methods work as usual; requests are reported per normalized endpoint (GET host/path with id-like segments collapsed to {id}) with an error count. Optional label = "name" prefixes endpoint keys - use it when the app has several clients. Where the client is stored in a struct or named in signatures, use hotpath::wrap::reqwest::Client (it resolves to the raw reqwest::Client when the feature is off); likewise hotpath::wrap::reqwest::Error for code that names the send() / execute() error type. Both error types support without_url(); response methods still return raw reqwest::Error. When both reqwest versions are enabled, use the versioned wrap::reqwest_012 path for 0.12 clients and errors. If the app already uses reqwest-middleware, attach hotpath::ReqwestHttpMiddleware::new() to its existing stack instead of the macro.
  • axum server profiling (axum 0.8 only): wrap the finished router once - let app = hotpath::axum!(Router::new().route(..).route(..)); (requires axum-0-8 feature). It expands to router.layer(hotpath::AxumLayer::new()), so it must come after the last .route(..)/.fallback(..)/.nest(..) call - routes added later are not profiled. With the feature off the macro returns the router unchanged, so the line stays unconditional. Requests are reported in a server section per matched route template (GET /users/{id}, nested routers include the nest prefix; fallback/nest_service requests fall back to the raw path with id-like segments collapsed to {id}) with request count, latency percentiles, and separate 4xx/5xx counts. If the app already stacks tower layers on the router, add .layer(hotpath::AxumLayer::new()) where it fits instead of the macro: layers added later run outside earlier ones, so placing hotpath first times only the handler, placing it last times the whole middleware stack (auth, compression, ...). Measurement covers the request until the response head is produced, so streaming/SSE bodies are not included; work detached via tokio::spawn/spawn_blocking counts only if the handler awaits it.
    • Route scoping: with the layer installed, SQL queries (sqlx/diesel) and outbound reqwest requests issued while a handler runs gain a Route column next to Source, keyed per route, so the same query under two routes appears as two rows - dividing a row's calls by that route's request count surfaces N+1 patterns. Tell the user this is on by default and can be disabled with HotpathGuardBuilder::route_scope(false) or HOTPATH_ROUTE_SCOPE=0. Caveat: async sqlx sqlite runs statements on its own worker thread, so it gets neither source nor route (PostgreSQL/MySQL sqlx, diesel, and toasty attribute normally).
    • Cap the number of routes shown with .server_limit(n) / HOTPATH_SERVER_LIMIT (default unlimited). Per-request allocations are not tracked.

7. Verify

bash
cargo check                       # feature off: must still compile, zero overhead
cargo check --features hotpath    # feature on
cargo run --features hotpath      # prints report on exit

Optionally verify alloc mode: cargo run --features 'hotpath,hotpath-alloc'.

Report what was instrumented and mention next steps: the live TUI (cargo install hotpath --features tui, then hotpath console while the app runs - metrics server listens on port 6770 by default), and HOTPATH_OUTPUT_FORMAT=json for machine-readable output. Report sections need no configuration: the default HOTPATH_REPORT=auto shows function and thread sections plus every instrumented section with data (channels, streams, futures, rw_locks, mutexes, sql, ...). Mention HOTPATH_REPORT only if the user wants to restrict output: an exact comma-separated list (e.g. HOTPATH_REPORT=functions-timing,sql), all, or auto with exclusions like HOTPATH_REPORT=auto,-threads / HOTPATH_REPORT=-threads.

Also explain to the user that hotpath is safe to keep as a regular (non-optional) dependency: unless the hotpath feature is enabled, it compiles zero third-party dependencies (only the hotpath crates themselves), and all macros expand to noops, so there is no compile-time bloat and no runtime overhead.

Rules

  • Never enable the hotpath feature by default (default = []); profiling must stay opt-in.
  • Keep edits minimal: dependency, main, and a sensible starting set of instrumented functions/primitives. Expand coverage only when the user asks.

Frequently asked questions

What does the Hotpath Init AI skill do?

Configure hotpath profiling in a Rust project. Adds the hotpath dependency with feature-gated setup, instruments main with hotpath::main, functions with measure/measure_all, and wraps channels, mutexes, rw_locks, streams, futures, reqwest clients, axum routers and byte-level I/O with hotpath macros. Use when the user wants to add or set up hotpath profiling in a crate.

Why use Hotpath Init on TypingMind?

Because you install it once and use it with any model. Hotpath Init is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Hotpath Init in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/pawurb/hotpath-rs/tree/main/skills/hotpath_init. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Hotpath Init?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Hotpath Init?

As many as you like. As long as a model supports skills, you can use Hotpath Init with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Hotpath Init AI skill free?

Yes. It is published on GitHub by pawurb under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇