diff --git a/README.md b/README.md index b694de3..8d98840 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ By running it as a worker and pushing metrics, we avoid the need to deploy a ded - [x] Workers - [x] D1 +- [x] Durable Objects - [ ] Queues -- [ ] Durable Objects - [ ] Zones ## Usage diff --git a/features/step_definitions/d1_query_response.json b/features/data/d1_query_response.json similarity index 100% rename from features/step_definitions/d1_query_response.json rename to features/data/d1_query_response.json diff --git a/features/data/durableobjects_query_response.json b/features/data/durableobjects_query_response.json new file mode 100644 index 0000000..d7066b5 --- /dev/null +++ b/features/data/durableobjects_query_response.json @@ -0,0 +1 @@ +{"data":{"viewer":{"accounts":[{"durableObjectsInvocationsAdaptiveGroups":[]}]}},"errors":null} \ No newline at end of file diff --git a/features/step_definitions/worker_query_response.json b/features/data/worker_query_response.json similarity index 100% rename from features/step_definitions/worker_query_response.json rename to features/data/worker_query_response.json diff --git a/features/step_definitions/cf_mock_server.ts b/features/step_definitions/cf_mock_server.ts index 2b6ed99..4c8c24a 100644 --- a/features/step_definitions/cf_mock_server.ts +++ b/features/step_definitions/cf_mock_server.ts @@ -7,8 +7,9 @@ export class CloudflareMockServer { start() { let self = this; - const workerQuery = fs.readFileSync('./features/step_definitions/worker_query_response.json').toString(); - const d1Query = fs.readFileSync('./features/step_definitions/d1_query_response.json').toString(); + const workerQuery = fs.readFileSync('./features/data/worker_query_response.json').toString(); + const d1Query = fs.readFileSync('./features/data/d1_query_response.json').toString(); + const durableObjectsQuery = fs.readFileSync('./features/data/durableobjects_query_response.json').toString(); this.server = http.createServer((req, res) => { var body = ""; req.on('readable', function() { @@ -23,6 +24,8 @@ export class CloudflareMockServer { res.setHeader('Content-Type', 'application/json'); if (body.indexOf('d1AnalyticsAdaptiveGroups') > -1) { res.end(d1Query); + } else if (body.indexOf('durableObjectsInvocationsAdaptiveGroups') > -1) { + res.end(durableObjectsQuery); } else { res.end(workerQuery); } diff --git a/gql/d1.graphql b/gql/d1_query.graphql similarity index 100% rename from gql/d1.graphql rename to gql/d1_query.graphql diff --git a/gql/durableobjects_query.graphql b/gql/durableobjects_query.graphql new file mode 100644 index 0000000..3ba48f7 --- /dev/null +++ b/gql/durableobjects_query.graphql @@ -0,0 +1,35 @@ +query GetDurableObjectsAnalyticsQuery($accountTag: string!, $datetimeStart: Time, $datetimeEnd: Time, $limit: Int!) { + viewer { + accounts(filter: {accountTag: $accountTag}) { + durableObjectsInvocationsAdaptiveGroups(limit: $limit, filter: { + datetimeMinute_geq: $datetimeStart, + datetimeMinute_lt: $datetimeEnd + }) { + dimensions { + scriptName + datetimeMinute + } + + sum { + errors + requests + } + + quantiles { + responseBodySizeP25 + responseBodySizeP50 + responseBodySizeP75 + responseBodySizeP90 + responseBodySizeP99 + responseBodySizeP999 + wallTimeP25 + wallTimeP50 + wallTimeP75 + wallTimeP90 + wallTimeP99 + wallTimeP999 + } + } + } + } +} \ No newline at end of file diff --git a/gql/queries.graphql b/gql/queries.graphql index 28e4e98..7fc1abe 100644 --- a/gql/queries.graphql +++ b/gql/queries.graphql @@ -1,42 +1,6 @@ -query GetDurableObjectsAnalyticsQuery($accountTag: string!, $datetimeStart: Time, $datetimeEnd: Time, $limit: Int!) { - viewer { - accounts(filter: {accountTag: $accountTag}) { - durableObjectsInvocationsAdaptiveGroups(limit: $limit, filter: { - datetimeMinute_geq: $datetimeStart, - datetimeMinute_lt: $datetimeEnd - }) { - dimensions { - scriptName - datetimeMinute - } - sum { - errors - requests - responseBodySize - wallTime - } - - quantiles { - responseBodySizeP25 - responseBodySizeP50 - responseBodySizeP75 - responseBodySizeP90 - responseBodySizeP99 - responseBodySizeP999 - wallTimeP25 - wallTimeP50 - wallTimeP75 - wallTimeP90 - wallTimeP99 - wallTimeP999 - } - } - } - } -} query GetQueueAnalyticsQuery($accountTag: string!, $datetimeStart: Time, $datetimeEnd: Time, $limit: Int!) { viewer { diff --git a/gql/workers.graphql b/gql/workers_query.graphql similarity index 100% rename from gql/workers.graphql rename to gql/workers_query.graphql diff --git a/src/gql.rs b/src/gql.rs index b7b58eb..1d5ba6c 100644 --- a/src/gql.rs +++ b/src/gql.rs @@ -12,7 +12,7 @@ use worker::console_log; #[derive(GraphQLQuery)] #[graphql( schema_path = "gql/schema.graphql", - query_path = "gql/workers.graphql", + query_path = "gql/workers_query.graphql", variables_derives = "Debug", response_derives = "Debug,Clone" )] @@ -21,21 +21,21 @@ pub struct GetWorkersAnalyticsQuery; #[derive(GraphQLQuery)] #[graphql( schema_path = "gql/schema.graphql", - query_path = "gql/d1.graphql", + query_path = "gql/d1_query.graphql", variables_derives = "Debug", response_derives = "Debug,Clone" )] pub struct GetD1AnalyticsQuery; -// #[derive(GraphQLQuery)] -// #[graphql( -// schema_path = "gql/schema.graphql", -// query_path = "gql/queries.graphql", -// variables_derives = "Debug", -// response_derives = "Debug,Clone" -// )] -// pub struct GetDurableObjectsAnalyticsQuery; -// +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "gql/schema.graphql", + query_path = "gql/durableobjects_query.graphql", + variables_derives = "Debug", + response_derives = "Debug,Clone" +)] +pub struct GetDurableObjectsAnalyticsQuery; + // #[derive(GraphQLQuery)] // #[graphql( // schema_path = "gql/schema.graphql", @@ -208,6 +208,81 @@ pub async fn do_get_d1_analytics_query(cloudflare_api_url: &String, cloudflare_a Ok(prometheus_registry_to_opentelemetry_metrics(registry, timestamp)) } +pub async fn do_get_durableobjects_analytics_query(cloudflare_api_url: &String, cloudflare_api_key: &String, variables: get_durable_objects_analytics_query::Variables) -> Result, Box> { + let request_body = GetDurableObjectsAnalyticsQuery::build_query(variables); + //console_log!("request_body: {:?}", request_body); + let client = reqwest::Client::new(); + let res = client.post(cloudflare_api_url) + .bearer_auth(cloudflare_api_key) + .json(&request_body).send().await?; + + if !res.status().is_success() { + console_log!("GraphQL query failed: {:?}", res.status()); + return Err(Box::new(res.error_for_status().unwrap_err())); + } + + let response_body: Response = res.json().await?; + if response_body.errors.is_some() { + console_log!("GraphQL query failed: {:?}", response_body.errors); + return Err(Box::new(worker::Error::JsError("graphql".parse().unwrap()))); + } + let response_data: get_durable_objects_analytics_query::ResponseData = response_body.data.expect("missing response data"); + + let registry = Registry::new(); + let do_errors_opts = Opts::new("cloudflare_durable_objects_errors", "Sum of errors"); + let do_errors = CounterVec::new(do_errors_opts, &["script_name"]).unwrap(); + registry.register(Box::new(do_errors.clone())).unwrap(); + + let do_requests_opts = Opts::new("cloudflare_durable_objects_requests", "Sum of requests"); + let do_requests = CounterVec::new(do_requests_opts, &["script_name"]).unwrap(); + registry.register(Box::new(do_requests.clone())).unwrap(); + + let do_response_body_size_bytes_opts = Opts::new("cloudflare_durable_objects_response_body_size_bytes", "Response body size - bytes"); + let do_response_body_size_bytes = GaugeVec::new(do_response_body_size_bytes_opts, &["script_name", "quantile"]).unwrap(); + registry.register(Box::new(do_response_body_size_bytes.clone())).unwrap(); + + let do_wall_time_microseconds_opts = Opts::new("cloudflare_durable_objects_wall_time_microseconds", "Wall time - microseconds"); + let do_wall_time_microseconds = GaugeVec::new(do_wall_time_microseconds_opts, &["script_name", "quantile"]).unwrap(); + registry.register(Box::new(do_wall_time_microseconds.clone())).unwrap(); + + let mut last_datetime: Option