Skip to content

Commit 20cd4df

Browse files
ardatandotansimha
authored andcommitted
feat(router): Hive Console Usage Reporting
Use the published package Go Fix More .. Update readme Enabled Enabled Improvements Go Update match patterns
1 parent 2a380fe commit 20cd4df

File tree

15 files changed

+540
-5
lines changed

15 files changed

+540
-5
lines changed

Cargo.lock

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,4 @@ retry-policies = "0.4.0"
5959
reqwest-retry = "0.7.0"
6060
reqwest-middleware = "0.4.2"
6161
vrl = { version = "0.28.0", features = ["compiler", "parser", "value", "diagnostic", "stdlib", "core"] }
62+
regex-automata = "0.4.10"

bin/router/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,12 @@ reqwest-retry = { workspace = true }
4545
reqwest-middleware = { workspace = true }
4646
vrl = { workspace = true }
4747
serde_json = { workspace = true }
48+
regex-automata = { workspace = true }
4849

4950
mimalloc = { version = "0.1.48", features = ["v3"] }
5051
moka = { version = "0.12.10", features = ["future"] }
5152
ulid = "1.2.1"
5253
tokio-util = "0.7.16"
5354
cookie = "0.18.1"
54-
regex-automata = "0.4.10"
5555
arc-swap = "1.7.1"
56+
hive-console-sdk = "0.1.0"

bin/router/src/lib.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::{
1919
},
2020
jwt::JwtAuthRuntime,
2121
logger::configure_logging,
22-
pipeline::graphql_request_handler,
22+
pipeline::{graphql_request_handler, usage_reporting::init_hive_user_agent},
2323
};
2424

2525
pub use crate::{schema_state::SchemaState, shared_state::RouterSharedState};
@@ -111,11 +111,23 @@ pub async fn configure_app_from_config(
111111
false => None,
112112
};
113113

114+
let hive_usage_agent = match router_config.usage_reporting.enabled {
115+
true => Some(init_hive_user_agent(
116+
bg_tasks_manager,
117+
&router_config.usage_reporting,
118+
)),
119+
false => None,
120+
};
121+
114122
let router_config_arc = Arc::new(router_config);
115123
let schema_state =
116124
SchemaState::new_from_config(bg_tasks_manager, router_config_arc.clone()).await?;
117125
let schema_state_arc = Arc::new(schema_state);
118-
let shared_state = Arc::new(RouterSharedState::new(router_config_arc, jwt_runtime)?);
126+
let shared_state = Arc::new(RouterSharedState::new(
127+
router_config_arc,
128+
jwt_runtime,
129+
hive_usage_agent,
130+
)?);
119131

120132
Ok((shared_state, schema_state_arc))
121133
}

bin/router/src/pipeline/mod.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::sync::Arc;
1+
use std::{sync::Arc, time::Instant};
22

33
use hive_router_plan_executor::execution::{
44
client_request_details::{ClientRequestDetails, JwtRequestDetails, OperationDetails},
@@ -46,6 +46,7 @@ pub mod normalize;
4646
pub mod parser;
4747
pub mod progressive_override;
4848
pub mod query_plan;
49+
pub mod usage_reporting;
4950
pub mod validation;
5051

5152
static GRAPHIQL_HTML: &str = include_str!("../../static/graphiql.html");
@@ -111,6 +112,7 @@ pub async fn execute_pipeline(
111112
shared_state: &Arc<RouterSharedState>,
112113
schema_state: &Arc<SchemaState>,
113114
) -> Result<PlanExecutionOutput, PipelineError> {
115+
let start = Instant::now();
114116
perform_csrf_prevention(req, &shared_state.router_config.csrf)?;
115117

116118
let mut execution_request = get_execution_request(req, body_bytes).await?;
@@ -190,5 +192,19 @@ pub async fn execute_pipeline(
190192
)
191193
.await?;
192194

195+
if shared_state.router_config.usage_reporting.enabled {
196+
if let Some(hive_usage_agent) = &shared_state.hive_usage_agent {
197+
usage_reporting::collect_usage_report(
198+
supergraph.supergraph_schema.clone(),
199+
start.elapsed(),
200+
req,
201+
&client_request_details,
202+
hive_usage_agent,
203+
&shared_state.router_config.usage_reporting,
204+
&execution_result,
205+
);
206+
}
207+
}
208+
193209
Ok(execution_result)
194210
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
use std::{
2+
sync::Arc,
3+
time::{Duration, SystemTime, UNIX_EPOCH},
4+
};
5+
6+
use async_trait::async_trait;
7+
use graphql_parser::schema::Document;
8+
use hive_console_sdk::agent::{ExecutionReport, UsageAgent};
9+
use hive_router_config::usage_reporting::UsageReportingConfig;
10+
use hive_router_plan_executor::execution::{
11+
client_request_details::ClientRequestDetails, plan::PlanExecutionOutput,
12+
};
13+
use ntex::web::HttpRequest;
14+
use rand::Rng;
15+
use tokio_util::sync::CancellationToken;
16+
17+
use crate::{
18+
background_tasks::{BackgroundTask, BackgroundTasksManager},
19+
consts::ROUTER_VERSION,
20+
};
21+
22+
pub fn init_hive_user_agent(
23+
bg_tasks_manager: &mut BackgroundTasksManager,
24+
usage_config: &UsageReportingConfig,
25+
) -> Arc<UsageAgent> {
26+
let user_agent = format!("hive-router/{}", ROUTER_VERSION);
27+
let hive_user_agent = hive_console_sdk::agent::UsageAgent::new(
28+
usage_config.access_token.clone(),
29+
usage_config.endpoint.clone(),
30+
usage_config.target_id.clone(),
31+
usage_config.buffer_size,
32+
usage_config.connect_timeout,
33+
usage_config.request_timeout,
34+
usage_config.accept_invalid_certs,
35+
usage_config.flush_interval,
36+
user_agent,
37+
);
38+
let hive_user_agent_arc = Arc::new(hive_user_agent);
39+
bg_tasks_manager.register_task(hive_user_agent_arc.clone());
40+
hive_user_agent_arc
41+
}
42+
43+
#[inline]
44+
pub fn collect_usage_report(
45+
schema: Arc<Document<'static, String>>,
46+
duration: Duration,
47+
req: &HttpRequest,
48+
client_request_details: &ClientRequestDetails,
49+
hive_usage_agent: &UsageAgent,
50+
usage_config: &UsageReportingConfig,
51+
execution_result: &PlanExecutionOutput,
52+
) {
53+
let mut rng = rand::rng();
54+
let sampled = rng.random::<f64>() < usage_config.sample_rate.as_f64();
55+
if !sampled {
56+
return;
57+
}
58+
if client_request_details
59+
.operation
60+
.name
61+
.is_some_and(|op_name| usage_config.exclude.contains(&op_name.to_string()))
62+
{
63+
return;
64+
}
65+
let client_name = get_header_value(req, &usage_config.client_name_header);
66+
let client_version = get_header_value(req, &usage_config.client_version_header);
67+
let timestamp = SystemTime::now()
68+
.duration_since(UNIX_EPOCH)
69+
.unwrap()
70+
.as_millis() as u64;
71+
let execution_report = ExecutionReport {
72+
schema,
73+
client_name: client_name.map(|s| s.to_owned()),
74+
client_version: client_version.map(|s| s.to_owned()),
75+
timestamp,
76+
duration,
77+
ok: execution_result.error_count == 0,
78+
errors: execution_result.error_count,
79+
operation_body: client_request_details.operation.query.to_owned(),
80+
operation_name: client_request_details
81+
.operation
82+
.name
83+
.map(|op_name| op_name.to_owned()),
84+
persisted_document_hash: None,
85+
};
86+
87+
if let Err(err) = hive_usage_agent.add_report(execution_report) {
88+
tracing::error!("Failed to send usage report: {}", err);
89+
}
90+
}
91+
92+
fn get_header_value<'req>(req: &'req HttpRequest, header_name: &str) -> Option<&'req str> {
93+
req.headers().get(header_name).and_then(|v| v.to_str().ok())
94+
}
95+
96+
#[async_trait]
97+
impl BackgroundTask for UsageAgent {
98+
fn id(&self) -> &str {
99+
"hive_console_usage_report_task"
100+
}
101+
102+
async fn run(&self, token: CancellationToken) {
103+
self.start_flush_interval(Some(token)).await
104+
}
105+
}

