Update scripts/health_checker.py
All checks were successful
Generate M3U Playlist / build (push) Successful in 2m7s

This commit is contained in:
stoney420 2025-06-28 02:22:49 +02:00
parent e8a792a46b
commit 4ed1b80ac4

View file

@ -1,16 +1,18 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Health Checker - Simple URL health checking for IPTV channels Health Checker - Simple health checking without external dependencies
""" """
import logging import logging
import requests import urllib.request
import urllib.error
import socket
import time
import concurrent.futures import concurrent.futures
from typing import Dict, List, Optional from typing import Dict, List, Optional
import time
class HealthChecker: class HealthChecker:
"""Simple health checker for IPTV channel URLs.""" """Simple health checker using only standard library."""
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
@ -18,29 +20,52 @@ class HealthChecker:
self.timeout = config.settings.get('health_check_timeout', 5) self.timeout = config.settings.get('health_check_timeout', 5)
self.max_workers = config.settings.get('max_workers', 4) 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: 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() start_time = time.time()
try: try:
response = requests.head( # Create request with proper headers
req = urllib.request.Request(
url, url,
timeout=self.timeout,
allow_redirects=True,
headers={'User-Agent': 'IPTV-Health-Checker/1.0'} 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 { return {
'url': url, 'url': url,
'status': 'healthy' if response.status_code < 400 else 'unhealthy', 'status': 'unhealthy',
'status_code': response.status_code, 'status_code': e.code,
'response_time': round(response_time, 2), 'response_time': time.time() - start_time,
'error': None '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 { return {
'url': url, 'url': url,
'status': 'timeout', 'status': 'timeout',
@ -49,15 +74,6 @@ class HealthChecker:
'error': 'Request timeout' '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: except Exception as e:
return { return {
'url': url, 'url': url,
@ -90,29 +106,68 @@ class HealthChecker:
"""Perform batch health check on multiple channels.""" """Perform batch health check on multiple channels."""
if not self.config.settings.get('enable_health_check', False): if not self.config.settings.get('enable_health_check', False):
self.logger.info("Health checking is disabled") 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...") self.logger.info(f"Starting health check for {len(channels)} channels...")
start_time = time.time() start_time = time.time()
results = [] results = []
# Use ThreadPoolExecutor for concurrent checks # Limit concurrent checks to avoid overwhelming servers
with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor: max_workers = min(self.max_workers, len(channels))
# 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 if max_workers > 1:
for future in concurrent.futures.as_completed(future_to_channel): # 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: try:
result = future.result() result = self.check_channel_health(channel)
results.append(result) results.append(result)
except Exception as e: except Exception as e:
channel = future_to_channel[future] self.logger.warning(f"Health check failed for {channel.get('Stream name', 'Unknown')}: {e}")
self.logger.error(f"Health check failed for {channel.get('Stream name', 'Unknown')}: {e}")
results.append({ results.append({
'channel_name': channel.get('Stream name', 'Unknown'), 'channel_name': channel.get('Stream name', 'Unknown'),
'url': channel.get('Stream URL', ''), 'url': channel.get('Stream URL', ''),
@ -217,32 +272,3 @@ class HealthChecker:
except Exception as e: except Exception as e:
self.logger.error(f"Could not save health report: {e}") self.logger.error(f"Could not save health report: {e}")
return None 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