Infrastructure·System Monitoring·Intermediate

Uptime Monitor

Build a comprehensive system uptime and component latency monitoring framework that continuously tracks exchange API response time percentiles, WebSocket connection stability metrics, and internal message processing pipeline delays with historical trend analysis and degradation early warning detection.

infrastructuremonitoring

Infrastructure Monitoring: Monitor System Uptime and Latency (Crypto Infrastructure)

This notebook provides a framework for monitoring the uptime and latency of critical infrastructure components, with a specific focus on applications within the cryptocurrency domain. Robust monitoring is essential for maintaining the reliability and performance of blockchain nodes, decentralized applications (dApps), and related services. By tracking key metrics like uptime and latency, operators can quickly detect anomalies, diagnose issues, and ensure a high-quality user experience.

Key Concepts Covered:

ConceptDescription
Uptime MonitoringVerifying that a service is operational and accessible. This typically involves periodic checks to an endpoint and assessing the response. For crypto, this could mean checking if a node is synchronized and responding to RPC calls.
Latency MonitoringMeasuring the delay between a request and a response. High latency can indicate network congestion, overloaded services, or inefficient processing. In crypto, low latency is crucial for trading, transaction propagation, and dApp responsiveness.
Exponential BackoffA strategy for retrying failed requests. It involves progressively increasing the wait time between retries to avoid overwhelming a potentially struggling service and to give it time to recover.
Sliding WindowA data structure (often a deque) used to keep track of a fixed number of recent data points, useful for calculating moving averages or recent statistics without storing all historical data.
State ManagementUsing dictionaries to maintain the current status and historical data of monitored services, allowing for flexible and extensible monitoring logic without relying on object-oriented programming.
Data VisualizationGraphical representation of monitoring data (e.g., time series plots for latency, bar charts for uptime trends) to quickly identify patterns, trends, and anomalies.

2. Dependency Installation

This section installs all the necessary Python libraries required for infrastructure monitoring, data handling, and visualization.

[13]
pip install requests pandas matplotlib seaborn collections
Requirement already satisfied: requests in /usr/local/lib/python3.12/dist-packages (2.32.4)
Requirement already satisfied: pandas in /usr/local/lib/python3.12/dist-packages (2.2.2)
Requirement already satisfied: matplotlib in /usr/local/lib/python3.12/dist-packages (3.10.0)
Requirement already satisfied: seaborn in /usr/local/lib/python3.12/dist-packages (0.13.2)
ERROR: Could not find a version that satisfies the requirement collections (from versions: none)
ERROR: No matching distribution found for collections


3. Library Imports

This section imports all the necessary Python libraries for monitoring, data processing, logging, and visualization. Standard libraries are imported first, followed by third-party libraries.

[14]
import time
import datetime
import logging
from collections import deque
import random

import requests
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

4. Core Functions

This section defines the core functions for initializing monitoring state, making API requests, processing responses, and managing retry logic with exponential backoff. Each function is presented in its own dedicated code block, complete with detailed explanations, type hints, and logger statements.

Function Name: create_monitoring_state

This function initializes the monitoring state dictionary for a given service. It sets up initial values for the service URL, status, last check time, and a deque for storing recent latency measurements. This dictionary will be passed around to other functions to maintain the state of the monitored service.

Parameters:

  • service_name (str): The name of the service to monitor (e.g., 'Ethereum RPC', 'Binance Exchange API').
  • url (str): The endpoint URL of the service to be monitored.
  • window_size (int, optional): The number of recent latency measurements to store in the sliding window. Defaults to 10.

Returns:

  • dict: An initialized state dictionary for the service.
[15]
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

def create_monitoring_state(
    service_name: str, url: str, window_size: int = 10
) -> dict:
    """
    Initializes the monitoring state dictionary for a given service.

    Parameters
    ----------
    service_name : str
        The name of the service to monitor.
    url : str
        The endpoint URL of the service to be monitored.
    window_size : int, optional
        The number of recent latency measurements to store in the sliding window,
        defaults to 10.

    Returns
    -------
    dict
        An initialized state dictionary for the service.
    """
    state = {
        "service_name": service_name,
        "url": url,
        "status": "UNKNOWN",  # e.g., 'UP', 'DOWN', 'DEGRADED'
        "last_check_time": None,
        "last_latency_ms": None,
        "latency_history": deque(maxlen=window_size),
        "uptime_downtime_events": [],  # Stores (timestamp, status) tuples
        "consecutive_failures": 0,
        "total_checks": 0,
        "successful_checks": 0,
    }
    logger.info(f"Monitoring state initialized for service: {service_name} with URL: {url}")
    return state

