Skip to content

Commit 9dbd596

Browse files
committed
Add live debugger sender FFI
Signed-off-by: Bob Weinand <[email protected]>
1 parent dd7c3c4 commit 9dbd596

File tree

5 files changed

+414
-271
lines changed

5 files changed

+414
-271
lines changed

Cargo.lock

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

live-debugger-ffi/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,9 @@ crate-type = ["lib", "staticlib", "cdylib"]
1111

1212
[dependencies]
1313
datadog-live-debugger = { path = "../live-debugger" }
14+
ddcommon = { path = "../ddcommon" }
1415
ddcommon-ffi = { path = "../ddcommon-ffi" }
1516
uuid = { version = "1.7.0", features = ["v4"] }
17+
serde_json = "1.0"
18+
tokio = "1.36.0"
19+
log = "0.4.21"

live-debugger-ffi/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
pub mod data;
55
pub mod evaluator;
66
pub mod parse;
7+
pub mod send_data;
78
pub mod sender;

live-debugger-ffi/src/send_data.rs

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use ddcommon_ffi::CharSlice;
5+
use std::borrow::Cow;
6+
use std::collections::hash_map;
7+
use std::mem::transmute;
8+
// Alias to prevent cbindgen panic
9+
use crate::data::Probe;
10+
use datadog_live_debugger::debugger_defs::{
11+
Capture as DebuggerCaptureAlias, Capture, Captures, DebuggerData, DebuggerPayload, Entry,
12+
Fields, Snapshot, SnapshotEvaluationError, Value as DebuggerValueAlias,
13+
};
14+
use datadog_live_debugger::sender::generate_new_id;
15+
use ddcommon_ffi::slice::AsBytes;
16+
17+
#[repr(C)]
18+
pub enum FieldType {
19+
STATIC,
20+
ARG,
21+
LOCAL,
22+
}
23+
24+
#[repr(C)]
25+
pub struct CaptureValue<'a> {
26+
pub r#type: CharSlice<'a>,
27+
pub value: CharSlice<'a>,
28+
pub fields: Option<Box<Fields<'a>>>,
29+
pub elements: Vec<DebuggerValue<'a>>,
30+
pub entries: Vec<Entry<'a>>,
31+
pub is_null: bool,
32+
pub truncated: bool,
33+
pub not_captured_reason: CharSlice<'a>,
34+
pub size: CharSlice<'a>,
35+
}
36+
37+
impl<'a> From<CaptureValue<'a>> for DebuggerValueAlias<'a> {
38+
fn from(val: CaptureValue<'a>) -> Self {
39+
DebuggerValueAlias {
40+
r#type: val.r#type.to_utf8_lossy(),
41+
value: if val.value.len() == 0 {
42+
None
43+
} else {
44+
Some(val.value.to_utf8_lossy())
45+
},
46+
fields: if let Some(boxed) = val.fields {
47+
*boxed
48+
} else {
49+
Fields::default()
50+
},
51+
elements: unsafe { transmute(val.elements) }, // SAFETY: is transparent
52+
entries: val.entries,
53+
is_null: val.is_null,
54+
truncated: val.truncated,
55+
not_captured_reason: if val.not_captured_reason.len() == 0 {
56+
None
57+
} else {
58+
Some(val.not_captured_reason.to_utf8_lossy())
59+
},
60+
size: if val.size.len() == 0 {
61+
None
62+
} else {
63+
Some(val.size.to_utf8_lossy())
64+
},
65+
}
66+
}
67+
}
68+
69+
/// cbindgen:no-export
70+
#[repr(transparent)]
71+
pub struct DebuggerValue<'a>(DebuggerValueAlias<'a>);
72+
/// cbindgen:no-export
73+
#[repr(transparent)]
74+
pub struct DebuggerCapture<'a>(DebuggerCaptureAlias<'a>);
75+
76+
#[repr(C)]
77+
pub struct ExceptionSnapshot<'a> {
78+
pub data: *mut DebuggerPayload<'a>,
79+
pub capture: *mut DebuggerCapture<'a>,
80+
}
81+
82+
#[no_mangle]
83+
pub extern "C" fn ddog_create_exception_snapshot<'a>(
84+
buffer: &mut Vec<DebuggerPayload<'a>>,
85+
service: CharSlice<'a>,
86+
language: CharSlice<'a>,
87+
id: CharSlice<'a>,
88+
exception_id: CharSlice<'a>,
89+
timestamp: u64,
90+
) -> *mut DebuggerCapture<'a> {
91+
let snapshot = DebuggerPayload {
92+
service: service.to_utf8_lossy(),
93+
source: "dd_debugger",
94+
timestamp,
95+
message: None,
96+
debugger: DebuggerData {
97+
snapshot: Snapshot {
98+
captures: Some(Captures {
99+
r#return: Some(Capture::default()),
100+
..Default::default()
101+
}),
102+
language: language.to_utf8_lossy(),
103+
id: id.to_utf8_lossy(),
104+
exception_id: Some(exception_id.to_utf8_lossy()),
105+
timestamp,
106+
..Default::default()
107+
},
108+
},
109+
};
110+
buffer.push(snapshot);
111+
unsafe {
112+
let captures = buffer
113+
.last_mut()
114+
.unwrap()
115+
.debugger
116+
.snapshot
117+
.captures
118+
.as_mut();
119+
transmute(captures.unwrap().r#return.as_mut().unwrap())
120+
}
121+
}
122+
123+
#[no_mangle]
124+
pub extern "C" fn ddog_create_log_probe_snapshot<'a>(
125+
probe: &'a Probe,
126+
message: Option<&CharSlice<'a>>,
127+
service: CharSlice<'a>,
128+
language: CharSlice<'a>,
129+
timestamp: u64,
130+
) -> Box<DebuggerPayload<'a>> {
131+
Box::new(DebuggerPayload {
132+
service: service.to_utf8_lossy(),
133+
source: "dd_debugger",
134+
timestamp,
135+
message: message.map(|m| m.to_utf8_lossy()),
136+
debugger: DebuggerData {
137+
snapshot: Snapshot {
138+
captures: Some(Captures {
139+
..Default::default()
140+
}),
141+
language: language.to_utf8_lossy(),
142+
id: Cow::Owned(generate_new_id().as_hyphenated().to_string()),
143+
probe: Some(probe.into()),
144+
timestamp,
145+
..Default::default()
146+
},
147+
},
148+
})
149+
}
150+
151+
#[no_mangle]
152+
pub extern "C" fn ddog_update_payload_message<'a>(
153+
payload: &mut DebuggerPayload<'a>,
154+
message: CharSlice<'a>,
155+
) {
156+
payload.message = Some(message.to_utf8_lossy());
157+
}
158+
159+
#[no_mangle]
160+
pub unsafe extern "C" fn ddog_snapshot_entry<'a>(
161+
payload: &mut DebuggerPayload<'a>,
162+
) -> *mut DebuggerCapture<'a> {
163+
transmute(
164+
payload
165+
.debugger
166+
.snapshot
167+
.captures
168+
.as_mut()
169+
.unwrap()
170+
.entry
171+
.insert(Capture::default()),
172+
)
173+
}
174+
175+
#[no_mangle]
176+
pub unsafe extern "C" fn ddog_snapshot_lines<'a>(
177+
payload: &mut DebuggerPayload<'a>,
178+
line: u32,
179+
) -> *mut DebuggerCapture<'a> {
180+
transmute(
181+
match payload
182+
.debugger
183+
.snapshot
184+
.captures
185+
.as_mut()
186+
.unwrap()
187+
.lines
188+
.entry(line)
189+
{
190+
hash_map::Entry::Occupied(e) => e.into_mut(),
191+
hash_map::Entry::Vacant(e) => e.insert(Capture::default()),
192+
},
193+
)
194+
}
195+
196+
#[no_mangle]
197+
pub unsafe extern "C" fn ddog_snapshot_exit<'a>(
198+
payload: &mut DebuggerPayload<'a>,
199+
) -> *mut DebuggerCapture<'a> {
200+
transmute(
201+
payload
202+
.debugger
203+
.snapshot
204+
.captures
205+
.as_mut()
206+
.unwrap()
207+
.r#return
208+
.as_mut()
209+
.unwrap(),
210+
)
211+
}
212+
213+
#[no_mangle]
214+
#[allow(improper_ctypes_definitions)] // Vec has a fixed size, and we care only about that here
215+
pub extern "C" fn ddog_snapshot_add_field<'a, 'b: 'a, 'c: 'a>(
216+
capture: &mut DebuggerCapture<'a>,
217+
r#type: FieldType,
218+
name: CharSlice<'b>,
219+
value: CaptureValue<'c>,
220+
) {
221+
let fields = match r#type {
222+
FieldType::STATIC => &mut capture.0.static_fields,
223+
FieldType::ARG => &mut capture.0.arguments,
224+
FieldType::LOCAL => &mut capture.0.locals,
225+
};
226+
fields.insert(name.to_utf8_lossy(), value.into());
227+
}
228+
229+
#[no_mangle]
230+
#[allow(improper_ctypes_definitions)] // Vec has a fixed size, and we care only about that here
231+
pub extern "C" fn ddog_capture_value_add_element<'a, 'b: 'a>(
232+
value: &mut CaptureValue<'a>,
233+
element: CaptureValue<'b>,
234+
) {
235+
value.elements.push(DebuggerValue(element.into()));
236+
}
237+
238+
#[no_mangle]
239+
#[allow(improper_ctypes_definitions)] // Vec has a fixed size, and we care only about that here
240+
pub extern "C" fn ddog_capture_value_add_entry<'a, 'b: 'a, 'c: 'a>(
241+
value: &mut CaptureValue<'a>,
242+
key: CaptureValue<'b>,
243+
element: CaptureValue<'c>,
244+
) {
245+
value.entries.push(Entry(key.into(), element.into()));
246+
}
247+
248+
#[no_mangle]
249+
#[allow(improper_ctypes_definitions)] // Vec has a fixed size, and we care only about that here
250+
pub extern "C" fn ddog_capture_value_add_field<'a, 'b: 'a, 'c: 'a>(
251+
value: &mut CaptureValue<'a>,
252+
key: CharSlice<'b>,
253+
element: CaptureValue<'c>,
254+
) {
255+
let fields = match value.fields {
256+
None => {
257+
value.fields = Some(Box::default());
258+
value.fields.as_mut().unwrap()
259+
}
260+
Some(ref mut f) => f,
261+
};
262+
fields.insert(key.to_utf8_lossy(), element.into());
263+
}
264+
265+
#[no_mangle]
266+
pub extern "C" fn ddog_snapshot_format_new_uuid(buf: &mut [u8; 36]) {
267+
generate_new_id().as_hyphenated().encode_lower(buf);
268+
}
269+
270+
#[no_mangle]
271+
pub extern "C" fn ddog_evaluation_error_first_msg(vec: &Vec<SnapshotEvaluationError>) -> CharSlice {
272+
CharSlice::from(vec[0].message.as_str())
273+
}
274+
275+
#[no_mangle]
276+
pub extern "C" fn ddog_evaluation_error_drop(_: Box<Vec<SnapshotEvaluationError>>) {}
277+
278+
#[no_mangle]
279+
pub extern "C" fn ddog_evaluation_error_snapshot<'a>(
280+
probe: &'a Probe,
281+
service: CharSlice<'a>,
282+
language: CharSlice<'a>,
283+
errors: Box<Vec<SnapshotEvaluationError>>,
284+
timestamp: u64,
285+
) -> Box<DebuggerPayload<'a>> {
286+
Box::new(DebuggerPayload {
287+
service: service.to_utf8_lossy(),
288+
source: "dd_debugger",
289+
timestamp,
290+
message: Some(Cow::Owned(format!(
291+
"Evaluation errors for probe id {}",
292+
probe.id
293+
))),
294+
debugger: DebuggerData {
295+
snapshot: Snapshot {
296+
language: language.to_utf8_lossy(),
297+
id: Cow::Owned(generate_new_id().as_hyphenated().to_string()),
298+
probe: Some(probe.into()),
299+
timestamp,
300+
evaluation_errors: *errors,
301+
..Default::default()
302+
},
303+
},
304+
})
305+
}
306+
307+
pub fn serialize_debugger_payload(payload: &DebuggerPayload) -> String {
308+
serde_json::to_string(payload).unwrap()
309+
}
310+
311+
#[no_mangle]
312+
pub extern "C" fn ddog_serialize_debugger_payload(
313+
payload: &DebuggerPayload,
314+
callback: extern "C" fn(CharSlice),
315+
) {
316+
let payload = serialize_debugger_payload(payload);
317+
callback(CharSlice::from(payload.as_str()))
318+
}
319+
320+
#[no_mangle]
321+
pub extern "C" fn ddog_drop_debugger_payload(_: Box<DebuggerPayload>) {}

0 commit comments

Comments
 (0)