Files
giglez/scripts/analyze_upload_logs.py
T
leetcrypt 9f73595b20 feat: RTL_433 protocol database import - iteration 1/5
- Expanded protocol database from 18 → 299 signatures (16.6x increase)
- Imported 281 protocols from RTL_433 open-source database (286 total devices)
- Created automated import script: scripts/import_rtl433_protocols.py
- Generated rtl433_protocols_imported.py with timing/frequency/modulation data
- Updated protocol_database.py to include RTL433_PROTOCOLS
- All 26 tests passing

Breakdown by category:
  - Weather: 116 protocols
  - Sensors: 36 protocols
  - TPMS: 25 protocols
  - Security: 23 protocols
  - Home Automation: 18 protocols
  - Other: 50+ protocols

Frequency coverage:
  - 433.92 MHz: 248 protocols
  - 315.00 MHz: 32 protocols
  - 915.00 MHz: 1 protocol

This provides comprehensive coverage of Sub-GHz IoT devices for accurate
identification from raw RF captures.
2026-02-14 18:55:55 -08:00

326 lines
12 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Upload Log Analyzer
Analyzes upload logs from beta testing to identify patterns, common errors,
and performance bottlenecks.
Usage:
python3 scripts/analyze_upload_logs.py --date 2026-01-15
python3 scripts/analyze_upload_logs.py --last 7 # Last 7 days
python3 scripts/analyze_upload_logs.py --upload-id upload_20260115_123456
"""
import argparse
import json
import re
from pathlib import Path
from datetime import datetime, timedelta
from collections import Counter, defaultdict
from typing import List, Dict, Any
import statistics
class UploadLogAnalyzer:
"""Analyze upload logs and generate reports"""
def __init__(self, log_dir: str = "logs/uploads"):
self.log_dir = Path(log_dir)
def analyze_date(self, date: str) -> Dict[str, Any]:
"""Analyze logs for a specific date (YYYY-MM-DD)"""
# Read main upload log
log_file = self.log_dir / f"uploads_{date}.log"
error_log = self.log_dir / f"upload_errors_{date}.log"
structured_log = self.log_dir / f"uploads_structured_{date}.jsonl"
if not log_file.exists():
return {"error": f"Log file not found: {log_file}"}
stats = {
"date": date,
"total_uploads": 0,
"total_files": 0,
"successful_files": 0,
"failed_files": 0,
"duplicate_files": 0,
"errors_by_type": Counter(),
"errors_by_stage": Counter(),
"parse_errors": [],
"gps_errors": [],
"performance": {
"parse_times": [],
"matching_times": [],
"total_times": []
},
"top_failures": [],
"user_stats": defaultdict(int)
}
# Parse structured JSON log
if structured_log.exists():
with open(structured_log, 'r') as f:
for line in f:
try:
entry = json.loads(line)
self._process_log_entry(entry, stats)
except json.JSONDecodeError:
continue
# Parse error log for details
if error_log.exists():
with open(error_log, 'r') as f:
for line in f:
self._process_error_line(line, stats)
# Calculate summary statistics
self._calculate_summary(stats)
return stats
def _process_log_entry(self, entry: Dict[str, Any], stats: Dict[str, Any]):
"""Process a single structured log entry"""
event = entry.get("event")
if event == "upload_attempt":
stats["total_uploads"] += 1
stats["total_files"] += entry.get("file_count", 0)
if entry.get("user_id"):
stats["user_stats"]["authenticated"] += 1
else:
stats["user_stats"]["anonymous"] += 1
elif event == "file_processing":
stage = entry.get("stage")
success = entry.get("success")
if stage == "complete" and success:
stats["successful_files"] += 1
elif not success:
if stage == "validation" and entry.get("metadata", {}).get("status") == "duplicate":
stats["duplicate_files"] += 1
else:
stats["failed_files"] += 1
stats["errors_by_stage"][stage] += 1
# Track performance
duration = entry.get("duration_ms")
if duration and success:
if stage == "parsing":
stats["performance"]["parse_times"].append(duration)
elif stage == "complete":
stats["performance"]["total_times"].append(duration)
elif event == "parse_error":
stats["parse_errors"].append({
"filename": entry.get("filename"),
"error_type": entry.get("error_type"),
"error_message": entry.get("error_message"),
"upload_id": entry.get("upload_id")
})
stats["errors_by_type"][entry.get("error_type", "Unknown")] += 1
elif event == "gps_validation_error":
stats["gps_errors"].append({
"filename": entry.get("filename"),
"latitude": entry.get("latitude"),
"longitude": entry.get("longitude"),
"reason": entry.get("reason")
})
stats["errors_by_type"]["GPS Validation"] += 1
elif event == "matching_results":
duration = entry.get("duration_ms")
if duration:
stats["performance"]["matching_times"].append(duration)
elif event == "performance_metrics":
# Aggregate performance metrics
for key, value in entry.items():
if key.endswith("_ms") and isinstance(value, (int, float)):
perf_key = key.replace("_ms", "_times")
if perf_key not in stats["performance"]:
stats["performance"][perf_key] = []
stats["performance"][perf_key].append(value)
def _process_error_line(self, line: str, stats: Dict[str, Any]):
"""Process error log line"""
# Extract error type from log line
error_match = re.search(r'(Exception|Error): (.+)', line)
if error_match:
error_type = error_match.group(1)
stats["errors_by_type"][error_type] += 1
def _calculate_summary(self, stats: Dict[str, Any]):
"""Calculate summary statistics"""
# Success rate
total = stats["successful_files"] + stats["failed_files"]
if total > 0:
stats["success_rate"] = round(stats["successful_files"] / total * 100, 1)
else:
stats["success_rate"] = 0.0
# Performance statistics
for key, times in stats["performance"].items():
if times:
stats["performance"][f"{key}_avg"] = round(statistics.mean(times), 2)
stats["performance"][f"{key}_median"] = round(statistics.median(times), 2)
stats["performance"][f"{key}_p95"] = round(statistics.quantiles(times, n=20)[18], 2) if len(times) > 20 else None
# Top failure reasons
stats["top_failures"] = stats["errors_by_type"].most_common(10)
def print_report(self, stats: Dict[str, Any]):
"""Print formatted report"""
print("=" * 80)
print(f"Upload Log Analysis - {stats['date']}")
print("=" * 80)
# Overview
print("\n📊 OVERVIEW")
print(f" Total Uploads: {stats['total_uploads']}")
print(f" Total Files: {stats['total_files']}")
print(f" Successful: {stats['successful_files']} ({stats['success_rate']}%)")
print(f" Failed: {stats['failed_files']}")
print(f" Duplicates: {stats['duplicate_files']}")
# User statistics
print("\n👥 USERS")
print(f" Authenticated: {stats['user_stats']['authenticated']}")
print(f" Anonymous: {stats['user_stats']['anonymous']}")
# Performance
print("\n⚡ PERFORMANCE")
perf = stats["performance"]
if perf.get("parse_times_avg"):
print(f" Parse Time: {perf['parse_times_avg']:.0f}ms avg, {perf['parse_times_median']:.0f}ms median")
if perf.get("matching_times_avg"):
print(f" Matching Time: {perf['matching_times_avg']:.0f}ms avg, {perf['matching_times_median']:.0f}ms median")
if perf.get("total_times_avg"):
print(f" Total Time: {perf['total_times_avg']:.0f}ms avg, {perf['total_times_median']:.0f}ms median")
# Errors
if stats["top_failures"]:
print("\n❌ TOP ERRORS")
for error_type, count in stats["top_failures"]:
print(f" {error_type:30} {count:5} occurrences")
# Parse errors detail
if stats["parse_errors"]:
print(f"\n🔍 PARSE ERRORS ({len(stats['parse_errors'])})")
for error in stats["parse_errors"][:5]: # Show first 5
print(f" {error['filename']:30} {error['error_type']}: {error['error_message'][:50]}")
# GPS errors detail
if stats["gps_errors"]:
print(f"\n📍 GPS ERRORS ({len(stats['gps_errors'])})")
for error in stats["gps_errors"][:5]: # Show first 5
print(f" {error['filename']:30} ({error['latitude']}, {error['longitude']}) - {error['reason']}")
print("\n" + "=" * 80)
def analyze_upload_id(self, upload_id: str) -> Dict[str, Any]:
"""Analyze a specific upload"""
# Search all structured logs
results = {
"upload_id": upload_id,
"events": [],
"files": [],
"errors": [],
"timeline": []
}
for log_file in self.log_dir.glob("uploads_structured_*.jsonl"):
with open(log_file, 'r') as f:
for line in f:
try:
entry = json.loads(line)
if entry.get("upload_id") == upload_id:
results["events"].append(entry)
results["timeline"].append({
"timestamp": entry.get("timestamp"),
"event": entry.get("event"),
"details": entry
})
except json.JSONDecodeError:
continue
# Sort timeline
results["timeline"].sort(key=lambda x: x["timestamp"])
return results
def print_upload_details(self, details: Dict[str, Any]):
"""Print detailed upload report"""
print("=" * 80)
print(f"Upload Details: {details['upload_id']}")
print("=" * 80)
print(f"\nTotal Events: {len(details['events'])}")
print("\n📅 TIMELINE:")
for event in details["timeline"]:
timestamp = event["timestamp"].split('T')[1][:12] # HH:MM:SS.mmm
print(f" {timestamp} | {event['event']:20} | {self._format_event_details(event['details'])}")
def _format_event_details(self, event: Dict[str, Any]) -> str:
"""Format event details for display"""
event_type = event.get("event")
if event_type == "upload_attempt":
return f"{event.get('file_count')} files, {event.get('total_size_mb')} MB"
elif event_type == "file_processing":
success = "✓" if event.get("success") else "✗"
return f"{success} {event.get('filename')} - {event.get('stage')} ({event.get('duration_ms', 0):.0f}ms)"
elif event_type == "parse_error":
return f"❌ {event.get('filename')} - {event.get('error_type')}"
elif event_type == "matching_results":
return f"🔍 {event.get('match_count')} matches ({event.get('duration_ms', 0):.0f}ms)"
elif event_type == "upload_complete":
return f"✅ {event.get('successful_count')} success, {event.get('failed_count')} failed"
else:
return json.dumps(event, indent=2)[:100]
def main():
parser = argparse.ArgumentParser(description="Analyze upload logs from beta testing")
parser.add_argument("--date", help="Date to analyze (YYYY-MM-DD)")
parser.add_argument("--last", type=int, help="Analyze last N days")
parser.add_argument("--upload-id", help="Analyze specific upload ID")
parser.add_argument("--log-dir", default="logs/uploads", help="Log directory")
args = parser.parse_args()
analyzer = UploadLogAnalyzer(log_dir=args.log_dir)
if args.upload_id:
# Analyze specific upload
details = analyzer.analyze_upload_id(args.upload_id)
analyzer.print_upload_details(details)
elif args.date:
# Analyze specific date
stats = analyzer.analyze_date(args.date)
analyzer.print_report(stats)
elif args.last:
# Analyze last N days
for i in range(args.last):
date = (datetime.now() - timedelta(days=i)).strftime("%Y-%m-%d")
print(f"\n{'='*80}")
print(f"Analyzing {date}")
print('='*80)
stats = analyzer.analyze_date(date)
analyzer.print_report(stats)
else:
# Default: analyze today
today = datetime.now().strftime("%Y-%m-%d")
stats = analyzer.analyze_date(today)
analyzer.print_report(stats)
if __name__ == "__main__":
main()