Skip to content

Commit 81dc87a

Browse files
committed
graphql: GraphQLMetrics
1 parent 94aa0f0 commit 81dc87a

File tree

18 files changed

+247
-279
lines changed

18 files changed

+247
-279
lines changed

graph/src/components/graphql.rs

+7
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ pub trait GraphQlRunner: Send + Sync + 'static {
4343
) -> Result<SubscriptionResult, SubscriptionError>;
4444

4545
fn load_manager(&self) -> Arc<LoadManager>;
46+
47+
fn metrics(&self) -> Arc<dyn GraphQLMetrics>;
48+
}
49+
50+
pub trait GraphQLMetrics: Send + Sync + 'static {
51+
fn observe_query_execution(&self, duration: Duration, results: &QueryResults) -> ();
52+
fn observe_query_parsing(&self, duration: Duration, results: &QueryResults) -> ();
4653
}
4754

4855
#[async_trait]

graph/src/data/query/result.rs

+1-22
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use serde::ser::*;
99
use serde::Serialize;
1010
use std::convert::TryFrom;
1111
use std::sync::Arc;
12-
use std::time::Duration;
1312

1413
fn serialize_data<S>(data: &Option<Data>, serializer: S) -> Result<S::Ok, S::Error>
1514
where
@@ -46,14 +45,12 @@ pub type Data = Object;
4645
/// A collection of query results that is serialized as a single result.
4746
pub struct QueryResults {
4847
results: Vec<Arc<QueryResult>>,
49-
pub validation_time: Option<Duration>,
5048
}
5149

5250
impl QueryResults {
5351
pub fn empty() -> Self {
5452
QueryResults {
5553
results: Vec::new(),
56-
validation_time: None,
5754
}
5855
}
5956

@@ -75,10 +72,6 @@ impl QueryResults {
7572
.filter_map(|result| result.deployment.as_ref())
7673
.next()
7774
}
78-
79-
pub fn set_validation_time(&mut self, duration: Duration) -> () {
80-
self.validation_time = Some(duration);
81-
}
8275
}
8376

8477
impl Serialize for QueryResults {
@@ -136,7 +129,6 @@ impl From<Data> for QueryResults {
136129
fn from(x: Data) -> Self {
137130
QueryResults {
138131
results: vec![Arc::new(x.into())],
139-
validation_time: None,
140132
}
141133
}
142134
}
@@ -145,25 +137,20 @@ impl From<QueryResult> for QueryResults {
145137
fn from(x: QueryResult) -> Self {
146138
QueryResults {
147139
results: vec![Arc::new(x)],
148-
validation_time: None,
149140
}
150141
}
151142
}
152143

153144
impl From<Arc<QueryResult>> for QueryResults {
154145
fn from(x: Arc<QueryResult>) -> Self {
155-
QueryResults {
156-
results: vec![x],
157-
validation_time: None,
158-
}
146+
QueryResults { results: vec![x] }
159147
}
160148
}
161149

162150
impl From<QueryExecutionError> for QueryResults {
163151
fn from(x: QueryExecutionError) -> Self {
164152
QueryResults {
165153
results: vec![Arc::new(x.into())],
166-
validation_time: None,
167154
}
168155
}
169156
}
@@ -172,7 +159,6 @@ impl From<Vec<QueryExecutionError>> for QueryResults {
172159
fn from(x: Vec<QueryExecutionError>) -> Self {
173160
QueryResults {
174161
results: vec![Arc::new(x.into())],
175-
validation_time: None,
176162
}
177163
}
178164
}
@@ -213,8 +199,6 @@ pub struct QueryResult {
213199
errors: Vec<QueryError>,
214200
#[serde(skip_serializing)]
215201
pub deployment: Option<DeploymentHash>,
216-
#[serde(skip_serializing)]
217-
pub validation_time: Option<Duration>,
218202
}
219203

220204
impl QueryResult {
@@ -223,7 +207,6 @@ impl QueryResult {
223207
data: Some(data),
224208
errors: Vec::new(),
225209
deployment: None,
226-
validation_time: None,
227210
}
228211
}
229212

@@ -236,7 +219,6 @@ impl QueryResult {
236219
data: self.data.clone(),
237220
errors: self.errors.clone(),
238221
deployment: self.deployment.clone(),
239-
validation_time: self.validation_time.clone(),
240222
}
241223
}
242224

