metrics: crate for collecting metrics

Summary:
We start off simple here. Python only really has counters so we only implement
counters. There are a lot of options on how to improve this and things get
slightly complicated when we look at the how ecosystem and fb303. Anyway,
simple start.

Reviewed By: quark-zju

Differential Revision: D23577874

fbshipit-source-id: d50f5b2ba302d900b254200308bff7446121ae1d
This commit is contained in:
Stefan Filip 2020-09-09 17:32:44 -07:00 committed by Facebook GitHub Bot
parent ead17552cf
commit 7f72a04c0e
2 changed files with 121 additions and 0 deletions

View File

@ -0,0 +1,8 @@
[package]
name = "hg-metrics"
version = "0.1.0"
edition = "2018"
[dependencies]
once_cell = "1.4"
parking_lot = "0.10.2"

View File

@ -0,0 +1,113 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
use std::borrow::Borrow;
use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use once_cell::sync::Lazy;
use parking_lot::RwLock;
pub fn increment_counter(key: impl Key, value: usize) {
METRICS.increment_counter(key, value)
}
pub fn summarize() -> Vec<(String, usize)> {
METRICS.summarize()
}
pub trait Key: Into<String> + Borrow<str> {}
impl<T> Key for T where T: Into<String> + Borrow<str> {}
pub static METRICS: Lazy<Metrics> = Lazy::new(Metrics::new);
pub struct Metrics {
counters: RwLock<HashMap<String, AtomicUsize>>,
}
impl Metrics {
fn new() -> Self {
let counters = RwLock::new(HashMap::new());
Self { counters }
}
fn increment_counter(&self, key: impl Key, value: usize) {
{
let counters = self.counters.read();
if let Some(counter) = counters.get(key.borrow()) {
// We could use Relaxed ordering but it makes tests awkward if we were to run on a
// weakly ordered system, (stress) tests are nice for this code.
counter.fetch_add(value, Ordering::Release);
return;
}
}
let mut counters = self.counters.write();
counters
.entry(key.into())
.and_modify(|c| {
c.fetch_add(value, Ordering::Release);
})
.or_insert_with(|| AtomicUsize::new(value));
}
fn summarize(&self) -> Vec<(String, usize)> {
let counters = self.counters.read();
let mut summary: Vec<(String, usize)> = counters
.iter()
.map(|(k, v)| (k.into(), v.load(Ordering::Acquire)))
.collect();
summary.sort();
summary
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_increment_string_key() {
let metrics = Metrics::new();
metrics.increment_counter(String::from("hello"), 2);
metrics.increment_counter(String::from("world"), 3);
metrics.increment_counter(String::from("hello"), 4);
assert_eq!(
metrics.summarize(),
vec![(String::from("hello"), 6), (String::from("world"), 3)]
);
}
#[test]
fn test_increment_str_key() {
let metrics = Metrics::new();
metrics.increment_counter("hello", 2);
metrics.increment_counter("world", 3);
metrics.increment_counter("hello", 4);
assert_eq!(
metrics.summarize(),
vec![(String::from("hello"), 6), (String::from("world"), 3)]
);
}
#[test]
fn test_increment_on_many_threads() {
static MY_METRICS: Lazy<Metrics> = Lazy::new(Metrics::new);
let handle = thread::spawn(move || {
for _i in 0..10000 {
MY_METRICS.increment_counter("key", 2);
}
});
for _i in 0..10000 {
MY_METRICS.increment_counter("key", 3);
}
handle.join().expect("waiting for spawned thread");
assert_eq!(MY_METRICS.summarize(), vec![(String::from("key"), 50000)]);
}
}