|
10 | 10 | import socket |
11 | 11 | import getpass |
12 | 12 | import datetime |
| 13 | +import argparse |
13 | 14 |
|
14 | 15 |
|
15 | 16 | def get_username() -> str: |
@@ -526,33 +527,126 @@ def get_output_tokens(self) -> int: |
526 | 527 | except Exception as e: |
527 | 528 | raise Exception(f"Failed to get output_tokens sum: {e}") from e |
528 | 529 |
|
| 530 | + def stats(self) -> dict: |
| 531 | + """ |
| 532 | + Generate summary statistics over the entire table. |
| 533 | + Includes rows with NULL values using 'NULL' as the key. |
| 534 | +
|
| 535 | + Returns: |
| 536 | + Dictionary with three top-level keys: |
| 537 | + - 'by_user': {user: {modelalias: cost, ...}, ...} |
| 538 | + - 'by_project': {project: {modelalias: cost, ...}, ...} |
| 539 | + - 'by_user_project': {"user / project": {modelalias: cost, ...}, ...} |
| 540 | +
|
| 541 | + NULL values are represented as 'NULL' in the dictionary keys. |
| 542 | +
|
| 543 | + Raises: |
| 544 | + Exception: If query fails |
| 545 | + """ |
| 546 | + try: |
| 547 | + conn = sqlite3.connect(self.db_path, timeout=5.0) |
| 548 | + try: |
| 549 | + result = { |
| 550 | + 'by_user': {}, |
| 551 | + 'by_project': {}, |
| 552 | + 'by_user_project': {} |
| 553 | + } |
| 554 | + |
| 555 | + # Get cost by user and modelalias |
| 556 | + cursor = conn.execute(''' |
| 557 | + SELECT user, modelalias, SUM(cost) as total_cost |
| 558 | + FROM logs |
| 559 | + GROUP BY user, modelalias |
| 560 | + ORDER BY user, modelalias |
| 561 | + ''') |
| 562 | + |
| 563 | + for row in cursor: |
| 564 | + user, modelalias, cost = row |
| 565 | + user_key = user if user is not None else 'NULL' |
| 566 | + if user_key not in result['by_user']: |
| 567 | + result['by_user'][user_key] = {} |
| 568 | + alias_key = modelalias if modelalias is not None else 'NULL' |
| 569 | + result['by_user'][user_key][alias_key] = float(cost) if cost else 0.0 |
| 570 | + |
| 571 | + # Get cost by project and modelalias |
| 572 | + cursor = conn.execute(''' |
| 573 | + SELECT project, modelalias, SUM(cost) as total_cost |
| 574 | + FROM logs |
| 575 | + GROUP BY project, modelalias |
| 576 | + ORDER BY project, modelalias |
| 577 | + ''') |
| 578 | + |
| 579 | + for row in cursor: |
| 580 | + project, modelalias, cost = row |
| 581 | + project_key = project if project is not None else 'NULL' |
| 582 | + if project_key not in result['by_project']: |
| 583 | + result['by_project'][project_key] = {} |
| 584 | + alias_key = modelalias if modelalias is not None else 'NULL' |
| 585 | + result['by_project'][project_key][alias_key] = float(cost) if cost else 0.0 |
| 586 | + |
| 587 | + # Get cost by user/project combination and modelalias |
| 588 | + cursor = conn.execute(''' |
| 589 | + SELECT user, project, modelalias, SUM(cost) as total_cost |
| 590 | + FROM logs |
| 591 | + GROUP BY user, project, modelalias |
| 592 | + ORDER BY user, project, modelalias |
| 593 | + ''') |
| 594 | + |
| 595 | + for row in cursor: |
| 596 | + user, project, modelalias, cost = row |
| 597 | + user_key = user if user is not None else 'NULL' |
| 598 | + project_key = project if project is not None else 'NULL' |
| 599 | + key = f"{user_key} / {project_key}" |
| 600 | + if key not in result['by_user_project']: |
| 601 | + result['by_user_project'][key] = {} |
| 602 | + alias_key = modelalias if modelalias is not None else 'NULL' |
| 603 | + result['by_user_project'][key][alias_key] = float(cost) if cost else 0.0 |
| 604 | + |
| 605 | + return result |
| 606 | + |
| 607 | + finally: |
| 608 | + conn.close() |
| 609 | + except Exception as e: |
| 610 | + raise Exception(f"Failed to generate stats: {e}") from e |
| 611 | + |
| 612 | +def get_args(): |
| 613 | + parser = argparse.ArgumentParser(description='Show costs') |
| 614 | + parser.add_argument('file', type=str, help='Cost database file') |
| 615 | + args = parser.parse_args() |
| 616 | + argsconfig = {} |
| 617 | + argsconfig.update(vars(args)) |
| 618 | + return argsconfig |
| 619 | + |
| 620 | + |
| 621 | +def main(): |
| 622 | + config = get_args() |
| 623 | + costs = Log2Sqlite(config['file']) |
| 624 | + all_stats = costs.stats() |
| 625 | + print("Cost by User:") |
| 626 | + |
| 627 | + def fflt(value: float): |
| 628 | + return f"{value:.8f}".rstrip('0').rstrip('.') |
| 629 | + |
| 630 | + for user, aliases in all_stats['by_user'].items(): |
| 631 | + total = fflt(sum(aliases.values())) |
| 632 | + print(f" {user}: ${total}") |
| 633 | + for alias, cost in aliases.items(): |
| 634 | + cost = fflt(cost) |
| 635 | + print(f" {alias}: ${cost}") |
| 636 | + |
| 637 | + # Print project costs |
| 638 | + print("\nCost by Project:") |
| 639 | + for project, aliases in all_stats['by_project'].items(): |
| 640 | + total = fflt(sum(aliases.values())) |
| 641 | + print(f" {project}: ${total}") |
| 642 | + |
| 643 | + # Print user/project combinations |
| 644 | + print("\nCost by User/Project:") |
| 645 | + for combo, aliases in all_stats['by_user_project'].items(): |
| 646 | + total = fflt(sum(aliases.values())) |
| 647 | + print(f" {combo}: ${total}") |
| 648 | + |
529 | 649 |
|
530 | 650 | # Example usage |
531 | 651 | if __name__ == '__main__': |
532 | | - # Create logger with defaults |
533 | | - logger = Log2Sqlite('api_usage.db', project='my_project', user='alice') |
534 | | - |
535 | | - # Log some entries |
536 | | - logger.log({ |
537 | | - 'model': 'gpt-4', |
538 | | - 'task': 'summarization', |
539 | | - 'cost': 0.05, |
540 | | - 'input_tokens': 1000, |
541 | | - 'output_tokens': 200, |
542 | | - 'datetime': '2024-02-06 10:30:00' |
543 | | - }) |
544 | | - |
545 | | - logger.log({ |
546 | | - 'model': 'gpt-3.5-turbo', |
547 | | - 'task': 'chat', |
548 | | - 'cost': 0.01, |
549 | | - 'input_tokens': 500, |
550 | | - 'output_tokens': 100, |
551 | | - 'datetime': '2024-02-06 11:00:00' |
552 | | - }) |
553 | | - |
554 | | - # Get aggregations (only for project='my_project', user='alice') |
555 | | - print(f"Total cost: ${logger.get_cost():.4f}") |
556 | | - print(f"Total input tokens: {logger.get_input_tokens()}") |
557 | | - print(f"Total output tokens: {logger.get_output_tokens()}") |
558 | | - print(f"Get all: {logger.get()}") |
| 652 | + main() |
0 commit comments