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>,
82}
83
84impl From<crate::BenchSample> for BenchSampleFfi {
85 fn from(sample: crate::BenchSample) -> Self {
86 Self {
87 duration_ns: sample.duration_ns,
88 cpu_time_ms: sample.cpu_time_ms,
89 peak_memory_kb: sample.peak_memory_kb,
90 }
91 }
92}
93
94impl From<BenchSampleFfi> for crate::BenchSample {
95 fn from(sample: BenchSampleFfi) -> Self {
96 Self {
97 duration_ns: sample.duration_ns,
98 cpu_time_ms: sample.cpu_time_ms,
99 peak_memory_kb: sample.peak_memory_kb,
100 }
101 }
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct BenchReportFfi {
107 pub spec: BenchSpecFfi,
109 pub samples: Vec<BenchSampleFfi>,
111 pub phases: Vec<SemanticPhaseFfi>,
113 pub timeline: Vec<HarnessTimelineSpanFfi>,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct SemanticPhaseFfi {
120 pub name: String,
121 pub duration_ns: u64,
122}
123
124impl From<crate::SemanticPhase> for SemanticPhaseFfi {
125 fn from(phase: crate::SemanticPhase) -> Self {
126 Self {
127 name: phase.name,
128 duration_ns: phase.duration_ns,
129 }
130 }
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct HarnessTimelineSpanFfi {
136 pub phase: String,
137 pub start_offset_ns: u64,
138 pub end_offset_ns: u64,
139 pub iteration: Option<u32>,
140}
141
142impl From<crate::HarnessTimelineSpan> for HarnessTimelineSpanFfi {
143 fn from(span: crate::HarnessTimelineSpan) -> Self {
144 Self {
145 phase: span.phase,
146 start_offset_ns: span.start_offset_ns,
147 end_offset_ns: span.end_offset_ns,
148 iteration: span.iteration,
149 }
150 }
151}
152
153impl From<crate::RunnerReport> for BenchReportFfi {
154 fn from(report: crate::RunnerReport) -> Self {
155 Self {
156 spec: report.spec.into(),
157 samples: report.samples.into_iter().map(Into::into).collect(),
158 phases: report.phases.into_iter().map(Into::into).collect(),
159 timeline: report.timeline.into_iter().map(Into::into).collect(),
160 }
161 }
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
166pub enum BenchErrorFfi {
167 InvalidIterations,
169 UnknownFunction { name: String },
171 ExecutionFailed { reason: String },
173 ConfigError { message: String },
175 IoError { message: String },
177}
178
179impl From<crate::types::BenchError> for BenchErrorFfi {
180 fn from(err: crate::types::BenchError) -> Self {
181 match err {
182 crate::types::BenchError::Runner(runner_err) => match runner_err {
183 crate::timing::TimingError::NoIterations { .. } => BenchErrorFfi::InvalidIterations,
184 crate::timing::TimingError::Execution(msg) => {
185 BenchErrorFfi::ExecutionFailed { reason: msg }
186 }
187 },
188 crate::types::BenchError::UnknownFunction(name, _) => {
189 BenchErrorFfi::UnknownFunction { name }
190 }
191 crate::types::BenchError::Execution(msg) => {
192 BenchErrorFfi::ExecutionFailed { reason: msg }
193 }
194 crate::types::BenchError::Io(e) => BenchErrorFfi::IoError {
195 message: e.to_string(),
196 },
197 crate::types::BenchError::Serialization(e) => BenchErrorFfi::ConfigError {
198 message: e.to_string(),
199 },
200 crate::types::BenchError::Config(msg) => BenchErrorFfi::ConfigError { message: msg },
201 crate::types::BenchError::Build(msg) => BenchErrorFfi::ExecutionFailed {
202 reason: format!("build error: {}", msg),
203 },
204 }
205 }
206}
207
208pub trait IntoFfi<T> {
210 fn into_ffi(self) -> T;
212}
213
214pub trait FromFfi<T> {
216 fn from_ffi(ffi: T) -> Self;
218}
219
220impl<T, U> IntoFfi<U> for T
222where
223 U: From<T>,
224{
225 fn into_ffi(self) -> U {
226 U::from(self)
227 }
228}
229
230impl<T, U> FromFfi<U> for T
231where
232 T: From<U>,
233{
234 fn from_ffi(ffi: U) -> Self {
235 T::from(ffi)
236 }
237}
238
239#[cfg(feature = "full")]
243pub fn run_benchmark_ffi(spec: BenchSpecFfi) -> Result<BenchReportFfi, BenchErrorFfi> {
244 let sdk_spec: crate::BenchSpec = spec.into();
245 crate::run_benchmark(sdk_spec)
246 .map(Into::into)
247 .map_err(Into::into)
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 #[test]
255 fn test_bench_spec_ffi_conversion() {
256 let sdk_spec = crate::BenchSpec {
257 name: "test".to_string(),
258 iterations: 100,
259 warmup: 10,
260 };
261
262 let ffi: BenchSpecFfi = sdk_spec.clone().into();
263 assert_eq!(ffi.name, "test");
264 assert_eq!(ffi.iterations, 100);
265 assert_eq!(ffi.warmup, 10);
266
267 let back: crate::BenchSpec = ffi.into();
268 assert_eq!(back.name, sdk_spec.name);
269 }
270
271 #[test]
272 fn test_bench_sample_ffi_conversion() {
273 let sdk_sample = crate::BenchSample {
274 duration_ns: 12345,
275 cpu_time_ms: Some(12),
276 peak_memory_kb: Some(48),
277 };
278 let ffi: BenchSampleFfi = sdk_sample.into();
279 assert_eq!(ffi.duration_ns, 12345);
280 assert_eq!(ffi.cpu_time_ms, Some(12));
281 assert_eq!(ffi.peak_memory_kb, Some(48));
282 }
283
284 #[test]
285 fn test_bench_report_ffi_conversion() {
286 let report = crate::RunnerReport {
287 spec: crate::BenchSpec {
288 name: "test".to_string(),
289 iterations: 2,
290 warmup: 1,
291 },
292 samples: vec![
293 crate::BenchSample {
294 duration_ns: 100,
295 cpu_time_ms: Some(3),
296 peak_memory_kb: Some(8),
297 },
298 crate::BenchSample {
299 duration_ns: 200,
300 cpu_time_ms: Some(5),
301 peak_memory_kb: Some(13),
302 },
303 ],
304 phases: vec![crate::SemanticPhase {
305 name: "prove".to_string(),
306 duration_ns: 300,
307 }],
308 timeline: vec![crate::HarnessTimelineSpan {
309 phase: "measured-benchmark".to_string(),
310 start_offset_ns: 0,
311 end_offset_ns: 100,
312 iteration: Some(0),
313 }],
314 };
315
316 let ffi: BenchReportFfi = report.into();
317 assert_eq!(ffi.spec.name, "test");
318 assert_eq!(ffi.samples.len(), 2);
319 assert_eq!(ffi.samples[0].duration_ns, 100);
320 assert_eq!(ffi.samples[0].cpu_time_ms, Some(3));
321 assert_eq!(ffi.samples[0].peak_memory_kb, Some(8));
322 assert_eq!(ffi.phases.len(), 1);
323 assert_eq!(ffi.phases[0].name, "prove");
324 assert_eq!(ffi.timeline.len(), 1);
325 assert_eq!(ffi.timeline[0].phase, "measured-benchmark");
326 }
327
328 #[test]
329 fn test_into_ffi_trait() {
330 let spec = crate::BenchSpec {
331 name: "test".to_string(),
332 iterations: 50,
333 warmup: 5,
334 };
335
336 let ffi: BenchSpecFfi = spec.into_ffi();
337 assert_eq!(ffi.iterations, 50);
338 }
339}