1use crate::{Delay, Duration, HeavyPdoTrace, LightPdo, LightPdoTrace};
51use dyn_clone::DynClone;
52
53#[cfg(feature = "serde")]
54use serde::{Deserialize, Serialize};
55
56#[cfg_attr(feature = "serde", typetag::serde)]
63pub trait HeavyPdoTraceConfig: DynClone + Send + std::fmt::Debug {
64 fn into_model(self: Box<Self>) -> Box<dyn HeavyPdoTrace>;
65}
66
67dyn_clone::clone_trait_object!(HeavyPdoTraceConfig);
68
69#[cfg_attr(feature = "serde", typetag::serde)]
73pub trait LightPdoTraceConfig: DynClone + Send + std::fmt::Debug {
74 fn into_model(self: Box<Self>) -> Box<dyn LightPdoTrace>;
75}
76
77dyn_clone::clone_trait_object!(LightPdoTraceConfig);
78
79pub const CELLREPLAY_PDO_SIZE: u64 = 1400;
84
85#[derive(Default, Debug, Clone, Copy, PartialEq)]
107pub struct StaticHeavyPdo {
108 pub interval: Duration,
109 pub bytes: u64,
110 pub count: usize,
111 current_count: usize,
112}
113
114#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(default))]
118#[derive(Default, Debug, Clone, Copy, PartialEq)]
119pub struct StaticHeavyPdoConfig {
120 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
121 #[cfg_attr(
122 all(feature = "serde", feature = "human"),
123 serde(with = "humantime_serde")
124 )]
125 pub interval: Option<Duration>,
126 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
127 pub bytes: Option<u64>,
128 pub count: usize,
129}
130
131#[derive(Default, Debug, Clone, PartialEq)]
151pub struct TraceHeavyPdo {
152 pub pattern: Vec<(Duration, u64)>,
153 pub index: usize,
154}
155
156#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(default))]
160#[derive(Default, Debug, Clone, PartialEq)]
161pub struct TraceHeavyPdoConfig {
162 #[cfg_attr(
163 all(feature = "serde", feature = "human"),
164 serde(with = "heavy_pattern_serde")
165 )]
166 pub pattern: Vec<(Duration, u64)>,
167}
168
169#[derive(Default)]
203pub struct RepeatedHeavyPdoPattern {
204 pub pattern: Vec<Box<dyn HeavyPdoTraceConfig>>,
205 pub count: usize,
206 current_model: Option<Box<dyn HeavyPdoTrace>>,
207 current_cycle: usize,
208 current_pattern: usize,
209}
210
211#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(default))]
215#[derive(Default, Debug, Clone)]
216pub struct RepeatedHeavyPdoPatternConfig {
217 pub pattern: Vec<Box<dyn HeavyPdoTraceConfig>>,
218 pub count: usize,
219}
220
221#[derive(Default, Debug, Clone, PartialEq)]
245pub struct TraceLightPdo {
246 pub pattern: Vec<LightPdo>,
247 pub pdo_size: u64,
248 pub index: usize,
249}
250
251#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(default))]
255#[derive(Default, Debug, Clone, PartialEq)]
256pub struct TraceLightPdoConfig {
257 pub pattern: Vec<LightPdo>,
258 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
259 pub pdo_size: Option<u64>,
260}
261
262impl HeavyPdoTrace for StaticHeavyPdo {
263 fn next_heavy_pdo(&mut self) -> Option<(Duration, u64)> {
264 if self.count != 0 && self.count == self.current_count {
265 None
266 } else {
267 self.current_count += 1;
268 Some((self.interval, self.bytes))
269 }
270 }
271}
272
273impl HeavyPdoTrace for TraceHeavyPdo {
274 fn next_heavy_pdo(&mut self) -> Option<(Duration, u64)> {
275 let pdo = self.pattern.get(self.index).copied();
276 if pdo.is_some() {
277 self.index += 1;
278 }
279 pdo
280 }
281}
282
283impl HeavyPdoTrace for RepeatedHeavyPdoPattern {
284 fn next_heavy_pdo(&mut self) -> Option<(Duration, u64)> {
285 if self.pattern.is_empty() || (self.count != 0 && self.current_cycle >= self.count) {
286 None
287 } else {
288 if self.current_model.is_none() {
289 self.current_model = Some(self.pattern[self.current_pattern].clone().into_model());
290 }
291 match self.current_model.as_mut().unwrap().next_heavy_pdo() {
292 Some(pdo) => Some(pdo),
293 None => {
294 self.current_model = None;
295 self.current_pattern += 1;
296 if self.current_pattern >= self.pattern.len() {
297 self.current_pattern = 0;
298 self.current_cycle += 1;
299 if self.count != 0 && self.current_cycle >= self.count {
300 return None;
301 }
302 }
303 self.next_heavy_pdo()
304 }
305 }
306 }
307 }
308}
309
310impl LightPdoTrace for TraceLightPdo {
311 fn next_light_pdo(&mut self) -> Option<LightPdo> {
312 let record = self.pattern.get(self.index).cloned();
313 if record.is_some() {
314 self.index += 1;
315 }
316 record
317 }
318
319 fn pdo_size(&self) -> u64 {
320 self.pdo_size
321 }
322}
323
324impl StaticHeavyPdoConfig {
325 pub fn new() -> Self {
326 Self {
327 interval: None,
328 bytes: None,
329 count: 0,
330 }
331 }
332
333 pub fn interval(mut self, interval: Duration) -> Self {
334 self.interval = Some(interval);
335 self
336 }
337
338 pub fn bytes(mut self, bytes: u64) -> Self {
339 self.bytes = Some(bytes);
340 self
341 }
342
343 pub fn count(mut self, count: usize) -> Self {
344 self.count = count;
345 self
346 }
347
348 pub fn build(self) -> StaticHeavyPdo {
349 StaticHeavyPdo {
350 interval: self.interval.unwrap_or_else(|| Duration::from_millis(1)),
351 bytes: self.bytes.unwrap_or(CELLREPLAY_PDO_SIZE),
352 count: self.count,
353 current_count: 0,
354 }
355 }
356}
357
358impl TraceHeavyPdoConfig {
359 pub fn new() -> Self {
360 Self { pattern: vec![] }
361 }
362
363 pub fn pattern(mut self, pattern: Vec<(Duration, u64)>) -> Self {
364 self.pattern = pattern;
365 self
366 }
367
368 pub fn build(self) -> TraceHeavyPdo {
369 TraceHeavyPdo {
370 pattern: self.pattern,
371 index: 0,
372 }
373 }
374}
375
376impl RepeatedHeavyPdoPatternConfig {
377 pub fn new() -> Self {
378 Self {
379 pattern: vec![],
380 count: 0,
381 }
382 }
383
384 pub fn pattern(mut self, pattern: Vec<Box<dyn HeavyPdoTraceConfig>>) -> Self {
385 self.pattern = pattern;
386 self
387 }
388
389 pub fn count(mut self, count: usize) -> Self {
390 self.count = count;
391 self
392 }
393
394 pub fn build(self) -> RepeatedHeavyPdoPattern {
395 RepeatedHeavyPdoPattern {
396 pattern: self.pattern,
397 count: self.count,
398 current_model: None,
399 current_cycle: 0,
400 current_pattern: 0,
401 }
402 }
403}
404
405impl TraceLightPdoConfig {
406 pub fn new() -> Self {
407 Self {
408 pattern: vec![],
409 pdo_size: None,
410 }
411 }
412
413 pub fn pattern(mut self, pattern: Vec<LightPdo>) -> Self {
414 self.pattern = pattern;
415 self
416 }
417
418 pub fn pdo_size(mut self, pdo_size: u64) -> Self {
419 self.pdo_size = Some(pdo_size);
420 self
421 }
422
423 pub fn build(self) -> TraceLightPdo {
424 TraceLightPdo {
425 pattern: self.pattern,
426 pdo_size: self.pdo_size.unwrap_or(CELLREPLAY_PDO_SIZE),
427 index: 0,
428 }
429 }
430}
431
432pub fn load_cellreplay_light_pdo_trace(trace: &str) -> Result<TraceLightPdoConfig, &'static str> {
461 let mut pattern: Vec<LightPdo> = vec![];
462 for line in trace.lines() {
463 let mut words = line.split_whitespace();
464 let mut next_ms = || -> Result<Option<Duration>, &'static str> {
465 match words.next() {
466 Some(word) => word
467 .parse::<u64>()
468 .map(|ms| Some(Duration::from_millis(ms)))
469 .map_err(|_| "values must be nonnegative integers in milliseconds"),
470 None => Ok(None),
471 }
472 };
473 let timestamp = next_ms()?.ok_or("invalid empty line")?;
474 let base_delay: Delay = next_ms()?.ok_or("each line must have a base delay")?;
475 let mut opportunities = vec![];
476 while let Some(opportunity) = next_ms()? {
477 if opportunities.last().is_some_and(|last| opportunity < *last) {
478 return Err("PDO times must be monotonically nondecreasing");
479 }
480 opportunities.push(opportunity);
481 }
482 if pattern
483 .last()
484 .is_some_and(|last| timestamp < last.timestamp)
485 {
486 return Err("timestamps must be monotonically nondecreasing");
487 }
488 pattern.push(LightPdo {
489 timestamp,
490 base_delay,
491 opportunities,
492 });
493 }
494 if pattern.is_empty() {
495 return Err("trace must have at least one line");
496 }
497 Ok(TraceLightPdoConfig::new().pattern(pattern))
498}
499
500macro_rules! impl_heavy_pdo_trace_config {
501 ($name:ident) => {
502 #[cfg_attr(feature = "serde", typetag::serde)]
503 impl HeavyPdoTraceConfig for $name {
504 fn into_model(self: Box<$name>) -> Box<dyn HeavyPdoTrace> {
505 Box::new(self.build())
506 }
507 }
508 };
509}
510
511impl_heavy_pdo_trace_config!(StaticHeavyPdoConfig);
512impl_heavy_pdo_trace_config!(TraceHeavyPdoConfig);
513impl_heavy_pdo_trace_config!(RepeatedHeavyPdoPatternConfig);
514
515#[cfg_attr(feature = "serde", typetag::serde)]
516impl LightPdoTraceConfig for TraceLightPdoConfig {
517 fn into_model(self: Box<TraceLightPdoConfig>) -> Box<dyn LightPdoTrace> {
518 Box::new(self.build())
519 }
520}
521
522impl<Model: HeavyPdoTrace + 'static> From<Model> for Box<dyn HeavyPdoTrace> {
523 fn from(model: Model) -> Self {
524 Box::new(model)
525 }
526}
527
528impl<Model: LightPdoTrace + 'static> From<Model> for Box<dyn LightPdoTrace> {
529 fn from(model: Model) -> Self {
530 Box::new(model)
531 }
532}
533
534pub trait Forever: HeavyPdoTraceConfig {
536 fn forever(self) -> RepeatedHeavyPdoPatternConfig;
537}
538
539#[macro_export]
541macro_rules! impl_forever_heavy_pdo {
542 ($name:ident) => {
543 impl Forever for $name {
544 fn forever(self) -> RepeatedHeavyPdoPatternConfig {
545 RepeatedHeavyPdoPatternConfig::new()
546 .pattern(vec![Box::new(self)])
547 .count(0)
548 }
549 }
550 };
551}
552
553impl_forever_heavy_pdo!(StaticHeavyPdoConfig);
554impl_forever_heavy_pdo!(TraceHeavyPdoConfig);
555
556impl Forever for RepeatedHeavyPdoPatternConfig {
557 fn forever(self) -> RepeatedHeavyPdoPatternConfig {
558 self.count(0)
559 }
560}
561
562#[cfg(all(feature = "serde", feature = "human"))]
563mod heavy_pattern_serde {
564 use super::Duration;
565 use serde::{Deserialize, Deserializer, Serialize, Serializer};
566
567 pub fn serialize<S: Serializer>(
568 v: &[(Duration, u64)],
569 serializer: S,
570 ) -> Result<S::Ok, S::Error> {
571 v.iter()
572 .map(|(interval, bytes)| (humantime_serde::Serde::from(interval), bytes))
573 .collect::<Vec<_>>()
574 .serialize(serializer)
575 }
576
577 pub fn deserialize<'de, D: Deserializer<'de>>(
578 deserializer: D,
579 ) -> Result<Vec<(Duration, u64)>, D::Error> {
580 let v: Vec<(humantime_serde::Serde<Duration>, u64)> = Vec::deserialize(deserializer)?;
581 Ok(v.into_iter()
582 .map(|(interval, bytes)| (interval.into_inner(), bytes))
583 .collect())
584 }
585}
586
587#[cfg(test)]
588mod test {
589 use super::*;
590
591 #[test]
592 fn test_static_heavy_pdo() {
593 let mut model = StaticHeavyPdoConfig::new()
594 .interval(Duration::from_millis(2))
595 .bytes(1400)
596 .count(2)
597 .build();
598 assert_eq!(
599 model.next_heavy_pdo(),
600 Some((Duration::from_millis(2), 1400))
601 );
602 assert_eq!(
603 model.next_heavy_pdo(),
604 Some((Duration::from_millis(2), 1400))
605 );
606 assert_eq!(model.next_heavy_pdo(), None);
607 }
608
609 #[test]
610 fn test_default_pdo_size() {
611 let mut heavy = StaticHeavyPdoConfig::new().count(1).build();
612 assert_eq!(
613 heavy.next_heavy_pdo(),
614 Some((Duration::from_millis(1), CELLREPLAY_PDO_SIZE))
615 );
616
617 let light = TraceLightPdoConfig::new().build();
618 assert_eq!(light.pdo_size(), CELLREPLAY_PDO_SIZE);
619 let light = load_cellreplay_light_pdo_trace("0 30 0").unwrap().build();
620 assert_eq!(light.pdo_size(), CELLREPLAY_PDO_SIZE);
621 let light = TraceLightPdoConfig::new().pdo_size(1500).build();
622 assert_eq!(light.pdo_size(), 1500);
623 }
624
625 #[test]
626 fn test_forever() {
627 let mut model = TraceHeavyPdoConfig::new()
628 .pattern(vec![(Duration::from_millis(1), 1500)])
629 .forever()
630 .build();
631 for _ in 0..5 {
632 assert_eq!(
633 model.next_heavy_pdo(),
634 Some((Duration::from_millis(1), 1500))
635 );
636 }
637 }
638
639 #[test]
640 fn test_empty_repeated() {
641 let mut model = RepeatedHeavyPdoPatternConfig::new().build();
642 assert_eq!(model.next_heavy_pdo(), None);
643 }
644
645 #[test]
646 fn test_load_cellreplay_light_pdo() {
647 assert_eq!(
648 load_cellreplay_light_pdo_trace(""),
649 Err("trace must have at least one line")
650 );
651 assert_eq!(
652 load_cellreplay_light_pdo_trace("0 30\n\n10 30"),
653 Err("invalid empty line")
654 );
655 assert_eq!(
656 load_cellreplay_light_pdo_trace("0"),
657 Err("each line must have a base delay")
658 );
659 assert_eq!(
660 load_cellreplay_light_pdo_trace("0 30 0 x"),
661 Err("values must be nonnegative integers in milliseconds")
662 );
663 assert_eq!(
664 load_cellreplay_light_pdo_trace("0 30 0 3 2"),
665 Err("PDO times must be monotonically nondecreasing")
666 );
667 assert_eq!(
668 load_cellreplay_light_pdo_trace("10 30 0\n5 30 0"),
669 Err("timestamps must be monotonically nondecreasing")
670 );
671
672 let mut trace = load_cellreplay_light_pdo_trace("0 30 0 1 1 4\n100 25\n")
674 .unwrap()
675 .build();
676 assert_eq!(
677 trace.next_light_pdo(),
678 Some(LightPdo {
679 timestamp: Duration::ZERO,
680 base_delay: Delay::from_millis(30),
681 opportunities: [0, 1, 1, 4].map(Duration::from_millis).to_vec(),
682 })
683 );
684 assert_eq!(
685 trace.next_light_pdo(),
686 Some(LightPdo {
687 timestamp: Duration::from_millis(100),
688 base_delay: Delay::from_millis(25),
689 opportunities: vec![],
690 })
691 );
692 assert_eq!(trace.next_light_pdo(), None);
693 }
694
695 #[test]
696 #[cfg(feature = "serde")]
697 fn test_serde() {
698 let heavy = Box::new(
699 RepeatedHeavyPdoPatternConfig::new()
700 .pattern(vec![Box::new(TraceHeavyPdoConfig::new().pattern(vec![
701 (Duration::from_millis(1), 1500),
702 (Duration::ZERO, 1500),
703 ])) as Box<dyn HeavyPdoTraceConfig>])
704 .count(2),
705 ) as Box<dyn HeavyPdoTraceConfig>;
706 let ser_str = serde_json::to_string(&heavy).unwrap();
707 #[cfg(feature = "human")]
708 let des_str = "{\"RepeatedHeavyPdoPatternConfig\":{\"pattern\":[{\"TraceHeavyPdoConfig\":{\"pattern\":[[\"1ms\",1500],[\"0s\",1500]]}}],\"count\":2}}";
709 #[cfg(not(feature = "human"))]
710 let des_str = "{\"RepeatedHeavyPdoPatternConfig\":{\"pattern\":[{\"TraceHeavyPdoConfig\":{\"pattern\":[[{\"secs\":0,\"nanos\":1000000},1500],[{\"secs\":0,\"nanos\":0},1500]]}}],\"count\":2}}";
711 assert_eq!(ser_str, des_str);
712 let des: Box<dyn HeavyPdoTraceConfig> = serde_json::from_str(des_str).unwrap();
713 let mut model = des.into_model();
714 assert_eq!(
715 model.next_heavy_pdo(),
716 Some((Duration::from_millis(1), 1500))
717 );
718
719 let light = Box::new(load_cellreplay_light_pdo_trace("5 30 0 2").unwrap())
720 as Box<dyn LightPdoTraceConfig>;
721 let ser_str = serde_json::to_string(&light).unwrap();
722 #[cfg(feature = "human")]
723 let des_str = "{\"TraceLightPdoConfig\":{\"pattern\":[{\"timestamp\":\"5ms\",\"base_delay\":\"30ms\",\"opportunities\":[\"0s\",\"2ms\"]}]}}";
724 #[cfg(not(feature = "human"))]
725 let des_str = "{\"TraceLightPdoConfig\":{\"pattern\":[{\"timestamp\":{\"secs\":0,\"nanos\":5000000},\"base_delay\":{\"secs\":0,\"nanos\":30000000},\"opportunities\":[{\"secs\":0,\"nanos\":0},{\"secs\":0,\"nanos\":2000000}]}]}}";
726 assert_eq!(ser_str, des_str);
727 let des: Box<dyn LightPdoTraceConfig> = serde_json::from_str(des_str).unwrap();
728 let mut model = des.into_model();
729 assert_eq!(
730 model.next_light_pdo().unwrap().opportunities,
731 vec![Duration::ZERO, Duration::from_millis(2)]
732 );
733 assert_eq!(model.pdo_size(), CELLREPLAY_PDO_SIZE);
734
735 let light = Box::new(
737 TraceLightPdoConfig::new()
738 .pattern(vec![LightPdo::default()])
739 .pdo_size(1500),
740 ) as Box<dyn LightPdoTraceConfig>;
741 let ser_str = serde_json::to_string(&light).unwrap();
742 assert!(ser_str.ends_with(",\"pdo_size\":1500}}"));
743 let des: Box<dyn LightPdoTraceConfig> = serde_json::from_str(&ser_str).unwrap();
744 assert_eq!(des.into_model().pdo_size(), 1500);
745 }
746}