How RTK Tracks Token Savings Using SQLite in core/tracking.rs
RTK tracks token savings by persisting every command execution to a local SQLite database at ~/.local/share/rtk/tracking.db, calculating savings via the record() method, and aggregating statistics through filtered SQL queries.
The rtk-ai/rtk repository implements a self-contained telemetry system that monitors command-line efficiency. By leveraging SQLite in src/core/tracking.rs, RTK persistently logs execution metadata and derives token-saving statistics to power the rtk gain reporting interface.
Database Initialization and Schema
Resolving the Database Path
The Tracker struct initializes its storage layer through get_db_path(), which resolves the database location in a strict priority order. According to the source code in src/core/tracking.rs (lines 55-71), the system checks:
- The
RTK_DB_PATHenvironment variable - The configuration file via
Config::load() - The default platform-specific directory (
~/.local/share/rtk/tracking.dbon Linux)
This resolution strategy ensures developers can override storage locations without recompiling the binary.
Creating the SQLite Schema
When Tracker::new() executes (lines 49-76), it opens or creates the SQLite database and executes a series of schema definitions. The primary commands table stores:
- Timestamps in UTC
- Original command strings versus RTK-wrapped equivalents
- Input and output token counts
- Calculated savings percentage
- Execution time in milliseconds (
exec_time_ms) - Project path for multi-project filtering
The initialization routine includes migrations that add exec_time_ms and project_path columns if they are missing from existing databases, ensuring backward compatibility.
Recording Command Executions
The record() method (lines 52-84) serves as the core persistence engine. It receives the original command, its RTK counterpart, estimated input and output token counts, and elapsed time in milliseconds.
The method performs two critical calculations:
- Absolute savings:
saved = input_tokens - output_tokens - Percentage savings:
pct = (saved / input_tokens) * 100(or0ifinput_tokensis0)
After computing these metrics, record() inserts a row with the current UTC timestamp and the current working directory as project_path. Immediately following insertion, the method prunes records older than the retention window—90 days by default—to prevent database bloat.
Querying and Aggregating Statistics
RTK exposes analytics through helper methods that prepare parameterized SQLite statements. The get_summary_filtered() function (lines 104-140) implements the core aggregation logic, using SQL SUM, AVG, and COUNT operations to compute totals and averages.
These queries respect project-scoped filtering via the project_path column, enabling per-directory analytics. Additional methods like get_by_command(), get_by_day(), and tokens_saved_30d() provide specialized breakdowns for daily, weekly, and monthly reports consumed by the rtk gain CLI.
Automatic Tracking with TimedExecution
For developer convenience, RTK provides the TimedExecution struct, which abstracts the entire tracking lifecycle. When TimedExecution::start() is called, it captures Instant::now() to measure elapsed time.
The struct implements estimate_tokens() (lines 120-123), a lightweight heuristic that approximates 1 token per 4 characters of text. This avoids expensive API calls while providing sufficiently accurate telemetry. Calling track() or track_passthrough() instantiates a Tracker on-the-fly and persists records without requiring manual database management.
Code Examples
use rtk::tracking::{Tracker, TimedExecution};
/// Manual recording of a command execution
fn manual_record() -> anyhow::Result<()> {
let tracker = Tracker::new()?; // opens/creates the DB
tracker.record(
"git status", // original command
"rtk git status", // RTK-wrapped command
1200, // estimated input tokens
300, // estimated output tokens
45, // execution time in ms
)?;
Ok(())
}
/// Automatic tracking with built-in timer
fn timed_example() -> anyhow::Result<()> {
let timer = TimedExecution::start(); // start stopwatch
// Execute command and capture outputs
let raw_output = std::fs::read_to_string("git_status.txt")?;
let filtered_out = std::fs::read_to_string("rtk_status.txt")?;
// Automatically estimates tokens and records execution
timer.track("git status", "rtk git status", &raw_output, &filtered_out);
Ok(())
}
/// Retrieve aggregated savings statistics
fn print_summary() -> anyhow::Result<()> {
let tracker = Tracker::new()?;
let summary = tracker.get_summary()?; // aggregates all records
println!(
"Saved {} tokens (average {:.1}% savings over {} commands)",
summary.total_saved,
summary.avg_savings_pct,
summary.total_commands,
);
Ok(())
}
Summary
- RTK stores execution data in
~/.local/share/rtk/tracking.dbusing theTrackerstruct defined insrc/core/tracking.rs. - The
record()method computes token savings asinput_tokens - output_tokensand derives percentage savings automatically. - Automatic pruning removes records older than 90 days (configurable via
DEFAULT_HISTORY_DAYSinsrc/core/constants.rs). - The
TimedExecutionhelper provides zero-configuration tracking with heuristic token estimation (~4 characters per token). - Aggregation queries in
get_summary_filtered()enable project-scoped analytics for thertk gainCLI.
Frequently Asked Questions
Where does RTK store the token tracking database?
By default, RTK creates the database at ~/.local/share/rtk/tracking.db on Linux systems. The get_db_path() function checks the RTK_DB_PATH environment variable first, then falls back to the tracking.database_path value from Config::load(), and finally uses the platform-specific default directory.
How does RTK calculate token savings percentages?
The record() method in src/core/tracking.rs calculates absolute savings as input_tokens - output_tokens. It then derives the percentage using (saved / input_tokens) * 100, defaulting to 0 when input_tokens equals zero to prevent division errors.
What is the retention period for command history in RTK?
RTK automatically prunes records older than 90 days after each insertion. This retention window is controlled by DEFAULT_HISTORY_DAYS defined in src/core/constants.rs, ensuring the database remains compact while preserving recent analytics.
How does the TimedExecution struct estimate token counts?
The estimate_tokens() method (lines 120-123) implements a simple heuristic where approximately 4 characters equal 1 token. This inexpensive calculation avoids API latency while providing sufficient accuracy for telemetry aggregation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →