analyze-token-usage.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. #!/usr/bin/env python3
  2. """
  3. Analyze token usage from Claude Code session transcripts.
  4. Breaks down usage by main session and individual subagents.
  5. """
  6. import json
  7. import sys
  8. from pathlib import Path
  9. from collections import defaultdict
  10. def analyze_main_session(filepath):
  11. """Analyze a session file and return token usage broken down by agent."""
  12. main_usage = {
  13. 'input_tokens': 0,
  14. 'output_tokens': 0,
  15. 'cache_creation': 0,
  16. 'cache_read': 0,
  17. 'messages': 0
  18. }
  19. # Track usage per subagent
  20. subagent_usage = defaultdict(lambda: {
  21. 'input_tokens': 0,
  22. 'output_tokens': 0,
  23. 'cache_creation': 0,
  24. 'cache_read': 0,
  25. 'messages': 0,
  26. 'description': None
  27. })
  28. with open(filepath, 'r') as f:
  29. for line in f:
  30. try:
  31. data = json.loads(line)
  32. # Main session assistant messages
  33. if data.get('type') == 'assistant' and 'message' in data:
  34. main_usage['messages'] += 1
  35. msg_usage = data['message'].get('usage', {})
  36. main_usage['input_tokens'] += msg_usage.get('input_tokens', 0)
  37. main_usage['output_tokens'] += msg_usage.get('output_tokens', 0)
  38. main_usage['cache_creation'] += msg_usage.get('cache_creation_input_tokens', 0)
  39. main_usage['cache_read'] += msg_usage.get('cache_read_input_tokens', 0)
  40. # Subagent tool results
  41. if data.get('type') == 'user' and 'toolUseResult' in data:
  42. result = data['toolUseResult']
  43. if 'usage' in result and 'agentId' in result:
  44. agent_id = result['agentId']
  45. usage = result['usage']
  46. # Get description from prompt if available
  47. if subagent_usage[agent_id]['description'] is None:
  48. prompt = result.get('prompt', '')
  49. # Extract first line as description
  50. first_line = prompt.split('\n')[0] if prompt else f"agent-{agent_id}"
  51. if first_line.startswith('You are '):
  52. first_line = first_line[8:] # Remove "You are "
  53. subagent_usage[agent_id]['description'] = first_line[:60]
  54. subagent_usage[agent_id]['messages'] += 1
  55. subagent_usage[agent_id]['input_tokens'] += usage.get('input_tokens', 0)
  56. subagent_usage[agent_id]['output_tokens'] += usage.get('output_tokens', 0)
  57. subagent_usage[agent_id]['cache_creation'] += usage.get('cache_creation_input_tokens', 0)
  58. subagent_usage[agent_id]['cache_read'] += usage.get('cache_read_input_tokens', 0)
  59. except Exception:
  60. pass
  61. return main_usage, dict(subagent_usage)
  62. def format_tokens(n):
  63. """Format token count with thousands separators."""
  64. return f"{n:,}"
  65. def calculate_cost(usage, input_cost_per_m=3.0, output_cost_per_m=15.0):
  66. """Calculate estimated cost in dollars."""
  67. total_input = usage['input_tokens'] + usage['cache_creation'] + usage['cache_read']
  68. input_cost = total_input * input_cost_per_m / 1_000_000
  69. output_cost = usage['output_tokens'] * output_cost_per_m / 1_000_000
  70. return input_cost + output_cost
  71. def main():
  72. if len(sys.argv) < 2:
  73. print("Usage: analyze-token-usage.py <session-file.jsonl>")
  74. sys.exit(1)
  75. main_session_file = sys.argv[1]
  76. if not Path(main_session_file).exists():
  77. print(f"Error: Session file not found: {main_session_file}")
  78. sys.exit(1)
  79. # Analyze the session
  80. main_usage, subagent_usage = analyze_main_session(main_session_file)
  81. print("=" * 100)
  82. print("TOKEN USAGE ANALYSIS")
  83. print("=" * 100)
  84. print()
  85. # Print breakdown
  86. print("Usage Breakdown:")
  87. print("-" * 100)
  88. print(f"{'Agent':<15} {'Description':<35} {'Msgs':>5} {'Input':>10} {'Output':>10} {'Cache':>10} {'Cost':>8}")
  89. print("-" * 100)
  90. # Main session
  91. cost = calculate_cost(main_usage)
  92. print(f"{'main':<15} {'Main session (coordinator)':<35} "
  93. f"{main_usage['messages']:>5} "
  94. f"{format_tokens(main_usage['input_tokens']):>10} "
  95. f"{format_tokens(main_usage['output_tokens']):>10} "
  96. f"{format_tokens(main_usage['cache_read']):>10} "
  97. f"${cost:>7.2f}")
  98. # Subagents (sorted by agent ID)
  99. for agent_id in sorted(subagent_usage.keys()):
  100. usage = subagent_usage[agent_id]
  101. cost = calculate_cost(usage)
  102. desc = usage['description'] or f"agent-{agent_id}"
  103. print(f"{agent_id:<15} {desc:<35} "
  104. f"{usage['messages']:>5} "
  105. f"{format_tokens(usage['input_tokens']):>10} "
  106. f"{format_tokens(usage['output_tokens']):>10} "
  107. f"{format_tokens(usage['cache_read']):>10} "
  108. f"${cost:>7.2f}")
  109. print("-" * 100)
  110. # Calculate totals
  111. total_usage = {
  112. 'input_tokens': main_usage['input_tokens'],
  113. 'output_tokens': main_usage['output_tokens'],
  114. 'cache_creation': main_usage['cache_creation'],
  115. 'cache_read': main_usage['cache_read'],
  116. 'messages': main_usage['messages']
  117. }
  118. for usage in subagent_usage.values():
  119. total_usage['input_tokens'] += usage['input_tokens']
  120. total_usage['output_tokens'] += usage['output_tokens']
  121. total_usage['cache_creation'] += usage['cache_creation']
  122. total_usage['cache_read'] += usage['cache_read']
  123. total_usage['messages'] += usage['messages']
  124. total_input = total_usage['input_tokens'] + total_usage['cache_creation'] + total_usage['cache_read']
  125. total_tokens = total_input + total_usage['output_tokens']
  126. total_cost = calculate_cost(total_usage)
  127. print()
  128. print("TOTALS:")
  129. print(f" Total messages: {format_tokens(total_usage['messages'])}")
  130. print(f" Input tokens: {format_tokens(total_usage['input_tokens'])}")
  131. print(f" Output tokens: {format_tokens(total_usage['output_tokens'])}")
  132. print(f" Cache creation tokens: {format_tokens(total_usage['cache_creation'])}")
  133. print(f" Cache read tokens: {format_tokens(total_usage['cache_read'])}")
  134. print()
  135. print(f" Total input (incl cache): {format_tokens(total_input)}")
  136. print(f" Total tokens: {format_tokens(total_tokens)}")
  137. print()
  138. print(f" Estimated cost: ${total_cost:.2f}")
  139. print(" (at $3/$15 per M tokens for input/output)")
  140. print()
  141. print("=" * 100)
  142. if __name__ == '__main__':
  143. main()