blob: 98a4ddb7a65ffb3e93708baa383deef313cf29d5 [file]
import logging
import os
import sys
import time
from datetime import datetime
from google.cloud import bigquery
from bazelci import BuildkiteClient
# --- Configuration ---
ORGs = ["bazel-testing", "bazel", "bazel-trusted"]
PROJECT_ID = "bazel-public"
DATASET_ID = "bazel_ci_metrics"
TABLE_ID = "infra_stats"
def setup_logging(level=logging.INFO):
"""Configures basic logging for the script."""
logging.basicConfig(
level=level,
format="%(asctime)s - %(levelname)-8s - %(message)s",
stream=sys.stdout,
)
def extract_platform(tags):
"""Extracts the platform from agent meta_data tags."""
for tag in tags:
if tag.startswith("queue="):
q = tag.split("=", 1)[1].strip()
return {"default": "linux", "arm64": "linux_arm64"}.get(q, q)
return "linux"
def calculate_agent_stats(agents):
"""Computes agent stats grouped by platform."""
agents_by_platform = {
"linux": {"total": 0, "busy": 0, "idle": 0, "disconnected": 0, "bootstrap_samples": []},
"linux_arm64": {"total": 0, "busy": 0, "idle": 0, "disconnected": 0, "bootstrap_samples": []},
"macos": {"total": 0, "busy": 0, "idle": 0, "disconnected": 0, "bootstrap_samples": []},
"macos_arm64": {"total": 0, "busy": 0, "idle": 0, "disconnected": 0, "bootstrap_samples": []},
"windows": {"total": 0, "busy": 0, "idle": 0, "disconnected": 0, "bootstrap_samples": []},
}
for a in agents:
platform = extract_platform(a.get("meta_data", []))
if platform not in agents_by_platform:
agents_by_platform[platform] = {
"total": 0, "busy": 0, "idle": 0, "disconnected": 0, "bootstrap_samples": []
}
platform_stats = agents_by_platform[platform]
platform_stats["total"] += 1
if a.get('job'):
platform_stats["busy"] += 1
if not a.get('job') and a.get('connection_state') == 'connected':
platform_stats["idle"] += 1
if a.get('connection_state') in ['disconnected', 'lost']:
platform_stats["disconnected"] += 1
created_at_str = a.get('created_at')
if created_at_str:
created_dt = datetime.fromisoformat(created_at_str.replace('Z', '+00:00'))
boot_ts = 0 # TODO: update after including it in the agent data
platform_stats["bootstrap_samples"].append((created_dt.timestamp() - boot_ts))
return agents_by_platform
def count_scheduled_jobs(builds):
"""Counts scheduled jobs grouped by platform."""
scheduled_by_platform = {"linux": 0, "linux_arm64": 0, "macos": 0, "macos_arm64": 0, "windows": 0}
for build in builds:
for job in build.get("jobs", []):
if job.get("state") == "scheduled":
platform = extract_platform(job.get("agent_query_rules", []))
scheduled_by_platform[platform] = scheduled_by_platform.get(platform, 0) + 1
return scheduled_by_platform
def get_org_metrics(org):
"""Fetches metrics for a single org and calculates stats per platform."""
logging.info(f"Pulling Data for Org: {org}")
bk_client = BuildkiteClient(org=org)
# 1. Fetch & Calculate Agent Stats
agents = bk_client.get_agents()
logging.info(f"Agent data pulled successfully")
agents_by_platform = calculate_agent_stats(agents)
# 2. Fetch & Count Scheduled Jobs (Queue Depth)
builds = bk_client.get_active_builds()
logging.info(f"Active builds data pulled successfully")
scheduled_by_platform = count_scheduled_jobs(builds)
timestamp = datetime.utcnow().isoformat()
rows = []
for platform, stats in agents_by_platform.items():
scheduled_jobs = scheduled_by_platform.get(platform, 0)
#Skip platforms with no info (didn't run any jobs)
if stats["total"] == 0 and scheduled_jobs == 0:
continue
avg_bootstrap_time = sum(stats["bootstrap_samples"]) / len(
stats["bootstrap_samples"]) if stats["bootstrap_samples"] else 0.0
rows.append({
"timestamp": timestamp,
"org": org,
"platform": platform,
"scheduled_jobs": scheduled_jobs,
"total_agents": stats["total"],
"busy_agents": stats["busy"],
"idle_agents": stats["idle"],
"disconnected_agents": stats["disconnected"],
"avg_bootstrap_time_s": avg_bootstrap_time
})
return rows
def push_to_bigquery(rows, retries):
if not rows:
logging.info("No data found to push to DB")
return
client = bigquery.Client(project=PROJECT_ID)
table_ref = f"{PROJECT_ID}.{DATASET_ID}.{TABLE_ID}"
logging.info(f"Pushing {len(rows)} rows to BigQuery...")
for attempt in range(retries):
errors = client.insert_rows_json(table_ref, rows)
if not errors:
logging.info(
f"Successfully inserted {len(rows)} metrics for timestamp {rows[0]['timestamp']}")
return
logging.warning(
f"Attempt {attempt + 1}/{retries} failed with errors: {errors}")
if attempt < retries - 1:
time.sleep(2 ** attempt)
# If all retries failed
logging.error(f"Failed to insert rows after {retries} attempts.")
sys.exit(1)
def main():
setup_logging()
logging.info(f"Starting Buildkite Poller")
try:
all_metrics = []
for org in ORGs:
metrics = get_org_metrics(org)
if metrics:
all_metrics.extend(metrics)
if all_metrics:
push_to_bigquery(all_metrics, 5)
except Exception as e:
logging.critical(f"ERROR in Poller: {e}")
sys.exit(1)
if __name__ == "__main__":
main()