@@ -292,7 +274,6 @@ impl From<QueryExecutionError> for QueryResult {
292274
data: None,
293275
errors: vec![e.into()],
294276
deployment: None,
295-
validation_time: None,
296277
}
297278
}
298279
}
@@ -303,7 +284,6 @@ impl From<QueryError> for QueryResult {
303284
data: None,
304285
errors: vec![e],
305286
deployment: None,
306-
validation_time: None,
307287
}
308288
}
309289
}
@@ -314,7 +294,6 @@ impl From<Vec<QueryExecutionError>> for QueryResult {
314294
data: None,
315295
errors: e.into_iter().map(QueryError::from).collect(),
316296
deployment: None,
317-
validation_time: None,
318297
}
319298
}
320299
}

graph/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ pub mod prelude {
102102
LightEthereumBlockExt,
103103
};
104104
pub use crate::components::graphql::{
105-
GraphQlRunner, QueryLoadManager, SubscriptionResultFuture,
105+
GraphQLMetrics, GraphQlRunner, QueryLoadManager, SubscriptionResultFuture,
106106
};
107107
pub use crate::components::link_resolver::{JsonStreamValue, JsonValueStream, LinkResolver};
108108
pub use crate::components::metrics::{

graphql/src/execution/query.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::collections::{BTreeMap, HashMap, HashSet};
88
use std::hash::{Hash, Hasher};
99
use std::iter::FromIterator;
1010
use std::sync::Arc;
11-
use std::time::{Duration, Instant};
11+
use std::time::Instant;
1212
use std::{collections::hash_map::DefaultHasher, convert::TryFrom};
1313

1414
use graph::data::graphql::{ext::TypeExt, ObjectOrInterface};
@@ -20,6 +20,7 @@ use graph::prelude::{
2020
};
2121

2222
use crate::execution::ast as a;
23+
use crate::metrics::GraphQLMetrics;
2324
use crate::query::{ast as qast, ext::BlockConstraint};
2425
use crate::schema::ast::{self as sast};
2526
use crate::values::coercion;
@@ -135,9 +136,6 @@ pub struct Query {
135136
pub query_text: Arc<String>,
136137
pub variables_text: Arc<String>,
137138
pub query_id: String,
138-
139-
/// Used only for metrics
140-
pub validation_time: Duration,
141139
}
142140

143141
impl Query {
@@ -152,12 +150,15 @@ impl Query {
152150
query: GraphDataQuery,
153151
max_complexity: Option<u64>,
154152
max_depth: u8,
153+
metrics: Option<Arc<GraphQLMetrics>>,
155154
) -> Result<Arc<Self>, Vec<QueryExecutionError>> {
156155
let validation_phase_start = Instant::now();
157156
let validation_errors =
158157
validate(schema.document(), &query.document, &GRAPHQL_VALIDATION_PLAN);
159158

160-
let validation_time = validation_phase_start.elapsed();
159+
if let Some(metrics) = metrics {
160+
metrics.observe_query_validation(validation_phase_start.elapsed(), schema.id());
161+
}
161162

162163
if !validation_errors.is_empty() {
163164
if !ENV_VARS.graphql.silent_graphql_validations {
@@ -259,7 +260,6 @@ impl Query {
259260
query_text: query.query_text.cheap_clone(),
260261
variables_text: query.variables_text.cheap_clone(),
261262
query_id,
262-
validation_time,
263263
};
264264

265265
Ok(Arc::new(query))

graphql/src/lib.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ mod store;
2424
/// The external interface for actually running queries
2525
mod runner;
2626

27+
/// Utilities for working with Prometheus.
28+
mod metrics;
29+
2730
/// Prelude that exports the most important traits and types.
2831
pub mod prelude {
2932
pub use super::execution::{ast as a, ExecutionContext, Query, Resolver};
@@ -34,12 +37,13 @@ pub mod prelude {
3437
pub use super::subscription::SubscriptionExecutionOptions;
3538
pub use super::values::MaybeCoercible;
3639

40+
pub use super::metrics::GraphQLMetrics;
3741
pub use super::runner::GraphQlRunner;
3842
pub use graph::prelude::s::ObjectType;
3943
}
4044

4145
#[cfg(debug_assertions)]
4246
pub mod test_support {
43-
pub use super::runner::ResultSizeMetrics;
47+
pub use super::metrics::GraphQLMetrics;
4448
pub use super::runner::INITIAL_DEPLOYMENT_STATE_FOR_TESTS;
4549
}

graphql/src/metrics.rs

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
use std::collections::HashMap;
2+
use std::fmt;
3+
use std::sync::Arc;
4+
use std::time::Duration;
5+
6+
use graph::data::query::QueryResults;
7+
use graph::prelude::{DeploymentHash, GraphQLMetrics as GraphQLMetricsTrait, MetricsRegistry};
8+
use graph::prometheus::{Gauge, Histogram, HistogramVec};
9+
10+
pub struct GraphQLMetrics {
11+
query_execution_time: Box<HistogramVec>,
12+
query_parsing_time: Box<HistogramVec>,
13+
query_validation_time: Box<HistogramVec>,
14+
query_result_size: Box<Histogram>,
15+
query_result_size_max: Box<Gauge>,
16+
}
17+
18+
impl fmt::Debug for GraphQLMetrics {
19+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20+
write!(f, "GraphQLMetrics {{ }}")
21+
}
22+
}
23+
24+
impl GraphQLMetricsTrait for GraphQLMetrics {
25+
fn observe_query_execution(&self, duration: Duration, results: &QueryResults) {
26+
let id = results
27+
.deployment_hash()
28+
.map(|h| h.as_str())
29+
.unwrap_or_else(|| {
30+
if results.not_found() {
31+
"notfound"
32+
} else {
33+
"unknown"
34+
}
35+
});
36+
let status = if results.has_errors() {
37+
"failed"
38+
} else {
39+
"success"
40+
};
41+
self.query_execution_time
42+
.with_label_values(&[id, status])
43+
.observe(duration.as_secs_f64());
44+
}
45+
46+
fn observe_query_parsing(&self, duration: Duration, results: &QueryResults) {
47+
let id = results
48+
.deployment_hash()
49+
.map(|h| h.as_str())
50+
.unwrap_or_else(|| {
51+
if results.not_found() {
52+
"notfound"
53+
} else {
54+
"unknown"
55+
}
56+
});
57+
self.query_parsing_time
58+
.with_label_values(&[id])
59+
.observe(duration.as_secs_f64());
60+
}
61+
}
62+
63+
impl GraphQLMetrics {
64+
pub fn new(registry: Arc<dyn MetricsRegistry>) -> Self {
65+
let query_execution_time = registry
66+
.new_histogram_vec(
67+
"query_execution_time",
68+
"Execution time for successful GraphQL queries",
69+
vec![String::from("deployment"), String::from("status")],
70+
vec![0.1, 0.5, 1.0, 10.0, 100.0],
71+
)
72+
.expect("failed to create `query_execution_time` histogram");
73+
let query_parsing_time = registry
74+
.new_histogram_vec(
75+
"query_parsing_time",
76+
"Parsing time for GraphQL queries",
77+
vec![String::from("deployment")],
78+
vec![0.1, 0.5, 1.0, 10.0, 100.0],
79+
)
80+
.expect("failed to create `query_parsing_time` histogram");
81+
82+
let query_validation_time = registry
83+
.new_histogram_vec(
84+
"query_validation_time",
85+
"Validation time for GraphQL queries",
86+
vec![String::from("deployment")],
87+
vec![0.1, 0.5, 1.0, 10.0, 100.0],
88+
)
89+
.expect("failed to create `query_validation_time` histogram");
90+
91+
let bins = (10..32).map(|n| 2u64.pow(n) as f64).collect::<Vec<_>>();
92+
let query_result_size = registry
93+
.new_histogram(
94+
"query_result_size",
95+
"the size of the result of successful GraphQL queries (in CacheWeight)",
96+
bins,
97+
)
98+
.unwrap();
99+
100+
let query_result_size_max = registry
101+
.new_gauge(
102+
"query_result_max",
103+
"the maximum size of a query result (in CacheWeight)",
104+
HashMap::new(),
105+
)
106+
.unwrap();
107+
108+
Self {
109+
query_execution_time,
110+
query_parsing_time,
111+
query_validation_time,
112+
query_result_size,
113+
query_result_size_max,
114+
}
115+
}
116+
117+
// Tests need to construct one of these, but normal code doesn't
118+
#[cfg(debug_assertions)]
119+
pub fn make(registry: Arc<dyn MetricsRegistry>) -> Self {
120+
Self::new(registry)
121+
}
122+
123+
pub fn observe_query_validation(&self, duration: Duration, id: &DeploymentHash) {
124+
self.query_validation_time
125+
.with_label_values(&[id.as_str()])
126+
.observe(duration.as_secs_f64());
127+
}
128+
129+
pub fn observe_query_result_size(&self, size: usize) {
130+
let size = size as f64;
131+
self.query_result_size.observe(size);
132+
if self.query_result_size_max.get() < size {
133+
self.query_result_size_max.set(size);
134+
}
135+
}
136+
}

0 commit comments

Comments
 (0)