-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1d2e9ac
commit 8510f33
Showing
4 changed files
with
140 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
target/ | ||
Cargo.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
[package] | ||
name = "rusty-json-prom-exporter" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
reqwest = { version = "0.11.22", features = ["blocking"] } | ||
serde_json = "1.0.108" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Rustbased exporter that converts arbitrary JSON to the .prom format. | ||
|
||
This small application works great in tandem with the Prometheus [node-exporter](https://github.com/prometheus/node_exporter). | ||
If you place the converted JSON -> prom data in the /var/lib/prometheus/node-exporter/ directory, the metrics will be added to the node-exporter output. | ||
|
||
Build the project using Cargo. | ||
|
||
Run the binary as | ||
|
||
``` | ||
./rusty-json-prom-exporter [url] [filename] | ||
``` | ||
|
||
or better yet, set up a linux systemd service: | ||
|
||
``` | ||
[Unit] | ||
Description=Exporter that converts arbitrary JSON to the .prom data format - written in Rust. | ||
[Service] | ||
ExecStart=<path to binary> <url> <filename>.prom | ||
Restart=always | ||
User=root | ||
[Install] | ||
WantedBy=multi-user.target | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
use serde_json::Value; | ||
use std::env; | ||
use std::fs::File; | ||
use std::io::prelude::*; | ||
use std::{thread, time, process}; | ||
|
||
const FIVE_SECONDS: time::Duration = time::Duration::from_secs(5); | ||
const THIRTY_SECONDS: time::Duration = time::Duration::from_secs(30); | ||
const MAX_RETRIES: usize = 5; | ||
|
||
fn main() { | ||
let args: Vec<String> = env::args().collect(); | ||
let mut data; | ||
|
||
if args.len() != 3 { | ||
eprintln!("Usage: rusty-json-prom-exporter [url] [filename]"); | ||
std::process::exit(1); | ||
} | ||
|
||
loop { | ||
match send_request_with_retry(&args[1], MAX_RETRIES) { | ||
Ok(response_data) => { | ||
data = response_data; | ||
} | ||
Err(err) => { | ||
eprintln!("Failed to send the request ({})", err); | ||
process::exit(1); // Is this bad? | ||
} | ||
} | ||
let parsed_data: Value = match serde_json::from_str(&data) { | ||
Ok(data) => data, | ||
Err(err) => { | ||
eprintln!("Could not parse JSON data ({})", err); | ||
std::process::exit(1); | ||
} | ||
}; | ||
let mut file = File::create(&args[2]).expect("Could not create file {&args[2]}"); | ||
unpack_dict(&parsed_data, "", &mut file); | ||
thread::sleep(FIVE_SECONDS); | ||
} | ||
} | ||
|
||
|
||
fn send_request_with_retry(url: &str, max_retries: usize) -> Result<String, reqwest::Error> { | ||
let mut retries = 0; | ||
loop { | ||
match reqwest::blocking::get(url) { | ||
Ok(response) => { | ||
return response.text(); | ||
} | ||
Err(_) => { | ||
retries += 1; | ||
if retries >= max_retries { | ||
eprintln!("Exceeded maximum retries - Exiting."); | ||
process::exit(1); | ||
} | ||
eprintln!("Connection refused - Retrying in 30 seconds... (Attempt {} of {})", retries, MAX_RETRIES); | ||
thread::sleep(THIRTY_SECONDS); | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
||
fn unpack_dict(data: &Value, path: &str, file: &mut File) { | ||
|
||
/* Help-text should be somewhat customizable in | ||
* the future, maybe by including a dictionary | ||
* that can substitue values based on metric name? */ | ||
|
||
let formatted_path = str::replace(path, "-", "_"); | ||
match data { | ||
Value::Number(num) => { | ||
let line = format!( | ||
"# HELP {}\n# TYPE gauge\n{} {}\n", | ||
formatted_path, formatted_path, num | ||
); | ||
file.write_all(line.as_bytes()).unwrap(); | ||
} | ||
Value::Bool(boolean) => { | ||
let numeric_bool: i8 = if *boolean { 1 } else { 0 }; | ||
let line: String = format!( | ||
"# HELP {}_bool\n# TYPE gauge\n{} {}\n", | ||
formatted_path, formatted_path, numeric_bool | ||
); | ||
file.write_all(line.as_bytes()).unwrap(); | ||
} | ||
Value::Object(map) => { | ||
for (key, value) in map { | ||
if path == "" { | ||
let new_path = format!("{}{}", formatted_path, key); | ||
unpack_dict(value, &new_path, file); | ||
} else { | ||
let new_path = format!("{}_{}", formatted_path, key); | ||
unpack_dict(value, &new_path, file); | ||
} | ||
} | ||
} | ||
_ => (), // Do not include in output, if not Value::Number or Value::Bool. | ||
} | ||
} |