Skip to content

Commit fc8fc37

Browse files
committed
feat: added open telemetry metrics
1 parent 13ca431 commit fc8fc37

7 files changed

Lines changed: 318 additions & 11 deletions

File tree

crates/taurus/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ version.workspace = true
44
edition.workspace = true
55

66
[dependencies]
7-
code0-flow = { workspace = true, features = ["flow_service", "flow_health", "flow_config"] }
7+
code0-flow = { workspace = true, features = ["flow_service", "flow_health", "flow_config", "flow_telemetry"] }
88
tucana = { workspace = true }
99
tokio = { workspace = true }
1010
log = { workspace = true }
11+
opentelemetry = { workspace = true }
1112
futures-lite ={ workspace = true }
1213
rand = { workspace = true }
1314
base64 = { workspace = true }
14-
env_logger = { workspace = true }
1515
async-nats = { workspace = true }
1616
prost = { workspace = true }
1717
tonic-health = { workspace = true }

crates/taurus/src/app/mod.rs

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
mod worker;
22

3+
use code0_flow::flow_config::environment::Environment;
34
use code0_flow::flow_config::load_env_file;
4-
use code0_flow::flow_config::mode::Mode::DYNAMIC;
5+
use code0_flow::flow_config::mode::Mode::{DYNAMIC, STATIC};
56
use code0_flow::flow_definition::Reader;
67
use code0_flow::flow_service::FlowUpdateService;
78
use std::sync::Arc;
@@ -18,12 +19,13 @@ use tucana::shared::module_status::StatusVariant;
1819
use crate::client::runtime_execution::TaurusRuntimeExecutionService;
1920
use crate::client::runtime_status::TaurusRuntimeStatusService;
2021
use crate::config::Config;
22+
use crate::telemetry::{self, TelemetrySettings};
2123

2224
pub async fn run() {
23-
init_logging();
2425
load_env_file();
2526

2627
let config = Config::new();
28+
let telemetry = init_telemetry(&config);
2729
let engine = ExecutionEngine::new();
2830
let client = connect_nats(&config).await;
2931

@@ -42,6 +44,7 @@ pub async fn run() {
4244
nats_remote,
4345
runtime_emitter,
4446
runtime_execution_service,
47+
mode_label(&config).to_string(),
4548
);
4649

4750
wait_for_shutdown(&mut worker_task, &mut health_task).await;
@@ -56,12 +59,36 @@ pub async fn run() {
5659
update_stopped_status(runtime_status_service.as_ref()).await;
5760

5861
log::info!("Taurus shutdown complete");
62+
telemetry.shutdown();
5963
}
6064

61-
fn init_logging() {
62-
env_logger::Builder::from_default_env()
63-
.filter_level(log::LevelFilter::Debug)
64-
.init();
65+
fn init_telemetry(config: &Config) -> telemetry::Telemetry {
66+
telemetry::Telemetry::initialize(
67+
&config.opentelemetry,
68+
TelemetrySettings {
69+
environment: environment_label(&config.environment),
70+
default_log_level: "debug",
71+
service_version: env!("CARGO_PKG_VERSION"),
72+
instrumentation_name: env!("CARGO_PKG_NAME"),
73+
initialize_metrics: Some(telemetry::metrics::initialize),
74+
},
75+
)
76+
.unwrap_or_else(|error| panic!("failed to initialize telemetry: {error}"))
77+
}
78+
79+
fn environment_label(environment: &Environment) -> &'static str {
80+
match environment {
81+
Environment::Development => "development",
82+
Environment::Staging => "staging",
83+
Environment::Production => "production",
84+
}
85+
}
86+
87+
fn mode_label(config: &Config) -> &'static str {
88+
match config.mode {
89+
STATIC => "static",
90+
DYNAMIC => "dynamic",
91+
}
6592
}
6693

