""" Business metrics for StarPunk operations Per v1.1.2 Phase 1: - Track note operations (create, update, delete) - Track feed generation and cache hits/misses - Track content statistics Per v1.1.2 Phase 3: - Track feed statistics by format - Track feed cache hit/miss rates - Provide feed statistics dashboard Example usage: >>> from starpunk.monitoring.business import track_note_created >>> track_note_created(note_id=123, content_length=500) """ from typing import Optional, Dict, Any from starpunk.monitoring.metrics import record_metric, get_metrics_stats def track_note_created(note_id: int, content_length: int, has_media: bool = False) -> None: """ Track note creation event Args: note_id: ID of created note content_length: Length of note content in characters has_media: Whether note has media attachments """ metadata = { 'note_id': note_id, 'content_length': content_length, 'has_media': has_media, } record_metric( 'render', # Use 'render' for business metrics 'note_created', content_length, metadata, force=True # Always track business events ) def track_note_updated(note_id: int, content_length: int, fields_changed: Optional[list] = None) -> None: """ Track note update event Args: note_id: ID of updated note content_length: New length of note content fields_changed: List of fields that were changed """ metadata = { 'note_id': note_id, 'content_length': content_length, } if fields_changed: metadata['fields_changed'] = ','.join(fields_changed) record_metric( 'render', 'note_updated', content_length, metadata, force=True ) def track_note_deleted(note_id: int) -> None: """ Track note deletion event Args: note_id: ID of deleted note """ metadata = { 'note_id': note_id, } record_metric( 'render', 'note_deleted', 0, # No meaningful duration for deletion metadata, force=True ) def track_feed_generated(format: str, item_count: int, duration_ms: float, cached: bool = False) -> None: """ Track feed generation event Args: format: Feed format (rss, atom, json) item_count: Number of items in feed duration_ms: Time taken to generate feed cached: Whether feed was served from cache """ metadata = { 'format': format, 'item_count': item_count, 'cached': cached, } operation = f'feed_{format}{"_cached" if cached else "_generated"}' record_metric( 'render', operation, duration_ms, metadata, force=True # Always track feed operations ) def track_cache_hit(cache_type: str, key: str) -> None: """ Track cache hit event Args: cache_type: Type of cache (feed, etc.) key: Cache key that was hit """ metadata = { 'cache_type': cache_type, 'key': key, } record_metric( 'render', f'{cache_type}_cache_hit', 0, metadata, force=True ) def track_cache_miss(cache_type: str, key: str) -> None: """ Track cache miss event Args: cache_type: Type of cache (feed, etc.) key: Cache key that was missed """ metadata = { 'cache_type': cache_type, 'key': key, } record_metric( 'render', f'{cache_type}_cache_miss', 0, metadata, force=True ) def get_feed_statistics() -> Dict[str, Any]: """ Get aggregated feed statistics from metrics buffer and feed cache. Analyzes metrics to provide feed-specific statistics including: - Total requests by format (RSS, ATOM, JSON) - Cache hit/miss rates by format - Feed generation times by format - Format popularity (percentage breakdown) - Feed cache internal statistics Returns: Dictionary with feed statistics: { 'by_format': { 'rss': {'generated': int, 'cached': int, 'total': int, 'avg_duration_ms': float}, 'atom': {...}, 'json': {...} }, 'cache': { 'hits': int, 'misses': int, 'hit_rate': float (0.0-1.0), 'entries': int, 'evictions': int }, 'total_requests': int, 'format_percentages': { 'rss': float, 'atom': float, 'json': float } } Example: >>> stats = get_feed_statistics() >>> print(f"RSS requests: {stats['by_format']['rss']['total']}") >>> print(f"Cache hit rate: {stats['cache']['hit_rate']:.2%}") """ # Get all metrics all_metrics = get_metrics_stats() # Initialize result structure result = { 'by_format': { 'rss': {'generated': 0, 'cached': 0, 'total': 0, 'avg_duration_ms': 0.0}, 'atom': {'generated': 0, 'cached': 0, 'total': 0, 'avg_duration_ms': 0.0}, 'json': {'generated': 0, 'cached': 0, 'total': 0, 'avg_duration_ms': 0.0}, }, 'cache': { 'hits': 0, 'misses': 0, 'hit_rate': 0.0, }, 'total_requests': 0, 'format_percentages': { 'rss': 0.0, 'atom': 0.0, 'json': 0.0, }, } # Get by_operation metrics if available by_operation = all_metrics.get('by_operation', {}) # Count feed operations by format for operation_name, op_stats in by_operation.items(): # Feed operations are named: feed_rss_generated, feed_rss_cached, etc. if operation_name.startswith('feed_'): parts = operation_name.split('_') if len(parts) >= 3: format_name = parts[1] # rss, atom, or json operation_type = parts[2] # generated or cached if format_name in result['by_format']: count = op_stats.get('count', 0) if operation_type == 'generated': result['by_format'][format_name]['generated'] = count # Track average duration for generated feeds result['by_format'][format_name]['avg_duration_ms'] = op_stats.get('avg_duration_ms', 0.0) elif operation_type == 'cached': result['by_format'][format_name]['cached'] = count # Update total for this format result['by_format'][format_name]['total'] = ( result['by_format'][format_name]['generated'] + result['by_format'][format_name]['cached'] ) # Track cache hits/misses elif operation_name == 'feed_cache_hit': result['cache']['hits'] = op_stats.get('count', 0) elif operation_name == 'feed_cache_miss': result['cache']['misses'] = op_stats.get('count', 0) # Calculate total requests across all formats result['total_requests'] = sum( fmt['total'] for fmt in result['by_format'].values() ) # Calculate cache hit rate total_cache_requests = result['cache']['hits'] + result['cache']['misses'] if total_cache_requests > 0: result['cache']['hit_rate'] = result['cache']['hits'] / total_cache_requests # Calculate format percentages if result['total_requests'] > 0: for format_name, fmt_stats in result['by_format'].items(): result['format_percentages'][format_name] = ( fmt_stats['total'] / result['total_requests'] ) # Get feed cache statistics if available try: from starpunk.feeds import get_cache feed_cache = get_cache() cache_stats = feed_cache.get_stats() # Merge cache stats (prefer FeedCache internal stats over metrics) result['cache']['entries'] = cache_stats.get('entries', 0) result['cache']['evictions'] = cache_stats.get('evictions', 0) # Use FeedCache hit rate if available and more accurate if cache_stats.get('hits', 0) + cache_stats.get('misses', 0) > 0: result['cache']['hits'] = cache_stats.get('hits', 0) result['cache']['misses'] = cache_stats.get('misses', 0) result['cache']['hit_rate'] = cache_stats.get('hit_rate', 0.0) except ImportError: # Feed cache not available, use defaults pass return result