A Rust implementation of the PCP instrumentation API

Related tags

Profiling hornet
Overview

hornet crates.io badge docs.rs badge Travis CI Build Status AppVeyor Build Status codecov

hornet is a Performance Co-Pilot (PCP) Memory Mapped Values (MMV) instrumentation library written in Rust.

Contents

What is PCP MMV instrumentation?

Performance Co-Pilot is a systems performance analysis framework with a distributed and scalable architecture. It supports a low overhead method for instrumenting applications called Memory Mapped Values (MMV), in which instrumented processes share part of their virtual memory address space with another monitoring process through a common memory-mapped file. The shared address space contains various performance analysis metrics stored in a structured binary data format called MMV; it's formal spec can be found here. When processes wish to update their metrics, they simply write certain bytes to the memory mapped file, and the monitoring process reads it at appropriate times. No explicit inter-process communication, synchronization or systems calls are involved.

Usage

  • Add the hornet dependency to your Cargo.toml

    [dependencies]
    hornet = "0.1.0"
  • Include the hornet crate in your code and import the following modules

    extern crate hornet;
    
    use hornet::client::Client;
    use hornet::client::metric::*;

API

There are essentially two kinds of metrics in hornet.

Singleton Metric

A singleton metric is a metric associated with a primitive value type, a Unit, a Semantics type, and some metadata. A primitive value can be any one of i64, u64, i32, u32, f64, f32, or String,

The primitive value type of a metric is determined implicitly at compile-time by the inital primitive value passed to the metric while creating it. The programmer also needn't worry about reading or writing data of the wrong primitive type from a metric, as the Rust compiler enforces type safety for a metric's primitive value during complilation.

Let's look at creating a simple i64 metric

let mut metric = Metric::new(
    "simple", // metric name
    1, // inital value of type i64
    Semantics::Counter,
    Unit::new().count(Count::One, 1).unwrap(), // unit with a 'count' dimension of power 1
    "Short text", // short description
    "Long text", // long description
).unwrap();

If we want to create an f64 metric, we simply pass an f64 inital value instead

let mut metric = Metric::new(
    "simple_f64", // metric name
    1.5, // inital value of type f64
    Semantics::Instant,
    Unit::new().count(Time::Sec, 1).unwrap(), // unit with a 'time' dimension of power 1
    "Short text", // short description
    "Long text", // long description
).unwrap();

And similarly for a String metric

let mut metric = Metric::new(
    "simple_string", // metric name
    "Hello, world!".to_string(), // inital value of type String
    Semantics::Discrete,
    Unit::new().unwrap(), // unit with no dimension
    "Short text", // short description
    "Long text", // long description
).unwrap();

The detailed API on singleton metrics can be found here.

Instance Metric

An instance metric is similar to a singleton metric in that it is also associated with a primitive valye type, Unit, and Semantics, but additionally also holds multiple independent primitive values of the same type. The same type inference rules also hold for instance metrics - the type of the inital value determines the type of the instance metric.

Before we can create an instance metric, we need to create what's called an instance domain. An instance domain is a set of String values that act as unique identifiers for the multiple independent values of an instance metric. Why have a separate object for this purpose? So that we can reuse the same identifiers as a "domain" for several different but related instance metrics. An example will clear this up.

Suppose we are modeling the fictional Acme Corporation factory. Let's assume we have three items that can be manufactured - Anvils, Rockets, and Giant Rubber Bands. Each item is associated with a "count" metric of how many copies have been manufactured so far, and a "time" metric of how much time has been spent manufacturing each item. We can create instance metrics like so

/* instance domain */
let indom = Indom::new(
    &["Anvils", "Rockets", "Giant_Rubber_Bands"],
    "Acme products", // short description
    "Most popular products produced by the Acme Corporation" // long description
).unwrap();
  
/* two instance metrics */

let mut counts = InstanceMetric::new(
    &indom,
    "products.count", // instance metric name
    0, // inital value of type i64
    Semantics::Counter,
    Unit::new().count(Count::One, 1).unwrap(),
    "Acme factory product throughput",
    "Monotonic increasing counter of products produced in the Acme Corporation factory since starting the Acme production application."
).unwrap();

let mut times = InstanceMetric::new(
    &indom,
    "products.time",  // instance metric name
    0.0, // inital value of type f64
    Semantics::Instance,
    Unit::new().time(Time::Sec, 1).unwrap(),
    "Time spent producing products",
    "Machine time spent producing Acme Corporation products."
).unwrap();

Here, our indom contains three identifiers - Anvils, Rockets and Giant_Rubber_Bands. We've created two instance metrics - counts of type i64 and times of type f64 with relevant units and semantics.

