Compare commits

...

4 Commits

Author SHA1 Message Date
Dustin ea9bcbd07a main: Implement main program logic
Finally, a working application!  The heavy lifting is handled by
*prometheus_exporter_base*, which sets up the Hyper server and registers
the *GET /metrics* path operation handler.  Really, all we have to
provide is the logic to connect to the BURP stats socket given the
options specified in the configuration file.  The `metrics::get_metrics`
function handles the generation of the metrics, which we return as a
string to be served to the HTTP client.
2022-02-12 15:15:29 -06:00
Dustin d690cf0982 config: Add application configuration
Naturally, we need a configuration mechanism in order to specify
connection parameters for the BURP stats socket.  Using the
`serde::Deserialize` trait will make this relatively straightforward and
allows us to choose from a wide variety of data formats as our
configuration language.  We will probably use TOML since it is pretty
simple to read and write for both humans and machines, and our data
model is very simple.
2022-02-12 15:15:29 -06:00
Dustin dc0c987358 burp: error: impl Display and Error
The `burp::error::Error` struct needs to implement the
`std::error::Error` trait (which requires that it also implement
`std::fmt::Display`) so that it can be used in dynamic dispatch
situations.  Notably, the `prometheus_exporter_base::render_prometheus`
function requires a closure that returns `Result<String, Box<dyn
std::error::Error>>`, so in order to use the `?` (early return)
operator on on the `metrics::get_metrics` function call, our error must
implement that trait.
2022-02-12 15:15:29 -06:00
Dustin c7feaa041a metrics: Add Prometheus metrics for BURP stats
The `metrics` module encapsulates the functionality for generating
Prometheus metrics from BURP stats.  Given a `BurpClient` instance, the
`get_metrics` function calls the necessary RPC methods to fetch the
latest backup statistics for each known BURP client.  It then generates
metrics in Prometheus plain text exposition format, using the excellent
[prometheus_exporter_base] crate.  The result is returned as a string,
which can then be used to produce an HTTP response to a Prometheus
scrape request.  The *prometheus_exporter_base* crate also provides a
simple HTTP server, based on [Hyper], which we will eventually use to
serve this response.

[prometheus_exporter_base]: https://github.com/MindFlavor/prometheus_exporter_base/
[Hyper]: https://hyper.rs/
2022-02-12 15:15:29 -06:00
6 changed files with 1145 additions and 1 deletions

648
Cargo.lock generated
View File

