Gemini CLI Usage Analyzer Project Banner
Introduction
Last week, I developed a lightweight command-line tool for analyzing Gemini CLI token usage and open-sourced it on GitHub. You can find the project at ceshine/gemini-cli-usage-analyzer. This post outlines why I built the tool, the technical challenges encountered during development, and the solutions implemented to resolve them.
Note: Currently, the tool focuses on single-project analysis. Unlike Claude Code, which centralizes logs (e.g., in ~/.claude on Linux) to analyze overall cross-project usage by default, Gemini CLI lacks a built-in mechanism for unified log management across different projects. Support for aggregating statistics across multiple projects is on the development roadmap.
Sample Output of the Tool
Motivation
I wanted to evaluate my token usage in the Gemini CLI to determine the value proposition of a paid subscription. Gemini CLI’s free and subscription plans lack transparency regarding their specific quotas. An analysis of tokens consumed by Gemini CLI during my daily development work would provide the most accurate assessment of whether the 249 monthly subscription fees are more cost-effective than utilizing paid API requests directly to power Gemini CLI.
I searched for a Gemini CLI equivalent of the ccusage tool for Claude Code but was unsuccessful. Unlike Claude Code, which provides simple, centrally managed JSONL logs in a single folder, Gemini CLI does not. Fortunately, it does offer more generic OpenTelemetry logs, which are compatible with OpenTelemetry-compliant tools. However, most OpenTelemetry-compatible tools I found focus on enterprise-level production use cases, which is overkill for this personal usage analysis.
Consequently, I developed a custom command-line tool modeled after ccusage to achieve three objectives:
- Learn how to process the OpenTelemetry log files generated by Gemini CLI, which could be beneficial for performing deeper analysis in the future (e.g., to debug agentic workflows).
- Build a realistic zero-to-one token-usage case study for Gemini CLI.
- Fulfill my initial requirement of analyzing Gemini CLI usage.
Setting up OpenTelemetry for Gemini CLI
(This section is a direct copy from the GitHub project’s README.)
To use this tool, you must enable local file-based OpenTelemetry support in the Gemini CLI.
Add the following telemetry configuration to your global settings file (usually at ~/.gemini/settings.json) or your project-specific .gemini/settings.json:
{
"telemetry": {
"enabled": true,
"target": "local",
"otlpEndpoint": "",
"outfile": ".gemini/telemetry.log"
}
}
Important Note: You may need to create the .gemini folder in your project directory if it doesn’t already exist.
Note: Changes to the telemetry configuration in settings.json often require restarting your Gemini CLI session for them to take effect.
Configuration Note:
The outfile setting above (".gemini/telemetry.log") tells the Gemini CLI to save telemetry logs inside the .gemini folder of the current working directory where the CLI is launched. This is the recommended setup, as it keeps logs specific to each project.
Alternatively, you can set outfile to an absolute path (e.g., "~/.gemini/telemetry.log") to aggregate logs from all projects into a single file. However, be aware that this makes it difficult to attribute token usage to specific projects.
Designing data processing pipeline
Why we need to transform the raw OpenTelemetry logs
The raw telemetry.log file generated by Gemini CLI contains a series of concatenated, indented JSON objects. Below are two sample objects (some content has been replaced by ... to save space):
{
"hrTime": [
1764998179,
847000000
],
"hrTimeObserved": [
1764998179,
847000000
],
"resource": {
"_rawAttributes": [
[
"host.arch",
"amd64"
],
...
],
"_asyncAttributesPending": false
},
"instrumentationScope": {
"name": "gemini-cli"
},
"attributes": {
"session.id": "45fa9468-d6b8-4c3f-945c-11a7d0cd2314",
"installation.id": "9d0d0b4c-264f-4892-89a8-4e6538af491b",
"interactive": true,
"user.email": "redacted@example.com",
"event.name": "gemini_cli.config",
"event.timestamp": "2025-12-06T05:16:19.842Z",
"model": "auto",
...
},
"_body": "CLI configuration loaded.",
"totalAttributesCount": 25,
"_isReadonly": true,
"_logRecordLimits": {
"attributeCountLimit": 128,
"attributeValueLengthLimit": null
}
}
{
"hrTime": ...,
"hrTimeObserved": ...,
"resource": {
"_rawAttributes": [...],
"_asyncAttributesPending": false
},
"instrumentationScope": ...,
"attributes": {
"session.id": "45fa9468-d6b8-4c3f-945c-11a7d0cd2314",
"installation.id": "9d0d0b4c-264f-4892-89a8-4e6538af491b",
"interactive": true,
"user.email": "redacted@example.com",
"event.name": "gemini_cli.api_request",
"event.timestamp": "2025-12-06T05:16:25.297Z",
"model": "gemini-2.5-flash-lite",
"prompt_id": "45fa9468-d6b8-4c3f-945c-11a7d0cd2314",
"request_text": "[{\"role\":\"user\",\"parts\":[{\"text\":\"You are a professional prompt engineering assistant. Complete the user's partial prompt with expert precision and clarity. User's input: \\\"I want to\\\" Continue this prompt by adding specific, actionable details that align with the user's intent. Focus on: clear, precise language; structured requirements; professional terminology; measurable outcomes. Length Guidelines: Keep suggestions concise (ideally 10-20 characters); prioritize brevity while maintaining clarity; use essential keywords only; avoid redundant phrases. Start your response with the exact user text (\\\"I want to\\\") followed by your completion. Provide practical, implementation-focused suggestions rather than creative interpretations. Format: Plain text only. Single completion. Match the user's language. Emphasize conciseness over elaboration.\"}]}]"
},
"_body": "API request to gemini-2.5-flash-lite.",
"totalAttributesCount": 9,
"_isReadonly": true,
"_logRecordLimits": ..
}
While splitting JSON objects using the \n}\n{\n delimiter is possible, it is non-standard. Converting the data to standard JSONL format is a more robust approach that facilitates easier processing. Additionally, there are many log entry types that are not relevant to our analysis, and for the relevant entries, many fields can still be pruned without affecting the analysis results. We can perform pruning and simplification on the JSONL files to reduce the size of these files. This is an important feature because the raw OpenTelemetry file can easily grow to a few hundred megabytes after a couple dozen interactions with the Gemini CLI.
The need for a deduplication mechanism
Ideally, the raw OpenTelemetry log file should be removed right after we converted its content to a JSONL file. However, this would create issues if there are running Gemini CLI instances writing to that file. Gemini CLI only checks for the existence of that file at the start of the session, so if the file is removed mid-session, all subsequent log entries will be lost.
To avoid data loss, the tool does not remove the raw OpenTelemetry log file by default; the user must explicitly provide the --enable-archiving flag. This introduces a secondary challenge: deduplication. We need a way to identify each log entry (a JSON object) to avoid ingesting it into the JSONL file multiple times. Unfortunately, I did not find any unique identifier in the log entry, so the solution I came up with is to use the event.timestamp values to separate entries that have already been ingested from those that haven’t. This solution is designed under the assumption that the log entries are being appended to the log file in chronological order.
In short, we find the latest event timestamp in the target JSONL file and ignore all log entries in the source log file that have a timestamp equal to or earlier than the latest event timestamp.
Simplification Levels
Currently, four simplification levels for the JSONL files have been implemented in the project:
- Level 0: No simplification. All log records and all their fields are retained.
- Level 1 (default): Filters records to include only
gemini_cli.api_responseandgemini_cli.api_requestevents. All fields within these selected records are retained.gemini_cli.api_responseevents provides the content of the model response and the input/output/cached/thoughts token counts.gemini_cli.api_requestevents provides the content of the user prompt (excluding the system prompt). The number of these events may be higher than the number ofgemini_cli.api_responseevents because some of the API requests will be aborted by the user (Gemini CLI) or fail.
- Level 2: Applies Level 1 filtering and further simplifies the structure of the retained records. Only the “attributes” and “_body” fields of the selected records are kept. Most of the metadata are removed. This slightly reduces the size of the resulting JSONL file.
- Level 3: The most aggressive simplification. Only
gemini_cli.api_responseevents are retained. For these events, only specific token-related attributes (duration_ms,input_token_count,output_token_count,cached_content_token_count,thoughts_token_count,total_token_count,tool_token_count,model, andsession.id) are preserved within the “attributes” field. The “_body” field is also retained. This greatly reduces the size of the resulting JSONL file becuase most of the file size in previous levels come from the now-removed prompts and model responses.
The gen_ai.client.inference.operation.details events may also be useful, as they contain the system prompt used to serve the API request. However, they do not provide detailed token counts and are only logged when the API request is processed successfully and the client receives a response from the API server.
I may adjust the exact implementation of these simplification levels later. For example, Level 2 currently has little effect. I might include more events in Level 1 and push the more restrictive event filtering down to Level 2.
You can find more information about the events and metrics that Gemini CLI writes to OpenTelemetry logs in the “Logs and metrics” section of the official documentation.
Putting it together
I made the gemini-cli-usage-analyzer command available system-wide by running uv tool install https://github.com/ceshine/gemini-cli-usage-analyzer.git (You need to have uv installed on your system first.) You can find other ways to install the tool in the project’s README.
The workflow I currently use looks like this:
- Run
gemini-cli-usage-analyzer stats .in the project folder where you run Gemini CLI to check the latest token usage statistics without removing the OpenTelemetry log file. - Run
gemini-cli-usage-analyzer stats . --enable-archivingwhen you’re done with all Gemini CLI sessions in this project folder to remove the raw OpenTelemetry log file after consuming the log file. - Run
gemini-cli-usage-analyzer simplify -l 3 .from time to time to reduce the size of the JSONL file in the current folder (.gemini/telemetry.json) when you’re confident that you won’t need to review the original prompts and model responses that were ingested since the lastsimplifyrun.
That’s it! This simple workflow currently satisfies my analytics requirements pretty well. I may add the cross-project aggregation feature mentioned at the beginning of this post. Analysis of prompt and agent usage could also be informative. Please feel free to watch the GitHub repository for future updates. Thank you for reading!
AI use disclosure
I used AI tools to revise my writing (primarily for grammar, lexical correctness, and fluency) in this post. However, I wrote most of the content myself; it was not generated with prompts.