Function Name: check_service_status

This function attempts to check the status of a given service by making an HTTP GET request to its URL. It incorporates an exponential backoff strategy for retries in case of transient failures, adding a random jitter to avoid thundering herd problems. It measures the latency of the successful request and updates the monitoring state accordingly.

Parameters:

  • state (dict): The current monitoring state dictionary for the service.
  • max_retries (int, optional): Maximum number of retries for the HTTP request. Defaults to 3.
  • initial_backoff (float, optional): Initial delay in seconds before the first retry. Defaults to 0.5.

Returns:

  • dict: The updated monitoring state dictionary with current status, latency, and check time.
[16]
def check_service_status(
    state: dict, max_retries: int = 3, initial_backoff: float = 0.5
) -> dict:
    """
    Checks the status of a service by making an HTTP GET request with exponential backoff.

    Parameters
    ----------
    state : dict
        Current monitoring state dictionary.
    max_retries : int, optional
        Maximum number of retries for the HTTP request, defaults to 3.
    initial_backoff : float, optional
        Initial delay in seconds before the first retry, defaults to 0.5.

    Returns
    -------
    dict
        Updated monitoring state dictionary.
    """
    service_name = state["service_name"]
    url = state["url"]
    retries = 0
    current_backoff = initial_backoff
    success = False
    latency_ms = None

    state["total_checks"] += 1

    while retries <= max_retries:
        try:
            start_time = time.time()
            logger.debug(
                f"Attempting to reach {service_name} at {url} (Attempt {retries + 1}/{max_retries + 1})"
            )
            response = requests.get(url, timeout=5)  # 5-second timeout
            end_time = time.time()
            latency_ms = (end_time - start_time) * 1000

            if response.status_code == 200:
                logger.info(f"{service_name} is UP. Latency: {latency_ms:.2f}ms")
                state["status"] = "UP"
                state["last_latency_ms"] = latency_ms
                state["latency_history"].append(latency_ms)
                state["successful_checks"] += 1
                state["consecutive_failures"] = 0
                success = True
                break
            else:
                logger.warning(
                    f"{service_name} returned status code {response.status_code}. "
                    f"Retrying... (Attempt {retries + 1})"
                )
        except requests.exceptions.Timeout:
            logger.warning(
                f"{service_name} request timed out after 5 seconds. "
                f"Retrying... (Attempt {retries + 1})"
            )
        except requests.exceptions.ConnectionError:
            logger.warning(
                f"Connection error for {service_name}. "
                f"Retrying... (Attempt {retries + 1})"
            )
        except requests.exceptions.RequestException as e:
            logger.error(
                f"An unexpected request error occurred for {service_name}: {e}. "
                f"Retrying... (Attempt {retries + 1})"
            )

        retries += 1
        if retries <= max_retries:
            jitter = random.uniform(0.5, 1.5)  # Add random jitter
            sleep_time = current_backoff * jitter
            logger.debug(
                f"Waiting {sleep_time:.2f}s before next retry for {service_name}"
            )
            time.sleep(sleep_time)
            current_backoff *= 2  # Exponential increase

    state["last_check_time"] = datetime.datetime.now()

    if not success:
        state["status"] = "DOWN"
        state["last_latency_ms"] = None
        state["consecutive_failures"] += 1
        logger.error(
            f"{service_name} is DOWN after {max_retries + 1} attempts."
        )

    # Log uptime/downtime event if status changed
    current_status = state["status"]
    if not state["uptime_downtime_events"] or \
       state["uptime_downtime_events"][-1][1] != current_status:
        state["uptime_downtime_events"].append((state["last_check_time"], current_status))

    return state

Function Name: summarize_monitoring_metrics

This function calculates and returns key monitoring metrics from the service's state, such as uptime percentage, average latency, and the number of successful vs. total checks. It provides a quick overview of the service's performance and reliability.