@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
@ -11,27 +20,83 @@ dependencies = [
"winapi",
]
[[package]]
name = "argh"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb41d85d92dfab96cb95ab023c265c5e4261bb956c0fb49ca06d90c570f1958"
dependencies = [
"argh_derive",
"argh_shared",
]
[[package]]
name = "argh_derive"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be69f70ef5497dd6ab331a50bd95c6ac6b8f7f17a7967838332743fbd58dc3b5"
dependencies = [
"argh_shared",
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "argh_shared"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6f8c380fa28aa1b36107cd97f0196474bb7241bb95a453c5c01a15ac74b2eac"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bumpalo"
version = "3.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
[[package]]
name = "burp_exporter"
version = "0.1.0"
dependencies = [
"argh",
"gethostname",
"openssl",
"prometheus_exporter_base",
"serde",
"serde_json",
"tokio",
"tokio-native-tls",
"toml",
"tracing",
"tracing-subscriber",
]
@ -54,6 +119,21 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "core-foundation"
version = "0.9.2"
@ -70,6 +150,28 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "ct-logs"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8"
dependencies = [
"sct",
]
[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "fastrand"
version = "1.7.0"
@ -79,6 +181,12 @@ dependencies = [
"instant",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
@ -94,6 +202,163 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "futures-channel"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
[[package]]
name = "futures-macro"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-task"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
[[package]]
name = "futures-util"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
dependencies = [
"futures-core",
"futures-macro",
"futures-task",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "gethostname"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4addc164932852d066774c405dbbdb7914742d2b39e39e1a7ca949c856d054d1"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "http"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http-body"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6"
dependencies = [
"bytes",
"http",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4"
[[package]]
name = "httpdate"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "humantime"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
dependencies = [
"quick-error",
]
[[package]]
name = "hyper"
version = "0.14.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64"
dependencies = [
"ct-logs",
"futures-util",
"hyper",
"log",
"rustls",
"rustls-native-certs",
"tokio",
"tokio-rustls",
"webpki",
]
[[package]]
name = "instant"
version = "0.1.12"
@ -109,6 +374,15 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]]
name = "js-sys"
version = "0.3.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -194,6 +468,83 @@ dependencies = [
"winapi",
]
[[package]]
name = "num"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
dependencies = [
"autocfg",
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.9.0"
@ -239,6 +590,12 @@ version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.24"
@ -254,6 +611,30 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "prometheus_exporter_base"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b214e11b8c1d9a9d659eb097b77326cb74fcde851477adb97d145222d17585"
dependencies = [
"clap",
"env_logger",
"http",
"hyper",
"hyper-rustls",
"log",
"num",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.15"
@ -278,6 +659,8 @@ version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
@ -305,6 +688,46 @@ dependencies = [
"winapi",
]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "rustls"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
dependencies = [
"base64",
"log",
"ring",
"sct",
"webpki",
]
[[package]]
name = "rustls-native-certs"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092"
dependencies = [
"openssl-probe",
"rustls",
"schannel",
"security-framework",
]
[[package]]
name = "ryu"
version = "1.0.9"
@ -321,6 +744,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "sct"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "security-framework"
version = "2.6.1"
@ -393,12 +826,40 @@ dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
[[package]]
name = "smallvec"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "socket2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "syn"
version = "1.0.86"
@ -424,6 +885,44 @@ dependencies = [
"winapi",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.4"
@ -471,6 +970,32 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
dependencies = [
"rustls",
"tokio",
"webpki",
]
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
[[package]]
name = "tower-service"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
[[package]]
name = "tracing"
version = "0.1.30"
@ -533,12 +1058,36 @@ dependencies = [
"tracing-log",
]
[[package]]
name = "try-lock"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
name = "unicode-segmentation"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "valuable"
version = "0.1.0"
@ -551,6 +1100,96 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "want"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
dependencies = [
"log",
"try-lock",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
[[package]]
name = "web-sys"
version = "0.3.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webpki"
version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -567,6 +1206,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"

View File

@ -6,11 +6,18 @@ license = "MIT OR Apache-2.0"
edition = "2021"
[dependencies]
argh = "^0.1.7"
openssl = "^0.10.38"
gethostname = "^0.2.2"
serde_json = "1.0.78"
tokio-native-tls = "^0.3.0"
toml = "^0.5.8"
tracing = "^0.1.30"
[dependencies.prometheus_exporter_base]
version = "^1.3.0"
features = ["hyper_server"]
[dependencies.serde]
version = "^1.0.136"
features = ["derive"]

View File

@ -1,4 +1,6 @@
//! Client error handling
use std::error;
use std::fmt;
use std::io;
use serde_json;
@ -40,3 +42,17 @@ impl From<serde_json::Error> for Error {
Self::Protocol(error.to_string())
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Io(e) => write!(f, "I/O error: {}", e),
Self::Tls(e) => write!(f, "TLS handshake error: {}", e),
Self::Command(e) => write!(f, "Error from BURP server: {}", e),
Self::Protocol(e) => write!(f, "BURP protocol error: {}", e),
Self::UnsupportedVersion(e) => write!(f, "{}", e),
}
}
}
impl error::Error for Error {}

89
src/config.rs Normal file
View File

@ -0,0 +1,89 @@
//! Application configuration
use gethostname::gethostname;
use serde::Deserialize;
/// Configuration options
#[derive(Clone, Debug, Deserialize)]
pub struct Config {
/// http listen address (default: `::`)
#[serde(default = "default_listen_host")]
pub listen_host: String,
/// http listen port (default: `9645`)
#[serde(default = "default_listen_port")]
pub listen_port: u16,
/// server hostname or ip address (default: `localhost`)
#[serde(default = "default_server")]
pub server: String,
/// server port (default: `4972`)
#[serde(default = "default_port")]
pub port: u16,
/// server name (for certificate validation)
#[serde(default)]
pub server_name: Option<String>,
/// ca certificate file
#[serde(default)]
pub ca_cert: Option<String>,
/// client identity file
#[serde(default)]
pub identity: Option<String>,
/// client identity file password
#[serde(default)]
pub identity_password: Option<String>,
/// client name (default: *local host name*)
#[serde(default = "default_client_name")]
pub client_name: String,
/// client password
#[serde(default)]
pub password: Option<String>,
}
impl Default for Config {
fn default() -> Self {
Self {
listen_host: default_listen_host(),
listen_port: default_listen_port(),
server: default_server(),
port: default_port(),
server_name: None,
ca_cert: None,
identity: None,
identity_password: None,
client_name: default_client_name(),
password: None,
}
}
}
#[doc(hidden)]
fn default_listen_host() -> String {
"::".into()
}
#[doc(hidden)]
fn default_listen_port() -> u16 {
9645
}
#[doc(hidden)]
fn default_server() -> String {
"localhost".into()
}
#[doc(hidden)]
fn default_port() -> u16 {
4972
}
#[doc(hidden)]
fn default_client_name() -> String {
gethostname().to_string_lossy().into()
}

View File

@ -1,2 +1,90 @@
fn main() {
use std::fs::File;
use std::io::prelude::*;
use std::net::IpAddr;
use std::path::Path;
use argh::FromArgs;
use prometheus_exporter_base::render_prometheus;
use toml;
use tracing::{debug, error};
mod burp;
mod config;
mod metrics;
use burp::client::ClientConnector;
use config::Config;
/// BURP exporter
#[derive(FromArgs)]
struct Args {
/// configuration file path
#[argh(option, short = 'c')]
config_file: Option<String>,
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
tracing_subscriber::fmt::init();
let args: Args = argh::from_env();
let config = match load_config(args.config_file) {
Ok(c) => c,
Err(e) => {
eprintln!("Failed to load configuration: {}", e);
::std::process::exit(1);
}
};
let host: IpAddr = match config.listen_host.parse() {
Ok(a) => a,
Err(e) => {
eprintln!("Invalid listen host ({}): {}", &config.listen_host, e);
::std::process::exit(1);
}
};
let addr = (host, config.listen_port).into();
debug!("Using configuration: {:?}", &config);
render_prometheus(addr, config, |_request, config| async move {
let mut connector = ClientConnector::new(&config.server);
if let Some(server_name) = &config.server_name {
connector = connector.server_name(server_name);
}
if let Some(ca_cert) = &config.ca_cert {
connector = connector.ca_cert_file(&ca_cert)?;
}
if let Some(identity_file) = &config.identity {
let password = match &config.identity_password {
Some(p) => &p,
None => "",
};
connector = connector.identity_file(identity_file, password)?;
}
debug!("Connecting to server: {}", &config.server);
let mut client = match connector.connect().await {
Ok(c) => Ok(c),
Err(e) => {
error!("Error connecting to server: {}", e);
Err(e)
}
}?;
if let Some(p) = &config.password {
client.handshake(&config.client_name, Some(p)).await
} else {
client.handshake(&config.client_name, None).await
}?;
Ok(metrics::get_metrics(&mut client).await?)
})
.await;
}
fn load_config<P: AsRef<Path>>(
path: Option<P>,
) -> Result<Config, Box<dyn std::error::Error>> {
if let Some(path) = path {
let mut f = File::open(path)?;
let mut contents = String::new();
f.read_to_string(&mut contents)?;
Ok(toml::from_str(&contents)?)
} else {
Ok(Config::default())
}
}

296
src/metrics.rs Normal file
View File

@ -0,0 +1,296 @@
//! BURP client backup metrics
use prometheus_exporter_base::prelude::*;
use tracing::error;
use crate::burp::client::Client;
use crate::burp::error::Error;
use crate::burp::model::BackupStats;
macro_rules! metric {
($client:ident, $count:literal, $value:expr) => {
PrometheusInstance::new()
.with_label("client", $client.as_ref())
.with_label("count", $count)
.with_value($value)
};
}
macro_rules! counter {
($metric:expr, $client:ident, $counter:ident) => {
$metric
.render_and_append_instance(&metric!(
$client,
"count",
$counter.count
))
.render_and_append_instance(&metric!(
$client,
"changed",
$counter.changed
))
.render_and_append_instance(&metric!(
$client,
"same",
$counter.same
))
.render_and_append_instance(&metric!(
$client,
"deleted",
$counter.deleted
))
.render_and_append_instance(&metric!(
$client,
"scanned",
$counter.scanned
))
};
}
/// BURP backup metrics
///
/// This structure manages the Prometheus metrics for a BURP backup.
struct Metrics<'a> {
backup_number: PrometheusMetric<'a>,
backup_timestamp: PrometheusMetric<'a>,
files: PrometheusMetric<'a>,
meta_data: PrometheusMetric<'a>,
directories: PrometheusMetric<'a>,
soft_links: PrometheusMetric<'a>,
hard_links: PrometheusMetric<'a>,
special_files: PrometheusMetric<'a>,
grand_total: PrometheusMetric<'a>,
bytes_estimated: PrometheusMetric<'a>,
bytes: PrometheusMetric<'a>,
bytes_received: PrometheusMetric<'a>,
bytes_sent: PrometheusMetric<'a>,
elapsed_time: PrometheusMetric<'a>,
}
impl<'a> Metrics<'a> {
pub fn new() -> Self {
Self {
backup_number: PrometheusMetric::build()
.with_name("burp_client_last_backup_number")
.with_metric_type(MetricType::Counter)
.with_help("Last backup number")
.build(),
backup_timestamp: PrometheusMetric::build()
.with_name("burp_client_last_backup_timestamp")
.with_metric_type(MetricType::Counter)
.with_help("Last backup timestamp")
.build(),
files: PrometheusMetric::build()
.with_name("burp_backup_files_count")
.with_metric_type(MetricType::Gauge)
.with_help("Files in last backup")
.build(),
meta_data: PrometheusMetric::build()
.with_name("burp_backup_metadata_count")
.with_metric_type(MetricType::Gauge)
.with_help("Metadata in last backup")
.build(),
directories: PrometheusMetric::build()
.with_name("burp_backup_directories_count")
.with_metric_type(MetricType::Gauge)
.with_help("Directories in last backup")
.build(),
soft_links: PrometheusMetric::build()
.with_name("burp_backup_soft_links_count")
.with_metric_type(MetricType::Gauge)
.with_help("Soft (symbolic) links in last backup")
.build(),
hard_links: PrometheusMetric::build()
.with_name("burp_backup_hard_links_count")
.with_metric_type(MetricType::Gauge)
.with_help("Hard links in last backup")
.build(),
special_files: PrometheusMetric::build()
.with_name("burp_backup_special_files_count")
.with_metric_type(MetricType::Gauge)
.with_help("Special files in last backup")
.build(),
grand_total: PrometheusMetric::build()
.with_name("burp_backup_total_count")
.with_metric_type(MetricType::Gauge)
.with_help("Total files in last backup")
.build(),
bytes_estimated: PrometheusMetric::build()
.with_name("burp_backup_estimated_bytes")
.with_metric_type(MetricType::Gauge)
.with_help("Estimated bytes in last backup")
.build(),
bytes: PrometheusMetric::build()
.with_name("burp_backup_bytes")
.with_metric_type(MetricType::Gauge)
.with_help("Size of last backup")
.build(),
bytes_received: PrometheusMetric::build()
.with_name("burp_backup_received_bytes")
.with_metric_type(MetricType::Gauge)
.with_help("Bytes received in last backup")
.build(),
bytes_sent: PrometheusMetric::build()
.with_name("burp_backup_sent_bytes")
.with_metric_type(MetricType::Gauge)
.with_help("Bytes sent in last backup")
.build(),
elapsed_time: PrometheusMetric::build()
.with_name("burp_backup_elapsed_time_seconds")
.with_metric_type(MetricType::Gauge)
.with_help("Time taken performing last backup")
.build(),
}
}
/// Add metrics for a client's latest backup
///
/// This method adds values for the `burp_client_last_backup_number`
/// and `burp_client_last_backup_timestamp` metrics.
pub fn add_backup<S: AsRef<str>>(
&mut self,
client: S,
number: u64,
timestamp: u64,
) {
self.backup_number.render_and_append_instance(
&PrometheusInstance::new()
.with_label("client", client.as_ref())
.with_value(number),
);
self.backup_timestamp.render_and_append_instance(
&PrometheusInstance::new()
.with_label("client", client.as_ref())
.with_value(timestamp),
);
}
/// Add metrics backup statistics
///
/// This method adds values for file, directory, metadata, and
/// soft/hard link count metrics, bytes total, sent, and received
/// metrics, and elapsed time metrics for a backup.
pub fn add_stats<S: AsRef<str>>(
&mut self,
client: S,
stats: &BackupStats,
) {
let mut time_start: Option<u64> = None;
let mut time_end: Option<u64> = None;
for counter in &stats.counters {
match counter.name.as_ref() {
"files" => {
counter!(self.files, client, counter);
}
"meta_data" => {
counter!(self.meta_data, client, counter);
}
"directories" => {
counter!(self.directories, client, counter);
}
"soft_links" => {
counter!(self.soft_links, client, counter);
}
"hard_links" => {
counter!(self.hard_links, client, counter);
}
"special_files" => {
counter!(self.special_files, client, counter);
}
"grand_total" => {
counter!(self.grand_total, client, counter);
}
"bytes_estimated" => {
self.bytes_estimated.render_and_append_instance(
&PrometheusInstance::new()
.with_label("client", client.as_ref())
.with_value(counter.count),
);
}
"bytes" => {
self.bytes.render_and_append_instance(
&PrometheusInstance::new()
.with_label("client", client.as_ref())
.with_value(counter.count),
);
}
"bytes_received" => {
self.bytes_received.render_and_append_instance(
&PrometheusInstance::new()
.with_label("client", client.as_ref())
.with_value(counter.count),
);
}
"bytes_sent" => {
self.bytes_sent.render_and_append_instance(
&PrometheusInstance::new()
.with_label("client", client.as_ref())
.with_value(counter.count),
);
}
"time_start" => {
time_start = Some(counter.count);
}
"time_end" => {
time_end = Some(counter.count);
}
_ => {}
}
}
if let Some(start) = time_start {
if let Some(end) = time_end {
self.elapsed_time.render_and_append_instance(
&PrometheusInstance::new()
.with_label("client", client.as_ref())
.with_value(end - start),
);
}
}
}
/// Render the metrics in Prometheus exposition format
pub fn to_string(&self) -> String {
let mut value = String::new();
value.push_str(&self.backup_number.render());
value.push_str(&self.backup_timestamp.render());
value.push_str(&self.files.render());
value.push_str(&self.meta_data.render());
value.push_str(&self.directories.render());
value.push_str(&self.soft_links.render());
value.push_str(&self.hard_links.render());
value.push_str(&self.special_files.render());
value.push_str(&self.grand_total.render());
value.push_str(&self.bytes_estimated.render());
value.push_str(&self.bytes.render());
value.push_str(&self.bytes_received.render());
value.push_str(&self.bytes_sent.render());
value.push_str(&self.elapsed_time.render());
value
}
}
/// Get current BURP metrics
///
/// This function returns the current BURP metrics as a string, in
/// Prometheus plain text exposition format.
pub async fn get_metrics(client: &mut Client) -> Result<String, Error> {
let mut metrics = Metrics::new();
for c in client.get_clients().await?.clients {
if let Some(b) = c.backups.iter().next() {
metrics.add_backup(&c.name, b.number, b.timestamp);
match client.get_backup_stats(&c.name, b.number).await {
Ok(s) => {
if let Some(s) = s {
metrics.add_stats(c.name, &s);
}
}
Err(e) => {
error!(
"Could not get backup for client {}: {}",
&c.name, e
);
}
}
};
}
Ok(metrics.to_string())
}