mirror of
https://github.com/thedotmack/claude-mem
synced 2026-04-26 01:25:10 +02:00
Features: - New UsageLogger utility that writes usage metrics to daily JSONL files - Captures token counts, costs, timing, and cache metrics from SDK result messages - Usage logs stored in ~/.claude-mem/usage-logs/ (one file per day) - Added analyze-usage.js script for analyzing usage patterns Usage data captured: - Token counts (input, output, cache creation, cache read) - Total cost in USD per API call - Duration metrics (total and API time) - Number of turns per session - Session and project attribution Analysis script features: - Aggregates totals by project and model - Shows cache hit rates and savings - Displays cost breakdowns and averages - npm scripts: usage:analyze and usage:today Files: - src/utils/usage-logger.ts (new) - src/services/worker-service.ts (modified - captures SDK result messages) - scripts/analyze-usage.js (new) - package.json (added usage:* npm scripts) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
135 lines
4.8 KiB
JavaScript
Executable File
135 lines
4.8 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* Analyze usage logs from ~/.claude-mem/usage-logs/
|
|
*
|
|
* Usage:
|
|
* node scripts/analyze-usage.js [date]
|
|
*
|
|
* Example:
|
|
* node scripts/analyze-usage.js 2025-11-03
|
|
* node scripts/analyze-usage.js # Uses today's date
|
|
*/
|
|
|
|
import { readFileSync, readdirSync } from 'fs';
|
|
import { join } from 'path';
|
|
import { homedir } from 'os';
|
|
|
|
const usageDir = join(homedir(), '.claude-mem', 'usage-logs');
|
|
|
|
// Get date from command line or use today
|
|
const targetDate = process.argv[2] || new Date().toISOString().split('T')[0];
|
|
const filename = `usage-${targetDate}.jsonl`;
|
|
const filepath = join(usageDir, filename);
|
|
|
|
console.log(`\n📊 Usage Analysis for ${targetDate}\n`);
|
|
console.log(`Reading from: ${filepath}\n`);
|
|
|
|
try {
|
|
const content = readFileSync(filepath, 'utf-8');
|
|
const lines = content.trim().split('\n');
|
|
|
|
let totalCost = 0;
|
|
let totalInputTokens = 0;
|
|
let totalOutputTokens = 0;
|
|
let totalCacheCreation = 0;
|
|
let totalCacheRead = 0;
|
|
const projectStats = {};
|
|
const modelStats = {};
|
|
|
|
lines.forEach(line => {
|
|
if (!line.trim()) return;
|
|
|
|
try {
|
|
const entry = JSON.parse(line);
|
|
|
|
// Aggregate totals
|
|
totalCost += entry.totalCostUsd || 0;
|
|
totalInputTokens += entry.usage?.inputTokens || 0;
|
|
totalOutputTokens += entry.usage?.outputTokens || 0;
|
|
totalCacheCreation += entry.usage?.cacheCreationInputTokens || 0;
|
|
totalCacheRead += entry.usage?.cacheReadInputTokens || 0;
|
|
|
|
// Project stats
|
|
if (!projectStats[entry.project]) {
|
|
projectStats[entry.project] = {
|
|
cost: 0,
|
|
sessions: new Set(),
|
|
tokens: 0
|
|
};
|
|
}
|
|
projectStats[entry.project].cost += entry.totalCostUsd || 0;
|
|
projectStats[entry.project].sessions.add(entry.sessionDbId);
|
|
projectStats[entry.project].tokens += (entry.usage?.inputTokens || 0) + (entry.usage?.outputTokens || 0);
|
|
|
|
// Model stats
|
|
if (!modelStats[entry.model]) {
|
|
modelStats[entry.model] = {
|
|
cost: 0,
|
|
calls: 0,
|
|
tokens: 0
|
|
};
|
|
}
|
|
modelStats[entry.model].cost += entry.totalCostUsd || 0;
|
|
modelStats[entry.model].calls += 1;
|
|
modelStats[entry.model].tokens += (entry.usage?.inputTokens || 0) + (entry.usage?.outputTokens || 0);
|
|
|
|
} catch (e) {
|
|
console.error(`Error parsing line: ${e.message}`);
|
|
}
|
|
});
|
|
|
|
// Print summary
|
|
console.log('═══════════════════════════════════════════════════════════\n');
|
|
console.log(`📈 Total Cost: $${totalCost.toFixed(4)}`);
|
|
console.log(`📊 Total API Calls: ${lines.length}`);
|
|
console.log(`\n🎯 Token Usage:`);
|
|
console.log(` Input Tokens: ${totalInputTokens.toLocaleString()}`);
|
|
console.log(` Output Tokens: ${totalOutputTokens.toLocaleString()}`);
|
|
console.log(` Cache Creation Tokens: ${totalCacheCreation.toLocaleString()}`);
|
|
console.log(` Cache Read Tokens: ${totalCacheRead.toLocaleString()}`);
|
|
console.log(` Total Tokens: ${(totalInputTokens + totalOutputTokens).toLocaleString()}`);
|
|
|
|
if (totalCacheRead > 0) {
|
|
const savings = ((totalCacheRead / (totalInputTokens + totalCacheRead)) * 100).toFixed(1);
|
|
console.log(` Cache Hit Rate: ${savings}%`);
|
|
}
|
|
|
|
console.log(`\n📁 By Project:`);
|
|
Object.entries(projectStats)
|
|
.sort((a, b) => b[1].cost - a[1].cost)
|
|
.forEach(([project, stats]) => {
|
|
console.log(` ${project}:`);
|
|
console.log(` Cost: $${stats.cost.toFixed(4)}`);
|
|
console.log(` Sessions: ${stats.sessions.size}`);
|
|
console.log(` Tokens: ${stats.tokens.toLocaleString()}`);
|
|
});
|
|
|
|
console.log(`\n🤖 By Model:`);
|
|
Object.entries(modelStats)
|
|
.sort((a, b) => b[1].cost - a[1].cost)
|
|
.forEach(([model, stats]) => {
|
|
console.log(` ${model}:`);
|
|
console.log(` Cost: $${stats.cost.toFixed(4)}`);
|
|
console.log(` Calls: ${stats.calls}`);
|
|
console.log(` Tokens: ${stats.tokens.toLocaleString()}`);
|
|
console.log(` Avg Cost/Call: $${(stats.cost / stats.calls).toFixed(4)}`);
|
|
});
|
|
|
|
console.log('\n═══════════════════════════════════════════════════════════\n');
|
|
|
|
} catch (error) {
|
|
if (error.code === 'ENOENT') {
|
|
console.error(`❌ No usage log found for ${targetDate}`);
|
|
console.log(`\nAvailable logs:`);
|
|
try {
|
|
const files = readdirSync(usageDir).filter(f => f.endsWith('.jsonl'));
|
|
files.forEach(f => console.log(` - ${f}`));
|
|
} catch (e) {
|
|
console.error(` Could not read usage logs directory`);
|
|
}
|
|
} else {
|
|
console.error(`❌ Error: ${error.message}`);
|
|
}
|
|
process.exit(1);
|
|
}
|