actix_web_isucon_measured/
lib.rs1use std::future::{ready, Ready};
2use std::sync::{Arc, RwLock};
3use std::collections::HashMap;
4use std::time::{Instant, Duration};
5use std::default::Default;
6use actix_web::dev::{self, Service, ServiceRequest, ServiceResponse, Transform};
7use actix_web::Error;
8use futures_util::future::LocalBoxFuture;
9
10type DataType = Arc<RwLock<HashMap<(String, String), Vec<Duration>>>>;
11#[derive(Debug, Clone, Default)]
12pub struct Measured {
13 pub mdata: DataType
14}
15pub enum SortOptions {
16 PATH,
17 METHOD,
18 CNT,
19 SUM,
20 AVG,
21 MAX,
22 MIN,
23}
24impl Measured {
25 pub fn tsv(&self, sortby: SortOptions) -> String {
26 let mdata = self.mdata.read().unwrap();
27 let mut v = vec![];
28 for ((path, method), val) in (*mdata).iter() {
29 let cnt = val.len();
30 let sum = val.iter().sum::<Duration>().as_millis();
31 let avg = sum / cnt as u128;
32 let min = val.iter().min().unwrap().as_millis();
33 let max = val.iter().max().unwrap().as_millis();
34 v.push((path, method, cnt, sum, avg, max, min));
35 }
36
37 match sortby {
38 SortOptions::PATH => v.sort_by(|a, b| b.0.partial_cmp(a.0).unwrap()),
39 SortOptions::METHOD => v.sort_by(|a, b| b.1.partial_cmp(a.1).unwrap()),
40 SortOptions::CNT => v.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap()),
41 SortOptions::SUM => v.sort_by(|a, b| b.3.partial_cmp(&a.3).unwrap()),
42 SortOptions::AVG => v.sort_by(|a, b| b.4.partial_cmp(&a.4).unwrap()),
43 SortOptions::MAX => v.sort_by(|a, b| b.5.partial_cmp(&a.5).unwrap()),
44 SortOptions::MIN => v.sort_by(|a, b| b.6.partial_cmp(&a.6).unwrap()),
45 }
46
47 let mut tsv = String::from("PATH\tMETHOD\tCNT\tSUM\tAVG\tMAX\tMIN\n");
48 for (path, method, cnt, sum, avg, max, min) in v.iter() {
49 tsv.push_str(format!("{path}\t{method}\t{cnt}\t{sum}\t{avg}\t{max}\t{min}\n").as_str());
50 }
51 tsv
52 }
53
54 pub fn clear(&self) {
55 self.mdata.write().unwrap().clear();
56 }
57}
58
59impl<S, B> Transform<S, ServiceRequest> for Measured
60where
61 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
62 S::Future: 'static,
63 B: 'static,
64{
65 type Response = ServiceResponse<B>;
66 type Error = Error;
67 type InitError = ();
68 type Transform = MeasuredMiddleware<S>;
69 type Future = Ready<Result<Self::Transform, Self::InitError>>;
70
71 fn new_transform(&self, service: S) -> Self::Future {
72 ready(Ok(MeasuredMiddleware {
73 mdata: self.mdata.clone(),
74 service
75 }))
76 }
77}
78
79
80pub struct MeasuredMiddleware<S> {
81 mdata: DataType,
82 service: S,
83}
84
85impl<S, B> Service<ServiceRequest> for MeasuredMiddleware<S>
86where
87 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
88 S::Future: 'static,
89 B: 'static,
90{
91 type Response = ServiceResponse<B>;
92 type Error = Error;
93 type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
94
95 dev::forward_ready!(service);
96
97 fn call(&self, req: ServiceRequest) -> Self::Future {
98 let mdata = self.mdata.clone();
99 let path = req.match_pattern().unwrap_or(req.path().to_string());
100 let method = req.method().to_string();
101
102 let fut = self.service.call(req);
103 Box::pin(async move {
104 let start = Instant::now();
105 let res = fut.await?;
106 mdata.write().unwrap().entry((path, method)).or_default().push(start.elapsed());
107 Ok(res)
108 })
109 }
110}
111
112