Update scripts/health_checker.py
All checks were successful
Generate M3U Playlist / build (push) Successful in 2m7s
All checks were successful
Generate M3U Playlist / build (push) Successful in 2m7s
This commit is contained in:
parent
e8a792a46b
commit
4ed1b80ac4
1 changed files with 95 additions and 69 deletions
|
@ -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(
|
||||
# Create request with proper headers
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
timeout=self.timeout,
|
||||
allow_redirects=True,
|
||||
headers={'User-Agent': 'IPTV-Health-Checker/1.0'}
|
||||
)
|
||||
|
||||
# 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 response.status_code < 400 else 'unhealthy',
|
||||
'status_code': response.status_code,
|
||||
'status': 'healthy' if status_code < 400 else 'unhealthy',
|
||||
'status_code': status_code,
|
||||
'response_time': round(response_time, 2),
|
||||
'error': None
|
||||
}
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
except urllib.error.HTTPError as e:
|
||||
return {
|
||||
'url': url,
|
||||
'status': 'unhealthy',
|
||||
'status_code': e.code,
|
||||
'response_time': time.time() - start_time,
|
||||
'error': f'HTTP {e.code}: {e.reason}'
|
||||
}
|
||||
|
||||
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,15 +106,29 @@ 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 = []
|
||||
|
||||
# Limit concurrent checks to avoid overwhelming servers
|
||||
max_workers = min(self.max_workers, len(channels))
|
||||
|
||||
if max_workers > 1:
|
||||
# Use ThreadPoolExecutor for concurrent checks
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor:
|
||||
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
|
||||
|
@ -106,13 +136,38 @@ class HealthChecker:
|
|||
}
|
||||
|
||||
# Collect results as they complete
|
||||
for future in concurrent.futures.as_completed(future_to_channel):
|
||||
for future in concurrent.futures.as_completed(future_to_channel, timeout=60):
|
||||
try:
|
||||
result = future.result()
|
||||
result = future.result(timeout=10)
|
||||
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', ''),
|
||||
'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 = self.check_channel_health(channel)
|
||||
results.append(result)
|
||||
except Exception as 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', ''),
|
||||
|
@ -217,32 +272,3 @@ 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
|
Loading…
Add table
Add a link
Reference in a new issue