The detailed API on instance metrics can be found here.

Updating metrics

So far we've seen how to create metrics with various attributes. Updating their primitive values is pretty simple.

For singleton metrics, the val(&self) -> &T method returns a reference to the underlying value, and the set_val(&mut self, new_val: T) -> io::Result<()> method updates the underlying value and writes to the memory mapped file. The arguments and return values for these methods are generic over the different primitive types for a metric, and hence are completely type safe.

For instance metrics, the val(&self, instance: &str) -> Option<&T> method returns a reference to the primitive value for the given instance identifier, if it exists. The set_val(&mut self, instance: &str, new_val: T) -> Option<io::Result<()>> method updates the primitive value for the given instance identifier, if it exists. These methods are similarly generic over primitive value types.

Special metrics

Singleton metrics and instance metrics are powerful and general enough to be used for a wide variety of performance analysis needs. However, for many common applications, simpler metric interfaces would be more appropriate and easy to use. Hence hornet includes 6 high-level metrics that are built on top of singleton and instance metrics, and they offer a more specialized and simpler API.

Counter

A Counter is a singleton metric of type u64, Counter semantics, and unit of 1 count dimension. It implements the following methods: up to increment by one, inc to increment by a delta, reset to set count to the inital count, and val to return the current count.

let mut c = Counter::new(
    "counter", // name
    1, // inital value
    "", "" // short and long description strings
).unwrap();

c.up(); // 2
c.inc(3); // 5
c.reset(); // 1

let count = c.val(); // 1

The CountVector is the instance metric version of the Counter. It holds multiple counts each associated with a String identifier.

Gauge

A Gauge is a singleton metric of type f64, Instant semantics, and unit of 1 count dimension. It implements the following methods: inc to increment the gauge by a delta, dec to decrement the gauge by a delta, set to set the gauge to an arbritrary value, and val which returns the current value of the gauge.

let mut gauge = Gauge::new("gauge", 1.5, "", "").unwrap();

gauge.set(3.0).unwrap(); // 3.0
gauge.inc(3.0).unwrap(); // 6.0
gauge.dec(1.5).unwrap(); // 4.5
gauge.reset().unwrap();  // 1.5

The GaugeVector is the instance metric version of the Gauge. It holds multiple gauge values each associated with an identifier.

Timer

A Timer is a singleton metric of type i64, Instant semantics, and a user specified time unit. It implements the following methods: start starts the timer by recording the current time, stop stops the timer by recording the current time and returns the elapsed time since the last start, and elapsed returns the total time elapsed so far between all start and stop pairs.

let mut timer = Timer::new("timer", Time::MSec, "", "").unwrap();

timer.start().unwrap();
let e1 = timer.stop().unwrap();

timer.start().unwrap();
let e2 = timer.stop().unwrap();

let elapsed = timer.elapsed(); // = e1 + e2

Histogram

A Histogram is a high dynamic range (HDR) histogram metric which records u64 data points and exports various statistics about the data. It is implemented using an instance metric of f64 type and Instance semantics. The Histogram metric is infact essentially a wrapper around the Histogram object from the hdrsample crate, and it exports the maximum, minimum, mean and standard deviation statistics to the MMV file.

let low = 1;
let high = 100;
let sigfig = 5;

let mut hist = Histogram::new(
    "histogram",
    low,
    high,
    sigfig,
    Unit::new().count(Count::One, 1).unwrap(),
    "Simple histogram example", ""
).unwrap();

let range = Range::new(low, high);
let mut thread_rng = thread_rng();

for _ in 0..100 {
    hist.record(range.ind_sample(&mut thread_rng)).unwrap();
}

Much of the Histogram API is largely similar to the hdrsample API.

Client

In order to export our metrics to a memory mapped file, we must first create a Client

let client = Client::new("client").unwrap(); // MMV file will be named 'client'

Now to export metrics, we simply call export

client.export(&mut [&mut metric1, &mut metric2, &mut metric3]);

If you have a valid PCP installation, the Client writes the MMV file to $PCP_TMP_DIR/mmv/, and otherwise it writes it to /tmp/mmv/.

After metrics are exported through a Client, all updates to their primitive values will show up in the MMV file.

Monitoring metrics

With a valid PCP installation on a machine, metrics can be monitored externally by using the follwing command

$ pminfo -f mmv._name_

where _name_ is the name passed to Client while creating it.

Another way to inspect metrics externally is to dump the contents of the MMV file itself. This can be done using a command line tool called mmvdump included in hornet. After issuing cargo build from within the project directory, mmvdump can be found built under target/debug/.

Usage of mmvdump is pretty straightforward

