Create the presentation
This commit is contained in:
commit
1dde2d697b
7 changed files with 723 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
svg-inkscape/
|
||||||
|
prometheus-exporter-rs.html
|
||||||
|
prometheus-exporter-rs.pdf
|
||||||
|
prometheus-exporter-rs.pyg
|
||||||
|
prometheus-exporter-rs.tex
|
Binary file not shown.
After Width: | Height: | Size: 371 KiB |
BIN
img/cargo.png
Normal file
BIN
img/cargo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
BIN
img/compiler_complaint.png
Normal file
BIN
img/compiler_complaint.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
1
img/prometheus-icon-color.svg
Normal file
1
img/prometheus-icon-color.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="-3.94 -1.44 438.62 432.87"><path fill="#E75225" d="M215.926 7.068c115.684.024 210.638 93.784 210.493 207.844-.148 115.793-94.713 208.252-212.912 208.169C97.95 423 4.52 329.143 4.601 213.221 4.68 99.867 99.833 7.044 215.926 7.068zm-63.947 73.001c2.652 12.978.076 25.082-3.846 36.988-2.716 8.244-6.47 16.183-8.711 24.539-3.694 13.769-7.885 27.619-9.422 41.701-2.21 20.25 5.795 38.086 19.493 55.822L86.527 225.94c.11 1.978-.007 2.727.21 3.361 5.968 17.43 16.471 32.115 28.243 45.957 1.246 1.465 4.082 2.217 6.182 2.221 62.782.115 125.565.109 188.347.028 1.948-.003 4.546-.369 5.741-1.618 13.456-14.063 23.746-30.079 30.179-50.257l-66.658 12.976c4.397-8.567 9.417-16.1 12.302-24.377 9.869-28.315 5.779-55.69-8.387-81.509-11.368-20.72-21.854-41.349-16.183-66.32-12.005 11.786-16.615 26.79-19.541 42.253-2.882 15.23-4.58 30.684-6.811 46.136-.317-.467-.728-.811-.792-1.212-.258-1.621-.499-3.255-.587-4.893-1.355-25.31-6.328-49.696-16.823-72.987-6.178-13.71-12.99-27.727-6.622-44.081-4.31 2.259-8.205 4.505-10.997 7.711-8.333 9.569-11.779 21.062-12.666 33.645-.757 10.75-1.796 21.552-3.801 32.123-2.107 11.109-5.448 21.998-12.956 32.209-3.033-21.81-3.37-43.38-22.928-57.237zm161.877 216.523H116.942v34.007h196.914v-34.007zm-157.871 51.575c-.163 28.317 28.851 49.414 64.709 47.883 29.716-1.269 56.016-24.51 53.755-47.883H155.985z"/></svg>
|
After Width: | Height: | Size: 1.4 KiB |
1
img/rust-logo-blk.svg
Normal file
1
img/rust-logo-blk.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg height="144" width="144" xmlns="http://www.w3.org/2000/svg"><path d="m71.05 23.68c-26.06 0-47.27 21.22-47.27 47.27s21.22 47.27 47.27 47.27 47.27-21.22 47.27-47.27-21.22-47.27-47.27-47.27zm-.07 4.2a3.1 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11zm7.12 5.12a38.27 38.27 0 0 1 26.2 18.66l-3.67 8.28c-.63 1.43.02 3.11 1.44 3.75l7.06 3.13a38.27 38.27 0 0 1 .08 6.64h-3.93c-.39 0-.55.26-.55.64v1.8c0 4.24-2.39 5.17-4.49 5.4-2 .23-4.21-.84-4.49-2.06-1.18-6.63-3.14-8.04-6.24-10.49 3.85-2.44 7.85-6.05 7.85-10.87 0-5.21-3.57-8.49-6-10.1-3.42-2.25-7.2-2.7-8.22-2.7h-40.6a38.27 38.27 0 0 1 21.41-12.08l4.79 5.02c1.08 1.13 2.87 1.18 4 .09zm-44.2 23.02a3.11 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11zm74.15.14a3.11 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11zm-68.29.5h5.42v24.44h-10.94a38.27 38.27 0 0 1 -1.24-14.61l6.7-2.98c1.43-.64 2.08-2.31 1.44-3.74zm22.62.26h12.91c.67 0 4.71.77 4.71 3.8 0 2.51-3.1 3.41-5.65 3.41h-11.98zm0 17.56h9.89c.9 0 4.83.26 6.08 5.28.39 1.54 1.26 6.56 1.85 8.17.59 1.8 2.98 5.4 5.53 5.4h16.14a38.27 38.27 0 0 1 -3.54 4.1l-6.57-1.41c-1.53-.33-3.04.65-3.37 2.18l-1.56 7.28a38.27 38.27 0 0 1 -31.91-.15l-1.56-7.28c-.33-1.53-1.83-2.51-3.36-2.18l-6.43 1.38a38.27 38.27 0 0 1 -3.32-3.92h31.27c.35 0 .59-.06.59-.39v-11.06c0-.32-.24-.39-.59-.39h-9.15zm-14.43 25.33a3.11 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11zm46.05.14a3.11 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11z"/><path d="m115.68 70.95a44.63 44.63 0 0 1 -44.63 44.63 44.63 44.63 0 0 1 -44.63-44.63 44.63 44.63 0 0 1 44.63-44.63 44.63 44.63 0 0 1 44.63 44.63zm-.84-4.31 6.96 4.31-6.96 4.31 5.98 5.59-7.66 2.87 4.78 6.65-8.09 1.32 3.4 7.46-8.19-.29 1.88 7.98-7.98-1.88.29 8.19-7.46-3.4-1.32 8.09-6.65-4.78-2.87 7.66-5.59-5.98-4.31 6.96-4.31-6.96-5.59 5.98-2.87-7.66-6.65 4.78-1.32-8.09-7.46 3.4.29-8.19-7.98 1.88 1.88-7.98-8.19.29 3.4-7.46-8.09-1.32 4.78-6.65-7.66-2.87 5.98-5.59-6.96-4.31 6.96-4.31-5.98-5.59 7.66-2.87-4.78-6.65 8.09-1.32-3.4-7.46 8.19.29-1.88-7.98 7.98 1.88-.29-8.19 7.46 3.4 1.32-8.09 6.65 4.78 2.87-7.66 5.59 5.98 4.31-6.96 4.31 6.96 5.59-5.98 2.87 7.66 6.65-4.78 1.32 8.09 7.46-3.4-.29 8.19 7.98-1.88-1.88 7.98 8.19-.29-3.4 7.46 8.09 1.32-4.78 6.65 7.66 2.87z" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/></svg>
|
After Width: | Height: | Size: 2.3 KiB |
716
prometheus-exporter-rs.org
Normal file
716
prometheus-exporter-rs.org
Normal file
|
@ -0,0 +1,716 @@
|
||||||
|
#+STARTUP: beamer
|
||||||
|
#+OPTIONS: ':nil *:t -:t ::t <:t H:3 \n:nil ^:nil arch:headline
|
||||||
|
#+OPTIONS: author:t broken-links:nil c:nil creator:nil
|
||||||
|
#+OPTIONS: d:(not "LOGBOOK") date:t e:t email:nil f:t inline:t num:t
|
||||||
|
#+OPTIONS: p:nil pri:nil prop:nil stat:t tags:t tasks:t tex:t
|
||||||
|
#+OPTIONS: timestamp:t title:t toc:t todo:t |:t
|
||||||
|
#+TITLE: @@latex:\includesvg[height=.25\textheight]{img/prometheus-icon-color}\hspace{1cm}\includesvg[height=.25\textheight]{img/rust-logo-blk}\newline @@Create a simple Prometheus Exporter with Rust
|
||||||
|
#+SUBTITLE: From scratch to Debian package.
|
||||||
|
#+DATE: \today
|
||||||
|
#+AUTHOR: [[mailto:finga@onders.org][finga]]
|
||||||
|
#+EMAIL: finga@onders.org
|
||||||
|
#+DESCRIPTION: Create a simple Prometheus Exporter with Rust, from scratch to Debian package.
|
||||||
|
#+LANGUAGE: en
|
||||||
|
#+KEYWORDS: prometheus rust programming
|
||||||
|
#+SELECT_TAGS: export
|
||||||
|
#+EXCLUDE_TAGS: noexport
|
||||||
|
#+CREATOR: Emacs 26.1 (Org mode 9.1.9)
|
||||||
|
|
||||||
|
#+OPTIONS: H:2
|
||||||
|
#+LATEX_CLASS: beamer
|
||||||
|
#+LATEX_CLASS_OPTIONS: [aspectratio=1610]
|
||||||
|
#+COLUMNS: %45ITEM %10BEAMER_env(Env) %10BEAMER_act(Act) %4BEAMER_col(Col) %8BEAMER_opt(Opt)
|
||||||
|
#+LATEX_HEADER: \usepackage{svg}\hypersetup{colorlinks=true,linkcolor=black,urlcolor=gray}
|
||||||
|
#+BEAMER_THEME: Frankfurt
|
||||||
|
#+BEAMER_COLOR_THEME: seagull
|
||||||
|
#+BEAMER_FONT_THEME:
|
||||||
|
#+BEAMER_INNER_THEME:
|
||||||
|
#+BEAMER_OUTER_THEME:
|
||||||
|
#+BEAMER_HEADER: \institute[INST]{\href{https://onders.org}{onders.org}}
|
||||||
|
|
||||||
|
* Objective
|
||||||
|
|
||||||
|
** Objective
|
||||||
|
|
||||||
|
*** Abstract
|
||||||
|
Acquire, (process) and serve metrics.
|
||||||
|
|
||||||
|
*** What we are actually going to do
|
||||||
|
Parse ~/proc/loadavg~ periodically and serve its latest values.
|
||||||
|
|
||||||
|
*** Example content of ~/proc/loadavg~
|
||||||
|
#+begin_src
|
||||||
|
0.13 0.14 0.09 1/273 3160
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Create a new Project
|
||||||
|
|
||||||
|
*** New Cargo Project
|
||||||
|
To begin we create a new Cargo project.
|
||||||
|
#+begin_src sh
|
||||||
|
$ cargo new loadavg-exporter
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Project settings
|
||||||
|
Add meta data to ~Cargo.toml~.
|
||||||
|
|
||||||
|
*** ~Cargo.toml~
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src toml
|
||||||
|
[package]
|
||||||
|
name = "loadavg-exporter"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["finga <finga@onders.org>"]
|
||||||
|
edition = "2021"
|
||||||
|
description = "Prometheus exporter example to export the load average."
|
||||||
|
readme = "README.md"
|
||||||
|
license = "GPL-3.0-or-later"
|
||||||
|
repository = "https://git.onders.org/finga/loadavg-exporter.git"
|
||||||
|
keywords = ["prometheus", "metrics"]
|
||||||
|
categories = ["command-line-utilities"]
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Read from ~/proc/loadavg~
|
||||||
|
*** ~src/main.rs~: Print content of ~/proc/loadavg~
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src rust
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{BufRead, BufReader},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn parse_loadavg<P>(filename: P)
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let file = File::open(&filename).unwrap();
|
||||||
|
for line in BufReader::new(file).lines() {
|
||||||
|
println!("{}", line.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
parse_loadavg("/proc/loadavg")
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Use of =Result<T, E>=
|
||||||
|
|
||||||
|
*** It is "just" an enum
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src rust
|
||||||
|
enum Result<T, E> {
|
||||||
|
Ok(T),
|
||||||
|
Err(E),
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
*** How to use =Result=
|
||||||
|
- ~Results~ "must" be used
|
||||||
|
- There is the question mark operator: =?=
|
||||||
|
- =?= can only be used in functions that return =Result=
|
||||||
|
- For more see the docs[fn:result]
|
||||||
|
- Historically the =?= operator replaces the =try!=[fn:try] macro
|
||||||
|
|
||||||
|
** Set some ~clippy~ settings
|
||||||
|
|
||||||
|
*** Create ~.cargo/config~
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src
|
||||||
|
[target.'cfg(feature = "cargo-clippy")']
|
||||||
|
rustflags = [
|
||||||
|
"-Dwarnings",
|
||||||
|
"-Dclippy::pedantic",
|
||||||
|
"-Dclippy::nursery",
|
||||||
|
"-Dclippy::cargo",
|
||||||
|
]
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Use ~anyhow~
|
||||||
|
|
||||||
|
*** Add dependency to ~Cargo.toml~
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src toml
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
*** Use ~anyhow~ where suitable
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src diff
|
||||||
|
+use anyhow::Result;
|
||||||
|
...
|
||||||
|
-fn parse_loadavg<P>(filename: P)
|
||||||
|
+fn parse_loadavg<P>(filename: P) -> Result<()>
|
||||||
|
...
|
||||||
|
- let file = File::open(&filename).unwrap();
|
||||||
|
+ let file = File::open(&filename)?;
|
||||||
|
for line in BufReader::new(file).lines() {
|
||||||
|
- println!("{}", line.unwrap());
|
||||||
|
+ println!("{}", line?);
|
||||||
|
}
|
||||||
|
+ Ok(())
|
||||||
|
}
|
||||||
|
...
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Create an atomic for =f64=
|
||||||
|
|
||||||
|
*** Create an atomic =f64= type
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src rust
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct AtomicF64 {
|
||||||
|
storage: AtomicU64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AtomicF64 {
|
||||||
|
fn store(&self, value: f64, ordering: Ordering) {
|
||||||
|
let as_u64 = value.to_bits();
|
||||||
|
self.storage.store(as_u64, ordering);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(&self, ordering: Ordering) -> f64 {
|
||||||
|
let as_u64 = self.storage.load(ordering);
|
||||||
|
f64::from_bits(as_u64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** What does the =#[derive(Default)]= attribute do?
|
||||||
|
This calls a procedural derive macro[fn:derive] to generate =AtomicF64::default()=.
|
||||||
|
|
||||||
|
*** What is generated?
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src rust
|
||||||
|
#[automatically_derived]
|
||||||
|
#[allow(unused_qualifications)]
|
||||||
|
impl ::core::default::Default for AtomicF64 {
|
||||||
|
#[inline]
|
||||||
|
fn default() -> AtomicF64 {
|
||||||
|
AtomicF64 {
|
||||||
|
storage: ::core::default::Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Create a storage for the metrics
|
||||||
|
|
||||||
|
*** Create a struct to keep the metrics
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src rust
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Metrics {
|
||||||
|
load_1: AtomicF64,
|
||||||
|
load_5: AtomicF64,
|
||||||
|
load_15: AtomicF64,
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Introduce logging
|
||||||
|
|
||||||
|
*** How to configure logging from the outside?
|
||||||
|
- The =RUST_LOG= environment variable is the answer
|
||||||
|
- Use =RUST_LOG=trace cargo r= to set the log level to ~trace~ and
|
||||||
|
run the project
|
||||||
|
- Use =RUST_LOG=error,path::to::module=trace= to set the overall
|
||||||
|
log level to ~error~ and the log level of =path::to::module= to
|
||||||
|
~trace~
|
||||||
|
|
||||||
|
*** Add new dependencies in ~Cargo.toml~
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src diff
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
+log = "0.4"
|
||||||
|
+env_logger = "0.9"
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Introduce logging
|
||||||
|
|
||||||
|
*** Generate some log messages
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src diff
|
||||||
|
use anyhow::Result;
|
||||||
|
+use log::{debug, info};
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{BufRead, BufReader},
|
||||||
|
...
|
||||||
|
fn parse_loadavg<P>(filename: P) -> Result<()>
|
||||||
|
where
|
||||||
|
- P: AsRef<Path>,
|
||||||
|
+ P: AsRef<Path> + std::fmt::Display,
|
||||||
|
{
|
||||||
|
+ debug!("Read load average from {}", filename);
|
||||||
|
let file = File::open(&filename)?;
|
||||||
|
...
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
+ env_logger::init();
|
||||||
|
+
|
||||||
|
+ info!("{} started", env!("CARGO_PKG_NAME"));
|
||||||
|
parse_loadavg("/proc/loadavg")?;
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Parse the ~loadavg~ line
|
||||||
|
|
||||||
|
*** Parse the fields
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src diff
|
||||||
|
-fn parse_loadavg<P>(filename: P) -> Result<()>
|
||||||
|
+fn parse_loadavg<P>(filename: P, metrics: Arc<Metrics>) -> Result<()>
|
||||||
|
...
|
||||||
|
let file = File::open(&filename)?;
|
||||||
|
- for line in BufReader::new(file).lines() {
|
||||||
|
- println!("{}", line?);
|
||||||
|
+ let mut data = String::new();
|
||||||
|
+
|
||||||
|
+ BufReader::new(file).read_to_string(&mut data)?;
|
||||||
|
+ let data = data.trim();
|
||||||
|
+ trace!("Data to parse: {}", data);
|
||||||
|
+ let fields: Vec<&str> = data.split(' ').collect();
|
||||||
|
+
|
||||||
|
+ if fields.len() != 5 {
|
||||||
|
+ bail!(
|
||||||
|
+ "Expected to read 5 space separated fields from {}",
|
||||||
|
+ filename
|
||||||
|
+ );
|
||||||
|
}
|
||||||
|
...
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Store the parsed values in our =Metrics= struct
|
||||||
|
|
||||||
|
*** Before returning we store the gathered data
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src diff
|
||||||
|
fn parse_loadavg<P>(filename: P, metrics: Arc<Metrics>) -> Result<()>
|
||||||
|
where
|
||||||
|
P: AsRef<Path> + std::fmt::Display,
|
||||||
|
{
|
||||||
|
...
|
||||||
|
+ trace!("Parsed fields: {:?}", fields);
|
||||||
|
+
|
||||||
|
+ metrics
|
||||||
|
+ .load_1
|
||||||
|
+ .store(fields[0].parse::<f64>()?, Ordering::Relaxed);
|
||||||
|
+ metrics
|
||||||
|
+ .load_5
|
||||||
|
+ .store(fields[1].parse::<f64>()?, Ordering::Relaxed);
|
||||||
|
+ metrics
|
||||||
|
+ .load_15
|
||||||
|
+ .store(fields[2].parse::<f64>()?, Ordering::Relaxed);
|
||||||
|
+
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Introduce =tokio=[fn:tokio]
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
*** Add =tokio= to =Cargo.toml=
|
||||||
|
#+begin_src diff
|
||||||
|
...
|
||||||
|
+tokio = { version = "1", features = ["macros", "time", "rt-multi-thread"] }
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
*** Add =use= statements to bring utilities into scope
|
||||||
|
#+begin_src diff
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use log::{debug, info, trace};
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{BufReader, Read},
|
||||||
|
path::Path,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicU64, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
+ time::Duration,
|
||||||
|
};
|
||||||
|
+use tokio::time::sleep;
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Periodically poll the data from =/proc/loadavg=
|
||||||
|
|
||||||
|
*** Call the loadavg parser in an infinite loop and sleep
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src rust
|
||||||
|
async fn poll_loadavg<P>(filename: P, interval: u64, metrics: Arc<Metrics>) -> Result<()>
|
||||||
|
where
|
||||||
|
P: AsRef<Path> + std::fmt::Display,
|
||||||
|
{
|
||||||
|
debug!("Reading {} every {} seconds", filename, interval);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
trace!("Polling loadavg from {}", filename);
|
||||||
|
parse_loadavg(&filename, metrics.clone())?;
|
||||||
|
sleep(Duration::from_secs(interval)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Make =main= use =tokio=
|
||||||
|
|
||||||
|
*** And adopt our =main()= to use it
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src diff
|
||||||
|
-fn main() -> Result<()> {
|
||||||
|
+#[tokio::main]
|
||||||
|
+async fn main() -> Result<()> {
|
||||||
|
...
|
||||||
|
- parse_loadavg("/proc/loadavg", Arc::clone(&metrics))?;
|
||||||
|
+ poll_loadavg("/proc/loadavg", 5, Arc::clone(&metrics)).await?;
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** What is =#[tokio::main]=?
|
||||||
|
|
||||||
|
*** It is syntactic sugar as following:
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src rust
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
println!("hello");
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
*** ... just generates this:
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src rust
|
||||||
|
fn main() {
|
||||||
|
let mut rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
|
rt.block_on(async {
|
||||||
|
println!("hello");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Serve the metrics data
|
||||||
|
|
||||||
|
*** Add dependencies to =Cargo.toml=...
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src diff
|
||||||
|
[dependencies]
|
||||||
|
...
|
||||||
|
+axum = "0.5"
|
||||||
|
...
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
*** and import further necessities in our program...
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src diff
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
+use axum::{routing::get, Extension, Router, Server};
|
||||||
|
use log::{debug, info, trace};
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{BufReader, Read},
|
||||||
|
+ net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||||
|
path::Path,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
-use tokio::time::sleep;
|
||||||
|
+use tokio::{spawn, time::sleep, try_join};
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Generate the response string
|
||||||
|
|
||||||
|
*** Create the served metrics data
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src rust
|
||||||
|
#[allow(clippy::unused_async)]
|
||||||
|
async fn serve_metrics(Extension(metrics): Extension<Arc<Metrics>>) -> String {
|
||||||
|
format!(
|
||||||
|
r"# HELP System load (1m).
|
||||||
|
# TYPE load_1 gauge
|
||||||
|
load_1 {}
|
||||||
|
# HELP System load (5m).
|
||||||
|
# TYPE load_5 gauge
|
||||||
|
load_5 {}
|
||||||
|
# HELP System load (15m).
|
||||||
|
# TYPE load_15 gauge
|
||||||
|
load_15 {}
|
||||||
|
",
|
||||||
|
metrics.load_1.load(Ordering::Relaxed),
|
||||||
|
metrics.load_5.load(Ordering::Relaxed),
|
||||||
|
metrics.load_15.load(Ordering::Relaxed),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Create a =Router= to route and serve the http endpoint
|
||||||
|
|
||||||
|
*** Basically thats our http listener
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src rust
|
||||||
|
async fn listen_http(address: IpAddr, port: u16, metrics: Arc<Metrics>) -> Result<()> {
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/metrics", get(serve_metrics))
|
||||||
|
.layer(Extension(metrics));
|
||||||
|
let addr = SocketAddr::from((address, port));
|
||||||
|
debug!("Listening on {}:{}", address, port);
|
||||||
|
Ok(Server::bind(&addr).serve(app.into_make_service()).await?)
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Read from file and serve http asynchronously
|
||||||
|
|
||||||
|
*** Poll loadavg, listen for http and return on error
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src diff
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
...
|
||||||
|
- poll_loadavg("/proc/loadavg", 5, Arc::clone(&metrics)).await?;
|
||||||
|
+
|
||||||
|
+ let (poller, listener) = try_join!(
|
||||||
|
+ spawn(poll_loadavg("/proc/loadavg", 5, Arc::clone(&metrics))),
|
||||||
|
+ spawn(listen_http(
|
||||||
|
+ IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
||||||
|
+ 8000,
|
||||||
|
+ Arc::clone(&metrics),
|
||||||
|
+ )),
|
||||||
|
+ )?;
|
||||||
|
+ poller?;
|
||||||
|
+ listener?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Early Conclusion
|
||||||
|
This concludes the first step of creating a small prometheus
|
||||||
|
exporter.
|
||||||
|
|
||||||
|
*** Note
|
||||||
|
We could have created a more simple solution without using =tokio=
|
||||||
|
and by parsing ~/proc/loadavg~ on each request. Still, this shows
|
||||||
|
a bit of =tokio= and further might not appliciable when slightly
|
||||||
|
more complex requirements are needed.
|
||||||
|
|
||||||
|
** The Finishing
|
||||||
|
Although this example already works here are some additional steps
|
||||||
|
to improve certain aspects.
|
||||||
|
|
||||||
|
*** Further improvements
|
||||||
|
- Parse command line arguments with ~clap~: The Command Line
|
||||||
|
Argument Parser
|
||||||
|
- Use ~cargo-deb~ to generate a Debian Package
|
||||||
|
- Cross compile the project for other architectures
|
||||||
|
|
||||||
|
* ~clap~
|
||||||
|
|
||||||
|
** Parse command line arguments with ~clap~
|
||||||
|
|
||||||
|
*** What is ~clap~
|
||||||
|
- There are several ways of how ~clap~ can be used (derive,
|
||||||
|
builder, etc..)
|
||||||
|
- Documentation: [[https://docs.rs/clap/latest/clap/]]
|
||||||
|
- Examples: [[https://github.com/clap-rs/clap/tree/master/examples]]
|
||||||
|
|
||||||
|
** Add build dependencies for ~clap~
|
||||||
|
|
||||||
|
*** Add ~clap~ dependencies to ~Cargo.toml~
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src diff
|
||||||
|
axum = "0.5"
|
||||||
|
+clap = { version = "3", features = ["derive"] }
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Have a struct holding all arguments
|
||||||
|
|
||||||
|
*** Create a struct which holds all arguments
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src rust
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[clap(about, version, author)]
|
||||||
|
struct Args {
|
||||||
|
/// The path to the file to parse the load average parameters.
|
||||||
|
#[clap(short, long, default_value = "/proc/loadavg")]
|
||||||
|
file: String,
|
||||||
|
/// The intervall how often the file is queried
|
||||||
|
#[clap(short, long, default_value = "10")]
|
||||||
|
interval: u64,
|
||||||
|
/// The IPv4 or IPv6 address where the metrics are served.
|
||||||
|
#[clap(short, long, default_value = "127.0.0.1")]
|
||||||
|
address: IpAddr,
|
||||||
|
/// The port where the metrics are served.
|
||||||
|
#[clap(short, long, default_value = "9111")]
|
||||||
|
port: u16,
|
||||||
|
/// Produce verbose output, multiple -v options increase the verbosity
|
||||||
|
#[clap(short, long, global = true, parse(from_occurrences))]
|
||||||
|
verbose: u8,
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Make use of the new arguments
|
||||||
|
|
||||||
|
*** Parse the arguments and set the log level accordingly
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src diff
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
- env_logger::init();
|
||||||
|
+ let cli = Cli::parse();
|
||||||
|
+
|
||||||
|
+ env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(
|
||||||
|
+ match cli.verbose {
|
||||||
|
+ 0 => "error",
|
||||||
|
+ 1 => "warn",
|
||||||
|
+ 2 => "info",
|
||||||
|
+ 3 => "debug",
|
||||||
|
+ _ => "trace",
|
||||||
|
+ },
|
||||||
|
+ ))
|
||||||
|
+ .init();
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Make use of the new arguments
|
||||||
|
|
||||||
|
*** Pass the argument values to the procedures
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src diff
|
||||||
|
let (poller, listener) = try_join!(
|
||||||
|
- spawn(poll_loadavg("/proc/loadavg", 5, Arc::clone(&metrics))),
|
||||||
|
- spawn(listen_http(
|
||||||
|
- IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
||||||
|
- 8000,
|
||||||
|
- Arc::clone(&metrics),
|
||||||
|
- )),
|
||||||
|
+ spawn(poll_loadavg(cli.file, cli.interval, Arc::clone(&metrics))),
|
||||||
|
+ spawn(listen_http(cli.address, cli.port, Arc::clone(&metrics))),
|
||||||
|
)?;
|
||||||
|
poller?;
|
||||||
|
listener?;
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
* Debian Package
|
||||||
|
|
||||||
|
** Build a Debian Package with the help of ~cargo-deb~
|
||||||
|
|
||||||
|
*** Create ~debian/service~
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src
|
||||||
|
[Unit]
|
||||||
|
Description=Load average Prometheus exporter
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=always
|
||||||
|
EnvironmentFile=/etc/default/loadavg-exporter
|
||||||
|
ExecStart=/usr/bin/loadavg-exporter $ARGS
|
||||||
|
Type=simple
|
||||||
|
ProtectSystem=strict
|
||||||
|
PrivateDevices=yes
|
||||||
|
PrivateUsers=yes
|
||||||
|
RestrictNamespaces=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Build a Debian Package with the help of ~cargo-deb~
|
||||||
|
|
||||||
|
*** Create ~debian/default~
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src
|
||||||
|
ARGS=""
|
||||||
|
# loadavg-exporter supports the following options:
|
||||||
|
# -a, --address <ADDRESS> The IPv4 or IPv6 address where the metrics are served
|
||||||
|
# [default: 127.0.0.1]
|
||||||
|
# -f, --file <FILE> The path to the file to parse the load average parameters
|
||||||
|
# [default: /proc/loadavg]
|
||||||
|
# -i, --interval <INTERVAL> The intervall how often the file is queried [default: 10]
|
||||||
|
# -p, --port <PORT> The port where the metrics are served [default: 9112]
|
||||||
|
# -v, --verbose Produce verbose output, multiple -v options increase the
|
||||||
|
# verbosity
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Build a Debian Package with the help of ~cargo-deb~
|
||||||
|
|
||||||
|
*** Add the ~package.metadata.deb~ section to ~Cargo.toml~
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src toml
|
||||||
|
[package.metadata.deb]
|
||||||
|
extended-description = "Load average Prometheus exporter."
|
||||||
|
section = "utility"
|
||||||
|
maintainer-scripts = "debian/"
|
||||||
|
systemd-units = { enable = true }
|
||||||
|
assets = [
|
||||||
|
["target/release/loadavg-exporter", "usr/bin/", "755"],
|
||||||
|
["debian/default", "etc/default/loadavg-exporter", "644"],
|
||||||
|
]
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
* Cross compile
|
||||||
|
|
||||||
|
** Cross compile for aarch64/arm64
|
||||||
|
You need to have the right compiler dependencies installed, on Debian
|
||||||
|
that is: ~dummy~
|
||||||
|
|
||||||
|
*** Add targets to ~.cargo/config~
|
||||||
|
@@latex:\scriptsize@@
|
||||||
|
#+begin_src
|
||||||
|
[target.armv7-unknown-linux-gnueabihf]
|
||||||
|
linker = "arm-linux-gnueabihf-gcc"
|
||||||
|
|
||||||
|
[target.aarch64-unknown-linux-gnu]
|
||||||
|
linker = "aarch64-linux-gnu-gcc"
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
* Examples
|
||||||
|
|
||||||
|
** Example 1: MightyOhm Geiger Counter
|
||||||
|
*** Abstract
|
||||||
|
A sensor connected to a system to log the sensor data.
|
||||||
|
|
||||||
|
*** Technology
|
||||||
|
- [[https://mightyohm.com/blog/products/geiger-counter/][MightyOhm Geiger Counter]]
|
||||||
|
- [[https://www.raspberrypi.com/products/raspberry-pi-3-model-b-plus/][RaspberryPi 3 Model B+]]
|
||||||
|
- [[https://prometheus.io/][Prometheus]]
|
||||||
|
- [[https://grafana.com/][Grafana]]
|
||||||
|
- [[https://www.rust-lang.org/][Rust]]
|
||||||
|
|
||||||
|
** Example 1: Sensor
|
||||||
|
|
||||||
|
*** Reading Data from the Sensor
|
||||||
|
The Geiger Counter's serial port is connected to the UART port of
|
||||||
|
the Pi. So our program need to listen on the serial line to
|
||||||
|
receive the Data
|
||||||
|
|
||||||
|
** Example 1: Prometheus
|
||||||
|
*** Prometheus Exporter
|
||||||
|
In order to pipe the Data to Prometheus we will open a port to
|
||||||
|
listen for incomming http requests to respond with the sensors
|
||||||
|
data.
|
||||||
|
|
||||||
|
# ** Reading from the Serial Line
|
||||||
|
# To start with reading data from the serial line we use the
|
||||||
|
# ~serialport~ crate. So we do need to add it to the projects
|
||||||
|
# dependencies.
|
||||||
|
# *** ~Cargo.toml~
|
||||||
|
# #+begin_src toml
|
||||||
|
# [dependencies]
|
||||||
|
# serialport = "4.1"
|
||||||
|
# #+end_src
|
||||||
|
# *** :B_ignoreheading:
|
||||||
|
# :PROPERTIES:
|
||||||
|
# :BEAMER_env: ignoreheading
|
||||||
|
# :END:
|
||||||
|
# And take a look at the ~seralport~
|
||||||
|
# documentation[fn:serialport_docs] and a simple
|
||||||
|
# example[fn:serialport_example_receive] about how to read from the
|
||||||
|
# serial line.
|
||||||
|
|
||||||
|
** Example 2: DHT11 Exporter
|
||||||
|
|
||||||
|
* Footnotes
|
||||||
|
[fn:result] =Result= docs: [[https://doc.rust-lang.org/std/result/]]
|
||||||
|
[fn:try] =try!= docs: https://doc.rust-lang.org/std/macro.try.html
|
||||||
|
[fn:derive] [[https://doc.rust-lang.org/reference/procedural-macros.html#derive-macros][https://doc.rust-lang.org/reference/procedural-macros.html]]
|
||||||
|
[fn:tokio] https://tokio.rs/
|
||||||
|
|
||||||
|
# [fn:serialport_docs] [[https://docs.rs/serialport/latest/serialport/][~serialport~ docs]]
|
||||||
|
# [fn:serialport_example_receive] [[https://github.com/serialport/serialport-rs/blob/main/examples/receive_data.rs][~serialport-rs/examples/receive_data.rs~]]
|
Loading…
Reference in a new issue