Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 80 additions & 1 deletion src/krkn_lib/telemetry/k8s/krkn_telemetry_kubernetes.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,15 @@ def get_prometheus_pod_data(
prometheus_container_name: str,
prometheus_namespace: str,
remote_archive_path: str = "/prometheus",
start_timestamp: Optional[int] = None,
end_timestamp: Optional[int] = None,
prometheus_url: Optional[str] = None,
prometheus_bearer_token: Optional[str] = None,
) -> list[(int, str)]:
"""
Downloads the prometheus metrics folder from a prometheus pod
Downloads the prometheus metrics folder from a prometheus pod.
Attempts API-based collection first (if parameters provided),
then falls back to filesystem backup.

:param telemetry_config: krkn telemetry conf section
will be stored
Expand All @@ -204,6 +210,14 @@ def get_prometheus_pod_data(
pod lives
:param remote_archive_path: (Optional) the path where prometheus logs
are stored, if not specified will default to `/prometheus`
:param start_timestamp: (Optional) Start time for API-based collection
(Unix timestamp in seconds)
:param end_timestamp: (Optional) End time for API-based collection
(Unix timestamp in seconds)
:param prometheus_url: (Optional) Prometheus API URL for API-based
collection
:param prometheus_bearer_token: (Optional) Bearer token for
Prometheus API authentication
:return: the list of the archive number and filenames downloaded
"""
file_list = list[(int, str)]()
Expand Down Expand Up @@ -255,6 +269,71 @@ def get_prometheus_pod_data(
if not prometheus_backup:
return file_list

# Try API-based collection if parameters provided
use_api = telemetry_config.get("prometheus_backup_use_api", True)

if (
use_api
and start_timestamp
and end_timestamp
and prometheus_url
):
try:
from krkn_lib.telemetry.prometheus_exporter import (
PrometheusExporter,
)

exporter = PrometheusExporter(
prometheus_url, prometheus_bearer_token
)

if exporter.test_connection():
# Get time window buffers from config
time_before = telemetry_config.get(
"prometheus_time_window_minutes_before", 10
)
time_after = telemetry_config.get(
"prometheus_time_window_minutes_after", 10
)

window_start = start_timestamp - (time_before * 60)
window_end = end_timestamp + (time_after * 60)

self.safe_logger.info(
f"Attempting Prometheus API collection for time "
f"window: {(window_end - window_start) / 60:.1f} "
f"minutes"
)

archive_file = exporter.export_metrics_snapshot(
window_start,
window_end,
archive_path,
f"prometheus-api-{request_id}",
)

if archive_file:
self.safe_logger.info(
f"Successfully collected via API: {archive_file}"
)
return [(0, archive_file)]
else:
self.safe_logger.warning(
"API export returned no data, falling back to "
"filesystem backup"
)
else:
self.safe_logger.warning(
"API connection test failed, falling back to "
"filesystem backup"
)
except Exception as e:
self.safe_logger.warning(
f"API collection failed: {str(e)}, falling back to "
f"filesystem backup"
)

# Fallback to existing filesystem backup
prometheus_pod = self.__kubecli.get_pod_info(
prometheus_pod_name, prometheus_namespace
)
Expand Down
22 changes: 21 additions & 1 deletion src/krkn_lib/telemetry/ocp/krkn_telemetry_openshift.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import threading
from queue import Queue
from typing import Optional

from krkn_lib.models.telemetry import ChaosRunTelemetry
from krkn_lib.ocp import KrknOpenshift
Expand Down Expand Up @@ -42,14 +43,28 @@ def get_ocp_prometheus_data(
self,
telemetry_config: dict,
request_id: str,
start_timestamp: Optional[int] = None,
end_timestamp: Optional[int] = None,
prometheus_url: Optional[str] = None,
prometheus_bearer_token: Optional[str] = None,
) -> list[(int, str)]:
"""
Downloads the Openshift prometheus metrics
Downloads the Openshift prometheus metrics.
Attempts API-based collection first (if parameters provided),
then falls back to filesystem backup.

:param telemetry_config: krkn telemetry conf section
will be stored
:param request_id: uuid of the session that will represent the
temporary archive files
:param start_timestamp: (Optional) Start time for API-based collection
(Unix timestamp in seconds)
:param end_timestamp: (Optional) End time for API-based collection
(Unix timestamp in seconds)
:param prometheus_url: (Optional) Prometheus API URL for API-based
collection
:param prometheus_bearer_token: (Optional) Bearer token for
Prometheus API authentication
:return: the list of the archive number and filenames downloaded
"""
prometheus_ocp_pod_name = "prometheus-k8s-0"
Expand All @@ -62,6 +77,11 @@ def get_ocp_prometheus_data(
prometheus_ocp_pod_name,
prometheus_ocp_container_name,
prometheus_ocp_namespace,
"/prometheus",
start_timestamp,
end_timestamp,
prometheus_url,
prometheus_bearer_token,
)

def collect_cluster_metadata(self, chaos_telemetry: ChaosRunTelemetry):
Expand Down
151 changes: 151 additions & 0 deletions src/krkn_lib/telemetry/prometheus_exporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"""
Prometheus API-based metrics exporter for time-window telemetry collection.
Provides faster alternative to filesystem-based backup by querying only
relevant time windows via Prometheus HTTP API.
"""

import json
import logging
import os
import tarfile
import tempfile
import time
from datetime import datetime
from typing import Optional

from krkn_lib.prometheus.krkn_prometheus import KrknPrometheus


class PrometheusExporter:
"""
Exports Prometheus metrics via HTTP API for specific time windows.
Significantly faster than filesystem backup for targeted time ranges.
Uses existing KrknPrometheus class for Prometheus connectivity.
"""

def __init__(
self,
prometheus_url: str,
bearer_token: Optional[str] = None,
):
"""
Initialize Prometheus API exporter.

:param prometheus_url: Base URL of Prometheus server
(e.g., 'https://prometheus-k8s.openshift-monitoring.svc:9091')
:param bearer_token: Optional bearer token for authentication
"""
self.prometheus_url = prometheus_url
self.prom_client = KrknPrometheus(prometheus_url, bearer_token)

def test_connection(self) -> bool:
"""
Test connection to Prometheus API.

:return: True if connection successful, False otherwise
"""
try:
# Try a simple query to test connection
self.prom_client.process_query("up")
return True
except Exception as e:
logging.debug(
f"Prometheus API connection test failed: {str(e)}"
)
return False

def export_metrics_snapshot(
self,
start_timestamp: int,
end_timestamp: int,
output_path: str,
archive_name: str,
) -> Optional[str]:
"""
Export metrics for a specific time window and create tar.gz archive.

:param start_timestamp: Start time (Unix timestamp in seconds)
:param end_timestamp: End time (Unix timestamp in seconds)
:param output_path: Directory where archive will be created
:param archive_name: Base name for the archive (without extension)
:return: Full path to created archive, or None on failure
"""
try:
duration_minutes = (end_timestamp - start_timestamp) / 60
logging.info(
f"Exporting Prometheus metrics from {start_timestamp} "
f"to {end_timestamp} using API "
f"(time window: {duration_minutes:.1f} minutes)"
)

# Convert Unix timestamps to datetime objects
start_time = datetime.fromtimestamp(start_timestamp)
end_time = datetime.fromtimestamp(end_timestamp)

# Calculate step size (1 point per minute for efficiency)
duration = end_timestamp - start_timestamp
# At least 60s, max 1000 points
step = max(60, duration // 1000)

logging.info(
"Querying Prometheus API for all metrics in time range..."
)

# Query all metrics using regex pattern
metrics_data = self.prom_client.process_prom_query_in_range(
query='{__name__=~".+"}',
start_time=start_time,
end_time=end_time,
granularity=step,
)

if not metrics_data:
logging.warning(
"No metrics data returned from Prometheus API"
)
return None

# Create temporary directory for archive contents
with tempfile.TemporaryDirectory() as temp_dir:
# Write metrics data
metrics_file = os.path.join(temp_dir, "metrics.json")
with open(metrics_file, "w") as f:
json.dump(metrics_data, f, indent=2)

# Write metadata
metadata = {
"start_timestamp": start_timestamp,
"end_timestamp": end_timestamp,
"duration_seconds": end_timestamp - start_timestamp,
"collection_method": "prometheus_api",
"prometheus_url": self.prometheus_url,
"export_timestamp": int(time.time()),
}
metadata_file = os.path.join(temp_dir, "metadata.json")
with open(metadata_file, "w") as f:
json.dump(metadata, f, indent=2)

# Create tar.gz archive
archive_path = os.path.join(output_path, f"{archive_name}.tar.gz")
with tarfile.open(archive_path, "w:gz") as tar:
tar.add(
metrics_file,
arcname="metrics.json",
)
tar.add(
metadata_file,
arcname="metadata.json",
)

size_mb = os.path.getsize(archive_path) / (1024 * 1024)
logging.info(
f"Successfully exported Prometheus metrics to "
f"{archive_path} ({size_mb:.2f} MB)"
)
return archive_path

except Exception as e:
logging.error(
f"Failed to export Prometheus metrics via API: {str(e)}"
)
return None