Skip to content

Commit

Permalink
graphql: GraphQLMetrics
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilkisiela committed Jul 22, 2022
1 parent 94aa0f0 commit 81dc87a
Show file tree
Hide file tree
Showing 18 changed files with 247 additions and 279 deletions.
7 changes: 7 additions & 0 deletions graph/src/components/graphql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ pub trait GraphQlRunner: Send + Sync + 'static {
) -> Result<SubscriptionResult, SubscriptionError>;

fn load_manager(&self) -> Arc<LoadManager>;

fn metrics(&self) -> Arc<dyn GraphQLMetrics>;
}

pub trait GraphQLMetrics: Send + Sync + 'static {
fn observe_query_execution(&self, duration: Duration, results: &QueryResults) -> ();
fn observe_query_parsing(&self, duration: Duration, results: &QueryResults) -> ();
}

#[async_trait]
Expand Down
23 changes: 1 addition & 22 deletions graph/src/data/query/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use serde::ser::*;
use serde::Serialize;
use std::convert::TryFrom;
use std::sync::Arc;
use std::time::Duration;

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

impl QueryResults {
pub fn empty() -> Self {
QueryResults {
results: Vec::new(),
validation_time: None,
}
}

Expand All @@ -75,10 +72,6 @@ impl QueryResults {
.filter_map(|result| result.deployment.as_ref())
.next()
}

pub fn set_validation_time(&mut self, duration: Duration) -> () {
self.validation_time = Some(duration);
}
}

impl Serialize for QueryResults {
Expand Down Expand Up @@ -136,7 +129,6 @@ impl From<Data> for QueryResults {
fn from(x: Data) -> Self {
QueryResults {
results: vec![Arc::new(x.into())],
validation_time: None,
}
}
}
Expand All @@ -145,25 +137,20 @@ impl From<QueryResult> for QueryResults {
fn from(x: QueryResult) -> Self {
QueryResults {
results: vec![Arc::new(x)],
validation_time: None,
}
}
}

impl From<Arc<QueryResult>> for QueryResults {
fn from(x: Arc<QueryResult>) -> Self {
QueryResults {
results: vec![x],
validation_time: None,
}
QueryResults { results: vec![x] }
}
}

impl From<QueryExecutionError> for QueryResults {
fn from(x: QueryExecutionError) -> Self {
QueryResults {
results: vec![Arc::new(x.into())],
validation_time: None,
}
}
}
Expand All @@ -172,7 +159,6 @@ impl From<Vec<QueryExecutionError>> for QueryResults {
fn from(x: Vec<QueryExecutionError>) -> Self {
QueryResults {
results: vec![Arc::new(x.into())],
validation_time: None,
}
}
}
Expand Down Expand Up @@ -213,8 +199,6 @@ pub struct QueryResult {
errors: Vec<QueryError>,
#[serde(skip_serializing)]
pub deployment: Option<DeploymentHash>,
#[serde(skip_serializing)]
pub validation_time: Option<Duration>,
}

impl QueryResult {
Expand All @@ -223,7 +207,6 @@ impl QueryResult {
data: Some(data),
errors: Vec::new(),
deployment: None,
validation_time: None,
}
}

Expand All @@ -236,7 +219,6 @@ impl QueryResult {
data: self.data.clone(),
errors: self.errors.clone(),
deployment: self.deployment.clone(),
validation_time: self.validation_time.clone(),
}
}

