Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions benchmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/usr/bin/env python3
"""
Benchmarking script for waka-readme-stats

This script runs performance benchmarks on various parts of the codebase
to identify bottlenecks and measure improvements.

Usage:
python benchmark.py --username <github_username> [--full]

Options:
--username GitHub username to use for benchmarking
--full Run full benchmark suite (including API calls)
--no-cache Disable caching for benchmarking
"""

import argparse
import os
import sys
import time
from pathlib import Path

# Add the parent directory to the path so we can import from sources
parent_dir = Path(__file__).resolve().parent
sys.path.append(str(parent_dir))

from sources.benchmarking import BenchmarkTracker, benchmark
from sources.manager_cache import CacheManager

# Import conditionally to avoid errors if running without full dependencies
try:
from sources.main import main as waka_main
except ImportError:
print("Failed to import main module. Make sure all dependencies are installed.")
sys.exit(1)


@benchmark(name="Full Execution", metadata={"type": "full_run"})
def run_full_benchmark(username, use_cache=True):
"""Run a full benchmark of the waka-readme-stats process.

Args:
username: GitHub username to use for benchmarking
use_cache: Whether to use caching during benchmarking
"""
# Set up environment variables for the test
os.environ["INPUT_GH_TOKEN"] = os.environ.get("GH_TOKEN", "")
os.environ["INPUT_WAKATIME_API_KEY"] = os.environ.get("WAKATIME_API_KEY", "")
os.environ["INPUT_SHOW_TIMEZONE"] = "True"
os.environ["INPUT_SHOW_LANGUAGE"] = "True"
os.environ["INPUT_SHOW_EDITORS"] = "True"
os.environ["INPUT_SHOW_PROJECTS"] = "True"
os.environ["INPUT_SHOW_OS"] = "True"
os.environ["INPUT_SHOW_COMMIT"] = "True"
os.environ["INPUT_SHOW_LANGUAGE_PER_REPO"] = "True"
os.environ["GITHUB_REPOSITORY"] = f"{username}/{username}"

# Control caching behavior
if not use_cache:
# Clear cache before running
cache_manager = CacheManager(username)
cache_manager.clear_cache()

# Run the main function
try:
waka_main()
except Exception as e:
print(f"Error running benchmark: {e}")


def print_system_info():
"""Print system information for context."""
import platform
import multiprocessing

print("System Information:")
print(f" - Python version: {platform.python_version()}")
print(f" - OS: {platform.system()} {platform.release()}")
print(f" - CPU cores: {multiprocessing.cpu_count()}")
print()


def main():
"""Main benchmark function."""
parser = argparse.ArgumentParser(description="Benchmark waka-readme-stats")
parser.add_argument(
"--username",
required=True,
help="GitHub username to use for benchmarking"
)
parser.add_argument(
"--full",
action="store_true",
help="Run full benchmark suite (including API calls)"
)
parser.add_argument(
"--no-cache",
action="store_true",
help="Disable caching for benchmarking"
)

args = parser.parse_args()

print("Starting benchmarks for waka-readme-stats...\n")
print_system_info()

# Run with cache
if not args.no_cache:
print("Running benchmark with caching enabled...")
start_time = time.time()
run_full_benchmark(args.username, use_cache=True)
print(f"Completed in {time.time() - start_time:.2f}s with caching enabled\n")

# Run without cache for comparison if requested
if args.no_cache:
print("Running benchmark with caching disabled...")
start_time = time.time()
run_full_benchmark(args.username, use_cache=False)
print(f"Completed in {time.time() - start_time:.2f}s with caching disabled\n")

# Print detailed benchmark results
print(BenchmarkTracker.get_summary())


if __name__ == "__main__":
main()
151 changes: 151 additions & 0 deletions sources/benchmarking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import time
from functools import wraps
from typing import Dict, Any, Callable, List, Optional, Tuple


class BenchmarkResult:
"""Contains the result of a performance benchmark."""

def __init__(self, name: str, execution_time: float, metadata: Optional[Dict[str, Any]] = None):
"""Initialize the benchmark result.

Args:
name: Name of the benchmarked function or operation
execution_time: Time taken to execute in seconds
metadata: Additional metadata about the benchmark
"""
self.name = name
self.execution_time = execution_time
self.metadata = metadata or {}

def __str__(self) -> str:
"""String representation of the benchmark result."""
return f"{self.name}: {self.execution_time:.4f}s"


class BenchmarkTracker:
"""Tracks and manages benchmarks for performance analysis."""

_results: List[BenchmarkResult] = []

@classmethod
def add_result(cls, result: BenchmarkResult) -> None:
"""Add a benchmark result to the tracker.

Args:
result: The benchmark result to add
"""
cls._results.append(result)

@classmethod
def get_results(cls) -> List[BenchmarkResult]:
"""Get all benchmark results.

Returns:
List of benchmark results
"""
return cls._results

@classmethod
def clear_results(cls) -> None:
"""Clear all benchmark results."""
cls._results.clear()

@classmethod
def get_total_execution_time(cls) -> float:
"""Get the total execution time of all benchmarks.

Returns:
Total execution time in seconds
"""
return sum(result.execution_time for result in cls._results)

@classmethod
def get_summary(cls) -> str:
"""Get a formatted summary of all benchmark results.

Returns:
Formatted summary string
"""
if not cls._results:
return "No benchmarks recorded."

summary = "Performance Benchmark Summary:\n"
summary += "=================================\n"

for result in cls._results:
summary += f"{result}\n"

# Add metadata if present
if result.metadata:
for key, value in result.metadata.items():
summary += f" - {key}: {value}\n"

summary += "=================================\n"
summary += f"Total execution time: {cls.get_total_execution_time():.4f}s\n"

return summary


def benchmark(name: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> Callable:
"""Decorator to benchmark a function's execution time.

Args:
name: Optional name for the benchmark
metadata: Optional metadata about the benchmark

Returns:
Decorated function
"""
def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
benchmark_name = name if name else func.__name__
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()

execution_time = end_time - start_time

# Add dynamic metadata if provided
final_metadata = metadata.copy() if metadata else {}
if 'args_count' not in final_metadata:
final_metadata['args_count'] = len(args)

benchmark_result = BenchmarkResult(
name=benchmark_name,
execution_time=execution_time,
metadata=final_metadata
)

BenchmarkTracker.add_result(benchmark_result)
return result
return wrapper
return decorator


def benchmark_block(name: str, metadata: Optional[Dict[str, Any]] = None) -> Tuple[Callable, Callable]:
"""Context manager for benchmarking a block of code.

Args:
name: Name for the benchmark
metadata: Optional metadata about the benchmark

Returns:
Start and end functions for the benchmark
"""
start_time = [0.0] # Use a list to allow modification in nested scope

def start() -> None:
start_time[0] = time.time()

def end() -> None:
execution_time = time.time() - start_time[0]
benchmark_result = BenchmarkResult(
name=name,
execution_time=execution_time,
metadata=metadata
)
BenchmarkTracker.add_result(benchmark_result)

return start, end
Loading
Loading