├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── p1 ├── Cargo.toml └── src │ └── main.rs ├── p2 ├── Cargo.toml └── src │ └── main.rs ├── p3 ├── Cargo.toml └── src │ ├── main.rs │ └── metrics.rs ├── p4 ├── Cargo.toml └── src │ ├── main.rs │ └── metrics.rs ├── p5 ├── Cargo.toml └── src │ ├── main.rs │ └── metrics.rs ├── p6 ├── Cargo.toml └── src │ ├── main.rs │ └── metrics.rs ├── p7 ├── Cargo.toml └── src │ ├── main.rs │ └── metrics.rs └── p8 ├── Cargo.toml ├── add_metrics ├── Cargo.toml └── src │ └── lib.rs └── src ├── main.rs └── metrics.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | /.idea/ 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "p1", 4 | "p2", 5 | "p3", 6 | "p4", 7 | "p5", 8 | "p6", 9 | "p7", 10 | "p8", 11 | ] 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Wenxuan Shi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Workshop 2 | 3 | ## P1 4 | 5 | A simple Actix-web application. 6 | 7 | ## P2 8 | 9 | Provide in-memory KV get / set via HTTP API. 10 | 11 | ## P3 12 | 13 | Collect metrics for the application. 14 | 15 | ## P4 16 | 17 | Allow Prometheus to collect metrics from the application. 18 | 19 | ## P5 20 | 21 | Performance when there are many metrics. 22 | 23 | ## P6 24 | 25 | Improve performance by using integer metrics and static metrics. 26 | 27 | ## P7 28 | 29 | Improve performance by using thread local metrics. 30 | -------------------------------------------------------------------------------- /p1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "p1" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | actix-web = "1.0.0-alpha" 8 | -------------------------------------------------------------------------------- /p1/src/main.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{web, App, HttpServer, Responder}; 2 | 3 | fn index() -> impl Responder { 4 | "It works!" 5 | } 6 | 7 | fn main() -> std::io::Result<()> { 8 | HttpServer::new(|| App::new().service(web::resource("/").to(index))) 9 | .bind("0.0.0.0:8088")? 10 | .run() 11 | } 12 | -------------------------------------------------------------------------------- /p2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "p2" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | actix-web = "1.0.0-alpha" 8 | evmap = "4.2.2" 9 | -------------------------------------------------------------------------------- /p2/src/main.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{web, App, HttpServer, Responder}; 2 | use evmap::{ReadHandle, WriteHandle}; 3 | use std::sync::{Arc, Mutex}; 4 | 5 | struct KvMap { 6 | pub read_handle: ReadHandle, 7 | pub write_handle: Arc>>, 8 | } 9 | 10 | fn api_get(map: web::Data, param: web::Path) -> impl Responder { 11 | let key = &*param; 12 | map.read_handle 13 | .get_and(key, |vals| format!("Key {} = {}", key, vals[0])) 14 | .unwrap_or_else(|| format!("Key {} does not exist", key)) 15 | } 16 | 17 | fn api_set(map: web::Data, param: web::Path<(String, String)>) -> impl Responder { 18 | let mut write_handle = map.write_handle.lock().unwrap(); 19 | let (key, value) = (¶m.0, ¶m.1); 20 | 21 | write_handle.update(key.to_owned(), value.to_owned()); 22 | write_handle.refresh(); 23 | format!("Put key {} = {}", key, value) 24 | } 25 | 26 | fn main() -> std::io::Result<()> { 27 | let (map_r, map_w_st) = evmap::new::(); 28 | let map_w = Arc::new(Mutex::new(map_w_st)); 29 | 30 | HttpServer::new(move || { 31 | App::new() 32 | .data(KvMap { 33 | read_handle: map_r.clone(), 34 | write_handle: map_w.clone(), 35 | }) 36 | .service(web::resource("/get/{key}").to(api_get)) 37 | .service(web::resource("/set/{key}/{value}").to(api_set)) 38 | }) 39 | .bind("0.0.0.0:8088")? 40 | .run() 41 | } 42 | -------------------------------------------------------------------------------- /p3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "p3" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | actix-web = "1.0.0-alpha" 8 | prometheus = "0.5" 9 | lazy_static = "1.3" 10 | evmap = "4.2.2" 11 | -------------------------------------------------------------------------------- /p3/src/main.rs: -------------------------------------------------------------------------------- 1 | mod metrics; 2 | 3 | use actix_web::{web, App, HttpServer, Responder}; 4 | use evmap::{ReadHandle, WriteHandle}; 5 | use std::sync::{Arc, Mutex}; 6 | 7 | use self::metrics::*; 8 | 9 | struct KvMap { 10 | pub read_handle: ReadHandle, 11 | pub write_handle: Arc>>, 12 | } 13 | 14 | fn api_get(map: web::Data, param: web::Path) -> impl Responder { 15 | let _timer = HTTP_REQUEST_DURATION 16 | .with_label_values(&["get"]) 17 | .start_timer(); 18 | 19 | let key = &*param; 20 | KEY_FLOW 21 | .with_label_values(&["read"]) 22 | .inc_by(key.len() as f64); 23 | 24 | map.read_handle 25 | .get_and(key, |vals| { 26 | VALUE_FLOW 27 | .with_label_values(&["read"]) 28 | .inc_by(vals[0].len() as f64); 29 | format!("Key {} = {}", key, vals[0]) 30 | }) 31 | .unwrap_or_else(|| format!("Key {} does not exist", key)) 32 | } 33 | 34 | fn api_set(map: web::Data, param: web::Path<(String, String)>) -> impl Responder { 35 | let _timer = HTTP_REQUEST_DURATION 36 | .with_label_values(&["set"]) 37 | .start_timer(); 38 | 39 | let mut write_handle = map.write_handle.lock().unwrap(); 40 | let (key, value) = (¶m.0, ¶m.1); 41 | KEY_FLOW 42 | .with_label_values(&["write"]) 43 | .inc_by(key.len() as f64); 44 | VALUE_FLOW 45 | .with_label_values(&["write"]) 46 | .inc_by(value.len() as f64); 47 | 48 | write_handle.update(key.to_owned(), value.to_owned()); 49 | write_handle.refresh(); 50 | format!("Put key {} = {}", key, value) 51 | } 52 | 53 | fn main() -> std::io::Result<()> { 54 | let (map_r, map_w_st) = evmap::new::(); 55 | let map_w = Arc::new(Mutex::new(map_w_st)); 56 | 57 | HttpServer::new(move || { 58 | App::new() 59 | .data(KvMap { 60 | read_handle: map_r.clone(), 61 | write_handle: map_w.clone(), 62 | }) 63 | .service(web::resource("/get/{key}").to(api_get)) 64 | .service(web::resource("/set/{key}/{value}").to(api_set)) 65 | }) 66 | .bind("0.0.0.0:8088")? 67 | .run() 68 | } 69 | -------------------------------------------------------------------------------- /p3/src/metrics.rs: -------------------------------------------------------------------------------- 1 | use prometheus::*; 2 | 3 | lazy_static::lazy_static! { 4 | pub static ref HTTP_REQUEST_DURATION: HistogramVec = register_histogram_vec!( 5 | "workshop_http_request_duration", 6 | "HTTP request durations", 7 | &["endpoint"], 8 | exponential_buckets(0.0005, 2.0, 20).unwrap() 9 | ).unwrap(); 10 | 11 | pub static ref KEY_FLOW: CounterVec = register_counter_vec!( 12 | "workshop_key_flow", 13 | "Number of flowed key bytes", 14 | &["op"] 15 | ).unwrap(); 16 | 17 | pub static ref VALUE_FLOW: CounterVec = register_counter_vec!( 18 | "workshop_value_flow", 19 | "Number of flowed value bytes", 20 | &["op"] 21 | ).unwrap(); 22 | } 23 | -------------------------------------------------------------------------------- /p4/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "p4" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | actix-web = "1.0.0-alpha" 8 | prometheus = "0.5" 9 | lazy_static = "1.3" 10 | evmap = "4.2.2" 11 | -------------------------------------------------------------------------------- /p4/src/main.rs: -------------------------------------------------------------------------------- 1 | mod metrics; 2 | 3 | use actix_web::{web, App, HttpServer, Responder}; 4 | use evmap::{ReadHandle, WriteHandle}; 5 | use std::sync::{Arc, Mutex}; 6 | 7 | use self::metrics::*; 8 | 9 | struct KvMap { 10 | pub read_handle: ReadHandle, 11 | pub write_handle: Arc>>, 12 | } 13 | 14 | fn api_get(map: web::Data, param: web::Path) -> impl Responder { 15 | let _timer = HTTP_REQUEST_DURATION 16 | .with_label_values(&["get"]) 17 | .start_timer(); 18 | 19 | let key = &*param; 20 | KEY_FLOW 21 | .with_label_values(&["read"]) 22 | .inc_by(key.len() as f64); 23 | 24 | map.read_handle 25 | .get_and(key, |vals| { 26 | VALUE_FLOW 27 | .with_label_values(&["read"]) 28 | .inc_by(vals[0].len() as f64); 29 | format!("Key {} = {}", key, vals[0]) 30 | }) 31 | .unwrap_or_else(|| format!("Key {} does not exist", key)) 32 | } 33 | 34 | fn api_set(map: web::Data, param: web::Path<(String, String)>) -> impl Responder { 35 | let _timer = HTTP_REQUEST_DURATION 36 | .with_label_values(&["set"]) 37 | .start_timer(); 38 | 39 | let mut write_handle = map.write_handle.lock().unwrap(); 40 | let (key, value) = (¶m.0, ¶m.1); 41 | KEY_FLOW 42 | .with_label_values(&["write"]) 43 | .inc_by(key.len() as f64); 44 | VALUE_FLOW 45 | .with_label_values(&["write"]) 46 | .inc_by(value.len() as f64); 47 | 48 | write_handle.update(key.to_owned(), value.to_owned()); 49 | write_handle.refresh(); 50 | format!("Put key {} = {}", key, value) 51 | } 52 | 53 | fn api_metrics() -> impl Responder { 54 | use prometheus::{Encoder, TextEncoder}; 55 | 56 | let _timer = HTTP_REQUEST_DURATION 57 | .with_label_values(&["metrics"]) 58 | .start_timer(); 59 | 60 | let metric_families = prometheus::gather(); 61 | let mut buffer = vec![]; 62 | let encoder = TextEncoder::new(); 63 | encoder.encode(&metric_families, &mut buffer).unwrap(); 64 | 65 | String::from_utf8(buffer).unwrap() 66 | } 67 | 68 | fn main() -> std::io::Result<()> { 69 | let (map_r, map_w_st) = evmap::new::(); 70 | let map_w = Arc::new(Mutex::new(map_w_st)); 71 | 72 | HttpServer::new(move || { 73 | App::new() 74 | .data(KvMap { 75 | read_handle: map_r.clone(), 76 | write_handle: map_w.clone(), 77 | }) 78 | .service(web::resource("/get/{key}").to(api_get)) 79 | .service(web::resource("/set/{key}/{value}").to(api_set)) 80 | .service(web::resource("/metrics").to(api_metrics)) 81 | }) 82 | .bind("0.0.0.0:8088")? 83 | .run() 84 | } 85 | -------------------------------------------------------------------------------- /p4/src/metrics.rs: -------------------------------------------------------------------------------- 1 | use prometheus::*; 2 | 3 | lazy_static::lazy_static! { 4 | pub static ref HTTP_REQUEST_DURATION: HistogramVec = register_histogram_vec!( 5 | "workshop_http_request_duration", 6 | "HTTP request durations", 7 | &["endpoint"], 8 | exponential_buckets(0.0005, 2.0, 20).unwrap() 9 | ).unwrap(); 10 | 11 | pub static ref KEY_FLOW: CounterVec = register_counter_vec!( 12 | "workshop_key_flow", 13 | "Number of flowed key bytes", 14 | &["op"] 15 | ).unwrap(); 16 | 17 | pub static ref VALUE_FLOW: CounterVec = register_counter_vec!( 18 | "workshop_value_flow", 19 | "Number of flowed value bytes", 20 | &["op"] 21 | ).unwrap(); 22 | } 23 | -------------------------------------------------------------------------------- /p5/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "p5" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | actix-web = "1.0.0-alpha" 8 | prometheus = "0.5" 9 | lazy_static = "1.3" 10 | evmap = "4.2.2" 11 | -------------------------------------------------------------------------------- /p5/src/main.rs: -------------------------------------------------------------------------------- 1 | mod metrics; 2 | 3 | use actix_web::{web, App, HttpServer, Responder}; 4 | use evmap::{ReadHandle, WriteHandle}; 5 | use std::sync::{Arc, Mutex}; 6 | 7 | use self::metrics::*; 8 | 9 | struct KvMap { 10 | pub read_handle: ReadHandle, 11 | pub write_handle: Arc>>, 12 | } 13 | 14 | fn api_get(map: web::Data, param: web::Path) -> impl Responder { 15 | let _timer = HTTP_REQUEST_DURATION 16 | .with_label_values(&["get"]) 17 | .start_timer(); 18 | 19 | let key = &*param; 20 | for _ in 0..100 { 21 | // Simulating a very fast api endpoint, or a lot of metrics 22 | KEY_FLOW 23 | .with_label_values(&["read"]) 24 | .inc_by(key.len() as f64); 25 | } 26 | 27 | map.read_handle 28 | .get_and(key, |vals| { 29 | for _ in 0..100 { 30 | VALUE_FLOW 31 | .with_label_values(&["read"]) 32 | .inc_by(vals[0].len() as f64); 33 | } 34 | format!("Key {} = {}", key, vals[0]) 35 | }) 36 | .unwrap_or_else(|| format!("Key {} does not exist", key)) 37 | } 38 | 39 | fn api_set(map: web::Data, param: web::Path<(String, String)>) -> impl Responder { 40 | let _timer = HTTP_REQUEST_DURATION 41 | .with_label_values(&["set"]) 42 | .start_timer(); 43 | 44 | let mut write_handle = map.write_handle.lock().unwrap(); 45 | let (key, value) = (¶m.0, ¶m.1); 46 | for _ in 0..100 { 47 | KEY_FLOW 48 | .with_label_values(&["write"]) 49 | .inc_by(key.len() as f64); 50 | VALUE_FLOW 51 | .with_label_values(&["write"]) 52 | .inc_by(value.len() as f64); 53 | } 54 | 55 | write_handle.update(key.to_owned(), value.to_owned()); 56 | write_handle.refresh(); 57 | format!("Put key {} = {}", key, value) 58 | } 59 | 60 | fn api_metrics() -> impl Responder { 61 | use prometheus::{Encoder, TextEncoder}; 62 | 63 | let _timer = HTTP_REQUEST_DURATION 64 | .with_label_values(&["metrics"]) 65 | .start_timer(); 66 | 67 | let metric_families = prometheus::gather(); 68 | let mut buffer = vec![]; 69 | let encoder = TextEncoder::new(); 70 | encoder.encode(&metric_families, &mut buffer).unwrap(); 71 | 72 | String::from_utf8(buffer).unwrap() 73 | } 74 | 75 | fn main() -> std::io::Result<()> { 76 | let (map_r, map_w_st) = evmap::new::(); 77 | let map_w = Arc::new(Mutex::new(map_w_st)); 78 | 79 | HttpServer::new(move || { 80 | App::new() 81 | .data(KvMap { 82 | read_handle: map_r.clone(), 83 | write_handle: map_w.clone(), 84 | }) 85 | .service(web::resource("/get/{key}").to(api_get)) 86 | .service(web::resource("/set/{key}/{value}").to(api_set)) 87 | .service(web::resource("/metrics").to(api_metrics)) 88 | }) 89 | .bind("0.0.0.0:8088")? 90 | .run() 91 | } 92 | -------------------------------------------------------------------------------- /p5/src/metrics.rs: -------------------------------------------------------------------------------- 1 | use prometheus::*; 2 | 3 | lazy_static::lazy_static! { 4 | pub static ref HTTP_REQUEST_DURATION: HistogramVec = register_histogram_vec!( 5 | "workshop_http_request_duration", 6 | "HTTP request durations", 7 | &["endpoint"], 8 | exponential_buckets(0.0005, 2.0, 20).unwrap() 9 | ).unwrap(); 10 | 11 | pub static ref KEY_FLOW: CounterVec = register_counter_vec!( 12 | "workshop_key_flow", 13 | "Number of flowed key bytes", 14 | &["op"] 15 | ).unwrap(); 16 | 17 | pub static ref VALUE_FLOW: CounterVec = register_counter_vec!( 18 | "workshop_value_flow", 19 | "Number of flowed value bytes", 20 | &["op"] 21 | ).unwrap(); 22 | } 23 | -------------------------------------------------------------------------------- /p6/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "p6" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | actix-web = "1.0.0-alpha" 8 | prometheus = "0.5" 9 | lazy_static = "1.3" 10 | evmap = "4.2.2" 11 | -------------------------------------------------------------------------------- /p6/src/main.rs: -------------------------------------------------------------------------------- 1 | mod metrics; 2 | 3 | use actix_web::{web, App, HttpServer, Responder}; 4 | use evmap::{ReadHandle, WriteHandle}; 5 | use std::sync::{Arc, Mutex}; 6 | 7 | use self::metrics::*; 8 | 9 | struct KvMap { 10 | pub read_handle: ReadHandle, 11 | pub write_handle: Arc>>, 12 | } 13 | 14 | fn api_get(map: web::Data, param: web::Path) -> impl Responder { 15 | let _timer = HTTP_REQUEST_DURATION_GET.start_timer(); 16 | 17 | let key = &*param; 18 | for _ in 0..100 { 19 | // Simulating a very fast api endpoint, or a lot of metrics 20 | KEY_FLOW_READ.inc_by(key.len() as i64); 21 | } 22 | 23 | map.read_handle 24 | .get_and(key, |vals| { 25 | for _ in 0..100 { 26 | VALUE_FLOW_READ.inc_by(vals[0].len() as i64); 27 | } 28 | format!("Key {} = {}", key, vals[0]) 29 | }) 30 | .unwrap_or_else(|| format!("Key {} does not exist", key)) 31 | } 32 | 33 | fn api_set(map: web::Data, param: web::Path<(String, String)>) -> impl Responder { 34 | let _timer = HTTP_REQUEST_DURATION_SET.start_timer(); 35 | 36 | let mut write_handle = map.write_handle.lock().unwrap(); 37 | let (key, value) = (¶m.0, ¶m.1); 38 | for _ in 0..100 { 39 | KEY_FLOW_WRITE.inc_by(key.len() as i64); 40 | VALUE_FLOW_WRITE.inc_by(value.len() as i64); 41 | } 42 | 43 | write_handle.update(key.to_owned(), value.to_owned()); 44 | write_handle.refresh(); 45 | format!("Put key {} = {}", key, value) 46 | } 47 | 48 | fn api_metrics() -> impl Responder { 49 | use prometheus::{Encoder, TextEncoder}; 50 | 51 | let _timer = HTTP_REQUEST_DURATION_METRICS.start_timer(); 52 | 53 | let metric_families = prometheus::gather(); 54 | let mut buffer = vec![]; 55 | let encoder = TextEncoder::new(); 56 | encoder.encode(&metric_families, &mut buffer).unwrap(); 57 | 58 | String::from_utf8(buffer).unwrap() 59 | } 60 | 61 | fn main() -> std::io::Result<()> { 62 | let (map_r, map_w_st) = evmap::new::(); 63 | let map_w = Arc::new(Mutex::new(map_w_st)); 64 | 65 | HttpServer::new(move || { 66 | App::new() 67 | .data(KvMap { 68 | read_handle: map_r.clone(), 69 | write_handle: map_w.clone(), 70 | }) 71 | .service(web::resource("/get/{key}").to(api_get)) 72 | .service(web::resource("/set/{key}/{value}").to(api_set)) 73 | .service(web::resource("/metrics").to(api_metrics)) 74 | }) 75 | .bind("0.0.0.0:8088")? 76 | .run() 77 | } 78 | -------------------------------------------------------------------------------- /p6/src/metrics.rs: -------------------------------------------------------------------------------- 1 | use prometheus::*; 2 | 3 | lazy_static::lazy_static! { 4 | pub static ref HTTP_REQUEST_DURATION: HistogramVec = register_histogram_vec!( 5 | "workshop_http_request_duration", 6 | "HTTP request durations", 7 | &["endpoint"], 8 | exponential_buckets(0.0005, 2.0, 20).unwrap() 9 | ).unwrap(); 10 | pub static ref HTTP_REQUEST_DURATION_GET: Histogram = HTTP_REQUEST_DURATION.with_label_values(&["get"]); 11 | pub static ref HTTP_REQUEST_DURATION_SET: Histogram = HTTP_REQUEST_DURATION.with_label_values(&["set"]); 12 | pub static ref HTTP_REQUEST_DURATION_METRICS: Histogram = HTTP_REQUEST_DURATION.with_label_values(&["metrics"]); 13 | 14 | pub static ref KEY_FLOW: IntCounterVec = register_int_counter_vec!( 15 | "workshop_key_flow", 16 | "Number of flowed key bytes", 17 | &["op"] 18 | ).unwrap(); 19 | pub static ref KEY_FLOW_READ: IntCounter = KEY_FLOW.with_label_values(&["read"]); 20 | pub static ref KEY_FLOW_WRITE: IntCounter = KEY_FLOW.with_label_values(&["write"]); 21 | 22 | pub static ref VALUE_FLOW: IntCounterVec = register_int_counter_vec!( 23 | "workshop_value_flow", 24 | "Number of flowed value bytes", 25 | &["op"] 26 | ).unwrap(); 27 | pub static ref VALUE_FLOW_READ: IntCounter = VALUE_FLOW.with_label_values(&["read"]); 28 | pub static ref VALUE_FLOW_WRITE: IntCounter = VALUE_FLOW.with_label_values(&["write"]); 29 | } 30 | -------------------------------------------------------------------------------- /p7/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "p7" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | actix-web = "1.0.0-alpha" 8 | prometheus = "0.5" 9 | lazy_static = "1.3" 10 | evmap = "4.2.2" 11 | coarsetime = "0.1" -------------------------------------------------------------------------------- /p7/src/main.rs: -------------------------------------------------------------------------------- 1 | mod metrics; 2 | 3 | use actix_web::{web, App, HttpServer, Responder}; 4 | use evmap::{ReadHandle, WriteHandle}; 5 | use std::sync::{Arc, Mutex}; 6 | 7 | use self::metrics::*; 8 | 9 | struct KvMap { 10 | pub read_handle: ReadHandle, 11 | pub write_handle: Arc>>, 12 | } 13 | 14 | fn api_get(map: web::Data, param: web::Path) -> impl Responder { 15 | let timer = coarsetime::Instant::now(); 16 | may_flush_metrics(); 17 | 18 | let key = &*param; 19 | for _ in 0..100 { 20 | // Simulating a very fast api endpoint, or a lot of metrics 21 | TLS_KEY_FLOW_READ.with(|m| m.borrow_mut().inc_by(key.len() as i64)); 22 | } 23 | 24 | let respond = map 25 | .read_handle 26 | .get_and(key, |vals| { 27 | for _ in 0..100 { 28 | TLS_VALUE_FLOW_READ.with(|m| m.borrow_mut().inc_by(vals[0].len() as i64)); 29 | } 30 | format!("Key {} = {}", key, vals[0]) 31 | }) 32 | .unwrap_or_else(|| format!("Key {} does not exist", key)); 33 | 34 | TLS_HTTP_REQUEST_DURATION_GET.with(|m| m.observe(timer.elapsed().as_f64())); 35 | 36 | respond 37 | } 38 | 39 | fn api_set(map: web::Data, param: web::Path<(String, String)>) -> impl Responder { 40 | may_flush_metrics(); 41 | let timer = coarsetime::Instant::now(); 42 | 43 | let mut write_handle = map.write_handle.lock().unwrap(); 44 | let (key, value) = (¶m.0, ¶m.1); 45 | for _ in 0..100 { 46 | TLS_KEY_FLOW_READ.with(|m| m.borrow_mut().inc_by(key.len() as i64)); 47 | TLS_VALUE_FLOW_READ.with(|m| m.borrow_mut().inc_by(value.len() as i64)); 48 | } 49 | 50 | write_handle.update(key.to_owned(), value.to_owned()); 51 | write_handle.refresh(); 52 | 53 | let respond = format!("Put key {} = {}", key, value); 54 | 55 | TLS_HTTP_REQUEST_DURATION_SET.with(|m| m.observe(timer.elapsed().as_f64())); 56 | 57 | respond 58 | } 59 | 60 | fn api_metrics() -> impl Responder { 61 | use prometheus::{Encoder, TextEncoder}; 62 | 63 | let timer = coarsetime::Instant::now(); 64 | may_flush_metrics(); 65 | 66 | let metric_families = prometheus::gather(); 67 | let mut buffer = vec![]; 68 | let encoder = TextEncoder::new(); 69 | encoder.encode(&metric_families, &mut buffer).unwrap(); 70 | 71 | let respond = String::from_utf8(buffer).unwrap(); 72 | 73 | TLS_HTTP_REQUEST_DURATION_METRICS.with(|m| m.observe(timer.elapsed().as_f64())); 74 | 75 | respond 76 | } 77 | 78 | fn main() -> std::io::Result<()> { 79 | let (map_r, map_w_st) = evmap::new::(); 80 | let map_w = Arc::new(Mutex::new(map_w_st)); 81 | 82 | HttpServer::new(move || { 83 | App::new() 84 | .data(KvMap { 85 | read_handle: map_r.clone(), 86 | write_handle: map_w.clone(), 87 | }) 88 | .service(web::resource("/get/{key}").to(api_get)) 89 | .service(web::resource("/set/{key}/{value}").to(api_set)) 90 | .service(web::resource("/metrics").to(api_metrics)) 91 | }) 92 | .bind("0.0.0.0:8088")? 93 | .run() 94 | } 95 | -------------------------------------------------------------------------------- /p7/src/metrics.rs: -------------------------------------------------------------------------------- 1 | use coarsetime::Instant; 2 | use prometheus::local::*; 3 | use prometheus::*; 4 | use std::cell::{Cell, RefCell}; 5 | 6 | lazy_static::lazy_static! { 7 | static ref HTTP_REQUEST_DURATION: HistogramVec = register_histogram_vec!( 8 | "workshop_http_request_duration", 9 | "HTTP request durations", 10 | &["endpoint"], 11 | exponential_buckets(0.0005, 2.0, 20).unwrap() 12 | ).unwrap(); 13 | static ref HTTP_REQUEST_DURATION_GET: Histogram = HTTP_REQUEST_DURATION.with_label_values(&["get"]); 14 | static ref HTTP_REQUEST_DURATION_SET: Histogram = HTTP_REQUEST_DURATION.with_label_values(&["set"]); 15 | static ref HTTP_REQUEST_DURATION_METRICS: Histogram = HTTP_REQUEST_DURATION.with_label_values(&["metrics"]); 16 | 17 | static ref KEY_FLOW: IntCounterVec = register_int_counter_vec!( 18 | "workshop_key_flow", 19 | "Number of flowed key bytes", 20 | &["op"] 21 | ).unwrap(); 22 | static ref KEY_FLOW_READ: IntCounter = KEY_FLOW.with_label_values(&["read"]); 23 | static ref KEY_FLOW_WRITE: IntCounter = KEY_FLOW.with_label_values(&["write"]); 24 | 25 | static ref VALUE_FLOW: IntCounterVec = register_int_counter_vec!( 26 | "workshop_value_flow", 27 | "Number of flowed value bytes", 28 | &["op"] 29 | ).unwrap(); 30 | static ref VALUE_FLOW_READ: IntCounter = VALUE_FLOW.with_label_values(&["read"]); 31 | static ref VALUE_FLOW_WRITE: IntCounter = VALUE_FLOW.with_label_values(&["write"]); 32 | } 33 | 34 | thread_local! { 35 | static THREAD_LAST_TICK_TIME: Cell = Cell::new(Instant::now()); 36 | 37 | pub static TLS_HTTP_REQUEST_DURATION_GET: LocalHistogram = HTTP_REQUEST_DURATION_GET.local(); 38 | pub static TLS_HTTP_REQUEST_DURATION_SET: LocalHistogram = HTTP_REQUEST_DURATION_SET.local(); 39 | pub static TLS_HTTP_REQUEST_DURATION_METRICS: LocalHistogram = HTTP_REQUEST_DURATION_METRICS.local(); 40 | pub static TLS_KEY_FLOW_READ: RefCell = RefCell::new(KEY_FLOW_READ.local()); 41 | pub static TLS_KEY_FLOW_WRITE: RefCell = RefCell::new(KEY_FLOW_WRITE.local()); 42 | pub static TLS_VALUE_FLOW_READ: RefCell = RefCell::new(VALUE_FLOW_READ.local()); 43 | pub static TLS_VALUE_FLOW_WRITE: RefCell = RefCell::new(VALUE_FLOW_WRITE.local()); 44 | } 45 | 46 | pub fn may_flush_metrics() { 47 | THREAD_LAST_TICK_TIME.with(|tls_last_tick| { 48 | let now = Instant::now(); 49 | let last_tick = tls_last_tick.get(); 50 | if now.duration_since(last_tick).as_f64() < 1.0 { 51 | return; 52 | } 53 | tls_last_tick.set(now); 54 | TLS_HTTP_REQUEST_DURATION_GET.with(|m| m.flush()); 55 | TLS_HTTP_REQUEST_DURATION_SET.with(|m| m.flush()); 56 | TLS_HTTP_REQUEST_DURATION_METRICS.with(|m| m.flush()); 57 | TLS_KEY_FLOW_READ.with(|m| m.borrow_mut().flush()); 58 | TLS_KEY_FLOW_WRITE.with(|m| m.borrow_mut().flush()); 59 | TLS_VALUE_FLOW_READ.with(|m| m.borrow_mut().flush()); 60 | TLS_VALUE_FLOW_WRITE.with(|m| m.borrow_mut().flush()); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /p8/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "p8" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | actix-web = "1.0.0-alpha" 8 | prometheus = "0.5" 9 | lazy_static = "1.3" 10 | evmap = "4.2.2" 11 | coarsetime = "0.1" 12 | 13 | [dependencies.add_metrics] 14 | version = "0.1" 15 | path = "./add_metrics" 16 | -------------------------------------------------------------------------------- /p8/add_metrics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "add_metrics" 3 | version = "0.1.0" 4 | authors = ["blackanger "] 5 | edition = "2018" 6 | 7 | [lib] 8 | proc-macro = true 9 | 10 | [dependencies] 11 | proc-macro2 = "0.4" 12 | quote = "0.6" 13 | syn = { version = "0.15", features = ["full", "visit-mut"] } -------------------------------------------------------------------------------- /p8/add_metrics/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit="256"] 2 | 3 | #[macro_use] 4 | extern crate quote; 5 | 6 | #[macro_use] 7 | extern crate syn; 8 | 9 | extern crate proc_macro; 10 | extern crate proc_macro2; 11 | 12 | use proc_macro::TokenStream; 13 | use syn::{ 14 | Ident, ItemFn, 15 | }; 16 | 17 | #[proc_macro_attribute] 18 | pub fn flush_metrics(args: TokenStream, function: TokenStream) -> TokenStream { 19 | assert_eq!(args.is_empty(), false); 20 | 21 | let mut function = parse_macro_input!(function as ItemFn); 22 | 23 | let body = function.block; 24 | let fn_name = format!("{}", args); 25 | let fn_name = Ident::new(&fn_name, proc_macro2::Span::call_site()); 26 | function.block = Box::new(parse_quote!({ 27 | let __wrap = (move || { 28 | #fn_name(); 29 | #body 30 | })(); 31 | __wrap 32 | })); 33 | 34 | TokenStream::from(quote!(#function)) 35 | } -------------------------------------------------------------------------------- /p8/src/main.rs: -------------------------------------------------------------------------------- 1 | mod metrics; 2 | 3 | use actix_web::{web, App, HttpServer, Responder}; 4 | use evmap::{ReadHandle, WriteHandle}; 5 | use std::sync::{Arc, Mutex}; 6 | 7 | use self::metrics::*; 8 | use add_metrics::flush_metrics; 9 | 10 | struct KvMap { 11 | pub read_handle: ReadHandle, 12 | pub write_handle: Arc>>, 13 | } 14 | 15 | #[flush_metrics(may_flush_metrics)] 16 | fn api_get(map: web::Data, param: web::Path) -> impl Responder { 17 | let timer = coarsetime::Instant::now(); 18 | 19 | let key = &*param; 20 | for _ in 0..100 { 21 | // Simulating a very fast api endpoint, or a lot of metrics 22 | TLS_KEY_FLOW_READ.with(|m| m.borrow_mut().inc_by(key.len() as i64)); 23 | } 24 | 25 | let respond = map 26 | .read_handle 27 | .get_and(key, |vals| { 28 | for _ in 0..100 { 29 | TLS_VALUE_FLOW_READ.with(|m| m.borrow_mut().inc_by(vals[0].len() as i64)); 30 | } 31 | format!("Key {} = {}", key, vals[0]) 32 | }) 33 | .unwrap_or_else(|| format!("Key {} does not exist", key)); 34 | 35 | TLS_HTTP_REQUEST_DURATION_GET.with(|m| m.observe(timer.elapsed().as_f64())); 36 | 37 | respond 38 | } 39 | 40 | #[flush_metrics(may_flush_metrics)] 41 | fn api_set(map: web::Data, param: web::Path<(String, String)>) -> impl Responder { 42 | let timer = coarsetime::Instant::now(); 43 | 44 | let mut write_handle = map.write_handle.lock().unwrap(); 45 | let (key, value) = (¶m.0, ¶m.1); 46 | for _ in 0..100 { 47 | TLS_KEY_FLOW_READ.with(|m| m.borrow_mut().inc_by(key.len() as i64)); 48 | TLS_VALUE_FLOW_READ.with(|m| m.borrow_mut().inc_by(value.len() as i64)); 49 | } 50 | 51 | write_handle.update(key.to_owned(), value.to_owned()); 52 | write_handle.refresh(); 53 | 54 | let respond = format!("Put key {} = {}", key, value); 55 | 56 | TLS_HTTP_REQUEST_DURATION_SET.with(|m| m.observe(timer.elapsed().as_f64())); 57 | 58 | respond 59 | } 60 | 61 | #[flush_metrics(may_flush_metrics)] 62 | fn api_metrics() -> impl Responder { 63 | use prometheus::{Encoder, TextEncoder}; 64 | let timer = coarsetime::Instant::now(); 65 | let metric_families = prometheus::gather(); 66 | let mut buffer = vec![]; 67 | let encoder = TextEncoder::new(); 68 | encoder.encode(&metric_families, &mut buffer).unwrap(); 69 | 70 | let respond = String::from_utf8(buffer).unwrap(); 71 | 72 | TLS_HTTP_REQUEST_DURATION_METRICS.with(|m| m.observe(timer.elapsed().as_f64())); 73 | 74 | respond 75 | } 76 | 77 | fn main() -> std::io::Result<()> { 78 | let (map_r, map_w_st) = evmap::new::(); 79 | let map_w = Arc::new(Mutex::new(map_w_st)); 80 | 81 | HttpServer::new(move || { 82 | App::new() 83 | .data(KvMap { 84 | read_handle: map_r.clone(), 85 | write_handle: map_w.clone(), 86 | }) 87 | .service(web::resource("/get/{key}").to(api_get)) 88 | .service(web::resource("/set/{key}/{value}").to(api_set)) 89 | .service(web::resource("/metrics").to(api_metrics)) 90 | }) 91 | .bind("0.0.0.0:8088")? 92 | .run() 93 | } 94 | -------------------------------------------------------------------------------- /p8/src/metrics.rs: -------------------------------------------------------------------------------- 1 | use coarsetime::Instant; 2 | use prometheus::local::*; 3 | use prometheus::*; 4 | use std::cell::{Cell, RefCell}; 5 | 6 | lazy_static::lazy_static! { 7 | static ref HTTP_REQUEST_DURATION: HistogramVec = register_histogram_vec!( 8 | "workshop_http_request_duration", 9 | "HTTP request durations", 10 | &["endpoint"], 11 | exponential_buckets(0.0005, 2.0, 20).unwrap() 12 | ).unwrap(); 13 | static ref HTTP_REQUEST_DURATION_GET: Histogram = HTTP_REQUEST_DURATION.with_label_values(&["get"]); 14 | static ref HTTP_REQUEST_DURATION_SET: Histogram = HTTP_REQUEST_DURATION.with_label_values(&["set"]); 15 | static ref HTTP_REQUEST_DURATION_METRICS: Histogram = HTTP_REQUEST_DURATION.with_label_values(&["metrics"]); 16 | 17 | static ref KEY_FLOW: IntCounterVec = register_int_counter_vec!( 18 | "workshop_key_flow", 19 | "Number of flowed key bytes", 20 | &["op"] 21 | ).unwrap(); 22 | static ref KEY_FLOW_READ: IntCounter = KEY_FLOW.with_label_values(&["read"]); 23 | static ref KEY_FLOW_WRITE: IntCounter = KEY_FLOW.with_label_values(&["write"]); 24 | 25 | static ref VALUE_FLOW: IntCounterVec = register_int_counter_vec!( 26 | "workshop_value_flow", 27 | "Number of flowed value bytes", 28 | &["op"] 29 | ).unwrap(); 30 | static ref VALUE_FLOW_READ: IntCounter = VALUE_FLOW.with_label_values(&["read"]); 31 | static ref VALUE_FLOW_WRITE: IntCounter = VALUE_FLOW.with_label_values(&["write"]); 32 | } 33 | 34 | thread_local! { 35 | static THREAD_LAST_TICK_TIME: Cell = Cell::new(Instant::now()); 36 | 37 | pub static TLS_HTTP_REQUEST_DURATION_GET: LocalHistogram = HTTP_REQUEST_DURATION_GET.local(); 38 | pub static TLS_HTTP_REQUEST_DURATION_SET: LocalHistogram = HTTP_REQUEST_DURATION_SET.local(); 39 | pub static TLS_HTTP_REQUEST_DURATION_METRICS: LocalHistogram = HTTP_REQUEST_DURATION_METRICS.local(); 40 | pub static TLS_KEY_FLOW_READ: RefCell = RefCell::new(KEY_FLOW_READ.local()); 41 | pub static TLS_KEY_FLOW_WRITE: RefCell = RefCell::new(KEY_FLOW_WRITE.local()); 42 | pub static TLS_VALUE_FLOW_READ: RefCell = RefCell::new(VALUE_FLOW_READ.local()); 43 | pub static TLS_VALUE_FLOW_WRITE: RefCell = RefCell::new(VALUE_FLOW_WRITE.local()); 44 | } 45 | 46 | pub fn may_flush_metrics() { 47 | THREAD_LAST_TICK_TIME.with(|tls_last_tick| { 48 | let now = Instant::now(); 49 | let last_tick = tls_last_tick.get(); 50 | if now.duration_since(last_tick).as_f64() < 1.0 { 51 | return; 52 | } 53 | tls_last_tick.set(now); 54 | TLS_HTTP_REQUEST_DURATION_GET.with(|m| m.flush()); 55 | TLS_HTTP_REQUEST_DURATION_SET.with(|m| m.flush()); 56 | TLS_HTTP_REQUEST_DURATION_METRICS.with(|m| m.flush()); 57 | TLS_KEY_FLOW_READ.with(|m| m.borrow_mut().flush()); 58 | TLS_KEY_FLOW_WRITE.with(|m| m.borrow_mut().flush()); 59 | TLS_VALUE_FLOW_READ.with(|m| m.borrow_mut().flush()); 60 | TLS_VALUE_FLOW_WRITE.with(|m| m.borrow_mut().flush()); 61 | }); 62 | } 63 | --------------------------------------------------------------------------------