Skip to main content

actix_web_isucon_measured/
lib.rs

1use 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