From 4ed1b80ac446a57e76cd866475e5195de83f825d Mon Sep 17 00:00:00 2001 From: stoney420 Date: Sat, 28 Jun 2025 02:22:49 +0200 Subject: [PATCH] Update scripts/health_checker.py --- scripts/health_checker.py | 164 ++++++++++++++++++++++---------------- 1 file changed, 95 insertions(+), 69 deletions(-) diff --git a/scripts/health_checker.py b/scripts/health_checker.py index ed5a1b8..2cae406 100644 --- a/scripts/health_checker.py +++ b/scripts/health_checker.py @@ -1,16 +1,18 @@ #!/usr/bin/env python3 """ -Health Checker - Simple URL health checking for IPTV channels +Health Checker - Simple health checking without external dependencies """ import logging -import requests +import urllib.request +import urllib.error +import socket +import time import concurrent.futures from typing import Dict, List, Optional -import time class HealthChecker: - """Simple health checker for IPTV channel URLs.""" + """Simple health checker using only standard library.""" def __init__(self, config): self.config = config @@ -18,29 +20,52 @@ class HealthChecker: self.timeout = config.settings.get('health_check_timeout', 5) self.max_workers = config.settings.get('max_workers', 4) + # Set default socket timeout + socket.setdefaulttimeout(self.timeout) + def check_single_url(self, url: str) -> Dict: - """Check a single URL for accessibility.""" + """Check a single URL for accessibility using urllib.""" start_time = time.time() try: - response = requests.head( - url, - timeout=self.timeout, - allow_redirects=True, + # Create request with proper headers + req = urllib.request.Request( + url, headers={'User-Agent': 'IPTV-Health-Checker/1.0'} ) - response_time = time.time() - start_time - + # Try to open the URL + with urllib.request.urlopen(req, timeout=self.timeout) as response: + response_time = time.time() - start_time + status_code = response.getcode() + + return { + 'url': url, + 'status': 'healthy' if status_code < 400 else 'unhealthy', + 'status_code': status_code, + 'response_time': round(response_time, 2), + 'error': None + } + + except urllib.error.HTTPError as e: return { 'url': url, - 'status': 'healthy' if response.status_code < 400 else 'unhealthy', - 'status_code': response.status_code, - 'response_time': round(response_time, 2), - 'error': None + 'status': 'unhealthy', + 'status_code': e.code, + 'response_time': time.time() - start_time, + 'error': f'HTTP {e.code}: {e.reason}' } - except requests.exceptions.Timeout: + except urllib.error.URLError as e: + return { + 'url': url, + 'status': 'unreachable', + 'status_code': None, + 'response_time': time.time() - start_time, + 'error': f'URL Error: {e.reason}' + } + + except socket.timeout: return { 'url': url, 'status': 'timeout', @@ -49,15 +74,6 @@ class HealthChecker: 'error': 'Request timeout' } - except requests.exceptions.ConnectionError: - return { - 'url': url, - 'status': 'unreachable', - 'status_code': None, - 'response_time': time.time() - start_time, - 'error': 'Connection error' - } - except Exception as e: return { 'url': url, @@ -90,29 +106,68 @@ class HealthChecker: """Perform batch health check on multiple channels.""" if not self.config.settings.get('enable_health_check', False): self.logger.info("Health checking is disabled") - return {'enabled': False, 'results': []} + return { + 'enabled': False, + 'results': [], + 'summary': { + 'total': len(channels), + 'healthy': 0, + 'health_percentage': 0, + 'total_check_time': 0 + } + } self.logger.info(f"Starting health check for {len(channels)} channels...") start_time = time.time() results = [] - # Use ThreadPoolExecutor for concurrent checks - with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor: - # Submit all health check tasks - future_to_channel = { - executor.submit(self.check_channel_health, channel): channel - for channel in channels - } - - # Collect results as they complete - for future in concurrent.futures.as_completed(future_to_channel): + # Limit concurrent checks to avoid overwhelming servers + max_workers = min(self.max_workers, len(channels)) + + if max_workers > 1: + # Use ThreadPoolExecutor for concurrent checks + try: + with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: + # Submit all health check tasks + future_to_channel = { + executor.submit(self.check_channel_health, channel): channel + for channel in channels + } + + # Collect results as they complete + for future in concurrent.futures.as_completed(future_to_channel, timeout=60): + try: + result = future.result(timeout=10) + results.append(result) + except Exception as e: + channel = future_to_channel[future] + self.logger.warning(f"Health check failed for {channel.get('Stream name', 'Unknown')}: {e}") + results.append({ + 'channel_name': channel.get('Stream name', 'Unknown'), + 'url': channel.get('Stream URL', ''), + 'status': 'error', + 'status_code': None, + 'response_time': 0, + 'error': str(e) + }) + except Exception as e: + self.logger.error(f"Concurrent health check failed: {e}") + # Fall back to sequential processing + for channel in channels: + try: + result = self.check_channel_health(channel) + results.append(result) + except Exception as channel_error: + self.logger.warning(f"Channel check failed: {channel_error}") + else: + # Sequential processing for single worker + for channel in channels: try: - result = future.result() + result = self.check_channel_health(channel) results.append(result) except Exception as e: - channel = future_to_channel[future] - self.logger.error(f"Health check failed for {channel.get('Stream name', 'Unknown')}: {e}") + self.logger.warning(f"Health check failed for {channel.get('Stream name', 'Unknown')}: {e}") results.append({ 'channel_name': channel.get('Stream name', 'Unknown'), 'url': channel.get('Stream URL', ''), @@ -216,33 +271,4 @@ class HealthChecker: except Exception as e: self.logger.error(f"Could not save health report: {e}") - return None - - -# Simple fallback for when requests is not available -class SimpleHealthChecker: - """Fallback health checker that doesn't require external dependencies.""" - - def __init__(self, config): - self.config = config - self.logger = logging.getLogger(__name__) - - def batch_health_check(self, channels: List[Dict]) -> Dict: - """Fallback that skips health checking.""" - self.logger.info("Health checking disabled (requests library not available)") - return { - 'enabled': False, - 'results': [], - 'summary': {'total': len(channels), 'healthy': 0, 'health_percentage': 0}, - 'total_time': 0 - } - - -# Try to use the full health checker, fall back to simple one if requests isn't available -try: - import requests - # If requests is available, use the full HealthChecker -except ImportError: - # If requests is not available, use the fallback - class HealthChecker(SimpleHealthChecker): - pass \ No newline at end of file + return None \ No newline at end of file