<img align="right" src="">

# Tachometer

Easy to read, low overhead instrumentation for BEAM's schedulers in order to determine a system's capacity for 
efficiently performing additional work.

## Motivation

Using Unix's `rup`, `top` or Erlang's `:cpu_sup` module as a guage of BEAM's capacity
to take on more work tends to be problematic for several reasons:

* If BEAM is not the only process running on the system, `rup`, `top`, and `:cpu_sup` will report high load values 
even if BEAM is not doing any work.  If the intention is to have the operating system give BEAM its fair share of 
CPU time in relation to other processes, having BEAM back off when total system load is high may result in it 
being under-scheduled on a busy system - it will be too "nice".
* Even if BEAM is the only process running on a system (such as in a container), BEAM's schedulers tend to 
[busy wait][1] and cause `rup`, `top`, and `:cpu_sup` to report [artificially high loads][2].
* They only work on Unix, and `:cpu_sup.util` doesn't work on Mac.
* It is also possible that load as reported by the OS will be artificially low - [Schedulers waiting for OS resources are considered utilized as they cannot handle more work. If the OS itself is holding up on non-CPU tasks it is still possible for Erlang’s schedulers not to be able to do more work and report a full ratio.][3]

Therefore, the best way to determine BEAM's capacity to take on work efficiently is to check it's scheduler usage 
through [`:erlang.statistics(:scheduler_wall_time)`][4] which returns the amount of time the schedulers have been 
active vs real time elapsed.

A scheduler is considered `active` if it is not idle and doing any of the following:
  - Executing process code
  - Executing linked-in-driver or NIF code
  - Executing built-in-functions, or any other runtime handling
  - Garbage collecting
  - Handling any other memory management

Tachometer polls the schedulers and returns their utilization ratio at any given moment.

## Installation
  1. Add tachometer to your list of dependencies in `mix.exs`:

        def deps do
          [{:tachometer, "~> 0.1.0"}]

  2. Ensure tachometer is started before your application:

        def application do
          [applications: [:tachometer]]

## Usage

`read/0` returns a float between `0` and `1` which represents fractional utilization of all schedulers.
iex(2)> spawn fn-> some_cpu_intensive_task() end

Tachometer's default polling interval is 1000ms.  
`set_poll_interval/1` can be used to change the polling interval.
# poll more often
iex(2)> Tachometer.set_poll_interval 500

Use `stop/0` to stop Tachometer:
iex(3)> Tachometer.stop                  
21:54:02.679 [info]  Application tachometer exited: normal

`start/1` accepts `poll_interval` in milliseconds as an optional parameter.  
Defaults to `Application.get_env(:tachometer, :poll_interval)` if set, otherwise 1000ms.

iex(4)> Tachometer.start 2000
22:07:34.147 [debug] Starting Tachometer with poll interval: 2000
{:ok, #PID<0.115.0>}

## Configuration

The default polling interval is 1000ms.  It can be overridden in your application's config.exs file:

config :tachometer, poll_interval: 2000

## Overhead
Even though Tachometer continuously polls the schedulers, it's pretty efficient.

On my quad-core (8 logical core) MacBook Pro with 2.5GHz Intel Core i7, at the default polling interval of 1000ms, 
scheduler usage is around 0.014%.  Even with a 10ms polling interval, scheduler usage is only ~0.8%.

Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
00:44:01.005 [info]  Starting Tachometer with poll interval: 1000
Interactive Elixir (1.2.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> * 100
iex(2)> * 100
iex(3)> * 100
iex(4)> * 100
iex(5)> Tachometer.set_poll_interval 100
iex(6)> * 100            
iex(7)> * 100
iex(8)> * 100
iex(9)> * 100
iex(10)> Tachometer.set_poll_interval 10  
iex(11)> * 100
iex(12)> * 100
iex(13)> * 100
iex(14)> * 100

## References


3. - Section 5.1.2