$ ./mmvdump simple.mmv

Version    = 1
Generated  = 1468770536
TOC count  = 3
Cluster    = 127
Process    = 29956
Flags      = process (0x2)

TOC[0]: toc offset 40, metrics offset 88 (1 entries)
  [725/88] simple.counter
      type=Int32 (0x0), sem=counter (0x1), pad=0x0
      unit=count (0x100000)
      (no indom)
      shorttext=A Simple Metric
      longtext=This is a simple counter metric to demonstrate the hornet API

TOC[1]: toc offset 56, values offset 192 (1 entries)
  [725/192] simple.counter = 42

TOC[2]: toc offset 72, strings offset 224 (2 entries)
  [1/224] A Simple Metric
  [2/480] This is a simple counter metric to demonstrate the hornet API

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

You might also like...
Modrinth API is a simple library for using Modrinth's API in Rust projects

Ferinth is a simple library for using the Modrinth API in Rust projects. It uses reqwest as its HTTP(S) client and deserialises responses to typed structs using serde.

A Rust API for Vega-Lite V4 to build chart with a rusty API.
A Rust API for Vega-Lite V4 to build chart with a rusty API.

Vega-Lite V4 for Rust A Rust API for Vega-Lite V4 to build chart with a rusty API. Similar to the Altair project in python, this crate build upon Vega

List public items (public API) of Rust library crates. Enables diffing public API between releases.

cargo wrapper for this library You probably want the cargo wrapper to this library. See https://github.com/Enselic/cargo-public-items. public_items Li

Uma lib para a API do Brasil API (para o Rust)
Uma lib para a API do Brasil API (para o Rust)

Uma lib para a API do BrasilAPI (para o Rust) Features CEP (Zip code) DDD Bank CNPJ IBGE Feriados Nacionais Tabela FIPE ISBN Registros de domínios br

Api testing tool made with rust to use for api developement (Kind of Tui)
Api testing tool made with rust to use for api developement (Kind of Tui)

Api testing tool made with rust to use for api developement (Kind of Tui) This Rust project provides a simple yet powerful tool for making HTTP reques

REST API server that abstracts the need to write CRUD methods by exposing a standardized API to interact with a Postgres database
REST API server that abstracts the need to write CRUD methods by exposing a standardized API to interact with a Postgres database

Basiliq Exposing a Postgres database via a REST API that follows the JSON:API specs. All in all, a tasty API. What is Basiliq Quickstart Ready to use

The Safe Network Core. API message definitions, routing and nodes, client core api.

safe_network The Safe Network Core. API message definitions, routing and nodes, client core api. License This Safe Network repository is licensed unde

API wrapper for the tankerkönig api

tankerkoenig-rs API wrapper for the tankerkoenig-api written in rust. Gives you ready deserialized structs and a easy to use and strictly typed api. I

List public items (public API) of library crates. Enables diffing public API between releases.

cargo-public-items List public items (the public API) of a Rust library crate by analyzing the rustdoc JSON of the crate. Automatically builds the rus

A repository full of manually generated hand curated JSON files, which contain the API Types that the Discord API returns.

Discord API Types A repository full of manually generated hand curated JSON files, which contain the API Types that the Discord API returns. Also did

A secure and efficient gateway for interacting with OpenAI's API, featuring load balancing, user request handling without individual API keys, and global access control.

OpenAI Hub OpenAI Hub is a comprehensive and robust tool designed to streamline and enhance your interaction with OpenAI's API. It features an innovat

Proxy copilot api to openai's gpt-4 api

Proxying Copilot API to OpenAI's GPT-4 API Usage Start the Server export GHU_TOKEN=ghu_xxxx; ./copilot2chat Or sh start.sh start # start the server th

A pure Rust implementation of WebRTC API
A pure Rust implementation of WebRTC API

A pure Rust implementation of WebRTC API

Rust implementation of the `URLPattern` web API

urlpattern This crate implements the URLPattern web API in Rust. We aim to follow the specification as closely as possible. Contributing We appreciate

The official rust implementation of the SpamProtectionBot API
The official rust implementation of the SpamProtectionBot API

SpamProtection-rs Table of contents About Supported Rust version Features How to use Credits License About SpamProtection-Rust is a Rust wrapper for I

A pure Rust implementation of the Web Local Storage API, for use in non-browser contexts

Rust Web Local Storage API A Rust implementation of the Web LocalStorage API, for use in non-browser contexts About the Web Local Storage API MDN docs

VST 2.4 API implementation in rust. Create plugins or hosts.

rust-vst2 A library to help facilitate creating VST plugins in rust. This library is a work in progress and as such does not yet implement all opcodes