Parameters:

  • state (dict): The current monitoring state dictionary for the service.

Returns:

  • dict: A dictionary containing summarized metrics like uptime_percentage, average_latency_ms, total_checks, successful_checks, and current_status.
[17]
def summarize_monitoring_metrics(state: dict) -> dict:
    """
    Calculates and returns key monitoring metrics from the service's state.

    Parameters
    ----------
    state : dict
        Current monitoring state dictionary.

    Returns
    -------
    dict
        A dictionary containing summarized metrics.
    """
    total_checks = state["total_checks"]
    successful_checks = state["successful_checks"]
    uptime_percentage = (successful_checks / total_checks * 100) if total_checks > 0 else 0.0
    average_latency_ms = (sum(state["latency_history"]) / len(state["latency_history"])) if state["latency_history"] else None

    summary = {
        "service_name": state["service_name"],
        "current_status": state["status"],
        "total_checks": total_checks,
        "successful_checks": successful_checks,
        "uptime_percentage": round(uptime_percentage, 2),
        "average_latency_ms": round(average_latency_ms, 2) if average_latency_ms is not None else None,
        "consecutive_failures": state["consecutive_failures"],
    }
    logger.info(f"Metrics summary for {state['service_name']}: {summary}")
    return summary

5. Demonstration/Visualization

This section demonstrates the usage of the core monitoring functions. It simulates the monitoring of multiple crypto-related services, collects their status and latency over time, and then visualizes these metrics using matplotlib and seaborn. This includes time series plots for latency, status change events, and summary statistics.

Function Name: simulate_monitoring

This function orchestrates the monitoring process for a list of services. It iteratively calls check_service_status for each service, introduces a small delay between checks to simulate a real-world monitoring interval, and stores historical data for visualization. This function uses a simple loop to simulate continuous monitoring over a specified number of iterations.

Parameters:

  • service_states (list[dict]): A list of initialized service state dictionaries.
  • iterations (int): The number of monitoring cycles to perform.
  • check_interval_seconds (float, optional): The delay between monitoring cycles in seconds. Defaults to 5.0.

Returns:

  • list[dict]: The updated list of service state dictionaries after the simulation.
[18]
def simulate_monitoring(
    service_states: list[dict], iterations: int, check_interval_seconds: float = 5.0
) -> list[dict]:
    """
    Simulates monitoring of multiple services over a specified number of iterations.

    Parameters
    ----------
    service_states : list[dict]
        A list of initialized service state dictionaries.
    iterations : int
        The number of monitoring cycles to perform.
    check_interval_seconds : float, optional
        The delay between monitoring cycles in seconds, defaults to 5.0.

    Returns
    -------
    list[dict]
        The updated list of service state dictionaries after the simulation.
    """
    logger.info(f"Starting monitoring simulation for {len(service_states)} services over {iterations} iterations.")
    for i in range(iterations):
        logger.info(f"--- Monitoring Cycle {i + 1}/{iterations} ---")
        for state in service_states:
            state = check_service_status(state)
            # Ensure latency history is captured over time for plotting
            if state['last_latency_ms'] is not None:
                # Append current latency and check time for detailed history
                if 'full_latency_history' not in state:
                    state['full_latency_history'] = []
                state['full_latency_history'].append({'timestamp': state['last_check_time'], 'latency_ms': state['last_latency_ms']})
            else:
                # If down, still record the timestamp with a null latency
                if 'full_latency_history' not in state:
                    state['full_latency_history'] = []
                state['full_latency_history'].append({'timestamp': state['last_check_time'], 'latency_ms': None})

        if i < iterations - 1:
            # Add random jitter to the check interval
            jitter = random.uniform(0.8, 1.2)
            sleep_time = check_interval_seconds * jitter
            logger.debug(f"Sleeping for {sleep_time:.2f}s before next cycle.")
            time.sleep(sleep_time)

    logger.info("Monitoring simulation completed.")
    return service_states

Simulate Monitoring for Crypto Services

We will now define a few example crypto infrastructure services and simulate their monitoring over several iterations. This simulation will gather latency and uptime data, including scenarios where a service might temporarily go down or experience latency spikes.