Expand Down Expand Up @@ -292,7 +274,6 @@ impl From<QueryExecutionError> for QueryResult {
data: None,
errors: vec![e.into()],
deployment: None,
validation_time: None,
}
}
}
Expand All @@ -303,7 +284,6 @@ impl From<QueryError> for QueryResult {
data: None,
errors: vec![e],
deployment: None,
validation_time: None,
}
}
}
Expand All @@ -314,7 +294,6 @@ impl From<Vec<QueryExecutionError>> for QueryResult {
data: None,
errors: e.into_iter().map(QueryError::from).collect(),
deployment: None,
validation_time: None,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion graph/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ pub mod prelude {
LightEthereumBlockExt,
};
pub use crate::components::graphql::{
GraphQlRunner, QueryLoadManager, SubscriptionResultFuture,
GraphQLMetrics, GraphQlRunner, QueryLoadManager, SubscriptionResultFuture,
};
pub use crate::components::link_resolver::{JsonStreamValue, JsonValueStream, LinkResolver};
pub use crate::components::metrics::{
Expand Down
12 changes: 6 additions & 6 deletions graphql/src/execution/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::collections::{BTreeMap, HashMap, HashSet};
use std::hash::{Hash, Hasher};
use std::iter::FromIterator;
use std::sync::Arc;
use std::time::{Duration, Instant};
use std::time::Instant;
use std::{collections::hash_map::DefaultHasher, convert::TryFrom};

use graph::data::graphql::{ext::TypeExt, ObjectOrInterface};
Expand All @@ -20,6 +20,7 @@ use graph::prelude::{
};

use crate::execution::ast as a;
use crate::metrics::GraphQLMetrics;
use crate::query::{ast as qast, ext::BlockConstraint};
use crate::schema::ast::{self as sast};
use crate::values::coercion;
Expand Down Expand Up @@ -135,9 +136,6 @@ pub struct Query {
pub query_text: Arc<String>,
pub variables_text: Arc<String>,
pub query_id: String,

/// Used only for metrics
pub validation_time: Duration,
}

impl Query {
Expand All @@ -152,12 +150,15 @@ impl Query {
query: GraphDataQuery,
max_complexity: Option<u64>,
max_depth: u8,
metrics: Option<Arc<GraphQLMetrics>>,
) -> Result<Arc<Self>, Vec<QueryExecutionError>> {
let validation_phase_start = Instant::now();
let validation_errors =
validate(schema.document(), &query.document, &GRAPHQL_VALIDATION_PLAN);

let validation_time = validation_phase_start.elapsed();
if let Some(metrics) = metrics {
metrics.observe_query_validation(validation_phase_start.elapsed(), schema.id());
}

if !validation_errors.is_empty() {
if !ENV_VARS.graphql.silent_graphql_validations {
Expand Down Expand Up @@ -259,7 +260,6 @@ impl Query {
query_text: query.query_text.cheap_clone(),
variables_text: query.variables_text.cheap_clone(),
query_id,
validation_time,
};

Ok(Arc::new(query))
Expand Down
6 changes: 5 additions & 1 deletion graphql/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ mod store;
/// The external interface for actually running queries
mod runner;

/// Utilities for working with Prometheus.
mod metrics;

/// Prelude that exports the most important traits and types.
pub mod prelude {
pub use super::execution::{ast as a, ExecutionContext, Query, Resolver};
Expand All @@ -34,12 +37,13 @@ pub mod prelude {
pub use super::subscription::SubscriptionExecutionOptions;
pub use super::values::MaybeCoercible;

pub use super::metrics::GraphQLMetrics;
pub use super::runner::GraphQlRunner;
pub use graph::prelude::s::ObjectType;
}

#[cfg(debug_assertions)]
pub mod test_support {
pub use super::runner::ResultSizeMetrics;
pub use super::metrics::GraphQLMetrics;
pub use super::runner::INITIAL_DEPLOYMENT_STATE_FOR_TESTS;
}
136 changes: 136 additions & 0 deletions graphql/src/metrics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use std::time::Duration;

use graph::data::query::QueryResults;
use graph::prelude::{DeploymentHash, GraphQLMetrics as GraphQLMetricsTrait, MetricsRegistry};
use graph::prometheus::{Gauge, Histogram, HistogramVec};

pub struct GraphQLMetrics {
query_execution_time: Box<HistogramVec>,
query_parsing_time: Box<HistogramVec>,
query_validation_time: Box<HistogramVec>,
query_result_size: Box<Histogram>,
query_result_size_max: Box<Gauge>,
}

impl fmt::Debug for GraphQLMetrics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "GraphQLMetrics {{ }}")
}
}

impl GraphQLMetricsTrait for GraphQLMetrics {
fn observe_query_execution(&self, duration: Duration, results: &QueryResults) {
let id = results
.deployment_hash()
.map(|h| h.as_str())
.unwrap_or_else(|| {
if results.not_found() {
"notfound"
} else {
"unknown"
}
});
let status = if results.has_errors() {
"failed"
} else {
"success"
};
self.query_execution_time
.with_label_values(&[id, status])
.observe(duration.as_secs_f64());
}

fn observe_query_parsing(&self, duration: Duration, results: &QueryResults) {
let id = results
.deployment_hash()
.map(|h| h.as_str())
.unwrap_or_else(|| {
if results.not_found() {
"notfound"
} else {
"unknown"
}
});
self.query_parsing_time
.with_label_values(&[id])
.observe(duration.as_secs_f64());
}
}

impl GraphQLMetrics {
pub fn new(registry: Arc<dyn MetricsRegistry>) -> Self {
let query_execution_time = registry
.new_histogram_vec(
"query_execution_time",
"Execution time for successful GraphQL queries",
vec![String::from("deployment"), String::from("status")],
vec![0.1, 0.5, 1.0, 10.0, 100.0],
)
.expect("failed to create `query_execution_time` histogram");
let query_parsing_time = registry
.new_histogram_vec(
"query_parsing_time",
"Parsing time for GraphQL queries",
vec![String::from("deployment")],
vec![0.1, 0.5, 1.0, 10.0, 100.0],
)
.expect("failed to create `query_parsing_time` histogram");

let query_validation_time = registry
.new_histogram_vec(
"query_validation_time",
"Validation time for GraphQL queries",
vec![String::from("deployment")],
vec![0.1, 0.5, 1.0, 10.0, 100.0],
)
.expect("failed to create `query_validation_time` histogram");

let bins = (10..32).map(|n| 2u64.pow(n) as f64).collect::<Vec<_>>();
let query_result_size = registry
.new_histogram(
"query_result_size",
"the size of the result of successful GraphQL queries (in CacheWeight)",
bins,
)
.unwrap();

let query_result_size_max = registry
.new_gauge(
"query_result_max",
"the maximum size of a query result (in CacheWeight)",
HashMap::new(),
)
.unwrap();

Self {
query_execution_time,
query_parsing_time,
query_validation_time,
query_result_size,
query_result_size_max,
}
}

// Tests need to construct one of these, but normal code doesn't
#[cfg(debug_assertions)]
pub fn make(registry: Arc<dyn MetricsRegistry>) -> Self {
Self::new(registry)
}

pub fn observe_query_validation(&self, duration: Duration, id: &DeploymentHash) {
self.query_validation_time
.with_label_values(&[id.as_str()])
.observe(duration.as_secs_f64());
}

pub fn observe_query_result_size(&self, size: usize) {
let size = size as f64;
self.query_result_size.observe(size);
if self.query_result_size_max.get() < size {
self.query_result_size_max.set(size);
}
}
}
Loading

0 comments on commit 81dc87a

Please sign in to comment.