TCPDashboard/dashboard/callbacks/system_health_modules/data_collection_callbacks.py
2025-06-12 13:27:30 +08:00

234 lines
9.3 KiB
Python

from dash import Output, Input, State, html, callback_context, no_update
import dash_bootstrap_components as dbc
from utils.logger import get_logger
from database.connection import DatabaseManager
from datetime import datetime, timedelta
from dashboard.callbacks.system_health_modules.common_health_utils import _check_data_collection_service_running
from config.constants.system_health_constants import (
DATA_FRESHNESS_RECENT_MINUTES,
DATA_FRESHNESS_STALE_HOURS
)
logger = get_logger()
def register_data_collection_callbacks(app):
"""Register data collection status and metrics callbacks."""
# Detailed Data Collection Service Status
@app.callback(
[Output('data-collection-service-status', 'children'),
Output('data-collection-metrics', 'children')],
[Input('interval-component', 'n_intervals'),
Input('refresh-data-status-btn', 'n_clicks')]
)
def update_data_collection_status(n_intervals, refresh_clicks):
"""Update detailed data collection service status and metrics."""
try:
service_status = _get_data_collection_service_status()
metrics = _get_data_collection_metrics()
return service_status, metrics
except Exception as e:
logger.error(f"Error updating data collection status: {e}")
error_div = dbc.Alert(
f"Error: {str(e)}",
color="danger",
dismissable=True
)
return error_div, error_div
# Individual Collectors Status
@app.callback(
Output('individual-collectors-status', 'children'),
[Input('interval-component', 'n_intervals'),
Input('refresh-data-status-btn', 'n_clicks')]
)
def update_individual_collectors_status(n_intervals, refresh_clicks):
"""Update individual data collector health status."""
try:
return _get_individual_collectors_status()
except Exception as e:
logger.error(f"Error updating individual collectors status: {e}")
return dbc.Alert(
f"Error: {str(e)}",
color="danger",
dismissable=True
)
# Data Collection Details Modal
@app.callback(
[Output("collection-details-modal", "is_open"),
Output("collection-details-content", "children")],
[Input("view-collection-details-btn", "n_clicks")],
[State("collection-details-modal", "is_open")]
)
def toggle_collection_details_modal(n_clicks, is_open):
"""Toggle and populate the collection details modal."""
if n_clicks:
details_content = _get_collection_details_content()
return not is_open, details_content
return is_open, no_update
# Collection Logs Modal
@app.callback(
[Output("collection-logs-modal", "is_open"),
Output("collection-logs-content", "children")],
[Input("view-collection-logs-btn", "n_clicks"),
Input("refresh-logs-btn", "n_clicks")],
[State("collection-logs-modal", "is_open")],
prevent_initial_call=True
)
def toggle_collection_logs_modal(logs_clicks, refresh_clicks, is_open):
"""Toggle and populate the collection logs modal."""
ctx = callback_context
if not ctx.triggered:
return is_open, no_update
triggered_id = ctx.triggered_id
if triggered_id in ["view-collection-logs-btn", "refresh-logs-btn"]:
logs_content = _get_collection_logs_content()
return True, logs_content
return is_open, no_update
@app.callback(
Output("collection-logs-modal", "is_open", allow_duplicate=True),
Input("close-logs-modal", "n_clicks"),
State("collection-logs-modal", "is_open"),
prevent_initial_call=True
)
def close_logs_modal(n_clicks, is_open):
if n_clicks:
return not is_open
return is_open
def _get_data_collection_service_status() -> html.Div:
"""Get detailed data collection service status."""
try:
is_running = _check_data_collection_service_running()
current_time = datetime.now().strftime('%H:%M:%S')
if is_running:
status_badge = dbc.Badge("Service Running", color="success", className="me-2")
status_text = html.P("Data collection service is actively collecting market data.", className="mb-0")
details = html.Div()
else:
status_badge = dbc.Badge("Service Stopped", color="danger", className="me-2")
status_text = html.P("Data collection service is not running.", className="text-danger")
details = html.Div([
html.P("To start the service, run:", className="mt-2 mb-1"),
html.Code("python scripts/start_data_collection.py")
])
return html.Div([
dbc.Row([
dbc.Col(status_badge, width="auto"),
dbc.Col(html.P(f"Checked: {current_time}", className="text-muted mb-0"), width="auto")
], align="center", className="mb-2"),
status_text,
details
])
except Exception as e:
return dbc.Alert(f"Error checking status: {e}", color="danger")
def _get_data_collection_metrics() -> html.Div:
"""Get data collection metrics."""
try:
db_manager = DatabaseManager()
db_manager.initialize()
with db_manager.get_session() as session:
from sqlalchemy import text
candles_count = session.execute(text("SELECT COUNT(*) FROM market_data")).scalar() or 0
tickers_count = session.execute(text("SELECT COUNT(*) FROM raw_trades WHERE data_type = 'ticker'")).scalar() or 0
latest_market_data = session.execute(text("SELECT MAX(timestamp) FROM market_data")).scalar()
latest_raw_data = session.execute(text("SELECT MAX(timestamp) FROM raw_trades")).scalar()
latest_data = max(d for d in [latest_market_data, latest_raw_data] if d) if any([latest_market_data, latest_raw_data]) else None
if latest_data:
time_diff = datetime.utcnow() - (latest_data.replace(tzinfo=None) if latest_data.tzinfo else latest_data)
if time_diff < timedelta(minutes=DATA_FRESHNESS_RECENT_MINUTES):
freshness_badge = dbc.Badge(f"Fresh ({time_diff.seconds // 60}m ago)", color="success")
elif time_diff < timedelta(hours=DATA_FRESHNESS_STALE_HOURS):
freshness_badge = dbc.Badge(f"Recent ({time_diff.seconds // 60}m ago)", color="warning")
else:
freshness_badge = dbc.Badge(f"Stale ({time_diff.total_seconds() // 3600:.1f}h ago)", color="danger")
else:
freshness_badge = dbc.Badge("No data", color="secondary")
return html.Div([
dbc.Row([
dbc.Col(html.Strong("Candles:")),
dbc.Col(f"{candles_count:,}", className="text-end")
]),
dbc.Row([
dbc.Col(html.Strong("Tickers:")),
dbc.Col(f"{tickers_count:,}", className="text-end")
]),
dbc.Row([
dbc.Col(html.Strong("Data Freshness:")),
dbc.Col(freshness_badge, className="text-end")
])
])
except Exception as e:
return dbc.Alert(f"Error loading metrics: {e}", color="danger")
def _get_individual_collectors_status() -> html.Div:
"""Get individual data collector status."""
try:
return dbc.Alert([
html.P("Individual collector health data will be displayed here when the data collection service is running.", className="mb-2"),
html.Hr(),
html.P("To start monitoring, run the following command:", className="mb-1"),
html.Code("python scripts/start_data_collection.py")
], color="info")
except Exception as e:
return dbc.Alert(f"Error checking collector status: {e}", color="danger")
def _get_collection_details_content() -> html.Div:
"""Get detailed collection information for modal."""
try:
return html.Div([
html.H5("Data Collection Service Details"),
html.P("Comprehensive data collection service information would be displayed here."),
html.Hr(),
html.H6("Configuration"),
html.P("Service configuration details..."),
html.H6("Performance Metrics"),
html.P("Detailed performance analytics..."),
html.H6("Health Status"),
html.P("Individual collector health information...")
])
except Exception as e:
return dbc.Alert(f"Error loading details: {e}", color="danger")
def _get_collection_logs_content() -> str:
"""Get recent collection service logs."""
try:
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return f"""[{current_time}] INFO - Data Collection Service Logs
Recent log entries would be displayed here from the data collection service.
This would include:
- Service startup/shutdown events
- Collector connection status changes
- Data collection statistics
- Error messages and warnings
- Performance metrics
To view real logs, check the logs/ directory or configure log file monitoring.
"""
except Exception as e:
return f"Error loading logs: {str(e)}"