bin/router/src/schema_state.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use arc_swap::{ArcSwap, Guard};
22
use async_trait::async_trait;
3+
use graphql_parser::schema::Document;
34
use graphql_tools::validation::utils::ValidationError;
45
use hive_router_config::{supergraph::SupergraphSource, HiveRouterConfig};
56
use hive_router_plan_executor::{
@@ -39,6 +40,7 @@ pub struct SupergraphData {
3940
pub metadata: SchemaMetadata,
4041
pub planner: Planner,
4142
pub subgraph_executor_map: SubgraphExecutorMap,
43+
pub supergraph_schema: Arc<Document<'static, String>>,
4244
}
4345

4446
#[derive(Debug, thiserror::Error)]
@@ -124,6 +126,7 @@ impl SchemaState {
124126
)?;
125127

126128
Ok(SupergraphData {
129+
supergraph_schema: Arc::new(parsed_supergraph_sdl),
127130
metadata,
128131
planner,
129132
subgraph_executor_map,

bin/router/src/shared_state.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use graphql_tools::validation::validate::ValidationPlan;
2+
use hive_console_sdk::agent::UsageAgent;
23
use hive_router_config::HiveRouterConfig;
34
use hive_router_plan_executor::headers::{
45
compile::compile_headers_plan, errors::HeaderRuleCompileError, plan::HeaderRulesPlan,
@@ -18,12 +19,14 @@ pub struct RouterSharedState {
1819
pub override_labels_evaluator: OverrideLabelsEvaluator,
1920
pub cors_runtime: Option<Cors>,
2021
pub jwt_auth_runtime: Option<JwtAuthRuntime>,
22+
pub hive_usage_agent: Option<Arc<UsageAgent>>,
2123
}
2224

2325
impl RouterSharedState {
2426
pub fn new(
2527
router_config: Arc<HiveRouterConfig>,
2628
jwt_auth_runtime: Option<JwtAuthRuntime>,
29+
hive_usage_agent: Option<Arc<UsageAgent>>,
2730
) -> Result<Self, SharedStateError> {
2831
Ok(Self {
2932
validation_plan: graphql_tools::validation::rules::default_rules_validation_plan(),
@@ -36,6 +39,7 @@ impl RouterSharedState {
3639
)
3740
.map_err(Box::new)?,
3841
jwt_auth_runtime,
42+
hive_usage_agent,
3943
})
4044
}
4145
}
@@ -48,4 +52,6 @@ pub enum SharedStateError {
4852
CORSConfig(#[from] Box<CORSConfigError>),
4953
#[error("invalid override labels config: {0}")]
5054
OverrideLabelsCompile(#[from] Box<OverrideLabelsCompileError>),
55+
#[error("error creating hive usage agent: {0}")]
56+
UsageAgent(#[from] Box<hive_console_sdk::agent::AgentError>),
5157
}

0 commit comments

Comments
 (0)