Skip to content

Commit 08ce2e2

Browse files
authored
feat(tracing): add query tracing for the session in the executeWithTracing method (#43)
closes #42 Signed-off-by: Daniel Boll <danielboll.academico@gmail.com>
1 parent a5d1252 commit 08ce2e2

File tree

8 files changed

+237
-6
lines changed

8 files changed

+237
-6
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ scylla = { version = "0.13.1", features = [
2424
] }
2525
uuid = { version = "1.4.1", features = ["serde", "v4", "fast-rng"] }
2626
serde_json = "1.0"
27+
serde = { version = "1.0", features = ["derive"] }
2728
openssl = { version = "0.10", features = ["vendored"] }
2829

2930
[build-dependencies]
3031
napi-build = "2.0.1"
3132

3233
[profile.release]
33-
lto = true
34+
lto = true

examples/tracing.mts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Cluster } from "../index.js";
2+
3+
const nodes = process.env.CLUSTER_NODES?.split(",") ?? ["127.0.0.1:9042"];
4+
5+
console.log(`Connecting to ${nodes}`);
6+
7+
const cluster = new Cluster({ nodes });
8+
const session = await cluster.connect();
9+
10+
const { tracing } = await session.executeWithTracing(
11+
"SELECT * FROM system_schema.scylla_tables",
12+
[],
13+
// {
14+
// prepare: true,
15+
// },
16+
);
17+
18+
console.log(tracing);

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ export class Metrics {
157157
export class ScyllaSession {
158158
metrics(): Metrics
159159
getClusterData(): Promise<ScyllaClusterData>
160+
executeWithTracing(query: string | Query | PreparedStatement, parameters?: Array<number | string | Uuid | Record<string, number | string | Uuid>> | undefined | null, options?: QueryOptions | undefined | null): Promise<any>
160161
/**
161162
* Sends a query to the database and receives a response.\
162163
* Returns only a single page of results, to receive multiple pages use (TODO: Not implemented yet)

rust-toolchain.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[toolchain]
2+
channel = "nightly"

src/session/scylla_session.rs

Lines changed: 133 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ use crate::query::batch_statement::ScyllaBatchStatement;
66
use crate::query::scylla_prepared_statement::PreparedStatement;
77
use crate::query::scylla_query::Query;
88
use crate::types::uuid::Uuid;
9-
use napi::bindgen_prelude::{Either3, Either4};
109
use napi::Either;
10+
use napi::bindgen_prelude::{Either3, Either4};
11+
use serde_json::json;
12+
13+
use scylla::statement::query::Query as ScyllaQuery;
1114

1215
use super::metrics;
1316
use super::topology::ScyllaClusterData;
@@ -45,6 +48,64 @@ impl ScyllaSession {
4548
cluster_data.into()
4649
}
4750

51+
#[allow(clippy::type_complexity)]
52+
#[napi]
53+
pub async fn execute_with_tracing(
54+
&self,
55+
query: Either3<String, &Query, &PreparedStatement>,
56+
parameters: Option<
57+
Vec<Either4<u32, String, &Uuid, HashMap<String, Either3<u32, String, &Uuid>>>>,
58+
>,
59+
options: Option<QueryOptions>,
60+
) -> napi::Result<serde_json::Value> {
61+
let values = QueryParameter::parser(parameters.clone()).ok_or_else(|| {
62+
napi::Error::new(
63+
napi::Status::InvalidArg,
64+
format!(
65+
"Something went wrong with your query parameters. {:?}",
66+
parameters
67+
),
68+
)
69+
})?;
70+
71+
let should_prepare = options.map_or(false, |options| options.prepare.unwrap_or(false));
72+
73+
match query {
74+
Either3::A(ref query_str) if should_prepare => {
75+
let mut prepared = self.session.prepare(query_str.clone()).await.map_err(|e| {
76+
napi::Error::new(
77+
napi::Status::InvalidArg,
78+
format!(
79+
"Something went wrong preparing your statement. - [{}]\n{}",
80+
query_str, e
81+
),
82+
)
83+
})?;
84+
prepared.set_tracing(true);
85+
self.execute_prepared(&prepared, values, query_str).await
86+
}
87+
Either3::A(query_str) => {
88+
let mut query = ScyllaQuery::new(query_str);
89+
query.set_tracing(true);
90+
self.execute_query(Either::B(query), values).await
91+
}
92+
Either3::B(query_ref) => {
93+
let mut query = query_ref.query.clone();
94+
query.set_tracing(true);
95+
96+
self.execute_query(Either::B(query), values).await
97+
}
98+
Either3::C(prepared_ref) => {
99+
let mut prepared = prepared_ref.prepared.clone();
100+
prepared.set_tracing(true);
101+
102+
self
103+
.execute_prepared(&prepared, values, prepared_ref.prepared.get_statement())
104+
.await
105+
}
106+
}
107+
}
108+
48109
/// Sends a query to the database and receives a response.\
49110
/// Returns only a single page of results, to receive multiple pages use (TODO: Not implemented yet)
50111
///
@@ -110,6 +171,18 @@ impl ScyllaSession {
110171
.await
111172
}
112173
}
174+
.map_err(|e| {
175+
napi::Error::new(
176+
napi::Status::InvalidArg,
177+
format!("Something went wrong with your query. - \n{}", e), // TODO: handle different queries here
178+
)
179+
})?
180+
.get("result")
181+
.cloned()
182+
.ok_or(napi::Error::new(
183+
napi::Status::InvalidArg,
184+
r#"Something went wrong with your query."#.to_string(), // TODO: handle different queries here
185+
))
113186
}
114187

115188
// Helper method to handle prepared statements
@@ -128,7 +201,36 @@ impl ScyllaSession {
128201
),
129202
)
130203
})?;
131-
Ok(QueryResult::parser(query_result))
204+
205+
let tracing = if let Some(tracing_id) = query_result.tracing_id {
206+
Some(crate::types::tracing::TracingInfo::from(
207+
self
208+
.session
209+
.get_tracing_info(&tracing_id)
210+
.await
211+
.map_err(|e| {
212+
napi::Error::new(
213+
napi::Status::InvalidArg,
214+
format!(
215+
"Something went wrong with your tracing info. - [{}]\n{}",
216+
query, e
217+
),
218+
)
219+
})?,
220+
))
221+
} else {
222+
None
223+
};
224+
225+
let result = QueryResult::parser(query_result);
226+
227+
dbg!(result.clone());
228+
dbg!(tracing.clone());
229+
230+
Ok(json!({
231+
"result": result,
232+
"tracing": tracing
233+
}))
132234
}
133235

134236
// Helper method to handle direct queries
@@ -142,7 +244,7 @@ impl ScyllaSession {
142244
Either::B(query_ref) => self.session.query(query_ref.clone(), values).await,
143245
}
144246
.map_err(|e| {
145-
let query_str = match query {
247+
let query_str = match query.clone() {
146248
Either::A(query_str) => query_str,
147249
Either::B(query_ref) => query_ref.contents.clone(),
148250
};
@@ -155,7 +257,34 @@ impl ScyllaSession {
155257
)
156258
})?;
157259

158-
Ok(QueryResult::parser(query_result))
260+
let tracing_info = if let Some(tracing_id) = query_result.tracing_id {
261+
Some(crate::types::tracing::TracingInfo::from(
262+
self
263+
.session
264+
.get_tracing_info(&tracing_id)
265+
.await
266+
.map_err(|e| {
267+
napi::Error::new(
268+
napi::Status::InvalidArg,
269+
format!(
270+
"Something went wrong with your tracing info. - [{}]\n{}",
271+
match query {
272+
Either::A(query_str) => query_str,
273+
Either::B(query_ref) => query_ref.contents.clone(),
274+
},
275+
e
276+
),
277+
)
278+
})?,
279+
))
280+
} else {
281+
None
282+
};
283+
284+
Ok(json!({
285+
"result": QueryResult::parser(query_result),
286+
"tracing": tracing_info
287+
}))
159288
}
160289

161290
#[allow(clippy::type_complexity)]

src/types/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
pub mod tracing;
12
pub mod uuid;

src/types/tracing.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use std::{collections::HashMap, net::IpAddr};
2+
3+
use serde::Serialize;
4+
5+
#[derive(Debug, Clone, PartialEq, Eq)]
6+
pub struct CqlTimestampWrapper(pub scylla::frame::value::CqlTimestamp);
7+
#[derive(Debug, Clone, PartialEq, Eq)]
8+
pub struct CqlTimeuuidWrapper(pub scylla::frame::value::CqlTimeuuid);
9+
10+
impl Serialize for CqlTimestampWrapper {
11+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
12+
where
13+
S: serde::Serializer,
14+
{
15+
serializer.serialize_i64(self.0.0)
16+
}
17+
}
18+
19+
impl Serialize for CqlTimeuuidWrapper {
20+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
21+
where
22+
S: serde::Serializer,
23+
{
24+
serializer.serialize_str(format!("{}", self.0).as_str())
25+
}
26+
}
27+
28+
/// Tracing info retrieved from `system_traces.sessions`
29+
/// with all events from `system_traces.events`
30+
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
31+
pub struct TracingInfo {
32+
pub client: Option<IpAddr>,
33+
pub command: Option<String>,
34+
pub coordinator: Option<IpAddr>,
35+
pub duration: Option<i32>,
36+
pub parameters: Option<HashMap<String, String>>,
37+
pub request: Option<String>,
38+
/// started_at is a timestamp - time since unix epoch
39+
pub started_at: Option<CqlTimestampWrapper>,
40+
41+
pub events: Vec<TracingEvent>,
42+
}
43+
44+
/// A single event happening during a traced query
45+
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
46+
pub struct TracingEvent {
47+
pub event_id: CqlTimeuuidWrapper,
48+
pub activity: Option<String>,
49+
pub source: Option<IpAddr>,
50+
pub source_elapsed: Option<i32>,
51+
pub thread: Option<String>,
52+
}
53+
54+
impl From<scylla::tracing::TracingInfo> for TracingInfo {
55+
fn from(info: scylla::tracing::TracingInfo) -> Self {
56+
Self {
57+
client: info.client,
58+
command: info.command,
59+
coordinator: info.coordinator,
60+
duration: info.duration,
61+
parameters: info.parameters,
62+
request: info.request,
63+
started_at: info.started_at.map(CqlTimestampWrapper),
64+
events: info.events.into_iter().map(TracingEvent::from).collect(),
65+
}
66+
}
67+
}
68+
69+
impl From<scylla::tracing::TracingEvent> for TracingEvent {
70+
fn from(event: scylla::tracing::TracingEvent) -> Self {
71+
Self {
72+
event_id: CqlTimeuuidWrapper(event.event_id),
73+
activity: event.activity,
74+
source: event.source,
75+
source_elapsed: event.source_elapsed,
76+
thread: event.thread,
77+
}
78+
}
79+
}

src/types/uuid.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use napi::Result;
22

33
#[napi()]
4-
#[derive(Debug, Clone, Copy)]
4+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55
pub struct Uuid {
66
pub(crate) uuid: uuid::Uuid,
77
}

0 commit comments

Comments
 (0)