Initial commit
This commit is contained in:
commit
e4aff3f39f
5 files changed
+1049
No files matched your search
+195
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* metric2fs-rs
|
||||
*/
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use filetime::{FileTime,set_file_mtime};
|
||||
use globset::Glob;
|
||||
|
||||
use hyper::{Body, Request, Response, Server, Method, StatusCode};
|
||||
use hyper::body;
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
|
||||
extern crate pretty_env_logger;
|
||||
#[macro_use] extern crate log;
|
||||
|
||||
fn write_metric(metric: String) {
|
||||
let dir = metric.replace(".", "/");
|
||||
|
||||
let mut path = PathBuf::new();
|
||||
path.push(&dir);
|
||||
|
||||
let res = fs::create_dir_all(&path);
|
||||
if let Err(res) = res {
|
||||
warn!("Could not create directory: {}", res);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
path.push(".md");
|
||||
|
||||
let res = OpenOptions::new().create(true).write(true).open(&path);
|
||||
if let Err(res) = res {
|
||||
warn!("Could not create metric: {}", res);
|
||||
}
|
||||
|
||||
let res = set_file_mtime(&path, FileTime::now());
|
||||
if let Err(res) = res {
|
||||
warn!("Could not update metric's timestamp: {}", res);
|
||||
}
|
||||
|
||||
debug!("Created metric: {}", metric)
|
||||
}
|
||||
|
||||
fn fetch_metric(expr: String) -> Result<Vec<String>> {
|
||||
let components = expr.split(".").collect::<Vec<&str>>();
|
||||
|
||||
let mut init_path = PathBuf::new();
|
||||
init_path.push(".");
|
||||
|
||||
info!("Searching metrics for expr: {}", expr);
|
||||
|
||||
let mut pathes = vec![];
|
||||
pathes.push(init_path);
|
||||
|
||||
for component in components.iter() {
|
||||
let gs = Glob::new(component)?.compile_matcher();
|
||||
|
||||
let mut new_pathes = vec![];
|
||||
for path in pathes.iter_mut() {
|
||||
if !path.is_dir() {
|
||||
continue;
|
||||
}
|
||||
|
||||
for entry in fs::read_dir(path)? {
|
||||
let dir = entry?;
|
||||
|
||||
if gs.is_match(dir.file_name()) {
|
||||
new_pathes.push(dir.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pathes = new_pathes;
|
||||
}
|
||||
|
||||
let mut metrics : Vec<String> = vec![];
|
||||
|
||||
for path in pathes {
|
||||
let mut pb = PathBuf::new();
|
||||
|
||||
pb.push(path);
|
||||
pb.push(".md");
|
||||
|
||||
let stat = fs::metadata(&pb);
|
||||
if let Ok(_stat) = stat {
|
||||
let metric_name = String::from(pb.to_str().unwrap());
|
||||
let metric_name = metric_name.strip_prefix("./").unwrap();
|
||||
let metric_name = metric_name.strip_suffix("/.md").unwrap();
|
||||
let metric_name = metric_name.replace("/", ".");
|
||||
|
||||
debug!("Found metric: {}", metric_name);
|
||||
metrics.push(metric_name.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
info!("Got {} metric(s).", metrics.len());
|
||||
|
||||
Ok(metrics)
|
||||
}
|
||||
|
||||
async fn handlers(req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||
let mut response = Response::new(Body::empty());
|
||||
|
||||
match (req.method(), req.uri().path()) {
|
||||
(&Method::GET, "/metric") => {
|
||||
let query = req.uri().query();
|
||||
let mut hm = HashMap::new();
|
||||
|
||||
for q in query.unwrap().split("&") {
|
||||
let parts = q.split("=").collect::<Vec<&str>>();
|
||||
if parts.len() != 2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
hm.insert(String::from(parts[0]), String::from(parts[1]));
|
||||
}
|
||||
|
||||
let entry = hm.get(&String::from("q"));
|
||||
|
||||
if entry.is_none() {
|
||||
*response.status_mut() = StatusCode::NOT_ACCEPTABLE;
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
let entry = entry.unwrap();
|
||||
|
||||
if let Ok(list) = fetch_metric(String::from(entry)) {
|
||||
*response.body_mut() = list.join("\n").into();
|
||||
}
|
||||
},
|
||||
(&Method::POST, "/metric") => {
|
||||
let content = body::to_bytes(req.into_body()).await;
|
||||
|
||||
if let Err(_) = content {
|
||||
*response.status_mut() = StatusCode::NOT_ACCEPTABLE;
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
let content = content.unwrap();
|
||||
|
||||
if !content.is_ascii() {
|
||||
*response.status_mut() = StatusCode::NOT_ACCEPTABLE;
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
if let Ok(content) = String::from_utf8(content.to_vec()) {
|
||||
let metrics = content.split("\n").collect::<Vec<&str>>();
|
||||
|
||||
for metric in metrics {
|
||||
debug!("Creating metric {}", metric);
|
||||
write_metric(String::from(metric));
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
*response.status_mut() = StatusCode::NOT_FOUND;
|
||||
},
|
||||
};
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn shutdown_signal() {
|
||||
// Wait for the CTRL+C signal
|
||||
tokio::signal::ctrl_c()
|
||||
.await
|
||||
.expect("failed to install CTRL+C signal handler");
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||
|
||||
let make_svc = make_service_fn(|_conn| async {
|
||||
Ok::<_, Infallible>(service_fn(handlers))
|
||||
});
|
||||
|
||||
if let Err(res) = std::env::set_current_dir("./db/") {
|
||||
panic!("{:?}", res);
|
||||
}
|
||||
|
||||
let server = Server::bind(&addr).serve(make_svc);
|
||||
let graceful = server.with_graceful_shutdown(shutdown_signal());
|
||||
|
||||
if let Err(e) = graceful.await {
|
||||
eprintln!("server error: {}", e);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user