6794
async fn connect_nats(config: &Config) -> async_nats::Client {

crates/taurus/src/app/worker.rs

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::time::Instant;
2-
31
use futures_lite::StreamExt;
42
use prost::Message;
53
use taurus_core::runtime::engine::{EmitType, ExecutionEngine, ExecutionId, RespondEmitter};
@@ -14,13 +12,15 @@ use tucana::shared::execution_result;
1412
use tucana::shared::{ExecutionFlow, ExecutionResult, NodeExecutionResult, Value};
1513

1614
use crate::client::runtime_execution::TaurusRuntimeExecutionService;
15+
use crate::telemetry::metrics;
1716

1817
pub fn spawn_worker(
1918
client: async_nats::Client,
2019
engine: ExecutionEngine,
2120
nats_remote: NATSRemoteRuntime,
2221
runtime_emitter: NATSRespondEmitter,
2322
mut runtime_execution_service: Option<TaurusRuntimeExecutionService>,
23+
flow_type: String,
2424
) -> JoinHandle<()> {
2525
tokio::spawn(async move {
2626
let mut execution_subscription = match client
@@ -50,6 +50,7 @@ pub fn spawn_worker(
5050
&nats_remote,
5151
&runtime_emitter,
5252
runtime_execution_service.as_mut(),
53+
flow_type.as_str(),
5354
).await;
5455
}
5556
None => {
@@ -71,6 +72,7 @@ async fn process_execution_message(
7172
nats_remote: &NATSRemoteRuntime,
7273
runtime_emitter: &NATSRespondEmitter,
7374
mut runtime_execution_service: Option<&mut TaurusRuntimeExecutionService>,
75+
flow_type: &str,
7476
) {
7577
let requested_execution_id = parse_execution_id_from_subject(&message.subject, "execution")
7678
.unwrap_or_else(|| {
@@ -101,6 +103,7 @@ async fn process_execution_message(
101103
};
102104

103105
let flow_id = flow.flow_id;
106+
let function_identifiers = function_identifiers_by_node_id(&flow);
104107
// Taurus app forwards all lifecycle events to emitter.
105108
// Direct request/reply responses remain disabled; delivery is emitter-only.
106109
let respond_emitter = |execution_id, emit_type: EmitType, value: Value| {
@@ -112,6 +115,8 @@ async fn process_execution_message(
112115
engine,
113116
Some(nats_remote),
114117
Some(&respond_emitter),
118+
flow_type,
119+
function_identifiers,
115120
)
116121
.await;
117122
log::debug!(
@@ -153,9 +158,12 @@ async fn execute_flow(
153158
engine: &ExecutionEngine,
154159
remote: Option<&dyn RemoteRuntime>,
155160
respond_emitter: Option<&dyn RespondEmitter>,
161+
flow_type: &str,
162+
function_identifiers: std::collections::HashMap<i64, String>,
156163
) -> FlowRunResult {
157164
let started_at = now_unix_micros();
158165
let flow_id = flow.flow_id;
166+
let project_id = flow.project_id;
159167
let input = flow.input_value.clone();
160168
let report = engine
161169
.execute_flow_with_execution_id_report_async(
@@ -167,6 +175,16 @@ async fn execute_flow(
167175
)
168176
.await;
169177
let finished_at = now_unix_micros();
178+
record_flow_metrics(
179+
flow_id,
180+
project_id,
181+
flow_type,
182+
started_at,
183+
finished_at,
184+
&report.signal,
185+
&report.node_execution_results,
186+
&function_identifiers,
187+
);
170188

171189
FlowRunResult {
172190
execution_id,
@@ -179,6 +197,67 @@ async fn execute_flow(
179197
}
180198
}
181199

200+
fn function_identifiers_by_node_id(flow: &ExecutionFlow) -> std::collections::HashMap<i64, String> {
201+
flow.node_functions
202+
.iter()
203+
.filter_map(|function| {
204+
function
205+
.database_id
206+
.map(|id| (id, function.runtime_function_id.clone()))
207+
})
208+
.collect()
209+
}
210+
211+
fn record_flow_metrics(
212+
flow_id: i64,
213+
project_id: i64,
214+
flow_type: &str,
215+
started_at: i64,
216+
finished_at: i64,
217+
signal: &Signal,
218+
node_execution_results: &[NodeExecutionResult],
219+
function_identifiers: &std::collections::HashMap<i64, String>,
220+
) {
221+
metrics::flow_execution(metrics::FlowExecution {
222+
flow_id,
223+
project_id,
224+
flow_type,
225+
outcome: signal_outcome(signal),
226+
duration_seconds: metrics::duration_seconds(started_at, finished_at),
227+
});
228+
229+
for result in node_execution_results {
230+
let node_id = metrics::result_node_id(result);
231+
let function_identifier = metrics::result_function_identifier(result)
232+
.map(ToOwned::to_owned)
233+
.or_else(|| node_id.and_then(|id| function_identifiers.get(&id).cloned()))
234+
.unwrap_or_else(|| "unknown".to_string());
235+
let (error_code, error_category) = metrics::node_result_error(result);
236+
237+
metrics::function_execution(metrics::FunctionExecution {
238+
flow_id,
239+
project_id,
240+
flow_type,
241+
function_identifier: function_identifier.as_str(),
242+
node_id,
243+
outcome: metrics::node_result_outcome(result),
244+
duration_seconds: metrics::duration_seconds(result.started_at, result.finished_at),
245+
error_code,
246+
error_category,
247+
});
248+
}
249+
}
250+
251+
fn signal_outcome(signal: &Signal) -> &'static str {
252+
match signal {
253+
Signal::Success(_) => "success",
254+
Signal::Failure(_) => "failure",
255+
Signal::Return(_) => "return",
256+
Signal::Respond(_) => "respond",
257+
Signal::Stop => "stop",
258+
}
259+
}
260+
182261
fn parse_execution_id_from_subject(
183262
subject: &async_nats::Subject,
184263
prefix: &str,
@@ -327,7 +406,17 @@ mod tests {
327406
let flow = execution_flow_from_fixture(fixture);
328407
let engine = ExecutionEngine::new();
329408

330-
let run_result = execute_flow(execution_id, flow, &engine, None, None).await;
409+
let function_identifiers = function_identifiers_by_node_id(&flow);
410+
let run_result = execute_flow(
411+
execution_id,
412+
flow,
413+
&engine,
414+
None,
415+
None,
416+
"test",
417+
function_identifiers,
418+
)
419+
.await;
331420

332421
println!(
333422
"started_at={} finished_at={} delta={}",

crates/taurus/src/config/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use code0_flow::flow_config::env_with_default;
22
use code0_flow::flow_config::environment::Environment;
33
use code0_flow::flow_config::mode::Mode;
44

5+
use crate::telemetry::OpenTelemetry;
6+
57
/// Struct for all relevant `Taurus` startup configurations
68
pub struct Config {
79
pub environment: Environment,
@@ -39,6 +41,9 @@ pub struct Config {
3941

4042
/// Timeout in seconds for remote runtime NATS flush and response waits.
4143
pub remote_runtime_timeout_secs: u64,
44+
45+
/// OpenTelemetry exporter configuration.
46+
pub opentelemetry: OpenTelemetry,
4247
}
4348

4449
/// Implementation for all relevant `Taurus` startup configurations
@@ -70,6 +75,23 @@ impl Config {
7075
10_u64,
7176
),
7277
remote_runtime_timeout_secs: env_with_default("REMOTE_RUNTIME_TIMEOUT_SECS", 30_u64),
78+
opentelemetry: OpenTelemetry {
79+
enabled: env_with_default("OPENTELEMETRY_ENABLED", false),
80+
service_name: env_with_default(
81+
"OPENTELEMETRY_SERVICE_NAME",
82+
env!("CARGO_PKG_NAME").to_string(),
83+
),
84+
logs_endpoint: optional_env("OPENTELEMETRY_LOGS_ENDPOINT"),
85+
metrics_endpoint: optional_env("OPENTELEMETRY_METRICS_ENDPOINT"),
86+
traces_endpoint: optional_env("OPENTELEMETRY_TRACES_ENDPOINT"),
87+
},
7388
}
7489
}
7590
}
91+
92+
fn optional_env(key: &str) -> Option<String> {
93+
std::env::var(key)
94+
.ok()
95+
.map(|value| value.trim().to_owned())
96+
.filter(|value| !value.is_empty())
97+
}

crates/taurus/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod app;
22
mod client;
33
mod config;
4+
mod telemetry;
45

56
#[tokio::main]
67
async fn main() {

0 commit comments

Comments
 (0)