From 6ff866c2062a98e9abb9c9e00b784baf8a3f15b4 Mon Sep 17 00:00:00 2001 From: bryn Date: Wed, 12 Jun 2024 22:11:29 +0100 Subject: [PATCH] Add cost information to protobuf traces --- .../src/plugins/demand_control/mod.rs | 2 + .../strategy/static_estimated.rs | 1 + .../plugins/telemetry/config_new/cost/mod.rs | 11 +- .../src/plugins/telemetry/config_new/mod.rs | 2 +- apollo-router/src/plugins/telemetry/mod.rs | 33 +- .../telemetry/tracing/apollo_telemetry.rs | 62 +- apollo-router/tests/apollo_reports.rs | 59 +- .../tests/fixtures/apollo_reports.router.yaml | 7 + ...llo_reports__demand_control_trace.snap.new | 569 ++++++++++++++++++ 9 files changed, 727 insertions(+), 19 deletions(-) create mode 100644 apollo-router/tests/snapshots/apollo_reports__demand_control_trace.snap.new diff --git a/apollo-router/src/plugins/demand_control/mod.rs b/apollo-router/src/plugins/demand_control/mod.rs index fc36115b2e9..57400ca40ca 100644 --- a/apollo-router/src/plugins/demand_control/mod.rs +++ b/apollo-router/src/plugins/demand_control/mod.rs @@ -43,6 +43,7 @@ pub(crate) struct CostContext { pub(crate) estimated: f64, pub(crate) actual: f64, pub(crate) result: &'static str, + pub(crate) strategy: &'static str, } impl Default for CostContext { @@ -51,6 +52,7 @@ impl Default for CostContext { estimated: 0.0, actual: 0.0, result: "COST_OK", + strategy: "COST_STRATEGY_UNKNOWN", } } } diff --git a/apollo-router/src/plugins/demand_control/strategy/static_estimated.rs b/apollo-router/src/plugins/demand_control/strategy/static_estimated.rs index 6e7447d6b46..41de6926b42 100644 --- a/apollo-router/src/plugins/demand_control/strategy/static_estimated.rs +++ b/apollo-router/src/plugins/demand_control/strategy/static_estimated.rs @@ -22,6 +22,7 @@ impl StrategyImpl for StaticEstimated { .and_then(|cost| { request.context.extensions().with_lock(|mut lock| { let cost_result = lock.get_or_default_mut::(); + cost_result.strategy = "static_estimated"; cost_result.estimated = cost; if cost > self.max { Err( diff --git a/apollo-router/src/plugins/telemetry/config_new/cost/mod.rs b/apollo-router/src/plugins/telemetry/config_new/cost/mod.rs index 02e20da6a87..b234d476f90 100644 --- a/apollo-router/src/plugins/telemetry/config_new/cost/mod.rs +++ b/apollo-router/src/plugins/telemetry/config_new/cost/mod.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use opentelemetry::metrics::MeterProvider; -use opentelemetry_api::KeyValue; +use opentelemetry_api::{Key, KeyValue}; use parking_lot::Mutex; use schemars::JsonSchema; use serde::Deserialize; @@ -24,6 +24,15 @@ use crate::services::supergraph::Request; use crate::services::supergraph::Response; use crate::Context; +pub(crate) const APOLLO_PRIVATE_COST_ESTIMATED: Key = + Key::from_static_str("apollo_private.cost.estimated"); +pub(crate) const APOLLO_PRIVATE_COST_ACTUAL: Key = + Key::from_static_str("apollo_private.cost.actual"); +pub(crate) const APOLLO_PRIVATE_COST_STRATEGY: Key = + Key::from_static_str("apollo_private.cost.strategy"); +pub(crate) const APOLLO_PRIVATE_COST_RESULT: Key = + Key::from_static_str("apollo_private.cost.result"); + static COST_ESTIMATED: &str = "cost.estimated"; static COST_ACTUAL: &str = "cost.actual"; static COST_DELTA: &str = "cost.delta"; diff --git a/apollo-router/src/plugins/telemetry/config_new/mod.rs b/apollo-router/src/plugins/telemetry/config_new/mod.rs index b66958db4fe..dd719e93009 100644 --- a/apollo-router/src/plugins/telemetry/config_new/mod.rs +++ b/apollo-router/src/plugins/telemetry/config_new/mod.rs @@ -18,7 +18,7 @@ pub(crate) mod attributes; pub(crate) mod conditions; mod conditional; -mod cost; +pub(crate) mod cost; pub(crate) mod events; mod experimental_when_header; pub(crate) mod extendable; diff --git a/apollo-router/src/plugins/telemetry/mod.rs b/apollo-router/src/plugins/telemetry/mod.rs index c980c333f8d..f3c355992ac 100644 --- a/apollo-router/src/plugins/telemetry/mod.rs +++ b/apollo-router/src/plugins/telemetry/mod.rs @@ -86,12 +86,17 @@ use crate::metrics::filter::FilterMeterProvider; use crate::metrics::meter_provider; use crate::plugin::Plugin; use crate::plugin::PluginInit; +use crate::plugins::demand_control::CostContext; use crate::plugins::telemetry::apollo::ForwardHeaders; use crate::plugins::telemetry::apollo_exporter::proto::reports::trace::node::Id::ResponseName; use crate::plugins::telemetry::apollo_exporter::proto::reports::StatsContext; use crate::plugins::telemetry::config::AttributeValue; use crate::plugins::telemetry::config::MetricsCommon; use crate::plugins::telemetry::config::TracingCommon; +use crate::plugins::telemetry::config_new::cost::{ + APOLLO_PRIVATE_COST_ACTUAL, APOLLO_PRIVATE_COST_ESTIMATED, APOLLO_PRIVATE_COST_RESULT, + APOLLO_PRIVATE_COST_STRATEGY, +}; use crate::plugins::telemetry::config_new::graphql::GraphQLInstruments; use crate::plugins::telemetry::config_new::instruments::SupergraphInstruments; use crate::plugins::telemetry::dynamic_attribute::SpanDynAttribute; @@ -588,15 +593,16 @@ impl Plugin for Telemetry { (req.context.clone(), custom_instruments, custom_attributes, supergraph_events, custom_graphql_instruments) }, - move |(ctx, custom_instruments, custom_attributes, supergraph_events, custom_graphql_instruments): (Context, SupergraphInstruments, Vec, SupergraphEvents, GraphQLInstruments), fut| { + move |(ctx, custom_instruments, mut custom_attributes, supergraph_events, custom_graphql_instruments): (Context, SupergraphInstruments, Vec, SupergraphEvents, GraphQLInstruments), fut| { let config = config_map_res.clone(); let sender = metrics_sender.clone(); let start = Instant::now(); async move { let span = Span::current(); - span.set_span_dyn_attributes(custom_attributes); let mut result: Result = fut.await; + add_cost_attributes(&ctx, &mut custom_attributes); + span.set_span_dyn_attributes(custom_attributes); match &result { Ok(resp) => { span.set_span_dyn_attributes(config.instrumentation.spans.supergraph.attributes.on_response(resp)); @@ -774,6 +780,29 @@ impl Plugin for Telemetry { } } +fn add_cost_attributes(context: &Context, custom_attributes: &mut Vec) { + context.extensions().with_lock(|c| { + if let Some(cost) = c.get::().cloned() { + custom_attributes.push(KeyValue::new( + APOLLO_PRIVATE_COST_ESTIMATED.clone(), + AttributeValue::I64(cost.estimated as i64), + )); + custom_attributes.push(KeyValue::new( + APOLLO_PRIVATE_COST_ACTUAL.clone(), + AttributeValue::I64(cost.actual as i64), + )); + custom_attributes.push(KeyValue::new( + APOLLO_PRIVATE_COST_RESULT.clone(), + AttributeValue::String(cost.result.into()), + )); + custom_attributes.push(KeyValue::new( + APOLLO_PRIVATE_COST_STRATEGY.clone(), + AttributeValue::String(cost.strategy.into()), + )); + } + }); +} + impl Telemetry { pub(crate) fn activate(&self) { let mut activation = self.activation.lock(); diff --git a/apollo-router/src/plugins/telemetry/tracing/apollo_telemetry.rs b/apollo-router/src/plugins/telemetry/tracing/apollo_telemetry.rs index a916e359869..4f210eebbc1 100644 --- a/apollo-router/src/plugins/telemetry/tracing/apollo_telemetry.rs +++ b/apollo-router/src/plugins/telemetry/tracing/apollo_telemetry.rs @@ -53,9 +53,9 @@ use crate::plugins::telemetry::apollo_exporter::proto::reports::trace::query_pla use crate::plugins::telemetry::apollo_exporter::proto::reports::trace::query_plan_node::ParallelNode; use crate::plugins::telemetry::apollo_exporter::proto::reports::trace::query_plan_node::ResponsePathElement; use crate::plugins::telemetry::apollo_exporter::proto::reports::trace::query_plan_node::SequenceNode; -use crate::plugins::telemetry::apollo_exporter::proto::reports::trace::Details; use crate::plugins::telemetry::apollo_exporter::proto::reports::trace::Http; use crate::plugins::telemetry::apollo_exporter::proto::reports::trace::QueryPlanNode; +use crate::plugins::telemetry::apollo_exporter::proto::reports::trace::{Details, Limits}; use crate::plugins::telemetry::apollo_exporter::ApolloExporter; use crate::plugins::telemetry::apollo_otlp_exporter::ApolloOtlpExporter; use crate::plugins::telemetry::config::Sampler; @@ -96,6 +96,14 @@ const APOLLO_PRIVATE_HTTP_RESPONSE_HEADERS: Key = Key::from_static_str("apollo_private.http.response_headers"); pub(crate) const APOLLO_PRIVATE_OPERATION_SIGNATURE: Key = Key::from_static_str("apollo_private.operation_signature"); +pub(crate) const APOLLO_PRIVATE_COST_ESTIMATED: Key = + Key::from_static_str("apollo_private.cost.estimated"); +pub(crate) const APOLLO_PRIVATE_COST_ACTUAL: Key = + Key::from_static_str("apollo_private.cost.actual"); +pub(crate) const APOLLO_PRIVATE_COST_STRATEGY: Key = + Key::from_static_str("apollo_private.cost.strategy"); +pub(crate) const APOLLO_PRIVATE_COST_RESULT: Key = + Key::from_static_str("apollo_private.cost.result"); pub(crate) const APOLLO_PRIVATE_FTV1: Key = Key::from_static_str("apollo_private.ftv1"); const PATH: Key = Key::from_static_str("graphql.path"); const SUBGRAPH_NAME: Key = Key::from_static_str("apollo.subgraph.name"); @@ -110,7 +118,7 @@ pub(crate) const OPERATION_SUBTYPE: Key = Key::from_static_str("apollo_private.o const EXT_TRACE_ID: Key = Key::from_static_str("trace_id"); /// The set of attributes to include when sending to the Apollo Reports protocol. -const REPORTS_INCLUDE_ATTRS: [Key; 18] = [ +const REPORTS_INCLUDE_ATTRS: [Key; 22] = [ APOLLO_PRIVATE_REQUEST, APOLLO_PRIVATE_DURATION_NS_KEY, APOLLO_PRIVATE_SENT_TIME_OFFSET, @@ -119,6 +127,10 @@ const REPORTS_INCLUDE_ATTRS: [Key; 18] = [ APOLLO_PRIVATE_HTTP_RESPONSE_HEADERS, APOLLO_PRIVATE_OPERATION_SIGNATURE, APOLLO_PRIVATE_FTV1, + APOLLO_PRIVATE_COST_STRATEGY, + APOLLO_PRIVATE_COST_RESULT, + APOLLO_PRIVATE_COST_ESTIMATED, + APOLLO_PRIVATE_COST_ACTUAL, PATH, SUBGRAPH_NAME, CLIENT_NAME_KEY, @@ -261,6 +273,7 @@ enum TreeData { operation_signature: String, operation_name: String, variables_json: HashMap, + limits: Option, }, QueryPlanNode(QueryPlanNode), DeferPrimary(DeferNodePrimary), @@ -395,6 +408,7 @@ impl Exporter { operation_signature, operation_name, variables_json, + limits, } => { root_trace.field_execution_weight = self.field_execution_weight; root_trace.signature = operation_signature; @@ -402,6 +416,7 @@ impl Exporter { variables_json, operation_name, }); + root_trace.limits = limits; results.push(root_trace.clone()); } TreeData::Execution(operation_type) => { @@ -572,6 +587,35 @@ impl Exporter { )] } SUPERGRAPH_SPAN_NAME => { + let limits = span + .attributes + .get(&APOLLO_PRIVATE_COST_RESULT) + .and_then(extract_string) + .map(|result| { + Limits { + result, + strategy: span + .attributes + .get(&APOLLO_PRIVATE_COST_STRATEGY) + .and_then(extract_string) + .unwrap_or_default(), + cost_estimated: span + .attributes + .get(&APOLLO_PRIVATE_COST_ESTIMATED) + .and_then(extract_u64) + .unwrap_or_default(), + cost_actual: span + .attributes + .get(&APOLLO_PRIVATE_COST_ACTUAL) + .and_then(extract_u64) + .unwrap_or_default(), + // Not extracted yet + depth: 0, + height: 0, + alias_count: 0, + root_field_count: 0, + } + }); //Currently some data is in the supergraph span as we don't have the a request hook in plugin. child_nodes.push(TreeData::Supergraph { operation_signature: span @@ -589,6 +633,7 @@ impl Exporter { .get(&APOLLO_PRIVATE_GRAPHQL_VARIABLES) .and_then(extract_json) .unwrap_or_default(), + limits, }); child_nodes } @@ -751,6 +796,7 @@ impl Exporter { .and_then(extract_string) .unwrap_or_default(), variables_json: HashMap::new(), + limits: None, }); child_nodes.push(TreeData::Execution( @@ -821,6 +867,18 @@ pub(crate) fn extract_i64(v: &Value) -> Option { } } +pub(crate) fn extract_u64(v: &Value) -> Option { + if let Value::I64(v) = v { + if *v > 0 { + Some(*v as u64) + } else { + None + } + } else { + None + } +} + pub(crate) fn extract_ftv1_trace_with_error_count( v: &Value, error_config: &ErrorConfiguration, diff --git a/apollo-router/tests/apollo_reports.rs b/apollo-router/tests/apollo_reports.rs index ba847ae4e1c..feb8fd6135c 100644 --- a/apollo-router/tests/apollo_reports.rs +++ b/apollo-router/tests/apollo_reports.rs @@ -54,6 +54,7 @@ async fn config( use_legacy_request_span: bool, batch: bool, reports: Arc>>, + demand_control: bool, ) -> (JoinHandle<()>, serde_json::Value) { std::env::set_var("APOLLO_KEY", "test"); std::env::set_var("APOLLO_GRAPH_REF", "test"); @@ -89,6 +90,10 @@ async fn config( Some(serde_json::Value::Bool(use_legacy_request_span)) }) .expect("Could not sub in endpoint"); + config = jsonpath_lib::replace_with(config, "$.preview_demand_control.enabled", &mut |_| { + Some(serde_json::Value::Bool(demand_control)) + }) + .expect("Could not sub in endpoint"); (task, config) } @@ -96,8 +101,9 @@ async fn get_router_service( reports: Arc>>, use_legacy_request_span: bool, mocked: bool, + demand_control: bool, ) -> (JoinHandle<()>, BoxCloneService) { - let (task, config) = config(use_legacy_request_span, false, reports).await; + let (task, config) = config(use_legacy_request_span, false, reports, demand_control).await; let builder = TestHarness::builder() .try_log_level("INFO") .configuration_json(config) @@ -121,8 +127,9 @@ async fn get_batch_router_service( reports: Arc>>, use_legacy_request_span: bool, mocked: bool, + demand_control: bool, ) -> (JoinHandle<()>, BoxCloneService) { - let (task, config) = config(use_legacy_request_span, true, reports).await; + let (task, config) = config(use_legacy_request_span, true, reports, demand_control).await; let builder = TestHarness::builder() .try_log_level("INFO") .configuration_json(config) @@ -211,6 +218,7 @@ async fn get_trace_report( reports: Arc>>, request: router::Request, use_legacy_request_span: bool, + demand_control: bool, ) -> Report { get_report( get_router_service, @@ -218,6 +226,7 @@ async fn get_trace_report( use_legacy_request_span, false, request, + demand_control, |r| { !r.traces_per_query .values() @@ -241,6 +250,7 @@ async fn get_batch_trace_report( use_legacy_request_span, false, request, + false, |r| { !r.traces_per_query .values() @@ -269,6 +279,7 @@ async fn get_metrics_report(reports: Arc>>, request: router::R false, false, request, + false, has_metrics, ) .await @@ -291,17 +302,19 @@ async fn get_metrics_report_mocked( false, true, request, + false, has_metrics, ) .await } async fn get_report bool + Send + Sync + Copy + 'static>( - service_fn: impl FnOnce(Arc>>, bool, bool) -> Fut, + service_fn: impl FnOnce(Arc>>, bool, bool, bool) -> Fut, reports: Arc>>, use_legacy_request_span: bool, mocked: bool, request: router::Request, + demand_control: bool, filter: T, ) -> Report where @@ -309,7 +322,13 @@ where { let _guard = TEST.lock().await; reports.lock().await.clear(); - let (task, mut service) = service_fn(reports.clone(), use_legacy_request_span, mocked).await; + let (task, mut service) = service_fn( + reports.clone(), + use_legacy_request_span, + mocked, + demand_control, + ) + .await; let response = service .ready() .await @@ -358,7 +377,7 @@ async fn get_batch_stats_report bool + Send + Sync + Copy + ' ) -> u64 { let _guard = TEST.lock().await; reports.lock().await.clear(); - let (task, mut service) = get_batch_router_service(reports.clone(), mocked, false).await; + let (task, mut service) = get_batch_router_service(reports.clone(), mocked, false, false).await; let response = service .ready() .await @@ -402,7 +421,7 @@ async fn non_defer() { .unwrap(); let req: router::Request = request.try_into().expect("could not convert request"); let reports = Arc::new(Mutex::new(vec![])); - let report = get_trace_report(reports, req, use_legacy_request_span).await; + let report = get_trace_report(reports, req, use_legacy_request_span, false).await; assert_report!(report); } } @@ -418,7 +437,7 @@ async fn test_condition_if() { .unwrap(); let req: router::Request = request.try_into().expect("could not convert request"); let reports = Arc::new(Mutex::new(vec![])); - let report = get_trace_report(reports, req, use_legacy_request_span).await; + let report = get_trace_report(reports, req, use_legacy_request_span, false).await; assert_report!(report); } } @@ -434,7 +453,7 @@ async fn test_condition_else() { .unwrap(); let req: router::Request = request.try_into().expect("could not convert request"); let reports = Arc::new(Mutex::new(vec![])); - let report = get_trace_report(reports, req, use_legacy_request_span).await; + let report = get_trace_report(reports, req, use_legacy_request_span, false).await; assert_report!(report); } } @@ -448,7 +467,7 @@ async fn test_trace_id() { .unwrap(); let req: router::Request = request.try_into().expect("could not convert request"); let reports = Arc::new(Mutex::new(vec![])); - let report = get_trace_report(reports, req, use_legacy_request_span).await; + let report = get_trace_report(reports, req, use_legacy_request_span, false).await; assert_report!(report); } } @@ -487,7 +506,7 @@ async fn test_client_name() { .unwrap(); let req: router::Request = request.try_into().expect("could not convert request"); let reports = Arc::new(Mutex::new(vec![])); - let report = get_trace_report(reports, req, use_legacy_request_span).await; + let report = get_trace_report(reports, req, use_legacy_request_span, false).await; assert_report!(report); } } @@ -502,7 +521,7 @@ async fn test_client_version() { .unwrap(); let req: router::Request = request.try_into().expect("could not convert request"); let reports = Arc::new(Mutex::new(vec![])); - let report = get_trace_report(reports, req, use_legacy_request_span).await; + let report = get_trace_report(reports, req, use_legacy_request_span, false).await; assert_report!(report); } } @@ -518,7 +537,7 @@ async fn test_send_header() { .unwrap(); let req: router::Request = request.try_into().expect("could not convert request"); let reports = Arc::new(Mutex::new(vec![])); - let report = get_trace_report(reports, req, use_legacy_request_span).await; + let report = get_trace_report(reports, req, use_legacy_request_span, false).await; assert_report!(report); } } @@ -560,7 +579,7 @@ async fn test_send_variable_value() { .unwrap(); let req: router::Request = request.try_into().expect("could not convert request"); let reports = Arc::new(Mutex::new(vec![])); - let report = get_trace_report(reports, req, use_legacy_request_span).await; + let report = get_trace_report(reports, req, use_legacy_request_span, false).await; assert_report!(report); } } @@ -620,3 +639,17 @@ async fn test_stats_mocked() { }); }); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_demand_control_trace() { + for use_legacy_request_span in [true, false] { + let request = supergraph::Request::fake_builder() + .query("query{topProducts{name reviews {author{name}} reviews{author{name}}}}") + .build() + .unwrap(); + let req: router::Request = request.try_into().expect("could not convert request"); + let reports = Arc::new(Mutex::new(vec![])); + let report = get_trace_report(reports, req, use_legacy_request_span, true).await; + assert_report!(report); + } +} diff --git a/apollo-router/tests/fixtures/apollo_reports.router.yaml b/apollo-router/tests/fixtures/apollo_reports.router.yaml index 846c40410d0..a3cd9c571a7 100644 --- a/apollo-router/tests/fixtures/apollo_reports.router.yaml +++ b/apollo-router/tests/fixtures/apollo_reports.router.yaml @@ -3,6 +3,13 @@ include_subgraph_errors: rhai: scripts: tests/fixtures main: test_callbacks.rhai +preview_demand_control: + mode: measure + enabled: false + strategy: + static_estimated: + max: 1500 + list_size: 10 telemetry: instrumentation: spans: diff --git a/apollo-router/tests/snapshots/apollo_reports__demand_control_trace.snap.new b/apollo-router/tests/snapshots/apollo_reports__demand_control_trace.snap.new new file mode 100644 index 00000000000..9d8e0e45129 --- /dev/null +++ b/apollo-router/tests/snapshots/apollo_reports__demand_control_trace.snap.new @@ -0,0 +1,569 @@ +--- +source: apollo-router/tests/apollo_reports.rs +assertion_line: 653 +expression: report +--- +header: + graph_ref: test + hostname: "[hostname]" + agent_version: "[agent_version]" + service_version: "" + runtime_version: rust + uname: "[uname]" + executable_schema_id: "[executable_schema_id]" +traces_per_query: + "# -\n{topProducts{name reviews{author{name}}reviews{author{name}}}}": + trace: + - start_time: + seconds: "[seconds]" + nanos: "[nanos]" + end_time: + seconds: "[seconds]" + nanos: "[nanos]" + duration_ns: "[duration_ns]" + root: ~ + is_incomplete: false + signature: "" + unexecuted_operation_body: "" + unexecuted_operation_name: "" + details: + variables_json: {} + operation_name: "" + client_name: "" + client_version: "" + operation_type: query + operation_subtype: "" + http: + method: 4 + request_headers: {} + response_headers: + my_trace_id: "[my_trace_id]" + status_code: 0 + cache_policy: ~ + query_plan: + node: + Sequence: + nodes: + - node: + Fetch: + service_name: products + trace_parsing_failed: false + trace: + start_time: + seconds: "[seconds]" + nanos: "[nanos]" + end_time: + seconds: "[seconds]" + nanos: "[nanos]" + duration_ns: "[duration_ns]" + root: + original_field_name: "" + type: "" + parent_type: "" + cache_policy: ~ + start_time: 0 + end_time: 0 + error: [] + child: + - original_field_name: "" + type: "[Product]" + parent_type: Query + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: "" + parent_type: "" + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: String! + parent_type: Product + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: [] + id: + ResponseName: upc + - original_field_name: "" + type: String + parent_type: Product + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: [] + id: + ResponseName: name + id: + Index: 0 + - original_field_name: "" + type: "" + parent_type: "" + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: String! + parent_type: Product + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: [] + id: + ResponseName: upc + - original_field_name: "" + type: String + parent_type: Product + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: [] + id: + ResponseName: name + id: + Index: 1 + - original_field_name: "" + type: "" + parent_type: "" + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: String! + parent_type: Product + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: [] + id: + ResponseName: upc + - original_field_name: "" + type: String + parent_type: Product + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: [] + id: + ResponseName: name + id: + Index: 2 + id: + ResponseName: topProducts + id: ~ + is_incomplete: false + signature: "" + unexecuted_operation_body: "" + unexecuted_operation_name: "" + details: ~ + client_name: "" + client_version: "" + operation_type: "" + operation_subtype: "" + http: ~ + cache_policy: ~ + query_plan: ~ + full_query_cache_hit: false + persisted_query_hit: false + persisted_query_register: false + registered_operation: false + forbidden_operation: false + field_execution_weight: 1 + limits: ~ + sent_time_offset: "[sent_time_offset]" + sent_time: + seconds: "[seconds]" + nanos: "[nanos]" + received_time: + seconds: "[seconds]" + nanos: "[nanos]" + - node: + Flatten: + response_path: + - id: + FieldName: topProducts + node: + node: + Fetch: + service_name: reviews + trace_parsing_failed: false + trace: + start_time: + seconds: "[seconds]" + nanos: "[nanos]" + end_time: + seconds: "[seconds]" + nanos: "[nanos]" + duration_ns: "[duration_ns]" + root: + original_field_name: "" + type: "" + parent_type: "" + cache_policy: ~ + start_time: 0 + end_time: 0 + error: [] + child: + - original_field_name: "" + type: "[_Entity]!" + parent_type: Query + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: "" + parent_type: "" + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: "[Review]" + parent_type: Product + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: "" + parent_type: "" + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: User + parent_type: Review + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: ID! + parent_type: User + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: [] + id: + ResponseName: id + id: + ResponseName: author + id: + Index: 0 + - original_field_name: "" + type: "" + parent_type: "" + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: User + parent_type: Review + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: ID! + parent_type: User + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: [] + id: + ResponseName: id + id: + ResponseName: author + id: + Index: 1 + id: + ResponseName: reviews + id: + Index: 0 + - original_field_name: "" + type: "" + parent_type: "" + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: "[Review]" + parent_type: Product + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: "" + parent_type: "" + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: User + parent_type: Review + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: ID! + parent_type: User + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: [] + id: + ResponseName: id + id: + ResponseName: author + id: + Index: 0 + id: + ResponseName: reviews + id: + Index: 1 + - original_field_name: "" + type: "" + parent_type: "" + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: "[Review]" + parent_type: Product + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: "" + parent_type: "" + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: User + parent_type: Review + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: ID! + parent_type: User + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: [] + id: + ResponseName: id + id: + ResponseName: author + id: + Index: 0 + id: + ResponseName: reviews + id: + Index: 2 + id: + ResponseName: _entities + id: ~ + is_incomplete: false + signature: "" + unexecuted_operation_body: "" + unexecuted_operation_name: "" + details: ~ + client_name: "" + client_version: "" + operation_type: "" + operation_subtype: "" + http: ~ + cache_policy: ~ + query_plan: ~ + full_query_cache_hit: false + persisted_query_hit: false + persisted_query_register: false + registered_operation: false + forbidden_operation: false + field_execution_weight: 1 + limits: ~ + sent_time_offset: "[sent_time_offset]" + sent_time: + seconds: "[seconds]" + nanos: "[nanos]" + received_time: + seconds: "[seconds]" + nanos: "[nanos]" + - node: + Flatten: + response_path: + - id: + FieldName: topProducts + - id: + FieldName: reviews + - id: + FieldName: author + node: + node: + Fetch: + service_name: accounts + trace_parsing_failed: false + trace: + start_time: + seconds: "[seconds]" + nanos: "[nanos]" + end_time: + seconds: "[seconds]" + nanos: "[nanos]" + duration_ns: "[duration_ns]" + root: + original_field_name: "" + type: "" + parent_type: "" + cache_policy: ~ + start_time: 0 + end_time: 0 + error: [] + child: + - original_field_name: "" + type: "[_Entity]!" + parent_type: Query + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: "" + parent_type: "" + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: String + parent_type: User + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: [] + id: + ResponseName: name + id: + Index: 0 + - original_field_name: "" + type: "" + parent_type: "" + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: + - original_field_name: "" + type: String + parent_type: User + cache_policy: ~ + start_time: "[start_time]" + end_time: "[end_time]" + error: [] + child: [] + id: + ResponseName: name + id: + Index: 1 + id: + ResponseName: _entities + id: ~ + is_incomplete: false + signature: "" + unexecuted_operation_body: "" + unexecuted_operation_name: "" + details: ~ + client_name: "" + client_version: "" + operation_type: "" + operation_subtype: "" + http: ~ + cache_policy: ~ + query_plan: ~ + full_query_cache_hit: false + persisted_query_hit: false + persisted_query_register: false + registered_operation: false + forbidden_operation: false + field_execution_weight: 1 + limits: ~ + sent_time_offset: "[sent_time_offset]" + sent_time: + seconds: "[seconds]" + nanos: "[nanos]" + received_time: + seconds: "[seconds]" + nanos: "[nanos]" + full_query_cache_hit: false + persisted_query_hit: false + persisted_query_register: false + registered_operation: false + forbidden_operation: false + field_execution_weight: 1 + limits: + result: COST_OK + strategy: static_estimated + cost_estimated: 230 + cost_actual: 19 + depth: 0 + height: 0 + alias_count: 0 + root_field_count: 0 + stats_with_context: [] + referenced_fields_by_type: {} + query_metadata: ~ +end_time: "[end_time]" +operation_count: 0 +operation_count_by_type: [] +traces_pre_aggregated: true +extended_references_enabled: false