[19]
services_to_monitor_configs = [
    {"name": "Dummy Local Service", "url": "https://www.google.com"}, # Simulating a local, always-up dummy service
    {"name": "Binance API", "url": "https://api.binance.com/api/v3/ping"},
    {"name": "Fake Down Service", "url": "http://this-url-does-not-exist-12345.com"} # This will simulate a perpetually down service
]

# Initialize monitoring states for each service
service_states = []
for config in services_to_monitor_configs:
    state = create_monitoring_state(config["name"], config["url"])
    service_states.append(state)

# Run the monitoring simulation
# Setting a shorter interval and fewer iterations for demonstration purposes
simulated_service_states = simulate_monitoring(service_states, iterations=10, check_interval_seconds=2.0)

logger.info("Simulation completed. Summarizing final states:")
for state in simulated_service_states:
    summary = summarize_monitoring_metrics(state)
    logger.info(f"  Service: {summary['service_name']}, Status: {summary['current_status']}, "
                f"Uptime: {summary['uptime_percentage']}%, Avg Latency: {summary['average_latency_ms']}ms")
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 1)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 2)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 3)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 4)
ERROR:__main__:Fake Down Service is DOWN after 4 attempts.
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 1)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 2)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 3)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 4)
ERROR:__main__:Fake Down Service is DOWN after 4 attempts.
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 1)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 2)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 3)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 4)
ERROR:__main__:Fake Down Service is DOWN after 4 attempts.
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 1)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 2)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 3)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 4)
ERROR:__main__:Fake Down Service is DOWN after 4 attempts.
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 1)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 2)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 3)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 4)
ERROR:__main__:Fake Down Service is DOWN after 4 attempts.
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 1)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 2)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 3)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 4)
ERROR:__main__:Fake Down Service is DOWN after 4 attempts.
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 1)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 2)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 3)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 4)
ERROR:__main__:Fake Down Service is DOWN after 4 attempts.
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 1)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 2)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 3)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 4)
ERROR:__main__:Fake Down Service is DOWN after 4 attempts.
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 1)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 2)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 3)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 4)
ERROR:__main__:Fake Down Service is DOWN after 4 attempts.
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 1)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 2)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 3)
WARNING:__main__:Connection error for Fake Down Service. Retrying... (Attempt 4)
ERROR:__main__:Fake Down Service is DOWN after 4 attempts.

Visualization: Latency and Status Over Time

This section provides functions to visualize the collected monitoring data. We'll plot latency trends and indicate status changes (UP/DOWN) over the simulated monitoring period.

Function Name: plot_monitoring_results

This function takes the simulated monitoring states and generates a time series plot for latency, highlighting periods of downtime for each service. It converts the raw full_latency_history into a Pandas DataFrame for easier plotting and ensures that None latency values (indicating downtime) are appropriately represented.

Parameters:

  • service_states (list[dict]): A list of finalized service state dictionaries from the simulation.

Returns:

  • None (displays plots directly).
[20]
def plot_monitoring_results(service_states: list[dict]) -> None:
    """
    Plots latency history and service status over time for all monitored services.

    Parameters
    ----------
    service_states : list[dict]
        A list of finalized service state dictionaries from the simulation.

    Returns
    -------
    None
        Displays plots directly.
    """
    num_services = len(service_states)
    plt.figure(figsize=(15, 5 * num_services))

    for i, state in enumerate(service_states):
        service_name = state["service_name"]
        full_history = state.get('full_latency_history', [])

        if not full_history:
            logger.warning(f"No full latency history data found for {service_name}. Skipping plot.")
            continue

        df_history = pd.DataFrame(full_history)
        df_history['timestamp'] = pd.to_datetime(df_history['timestamp'])
        df_history = df_history.set_index('timestamp')

        plt.subplot(num_services, 1, i + 1)
        sns.lineplot(x=df_history.index, y='latency_ms', data=df_history, marker='o', label=f'{service_name} Latency')

        # Mark downtime periods
        downtime_periods = df_history[df_history['latency_ms'].isnull()]
        if not downtime_periods.empty:
            for start, end in zip(downtime_periods.index[:-1], downtime_periods.index[1:]):
                plt.axvspan(start, end, color='red', alpha=0.3, label='_nolegend_')

            # For single downtime point or the very last one
            if len(downtime_periods) == 1:
                plt.axvline(x=downtime_periods.index[0], color='red', linestyle='--', alpha=0.5, label='Downtime')
            else:
                plt.axvline(x=downtime_periods.index[-1], color='red', linestyle='--', alpha=0.5, label='Downtime')

        plt.title(f'Latency and Uptime for {service_name}')
        plt.ylabel('Latency (ms)')
        plt.xlabel('Time')
        plt.grid(True)
        plt.legend()
        plt.tight_layout()

    plt.show()

