1use crate::domain::Domain;
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
16pub enum KernelMode {
17 Batch,
25
26 Ring,
34}
35
36impl KernelMode {
37 #[must_use]
39 pub const fn is_batch(&self) -> bool {
40 matches!(self, KernelMode::Batch)
41 }
42
43 #[must_use]
45 pub const fn is_ring(&self) -> bool {
46 matches!(self, KernelMode::Ring)
47 }
48
49 #[must_use]
51 pub const fn typical_overhead_us(&self) -> f64 {
52 match self {
53 KernelMode::Batch => 30.0, KernelMode::Ring => 0.3, }
56 }
57
58 #[must_use]
60 pub const fn as_str(&self) -> &'static str {
61 match self {
62 KernelMode::Batch => "batch",
63 KernelMode::Ring => "ring",
64 }
65 }
66}
67
68impl fmt::Display for KernelMode {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 write!(f, "{}", self.as_str())
71 }
72}
73
74#[derive(Clone, Debug, Serialize, Deserialize)]
78pub struct KernelMetadata {
79 pub id: String,
81
82 pub mode: KernelMode,
84
85 pub domain: Domain,
87
88 pub description: String,
90
91 pub expected_throughput: u64,
93
94 pub target_latency_us: f64,
96
97 pub requires_gpu_native: bool,
101
102 pub version: u32,
104}
105
106impl KernelMetadata {
107 #[must_use]
109 pub fn batch(id: impl Into<String>, domain: Domain) -> Self {
110 Self {
111 id: id.into(),
112 mode: KernelMode::Batch,
113 domain,
114 description: String::new(),
115 expected_throughput: 10_000,
116 target_latency_us: 50.0,
117 requires_gpu_native: false,
118 version: 1,
119 }
120 }
121
122 #[must_use]
124 pub fn ring(id: impl Into<String>, domain: Domain) -> Self {
125 Self {
126 id: id.into(),
127 mode: KernelMode::Ring,
128 domain,
129 description: String::new(),
130 expected_throughput: 100_000,
131 target_latency_us: 1.0,
132 requires_gpu_native: true,
133 version: 1,
134 }
135 }
136
137 #[must_use]
139 pub fn with_description(mut self, description: impl Into<String>) -> Self {
140 self.description = description.into();
141 self
142 }
143
144 #[must_use]
146 pub fn with_throughput(mut self, ops_per_sec: u64) -> Self {
147 self.expected_throughput = ops_per_sec;
148 self
149 }
150
151 #[must_use]
153 pub fn with_latency_us(mut self, latency_us: f64) -> Self {
154 self.target_latency_us = latency_us;
155 self
156 }
157
158 #[must_use]
160 pub fn with_gpu_native(mut self, required: bool) -> Self {
161 self.requires_gpu_native = required;
162 self
163 }
164
165 #[must_use]
167 pub fn with_version(mut self, version: u32) -> Self {
168 self.version = version;
169 self
170 }
171
172 #[must_use]
176 pub fn feature_string(&self) -> String {
177 let name = self.id.rsplit('/').next().unwrap_or(&self.id);
179 let name = to_pascal_case(name);
180 format!("{}.{}", self.domain, name)
181 }
182
183 #[must_use]
185 pub fn full_id(&self) -> String {
186 format!("{}/{}", self.domain.as_str().to_lowercase(), self.id)
187 }
188}
189
190impl Default for KernelMetadata {
191 fn default() -> Self {
192 Self::batch("unnamed", Domain::Core)
193 }
194}
195
196fn to_pascal_case(s: &str) -> String {
198 s.split(['-', '_'])
199 .filter(|part| !part.is_empty())
200 .map(|part| {
201 let mut chars = part.chars();
202 match chars.next() {
203 Some(first) => first.to_uppercase().chain(chars).collect::<String>(),
204 None => String::new(),
205 }
206 })
207 .collect()
208}
209
210#[derive(Default)]
212pub struct KernelMetadataBuilder {
213 id: Option<String>,
214 mode: Option<KernelMode>,
215 domain: Option<Domain>,
216 description: String,
217 expected_throughput: u64,
218 target_latency_us: f64,
219 requires_gpu_native: bool,
220 version: u32,
221}
222
223impl KernelMetadataBuilder {
224 #[must_use]
226 pub fn new() -> Self {
227 Self {
228 expected_throughput: 10_000,
229 target_latency_us: 50.0,
230 version: 1,
231 ..Default::default()
232 }
233 }
234
235 #[must_use]
237 pub fn id(mut self, id: impl Into<String>) -> Self {
238 self.id = Some(id.into());
239 self
240 }
241
242 #[must_use]
244 pub fn mode(mut self, mode: KernelMode) -> Self {
245 self.mode = Some(mode);
246 self
247 }
248
249 #[must_use]
251 pub fn domain(mut self, domain: Domain) -> Self {
252 self.domain = Some(domain);
253 self
254 }
255
256 #[must_use]
258 pub fn description(mut self, description: impl Into<String>) -> Self {
259 self.description = description.into();
260 self
261 }
262
263 #[must_use]
265 pub fn throughput(mut self, ops_per_sec: u64) -> Self {
266 self.expected_throughput = ops_per_sec;
267 self
268 }
269
270 #[must_use]
272 pub fn latency_us(mut self, latency_us: f64) -> Self {
273 self.target_latency_us = latency_us;
274 self
275 }
276
277 #[must_use]
279 pub fn gpu_native(mut self, required: bool) -> Self {
280 self.requires_gpu_native = required;
281 self
282 }
283
284 #[must_use]
286 pub fn version(mut self, version: u32) -> Self {
287 self.version = version;
288 self
289 }
290
291 #[must_use]
297 pub fn build(self) -> KernelMetadata {
298 KernelMetadata {
299 id: self.id.expect("id is required"),
300 mode: self.mode.expect("mode is required"),
301 domain: self.domain.expect("domain is required"),
302 description: self.description,
303 expected_throughput: self.expected_throughput,
304 target_latency_us: self.target_latency_us,
305 requires_gpu_native: self.requires_gpu_native,
306 version: self.version,
307 }
308 }
309
310 #[must_use]
314 pub fn try_build(self) -> Option<KernelMetadata> {
315 Some(KernelMetadata {
316 id: self.id?,
317 mode: self.mode?,
318 domain: self.domain?,
319 description: self.description,
320 expected_throughput: self.expected_throughput,
321 target_latency_us: self.target_latency_us,
322 requires_gpu_native: self.requires_gpu_native,
323 version: self.version,
324 })
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331
332 #[test]
333 fn test_kernel_mode_properties() {
334 assert!(KernelMode::Batch.is_batch());
335 assert!(!KernelMode::Batch.is_ring());
336 assert!(KernelMode::Ring.is_ring());
337 assert!(!KernelMode::Ring.is_batch());
338 }
339
340 #[test]
341 fn test_kernel_metadata_batch() {
342 let meta = KernelMetadata::batch("pagerank", Domain::GraphAnalytics)
343 .with_description("PageRank centrality")
344 .with_throughput(100_000)
345 .with_latency_us(10.0);
346
347 assert_eq!(meta.id, "pagerank");
348 assert_eq!(meta.mode, KernelMode::Batch);
349 assert_eq!(meta.domain, Domain::GraphAnalytics);
350 assert!(!meta.requires_gpu_native);
351 }
352
353 #[test]
354 fn test_kernel_metadata_ring() {
355 let meta = KernelMetadata::ring("pagerank", Domain::GraphAnalytics);
356
357 assert_eq!(meta.mode, KernelMode::Ring);
358 assert!(meta.requires_gpu_native);
359 }
360
361 #[test]
362 fn test_feature_string() {
363 let meta = KernelMetadata::ring("pagerank", Domain::GraphAnalytics);
364 assert_eq!(meta.feature_string(), "GraphAnalytics.Pagerank");
365
366 let meta = KernelMetadata::ring("graph/degree-centrality", Domain::GraphAnalytics);
367 assert_eq!(meta.feature_string(), "GraphAnalytics.DegreeCentrality");
368 }
369
370 #[test]
371 fn test_to_pascal_case() {
372 assert_eq!(to_pascal_case("pagerank"), "Pagerank");
373 assert_eq!(to_pascal_case("degree-centrality"), "DegreeCentrality");
374 assert_eq!(to_pascal_case("snake_case"), "SnakeCase");
375 assert_eq!(to_pascal_case("mixed-snake_case"), "MixedSnakeCase");
376 }
377
378 #[test]
379 fn test_builder() {
380 let meta = KernelMetadataBuilder::new()
381 .id("test-kernel")
382 .mode(KernelMode::Ring)
383 .domain(Domain::Core)
384 .throughput(50_000)
385 .latency_us(0.5)
386 .build();
387
388 assert_eq!(meta.id, "test-kernel");
389 assert_eq!(meta.mode, KernelMode::Ring);
390 assert_eq!(meta.expected_throughput, 50_000);
391 }
392}