Skip to content

Commit f3196be

Browse files
committed
feat(): gather zone level statistics
1 parent d3826b7 commit f3196be

File tree

5 files changed

+182
-2
lines changed

5 files changed

+182
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ By running it as a worker and pushing metrics, we avoid the need to deploy a ded
1313
- [x] D1
1414
- [x] Durable Objects
1515
- [x] Queues
16-
- [ ] Zones
16+
- [x] Zones
1717

1818
## Usage
1919

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
{
2+
"data": {
3+
"viewer": {
4+
"zones": [
5+
{
6+
"zoneTag": "zone123abc",
7+
"httpRequestsAdaptiveGroups": [
8+
{
9+
"count": 150,
10+
"dimensions": {
11+
"edgeResponseStatus": 200,
12+
"clientRequestHTTPHost": "example.com"
13+
}
14+
},
15+
{
16+
"count": 25,
17+
"dimensions": {
18+
"edgeResponseStatus": 304,
19+
"clientRequestHTTPHost": "example.com"
20+
}
21+
},
22+
{
23+
"count": 10,
24+
"dimensions": {
25+
"edgeResponseStatus": 404,
26+
"clientRequestHTTPHost": "example.com"
27+
}
28+
},
29+
{
30+
"count": 5,
31+
"dimensions": {
32+
"edgeResponseStatus": 500,
33+
"clientRequestHTTPHost": "example.com"
34+
}
35+
},
36+
{
37+
"count": 80,
38+
"dimensions": {
39+
"edgeResponseStatus": 200,
40+
"clientRequestHTTPHost": "api.example.com"
41+
}
42+
},
43+
{
44+
"count": 3,
45+
"dimensions": {
46+
"edgeResponseStatus": 401,
47+
"clientRequestHTTPHost": "api.example.com"
48+
}
49+
}
50+
]
51+
},
52+
{
53+
"zoneTag": "zone456def",
54+
"httpRequestsAdaptiveGroups": [
55+
{
56+
"count": 200,
57+
"dimensions": {
58+
"edgeResponseStatus": 200,
59+
"clientRequestHTTPHost": "myapp.io"
60+
}
61+
},
62+
{
63+
"count": 15,
64+
"dimensions": {
65+
"edgeResponseStatus": 503,
66+
"clientRequestHTTPHost": "myapp.io"
67+
}
68+
}
69+
]
70+
}
71+
]
72+
}
73+
},
74+
"errors": null
75+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
query GetZoneHttpRequestsQuery($zoneIDs: [String!], $datetimeStart: Time!, $datetimeEnd: Time!, $limit: Int!) {
2+
viewer {
3+
zones(filter: { zoneTag_in: $zoneIDs }) {
4+
zoneTag
5+
httpRequestsAdaptiveGroups(limit: $limit, filter: {
6+
datetime_geq: $datetimeStart,
7+
datetime_lt: $datetimeEnd,
8+
requestSource_in: ["eyeball"]
9+
}) {
10+
count
11+
dimensions {
12+
edgeResponseStatus
13+
clientRequestHTTPHost
14+
}
15+
}
16+
}
17+
}
18+
}

src/gql.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ pub struct GetQueueBacklogAnalyticsQuery;
4444
)]
4545
pub struct GetQueueOperationsAnalyticsQuery;
4646

47+
#[derive(GraphQLQuery)]
48+
#[graphql(
49+
schema_path = "gql/schema.graphql",
50+
query_path = "gql/zone_http_requests_query.graphql"
51+
)]
52+
pub struct GetZoneHttpRequestsQuery;
53+
4754
#[allow(non_camel_case_types)]
4855
type float32 = f32;
4956

@@ -62,6 +69,9 @@ type uint32 = u32;
6269
#[allow(non_camel_case_types)]
6370
type float64 = f64;
6471