Execute Plotting Function

Now, let's call the plot_monitoring_results function with the simulated_service_states to visualize the data collected during the simulation.

[21]
plot_monitoring_results(simulated_service_states)
cell output

Summarize Uptime and Latency with DataFrame

Finally, let's present a summary of the uptime percentage and average latency for all services in a tabular format using a Pandas DataFrame.

[22]
summary_data = []
for state in simulated_service_states:
    summary_data.append(summarize_monitoring_metrics(state))

df_summary = pd.DataFrame(summary_data)
print(df_summary.to_markdown(index=False))
| service_name        | current_status   |   total_checks |   successful_checks |   uptime_percentage |   average_latency_ms |   consecutive_failures |
|:--------------------|:-----------------|---------------:|--------------------:|--------------------:|---------------------:|-----------------------:|
| Dummy Local Service | UP               |             10 |                  10 |                 100 |                57.23 |                      0 |
| Binance API         | UP               |             10 |                  10 |                 100 |               134.56 |                      0 |
| Fake Down Service   | DOWN             |             10 |                   0 |                   0 |               nan    |                     10 |

6. Production Considerations

When deploying infrastructure monitoring solutions in a production environment, several factors need to be taken into account to ensure reliability, scalability, and maintainability. This table outlines key best practices for production-grade monitoring of crypto infrastructure.

AspectDescription
Alerting & NotificationsIntegrate with robust alerting systems (e.g., PagerDuty, Slack, email) to notify operators immediately when critical thresholds are breached (e.g., service down, latency spikes, high error rates). Define clear escalation policies.
Persistent StorageFor long-term historical analysis and compliance, monitoring data should be stored in a persistent database (e.g., Prometheus with Grafana, InfluxDB, PostgreSQL). The in-memory deque is suitable for immediate sliding window averages but not for indefinite storage.
Authentication & SecurityEnsure that all API calls to services, especially sensitive crypto infrastructure, are authenticated using secure methods (API keys, OAuth, mTLS). Encrypt data in transit and at rest. Restrict access to monitoring systems.
ScalabilityAs the number of monitored services grows, consider distributed monitoring agents or horizontally scalable monitoring platforms. Avoid single points of failure in the monitoring infrastructure itself.
Configuration ManagementUse configuration management tools (e.g., Ansible, Terraform) to define and deploy monitoring configurations. Avoid hardcoding service URLs or API keys directly in code; use environment variables or a secure secret management system.
Health Checks & LivenessBeyond basic uptime, implement application-level health checks that verify the functional correctness of the service (e.g., for a blockchain node, check if it's synced to the latest block, not just if the RPC endpoint responds).
Cost OptimizationBe mindful of the cost implications of monitoring, especially for external API calls and data storage. Optimize polling intervals, data retention policies, and choose cost-effective monitoring solutions without compromising critical visibility.
Monitoring of MonitoringEnsure your monitoring system itself is monitored. Implement health checks for monitoring agents, data pipelines, and alerting mechanisms to guarantee that you're alerted if the monitoring system fails to collect or report data.

7. Conclusion

This notebook has provided a foundational framework for monitoring the uptime and latency of critical infrastructure, with a particular emphasis on crypto-related services. We've implemented core functions for state management, reliable service checks with exponential backoff, and methods to summarize and visualize monitoring metrics.

The demonstration showcased how to simulate real-world monitoring scenarios, track service health, and identify potential issues such as downtime or increased latency. The visualizations provided a clear overview of service performance over time, while the summary table offered quick, actionable insights.

Effective infrastructure monitoring is paramount in the fast-paced and high-stakes environment of cryptocurrency. By adapting and expanding upon the principles and code laid out here, developers and operators can build robust monitoring systems to ensure the continuous availability and optimal performance of their crypto infrastructure.