Implementation of the RealWorld backend API spec in Actix, Rust's powerful actor system and most fun web framework.
Implementation of the RealWorld backend API spec in Actix, Rust's powerful actor system and most fun web framework.

Actix codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the RealWorld spec and API. ❗ (2021/05/13) This cod

An implementation of the ZITADEL gRPC API in Rust.

An implementation of the ZITADEL gRPC API in Rust. Complemented with other useful elements such as ServiceAccount auth.

Comments
  • Implement low-level MMV API and mmvdump

    Implement low-level MMV API and mmvdump

    This will also be useful for implementing the mmvdump command and for testing MMV writing.

    • [x] Print string repr for unit
    • [x] Print string repr for mmv flags
    • [x] Check for invalid indom id
    • [x] Check for invalid item id
    opened by saurvs 19
  • Implement instance metrics with explicit indoms

    Implement instance metrics with explicit indoms

    This is a slightly more complex API which makes Indom explicit to the user. As opposed to https://github.com/performancecopilot/hornet/pull/20, this API allows setting of the Indom's short and long text, and also allows sharing indoms between instance metrics.

    I've also added an Indom cache while writing the MMV to write only one indom block per unique Indom, and a string cache for constant strings as well.

    opened by saurvs 18
  • Implement illustrative examples of the API

    Implement illustrative examples of the API

    At least one for each of Counter, Gauge, and Timer, CountVector, GaugeVector, and Histogram.

    Implement the ACME factory example as well (https://github.com/performancecopilot/speed/blob/master/examples/acme/main.go).

    opened by saurvs 1
  • Add initial support for exporting metrics in MMV v1

    Add initial support for exporting metrics in MMV v1

    Port over github.com/saurvs/mmv-rs/blob/master/src/lib.rs with some updates like idiomatic support for dimensions, support for all metric value types (notably string), possible changes to the API for MMV and Metric, and refactoring the code to be more readable/efficient.

    Also add basic tests to validate the generated MMV header.

    opened by saurvs 1
Owner
Performance Co-Pilot
The Performance Co-Pilot Project
Performance Co-Pilot
A stopwatch library for Rust. Used to time things.

rust-stopwatch This is a simple module used to time things in Rust. Usage To use, add the following line to Cargo.toml under [dependencies]: stopwatch

Chucky Ellison 77 Dec 11, 2022
An intrusive flamegraph profiling tool for rust.

FLAME A cool flamegraph library for rust Flamegraphs are a great way to view profiling information. At a glance, they give you information about how m

null 631 Jan 3, 2023
Experimental one-shot benchmarking/profiling harness for Rust

Iai Experimental One-shot Benchmark Framework in Rust Getting Started | User Guide | Released API Docs | Changelog Iai is an experimental benchmarking

Brook Heisler 409 Dec 25, 2022
Benchmark for Rust and humans

bma-benchmark Benchmark for Rust and humans What is this for I like testing different libraries, crates and algorithms. I do benchmarks on prototypes

Altertech 11 Jan 17, 2022
Rust wrapper for COCO benchmark functions.

Coco Rust bindings for the COCO Numerical Black-Box Optimization Benchmarking Framework. See https://github.com/numbbo/coco and https://numbbo.github.

Leopold Luley 1 Nov 15, 2022
A http server benchmark tool written in rust 🦀

rsb - rust benchmark rsb is a http server benchmark tool written in rust. The development of this tool is mainly inspired by the bombardier project, a

Michael 45 Apr 10, 2023
🐦 Friendly little instrumentation profiler for Rust 🦀

?? puffin The friendly little instrumentation profiler for Rust How to use fn my_function() { puffin::profile_function!(); ... if ... {

Embark 848 Dec 29, 2022
Safe Rust bindings to the DynamoRIO dynamic binary instrumentation framework.

Introduction The dynamorio-rs crate provides safe Rust bindings to the DynamoRIO dynamic binary instrumentation framework, essentially allowing you to

S.J.R. van Schaik 17 Nov 21, 2022
Prometheus instrumentation service for the NGINX RTMP module.

nginx-rtmp-exporter Prometheus instrumentation service for the NGINX RTMP module. Usage nginx-rtmp-exporter [OPTIONS] --scrape-url <SCRAPE_URL> O

kaylen ✨ 2 Jul 3, 2022
Modrinth API is a simple library for using, you guessed it, the Modrinth API in Rust projects

Modrinth API is a simple library for using, you guessed it, the Modrinth API in Rust projects. It uses reqwest as its HTTP(S) client and deserialises responses to typed structs using serde.

null 21 Jan 1, 2023