runnerd_core/observe/
mirror.rs1use crate::store::MirrorPosition;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct Served {
13 pub at: u64,
15 pub address: String,
17 pub bytes: u64,
19}
20
21pub fn parse(line: &str) -> Option<Served> {
24 let (timestamp, line) = line.split_once(' ')?;
25 let at = crate::time::parse_ns(timestamp)?;
26 let (address, rest) = line.split_once(" - - [")?;
27 let (_, rest) = rest.split_once("] \"")?;
28 let (_request, rest) = rest.split_once("\" ")?;
29 let mut fields = rest.split(' ');
30 let _status: u16 = fields.next()?.parse().ok()?;
31 let bytes = match fields.next()? {
32 "-" => 0,
33 bytes => bytes.parse().ok()?,
34 };
35 if address.parse::<std::net::IpAddr>().is_err() {
36 return None;
37 }
38 Some(Served {
39 at,
40 address: address.to_owned(),
41 bytes,
42 })
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47struct Lease {
48 address: String,
49 runner: String,
50 from: u64,
51 until: Option<u64>,
52}
53
54#[derive(Debug, Default)]
56pub struct Leases {
57 leases: Vec<Lease>,
58}
59
60impl Leases {
61 pub fn start(&mut self, address: &str, runner: &str, from: u64) {
63 self.leases.push(Lease {
64 address: address.to_owned(),
65 runner: runner.to_owned(),
66 from,
67 until: None,
68 });
69 }
70
71 pub fn end(&mut self, runner: &str, until: u64) {
73 for lease in &mut self.leases {
74 if lease.runner == runner && lease.until.is_none() {
75 lease.until = Some(until);
76 }
77 }
78 }
79
80 pub fn forget_before(&mut self, before: u64) {
82 self.leases
83 .retain(|lease| lease.until.is_none_or(|until| until >= before));
84 }
85
86 pub fn holder(&self, address: &str, at: u64) -> Option<&str> {
88 self.leases
89 .iter()
90 .find(|lease| {
91 lease.address == address
92 && lease.from <= at
93 && lease.until.is_none_or(|until| at < until)
94 })
95 .map(|lease| lease.runner.as_str())
96 }
97
98 pub fn attribute(
100 &self,
101 log: &str,
102 after: MirrorPosition,
103 ) -> (Vec<(String, u64)>, MirrorPosition) {
104 let mut totals: Vec<(String, u64)> = Vec::new();
105 let mut until = after;
106 let mut at_after = 0;
108 for served in log.lines().filter_map(parse) {
109 if served.at < after.at {
110 continue;
111 }
112 if served.at == after.at {
113 at_after += 1;
114 if at_after <= after.seen {
115 continue;
116 }
117 }
118 if served.at == until.at {
119 until.seen += 1;
120 } else {
121 until = MirrorPosition {
122 at: served.at,
123 seen: 1,
124 };
125 }
126 let Some(runner) = self.holder(&served.address, served.at / 1_000_000) else {
127 continue;
128 };
129 match totals.iter_mut().find(|(name, _)| name == runner) {
130 Some((_, total)) => *total += served.bytes,
131 None => totals.push((runner.to_owned(), served.bytes)),
132 }
133 }
134 (totals, until)
135 }
136}