#!/usr/bin/env node const apiToken = process.env.CLOUDFLARE_API_TOKEN; const zoneTag = process.env.CLOUDFLARE_ZONE_TAG; const hostname = process.env.CLOUDFLARE_HOSTNAME || "linearvoid.tv"; const windowMinutes = Number.parseInt(process.env.CLOUDFLARE_CACHE_HIT_RATIO_WINDOW_MINUTES || "60", 10); const endpoint = process.env.CLOUDFLARE_GRAPHQL_ENDPOINT || "https://api.cloudflare.com/client/v4/graphql"; if (!apiToken || !zoneTag) { console.error("Missing required environment variables: CLOUDFLARE_API_TOKEN, CLOUDFLARE_ZONE_TAG"); process.exit(1); } const end = new Date(); const start = new Date(end.getTime() - windowMinutes * 60 * 1000); const iso = (value) => value.toISOString(); const query = ` query CacheHitRatio($zoneTag: string, $start: Time, $end: Time, $hostname: String) { viewer { zones(filter: { zoneTag: $zoneTag }) { requests: httpRequestsAdaptiveGroups( limit: 1000 filter: { datetime_geq: $start datetime_lt: $end clientRequestHTTPHost: $hostname requestSource: "eyeball" } ) { count dimensions { cacheStatus } } } } } `; async function main() { const response = await fetch(endpoint, { method: "POST", headers: { Authorization: `Bearer ${apiToken}`, "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify({ query, variables: { zoneTag, hostname, start: iso(start), end: iso(end), }, }), }); const payload = await response.json(); if (!response.ok || payload.errors) { const message = payload.errors ? JSON.stringify(payload.errors) : `HTTP ${response.status}`; throw new Error(message); } const groups = payload?.data?.viewer?.zones?.[0]?.requests || []; let totalRequests = 0; let hitRequests = 0; for (const group of groups) { const count = Number(group?.count || 0); const status = String(group?.dimensions?.cacheStatus || "").toUpperCase(); totalRequests += count; if (status === "HIT") { hitRequests += count; } } const ratio = totalRequests > 0 ? hitRequests / totalRequests : 0; console.log( `# HELP linearvoid_cache_hit_ratio Cloudflare cache hit ratio for the selected hostname and time window.`, ); console.log(`# TYPE linearvoid_cache_hit_ratio gauge`); console.log( `linearvoid_cache_hit_ratio{zone="${zoneTag}",hostname="${hostname}",window_minutes="${windowMinutes}"} ${ratio.toFixed(6)}`, ); console.log(`# HELP linearvoid_cache_hit_requests_total Cloudflare cache HIT requests counted in the time window.`); console.log(`# TYPE linearvoid_cache_hit_requests_total counter`); console.log( `linearvoid_cache_hit_requests_total{zone="${zoneTag}",hostname="${hostname}",window_minutes="${windowMinutes}"} ${hitRequests}`, ); console.log( `# HELP linearvoid_cache_total_requests_total Cloudflare total eyeball requests counted in the time window.`, ); console.log(`# TYPE linearvoid_cache_total_requests_total counter`); console.log( `linearvoid_cache_total_requests_total{zone="${zoneTag}",hostname="${hostname}",window_minutes="${windowMinutes}"} ${totalRequests}`, ); } main().catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exit(1); });