dcso3/
perf.rs

1/*
2Copyright 2024 Eric Stokes.
3
4This file is part of dcso3.
5
6dcso3 is free software: you can redistribute it and/or modify it under
7the terms of the MIT License.
8
9dcso3 is distributed in the hope that it will be useful, but WITHOUT
10ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11FITNESS FOR A PARTICULAR PURPOSE.
12*/
13
14use bytes::{BufMut, BytesMut};
15use chrono::prelude::*;
16use compact_str::format_compact;
17use hdrhistogram::{
18    serialization::{Deserializer, Serializer, V2DeflateSerializer},
19    Histogram,
20};
21use log::info;
22use serde::{de, ser, Deserialize, Serialize};
23use std::{
24    borrow::{Borrow, BorrowMut},
25    cell::RefCell,
26    ops::{Deref, DerefMut},
27    sync::Arc,
28};
29
30#[derive(Debug, Clone)]
31pub struct HistogramSer(Histogram<u64>);
32
33impl Borrow<Histogram<u64>> for HistogramSer {
34    fn borrow(&self) -> &Histogram<u64> {
35        &self.0
36    }
37}
38
39impl BorrowMut<Histogram<u64>> for HistogramSer {
40    fn borrow_mut(&mut self) -> &mut Histogram<u64> {
41        &mut self.0
42    }
43}
44
45impl AsRef<Histogram<u64>> for HistogramSer {
46    fn as_ref(&self) -> &Histogram<u64> {
47        &self.0
48    }
49}
50
51impl Deref for HistogramSer {
52    type Target = Histogram<u64>;
53
54    fn deref(&self) -> &Self::Target {
55        &self.0
56    }
57}
58
59impl DerefMut for HistogramSer {
60    fn deref_mut(&mut self) -> &mut Self::Target {
61        &mut self.0
62    }
63}
64
65impl Default for HistogramSer {
66    fn default() -> Self {
67        Self(Histogram::new_with_bounds(1, 1_000_000_000, 3).unwrap())
68    }
69}
70
71impl Serialize for HistogramSer {
72    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
73    where
74        S: serde::Serializer,
75    {
76        use base64::prelude::*;
77        use ser::Error;
78        thread_local! {
79            static SER: RefCell<(V2DeflateSerializer, BytesMut, String)> = RefCell::new(
80                (V2DeflateSerializer::default(), BytesMut::new(), String::new()));
81        }
82        SER.with_borrow_mut(|(ser, buf, sbuf)| {
83            buf.clear();
84            sbuf.clear();
85            let mut w = buf.writer();
86            ser.serialize(&self.0, &mut w)
87                .map_err(|e| S::Error::custom(format_compact!("{e:?}")))?;
88            BASE64_STANDARD.encode_string(buf, sbuf);
89            serializer.serialize_str(&sbuf)
90        })
91    }
92}
93
94struct HistogramSerVisitor;
95
96impl<'de> de::Visitor<'de> for HistogramSerVisitor {
97    type Value = HistogramSer;
98
99    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
100        write!(formatter, "expecting a string")
101    }
102
103    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
104    where
105        E: de::Error,
106    {
107        use base64::prelude::*;
108        thread_local! {
109            static SER: RefCell<(Deserializer, Vec<u8>)> = RefCell::new(
110                (Deserializer::default(), Vec::new()));
111        }
112        SER.with_borrow_mut(|(des, buf)| {
113            buf.clear();
114            BASE64_STANDARD
115                .decode_vec(v, buf)
116                .map_err(|e| E::custom(format_compact!("{e:?}")))?;
117            let h = des
118                .deserialize(&mut &buf[..])
119                .map_err(|e| E::custom(format_compact!("{e:?}")))?;
120            Ok(HistogramSer(h))
121        })
122    }
123
124    fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
125    where
126        E: de::Error,
127    {
128        self.visit_str(&v)
129    }
130}
131
132impl<'de> Deserialize<'de> for HistogramSer {
133    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
134    where
135        D: de::Deserializer<'de>,
136    {
137        deserializer.deserialize_str(HistogramSerVisitor)
138    }
139}
140
141pub struct Snap<'a> {
142    ts: DateTime<Utc>,
143    perf: Option<&'a mut HistogramSer>,
144}
145
146impl<'a> Drop for Snap<'a> {
147    fn drop(&mut self) {
148        self.commit();
149    }
150}
151
152impl<'a> Snap<'a> {
153    pub fn new(perf: &'a mut HistogramSer) -> Self {
154        Self {
155            ts: Utc::now(),
156            perf: Some(perf),
157        }
158    }
159
160    pub fn commit(&mut self) {
161        if let Some(h) = self.perf.take() {
162            if let Some(ns) = (Utc::now() - self.ts).num_nanoseconds() {
163                if ns >= 1 && ns <= 1_000_000_000 {
164                    **h += ns as u64;
165                }
166            }
167        }
168    }
169}
170
171#[derive(Debug, Clone, Copy)]
172pub struct HistStat {
173    pub name: &'static str,
174    pub unit: &'static str,
175    pub n: u64,
176    pub mean: u64,
177    pub twenty_five: u64,
178    pub fifty: u64,
179    pub ninety: u64,
180    pub ninety_nine: u64,
181    pub ninety_nine_nine: u64,
182}
183
184impl HistStat {
185    pub fn empty(name: &'static str, ns: bool) -> Self {
186        Self {
187            name,
188            unit: if ns { "ns" } else { "us" },
189            n: 0,
190            mean: 0,
191            twenty_five: 0,
192            fifty: 0,
193            ninety: 0,
194            ninety_nine: 0,
195            ninety_nine_nine: 0,
196        }
197    }
198
199    pub fn new(h: &Histogram<u64>, name: &'static str, ns: bool) -> Self {
200        let n = h.len();
201        let d = if ns { 1 } else { 1000 };
202        let unit = if ns { "ns" } else { "us" };
203        let mean = h.mean().trunc() as u64 / d;
204        let twenty_five = h.value_at_quantile(0.25) / d;
205        let fifty = h.value_at_quantile(0.5) / d;
206        let ninety = h.value_at_quantile(0.9) / d;
207        let ninety_nine = h.value_at_quantile(0.99) / d;
208        let ninety_nine_nine = h.value_at_quantile(0.999) / d;
209        Self {
210            name,
211            unit,
212            n,
213            mean,
214            twenty_five,
215            fifty,
216            ninety,
217            ninety_nine,
218            ninety_nine_nine,
219        }
220    }
221
222    pub fn log(&self, pad: usize) {
223        let Self {
224            name,
225            unit,
226            n,
227            mean,
228            twenty_five,
229            fifty,
230            ninety,
231            ninety_nine,
232            ninety_nine_nine,
233        } = self;
234        if *n > 0 {
235            info!(
236                "{name:pad$}: n: {n:>6}, mean: {mean:>5}{unit}, 25th: {twenty_five:>5}{unit}, 50th: {fifty:>5}{unit}, 90th: {ninety:>5}{unit}, 99th: {ninety_nine:>6}{unit}, 99.9th: {ninety_nine_nine:>6}{unit}"
237            );
238        }
239    }
240}
241
242#[derive(Debug, Clone, Default, Serialize, Deserialize)]
243pub struct PerfInner {
244    pub get_position: HistogramSer,
245    pub get_point: HistogramSer,
246    pub get_velocity: HistogramSer,
247    pub in_air: HistogramSer,
248    pub get_ammo: HistogramSer,
249    pub add_group: HistogramSer,
250    pub add_static_object: HistogramSer,
251    pub unit_is_exist: HistogramSer,
252    pub unit_get_by_name: HistogramSer,
253    pub unit_get_desc: HistogramSer,
254    pub land_is_visible: HistogramSer,
255    pub land_get_height: HistogramSer,
256    pub timer_schedule_function: HistogramSer,
257    pub timer_remove_function: HistogramSer,
258    pub timer_get_time: HistogramSer,
259    pub timer_get_abs_time: HistogramSer,
260    pub timer_get_time0: HistogramSer,
261}
262
263#[derive(Debug, Default, Serialize, Deserialize)]
264pub struct Perf(pub Arc<PerfInner>);
265
266static mut PERF: Option<Perf> = None;
267
268impl Deref for Perf {
269    type Target = PerfInner;
270
271    fn deref(&self) -> &Self::Target {
272        &self.0
273    }
274}
275
276impl Clone for Perf {
277    fn clone(&self) -> Self {
278        Self(Arc::clone(&self.0))
279    }
280}
281
282impl Perf {
283    pub unsafe fn get_mut() -> &'static mut Perf {
284        #[allow(static_mut_refs)]
285        let perf = PERF.as_mut();
286        match perf {
287            Some(perf) => perf,
288            None => {
289                PERF = Some(Perf::default());
290                #[allow(static_mut_refs)]
291                PERF.as_mut().unwrap()
292            }
293        }
294    }
295
296    pub unsafe fn reset() {
297        PERF = None;
298    }
299
300    pub fn stat(&self) -> PerfStat {
301        let PerfInner {
302            get_position,
303            get_point,
304            get_velocity,
305            in_air,
306            get_ammo,
307            add_group,
308            add_static_object,
309            unit_is_exist,
310            unit_get_by_name,
311            unit_get_desc,
312            land_is_visible,
313            land_get_height,
314            timer_schedule_function,
315            timer_remove_function,
316            timer_get_time,
317            timer_get_abs_time,
318            timer_get_time0,
319        } = &*self.0;
320        PerfStat {
321            get_position: HistStat::new(&get_position, "Unit.getPosition", false),
322            add_group: HistStat::new(&add_group, "Coalition.addGroup", false),
323            add_static_object: HistStat::new(
324                &add_static_object,
325                "Coalition.addStaticObject",
326                false,
327            ),
328            get_ammo: HistStat::new(&get_ammo, "Unit.getAmmo", false),
329            get_point: HistStat::new(&get_point, "Unit.getPoint", false),
330            get_velocity: HistStat::new(&get_velocity, "Unit.getVelocity", false),
331            in_air: HistStat::new(&in_air, "Unit.inAir", false),
332            land_get_height: HistStat::new(&land_get_height, "Land.getHeight", false),
333            land_is_visible: HistStat::new(&land_is_visible, "Land.isVisible", false),
334            timer_get_abs_time: HistStat::new(&timer_get_abs_time, "Timer.getAbsTime", false),
335            timer_get_time: HistStat::new(&timer_get_time, "Timer.getTime", false),
336            timer_get_time0: HistStat::new(&timer_get_time0, "Timer.getTime0", false),
337            timer_remove_function: HistStat::new(
338                &timer_remove_function,
339                "Timer.removeFunction",
340                false,
341            ),
342            timer_schedule_function: HistStat::new(
343                &timer_schedule_function,
344                "Timer.scheduleFunction",
345                false,
346            ),
347            unit_get_by_name: HistStat::new(&unit_get_by_name, "Unit.getByName", false),
348            unit_get_desc: HistStat::new(&unit_get_desc, "Unit.getDesc", false),
349            unit_is_exist: HistStat::new(&unit_is_exist, "Unit.isExist", false),
350        }
351    }
352}
353
354#[derive(Debug, Clone, Copy)]
355pub struct PerfStat {
356    pub get_position: HistStat,
357    pub get_point: HistStat,
358    pub get_velocity: HistStat,
359    pub in_air: HistStat,
360    pub get_ammo: HistStat,
361    pub add_group: HistStat,
362    pub add_static_object: HistStat,
363    pub unit_is_exist: HistStat,
364    pub unit_get_by_name: HistStat,
365    pub unit_get_desc: HistStat,
366    pub land_is_visible: HistStat,
367    pub land_get_height: HistStat,
368    pub timer_schedule_function: HistStat,
369    pub timer_remove_function: HistStat,
370    pub timer_get_time: HistStat,
371    pub timer_get_abs_time: HistStat,
372    pub timer_get_time0: HistStat,
373}
374
375impl Default for PerfStat {
376    fn default() -> Self {
377        PerfStat {
378            get_position: HistStat::empty("Unit.getPosition", false),
379            add_group: HistStat::empty("Coalition.addGroup", false),
380            add_static_object: HistStat::empty("Coalition.addStaticObject", false),
381            get_ammo: HistStat::empty("Unit.getAmmo", false),
382            get_point: HistStat::empty("Unit.getPoint", false),
383            get_velocity: HistStat::empty("Unit.getVelocity", false),
384            in_air: HistStat::empty("Unit.inAir", false),
385            land_get_height: HistStat::empty("Land.getHeight", false),
386            land_is_visible: HistStat::empty("Land.isVisible", false),
387            timer_get_abs_time: HistStat::empty("Timer.getAbsTime", false),
388            timer_get_time: HistStat::empty("Timer.getTime", false),
389            timer_get_time0: HistStat::empty("Timer.getTime0", false),
390            timer_remove_function: HistStat::empty("Timer.removeFunction", false),
391            timer_schedule_function: HistStat::empty("Timer.scheduleFunction", false),
392            unit_get_by_name: HistStat::empty("Unit.getByName", false),
393            unit_get_desc: HistStat::empty("Unit.getDesc", false),
394            unit_is_exist: HistStat::empty("Unit.isExist", false),
395        }
396    }
397}
398
399impl PerfStat {
400    pub fn log(&self) {
401        use std::cmp::max;
402        let Self {
403            get_position,
404            get_point,
405            get_velocity,
406            in_air,
407            get_ammo,
408            add_group,
409            add_static_object,
410            unit_is_exist,
411            unit_get_by_name,
412            unit_get_desc,
413            land_is_visible,
414            land_get_height,
415            timer_schedule_function,
416            timer_remove_function,
417            timer_get_time,
418            timer_get_abs_time,
419            timer_get_time0,
420        } = self;
421        let stats = [
422            get_position,
423            get_point,
424            get_velocity,
425            in_air,
426            get_ammo,
427            add_group,
428            add_static_object,
429            unit_is_exist,
430            unit_get_by_name,
431            unit_get_desc,
432            land_is_visible,
433            land_get_height,
434            timer_schedule_function,
435            timer_remove_function,
436            timer_get_time,
437            timer_get_abs_time,
438            timer_get_time0,
439        ];
440        let max_len = stats.iter().fold(0, |l, st| max(l, st.name.len()));
441        for st in stats {
442            st.log(max_len);
443        }
444    }
445}
446
447#[cfg(feature = "perf")]
448#[macro_export]
449macro_rules! record_perf {
450    ($key:ident, $e:expr) => {{
451        use crate::perf::{Perf, Snap};
452        use std::sync::Arc;
453        let t = unsafe { Perf::get_mut() };
454        let t = Arc::make_mut(&mut t.0); // takes about 20ns if we don't need to clone
455        let mut snap = Snap::new(&mut t.$key);
456        let res = $e;
457        snap.commit();
458        res
459    }};
460}
461
462#[cfg(not(feature = "perf"))]
463#[macro_export]
464macro_rules! record_perf {
465    ($key:ident, $e:expr) => {
466        $e
467    };
468}
469
470pub fn record_perf(h: &mut HistogramSer, start_ts: DateTime<Utc>) {
471    if let Some(ns) = (Utc::now() - start_ts).num_nanoseconds() {
472        if ns >= 1 && ns <= 1_000_000_000 {
473            **h += ns as u64;
474        }
475    }
476}