getAnalytics method
Compute usage analytics for a time period.
Implementation
Future<UsageAnalytics> getAnalytics({
required DateTime start,
required DateTime end,
}) async {
final allEntries = <HistoryEntry>[];
final sessionIds = <String>{};
// Load all sessions in range.
final dir = Directory(_baseDir);
if (await dir.exists()) {
await for (final entity in dir.list()) {
if (entity is File && entity.path.endsWith('.jsonl')) {
final sid = entity.uri.pathSegments.last.replaceAll('.jsonl', '');
final entries = await getSessionHistory(sid);
final inRange = entries.where(
(e) => e.timestamp.isAfter(start) && e.timestamp.isBefore(end),
);
if (inRange.isNotEmpty) {
allEntries.addAll(inRange);
sessionIds.add(sid);
}
}
}
}
// Compute metrics.
final toolCounts = <String, int>{};
final modelCounts = <String, int>{};
final commandCounts = <String, int>{};
int totalInput = 0;
int totalOutput = 0;
double totalCost = 0;
int messages = 0;
int toolUses = 0;
for (final e in allEntries) {
if (e.type == HistoryEntryType.message) messages++;
if (e.type == HistoryEntryType.toolUse) {
toolUses++;
if (e.toolName != null) {
toolCounts[e.toolName!] = (toolCounts[e.toolName!] ?? 0) + 1;
}
}
if (e.type == HistoryEntryType.command) {
final cmd = e.metadata?['command'] as String?;
if (cmd != null) {
commandCounts[cmd] = (commandCounts[cmd] ?? 0) + 1;
}
}
totalInput += e.tokenCount ?? 0;
totalCost += e.cost ?? 0;
}
// Daily breakdown.
final dailyMap = <String, DailyUsage>{};
for (final e in allEntries) {
final dayKey =
'${e.timestamp.year}-${e.timestamp.month.toString().padLeft(2, '0')}-${e.timestamp.day.toString().padLeft(2, '0')}';
final existing = dailyMap[dayKey];
dailyMap[dayKey] = DailyUsage(
date: DateTime(e.timestamp.year, e.timestamp.month, e.timestamp.day),
sessions: existing?.sessions ?? 0,
messages:
(existing?.messages ?? 0) +
(e.type == HistoryEntryType.message ? 1 : 0),
tokens: (existing?.tokens ?? 0) + (e.tokenCount ?? 0),
cost: (existing?.cost ?? 0) + (e.cost ?? 0),
);
}
return UsageAnalytics(
periodStart: start,
periodEnd: end,
totalSessions: sessionIds.length,
totalMessages: messages,
totalToolUses: toolUses,
totalInputTokens: totalInput,
totalOutputTokens: totalOutput,
totalCost: totalCost,
toolUsageCounts: toolCounts,
modelUsageCounts: modelCounts,
commandUsageCounts: commandCounts,
averageTokensPerMessage: messages > 0 ? totalInput / messages : 0,
averageCostPerSession: sessionIds.isNotEmpty
? totalCost / sessionIds.length
: 0,
dailyBreakdown: dailyMap.values.toList()
..sort((a, b) => a.date.compareTo(b.date)),
);
}