1use 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); 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}