Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolas-semaphor committed Dec 8, 2023
1 parent 1d2e9ac commit 8510f33
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
target/
Cargo.lock
10 changes: 10 additions & 0 deletions Cargo.toml
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"
27 changes: 27 additions & 0 deletions README.md
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
```
101 changes: 101 additions & 0 deletions src/main.rs
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.
}
}

0 comments on commit 8510f33

Please sign in to comment.