72+
#[allow(non_camel_case_types)]
73+
type uint16 = u16;
74+
6575
pub async fn do_get_workers_analytics_query(cloudflare_api_url: &String, cloudflare_api_key: &String, variables: get_workers_analytics_query::Variables) -> Result<Vec<Metric>, Box<dyn Error>> {
6676
let request_body = GetWorkersAnalyticsQuery::build_query(variables);
6777
//console_log!("request_body: {:?}", request_body);
@@ -411,6 +421,53 @@ pub async fn do_get_queue_operations_analytics_query(cloudflare_api_url: &String
411421
Ok(prometheus_registry_to_opentelemetry_metrics(registry, timestamp_nanos))
412422
}
413423

424+
pub async fn do_get_zone_http_requests_query(cloudflare_api_url: &String, cloudflare_api_key: &String, variables: get_zone_http_requests_query::Variables) -> Result<Vec<Metric>, Box<dyn Error>> {
425+
let request_body = GetZoneHttpRequestsQuery::build_query(variables);
426+
let client = reqwest::Client::new();
427+
let res = client.post(cloudflare_api_url)
428+
.bearer_auth(cloudflare_api_key)
429+
.json(&request_body).send().await?;
430+
431+
if !res.status().is_success() {
432+
console_log!("GraphQL query failed: {:?}", res.status());
433+
return Err(Box::new(res.error_for_status().unwrap_err()));
434+
}
435+
436+
let response_body: Response<get_zone_http_requests_query::ResponseData> = res.json().await?;
437+
if response_body.errors.is_some() {
438+
console_log!("GraphQL query failed: {:?}", response_body.errors);
439+
return Err(Box::new(worker::Error::JsError("graphql".parse().unwrap())));
440+
}
441+
let response_data: get_zone_http_requests_query::ResponseData = response_body.data.expect("missing response data");
442+
443+
let registry = Registry::new();
444+
let zone_requests_status_host_opts = Opts::new("cloudflare_zone_requests_status_host", "Count of requests per edge HTTP status per host");
445+
let zone_requests_status_host = CounterVec::new(zone_requests_status_host_opts, &["zone", "status", "host"]).unwrap();
446+
registry.register(Box::new(zone_requests_status_host.clone())).unwrap();
447+
448+
let last_datetime: Option<Time> = None;
449+
for zone in response_data.viewer.unwrap().zones.iter() {
450+
let zone_tag = zone.zone_tag.clone();
451+
for group in zone.http_requests_adaptive_groups.iter() {
452+
let dimensions = group.dimensions.as_ref().unwrap();
453+
let status = dimensions.edge_response_status.to_string();
454+
let host = dimensions.client_request_http_host.clone();
455+
let count = group.count;
456+
457+
zone_requests_status_host.with_label_values(&[zone_tag.as_str(), status.as_str(), host.as_str()]).inc_by(count as f64);
458+
}
459+
}
460+
461+
let timestamp_nanos: u64 = last_datetime.map(|datetime| {
462+
let datetime: NaiveDateTime = NaiveDateTime::parse_from_str(&datetime, "%+").unwrap();
463+
datetime.and_utc().timestamp_nanos_opt().unwrap_or(0) as u64
464+
}).unwrap_or_else(|| {
465+
systemtime_to_nanos(SystemTime::now())
466+
});
467+
468+
Ok(prometheus_registry_to_opentelemetry_metrics(registry, timestamp_nanos))
469+
}
470+
414471
fn systemtime_to_nanos(time: web_time::SystemTime) -> u64 {
415472
let duration = time.duration_since(web_time::SystemTime::UNIX_EPOCH).unwrap();
416473
duration.as_nanos() as u64

src/lib.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use prost::Message;
77
use worker::*;
88
use worker::js_sys::Uint8Array;
99
use worker::wasm_bindgen::JsValue;
10-
use crate::gql::{get_workers_analytics_query, do_get_workers_analytics_query, do_get_d1_analytics_query, get_d1_analytics_query, do_get_durableobjects_analytics_query, get_durable_objects_analytics_query, do_get_queue_backlog_analytics_query, get_queue_backlog_analytics_query, do_get_queue_operations_analytics_query, get_queue_operations_analytics_query};
10+
use crate::gql::{get_workers_analytics_query, do_get_workers_analytics_query, do_get_d1_analytics_query, get_d1_analytics_query, do_get_durableobjects_analytics_query, get_durable_objects_analytics_query, do_get_queue_backlog_analytics_query, get_queue_backlog_analytics_query, do_get_queue_operations_analytics_query, get_queue_operations_analytics_query, do_get_zone_http_requests_query, get_zone_http_requests_query};
1111

1212
mod gql;
1313
mod metrics;
@@ -156,6 +156,36 @@ async fn do_trigger(env: Env) -> Result<()> {
156156
return Err(Error::JsError(e.to_string()));
157157
}
158158
};
159+
160+
// Zone HTTP requests metrics (optional - only if CLOUDFLARE_ZONE_IDS is set)
161+
if let Ok(zone_ids_var) = env.var("CLOUDFLARE_ZONE_IDS") {
162+
let zone_ids: Vec<String> = zone_ids_var.to_string()
163+
.split(',')
164+
.map(|s| s.trim().to_string())
165+
.filter(|s| !s.is_empty())
166+
.collect();
167+
168+
if !zone_ids.is_empty() {
169+
let result = do_get_zone_http_requests_query(&cloudflare_api_url, &cloudflare_api_key, get_zone_http_requests_query::Variables {
170+
zone_i_ds: Some(zone_ids),
171+
datetime_start: start.to_rfc3339(),
172+
datetime_end: end.to_rfc3339(),
173+
limit: 9999,
174+
}).await;
175+
match result {
176+
Ok(metrics) => {
177+
for metric in metrics {
178+
all_metrics.push(metric);
179+
}
180+
},
181+
Err(e) => {
182+
console_log!("Querying Cloudflare API for zone HTTP requests failed: {:?}", e);
183+
return Err(Error::JsError(e.to_string()));
184+
}
185+
};
186+
}
187+
}
188+
159189
console_log!("Done fetching!");
160190

161191
do_push_metrics(env, all_metrics).await

0 commit comments

Comments
 (0)