1use serde::{Deserialize, Serialize};
33
34pub use crate::uniffi_types::{
36 BenchErrorVariant, BenchReportTemplate, BenchSampleTemplate, BenchSpecTemplate, FromSdkError,
37 FromSdkReport, FromSdkSample, FromSdkSpec,
38};
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct BenchSpecFfi {
45 pub name: String,
47 pub iterations: u32,
49 pub warmup: u32,
51}
52
53impl From<crate::BenchSpec> for BenchSpecFfi {
54 fn from(spec: crate::BenchSpec) -> Self {
55 Self {
56 name: spec.name,
57 iterations: spec.iterations,
58 warmup: spec.warmup,
59 }
60 }
61}
62
63impl From<BenchSpecFfi> for crate::BenchSpec {
64 fn from(spec: BenchSpecFfi) -> Self {
65 Self {
66 name: spec.name,
67 iterations: spec.iterations,
68 warmup: spec.warmup,
69 }
70 }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct BenchSampleFfi {
76 pub duration_ns: u64,
78 pub cpu_time_ms: Option<u64>,
80 pub peak_memory_kb: Option<u64>,
85 pub process_peak_memory_kb: Option<u64>,
87}
88
89impl From<crate::BenchSample> for BenchSampleFfi {
90 fn from(sample: crate::BenchSample) -> Self {
91 Self {
92 duration_ns: sample.duration_ns,
93 cpu_time_ms: sample.cpu_time_ms,
94 peak_memory_kb: sample.peak_memory_kb,
95 process_peak_memory_kb: sample.process_peak_memory_kb,
96 }
97 }
98}
99
100impl From<BenchSampleFfi> for crate::BenchSample {
101 fn from(sample: BenchSampleFfi) -> Self {
102 Self {
103 duration_ns: sample.duration_ns,
104 cpu_time_ms: sample.cpu_time_ms,
105 peak_memory_kb: sample.peak_memory_kb,
106 process_peak_memory_kb: sample.process_peak_memory_kb,
107 }
108 }
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct BenchReportFfi {
114 pub spec: BenchSpecFfi,
116 pub samples: Vec<BenchSampleFfi>,
118 pub phases: Vec<SemanticPhaseFfi>,
120 pub timeline: Vec<HarnessTimelineSpanFfi>,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct SemanticPhaseFfi {
127 pub name: String,
128 pub duration_ns: u64,
129}
130
131impl From<crate::SemanticPhase> for SemanticPhaseFfi {
132 fn from(phase: crate::SemanticPhase) -> Self {
133 Self {
134 name: phase.name,
135 duration_ns: phase.duration_ns,
136 }
137 }
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct HarnessTimelineSpanFfi {
143 pub phase: String,
144 pub start_offset_ns: u64,
145 pub end_offset_ns: u64,
146 pub iteration: Option<u32>,
147}
148
149impl From<crate::HarnessTimelineSpan> for HarnessTimelineSpanFfi {
150 fn from(span: crate::HarnessTimelineSpan) -> Self {
151 Self {
152 phase: span.phase,
153 start_offset_ns: span.start_offset_ns,
154 end_offset_ns: span.end_offset_ns,
155 iteration: span.iteration,
156 }
157 }
158}
159
160impl From<crate::RunnerReport> for BenchReportFfi {
161 fn from(report: crate::RunnerReport) -> Self {
162 Self {
163 spec: report.spec.into(),
164 samples: report.samples.into_iter().map(Into::into).collect(),
165 phases: report.phases.into_iter().map(Into::into).collect(),
166 timeline: report.timeline.into_iter().map(Into::into).collect(),
167 }
168 }
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
173pub enum BenchErrorFfi {
174 InvalidIterations,
176 UnknownFunction { name: String },
178 ExecutionFailed { reason: String },
180 ConfigError { message: String },
182 IoError { message: String },
184}
185
186impl From<crate::types::BenchError> for BenchErrorFfi {
187 fn from(err: crate::types::BenchError) -> Self {
188 match err {
189 crate::types::BenchError::Runner(runner_err) => match runner_err {
190 crate::timing::TimingError::NoIterations { .. } => BenchErrorFfi::InvalidIterations,
191 crate::timing::TimingError::Execution(msg) => {
192 BenchErrorFfi::ExecutionFailed { reason: msg }
193 }
194 },
195 crate::types::BenchError::UnknownFunction(name, _) => {
196 BenchErrorFfi::UnknownFunction { name }
197 }
198 crate::types::BenchError::Execution(msg) => {
199 BenchErrorFfi::ExecutionFailed { reason: msg }
200 }
201 crate::types::BenchError::Io(e) => BenchErrorFfi::IoError {
202 message: e.to_string(),
203 },
204 crate::types::BenchError::Serialization(e) => BenchErrorFfi::ConfigError {
205 message: e.to_string(),
206 },
207 crate::types::BenchError::Config(msg) => BenchErrorFfi::ConfigError { message: msg },
208 crate::types::BenchError::Build(msg) => BenchErrorFfi::ExecutionFailed {
209 reason: format!("build error: {}", msg),
210 },
211 }
212 }
213}
214
215pub trait IntoFfi<T> {
217 fn into_ffi(self) -> T;
219}
220
221pub trait FromFfi<T> {
223 fn from_ffi(ffi: T) -> Self;
225}
226
227impl<T, U> IntoFfi<U> for T
229where
230 U: From<T>,
231{
232 fn into_ffi(self) -> U {
233 U::from(self)
234 }
235}
236
237impl<T, U> FromFfi<U> for T
238where
239 T: From<U>,
240{
241 fn from_ffi(ffi: U) -> Self {
242 T::from(ffi)
243 }
244}
245
246#[cfg(feature = "registry")]
250pub fn run_benchmark_ffi(spec: BenchSpecFfi) -> Result<BenchReportFfi, BenchErrorFfi> {
251 let sdk_spec: crate::BenchSpec = spec.into();
252 crate::run_benchmark(sdk_spec)
253 .map(Into::into)
254 .map_err(Into::into)
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn test_bench_spec_ffi_conversion() {
263 let sdk_spec = crate::BenchSpec {
264 name: "test".to_string(),
265 iterations: 100,
266 warmup: 10,
267 };
268
269 let ffi: BenchSpecFfi = sdk_spec.clone().into();
270 assert_eq!(ffi.name, "test");
271 assert_eq!(ffi.iterations, 100);
272 assert_eq!(ffi.warmup, 10);
273
274 let back: crate::BenchSpec = ffi.into();
275 assert_eq!(back.name, sdk_spec.name);
276 }
277
278 #[test]
279 fn test_bench_sample_ffi_conversion() {
280 let sdk_sample = crate::BenchSample {
281 duration_ns: 12345,
282 cpu_time_ms: Some(12),
283 peak_memory_kb: Some(48),
284 process_peak_memory_kb: Some(1024),
285 };
286 let ffi: BenchSampleFfi = sdk_sample.into();
287 assert_eq!(ffi.duration_ns, 12345);
288 assert_eq!(ffi.cpu_time_ms, Some(12));
289 assert_eq!(ffi.peak_memory_kb, Some(48));
290 assert_eq!(ffi.process_peak_memory_kb, Some(1024));
291 }
292
293 #[test]
294 fn test_bench_report_ffi_conversion() {
295 let report = crate::RunnerReport {
296 spec: crate::BenchSpec {
297 name: "test".to_string(),
298 iterations: 2,
299 warmup: 1,
300 },
301 samples: vec![
302 crate::BenchSample {
303 duration_ns: 100,
304 cpu_time_ms: Some(3),
305 peak_memory_kb: Some(8),
306 process_peak_memory_kb: Some(108),
307 },
308 crate::BenchSample {
309 duration_ns: 200,
310 cpu_time_ms: Some(5),
311 peak_memory_kb: Some(13),
312 process_peak_memory_kb: Some(113),
313 },
314 ],
315 phases: vec![crate::SemanticPhase {
316 name: "prove".to_string(),
317 duration_ns: 300,
318 }],
319 timeline: vec![crate::HarnessTimelineSpan {
320 phase: "measured-benchmark".to_string(),
321 start_offset_ns: 0,
322 end_offset_ns: 100,
323 iteration: Some(0),
324 }],
325 };
326
327 let ffi: BenchReportFfi = report.into();
328 assert_eq!(ffi.spec.name, "test");
329 assert_eq!(ffi.samples.len(), 2);
330 assert_eq!(ffi.samples[0].duration_ns, 100);
331 assert_eq!(ffi.samples[0].cpu_time_ms, Some(3));
332 assert_eq!(ffi.samples[0].peak_memory_kb, Some(8));
333 assert_eq!(ffi.samples[0].process_peak_memory_kb, Some(108));
334 assert_eq!(ffi.phases.len(), 1);
335 assert_eq!(ffi.phases[0].name, "prove");
336 assert_eq!(ffi.timeline.len(), 1);
337 assert_eq!(ffi.timeline[0].phase, "measured-benchmark");
338 }
339
340 #[test]
341 fn test_into_ffi_trait() {
342 let spec = crate::BenchSpec {
343 name: "test".to_string(),
344 iterations: 50,
345 warmup: 5,
346 };
347
348 let ffi: BenchSpecFfi = spec.into_ffi();
349 assert_eq!(ffi.iterations, 50);
350 }
351}