Home

Awesome

                   _____   ____  _   _  ____   _____
                  |  __ \ / __ \| \ | |/ __ \ / ____|
                  | |__) | |  | |  \| | |  | | (___
                  |  ___/| |  | | . ` | |  | |\___ \
                  | |    | |__| | |\  | |__| |____) |
                  |_|     \____/|_| \_|\____/|_____/

Description

Ponos is an Erlang application that exposes a flexible load generator API. Ponos [1] is named after the Greek god of hard labor and toil (http://en.wikipedia.org/wiki/Ponos).

Written by Jonathan Olsson jonathan@klarna.com with contributions from Cons Åhs cons@klarna.com

Resources

The main place to find information about ponos is the github page.

Documentation is available as edoc as well as on the ponos wiki.

Quick Start Guide

$> git clone https://github.com/klarna/ponos.git
$> cd ponos
$> make
$> erl -pa ebin -s ponos
1> Args = [ {name, unique_name_of_type_atom}
1>        , {task, fun() -> ok end}
1>        , {load_spec, ponos_load_specs:make_constant(10.0)}
1>        ].
2> ponos:add_load_generators([Args]).
3> ponos:init_load_generators().
4> application:stop(ponos).

Introduction

Ponos is a simple yet powerful erlang application used to generate load at configurable frequencies. It's designed to be lightweight, straight forward to use and to require minimal configuration.

Load Generators

There are only three required parts of a load generator:

Adding and Starting Load Generators

1> Name = foo.
2> Task = fun() -> do_your_thing end.
3> LoadSpec = ponos_load_specs:make_sawtooth(10, 20.0).
4> Options = [{duration, 60*1000}, {auto_init, false}].
5> Args = [{name,Name},{task,Task},{load_spec,LoadSpec},{options,Options}].
6> [ok] = ponos:add_load_generators([Args]).
7> ponos:init_load_generators([Name]).

ponos:add_load_generators/1 takes a list of proplists where each proplist denotes the arguments needed to add a new load generator to ponos. The options argument is optional, and may contain all or none of the available options.

Options:

For convenience, all operations on load generators permits referring to a single load generator or a list of generators.

<a name="ponos_load_specs"></a> ponos_load_specs

This module provides a set of predefined constructors for typical load patterns. A load specification defines the characteristics of load and is implemented as a function that maps time to intensity: fun(T) -> I where T is passed time in milliseconds and I is the intensity expressed as calls per second. The user may implement its own load specifications.

A Note About Sampling Intervals and Timers

A load generator is implemented as a gen_server receiving a tick every millisecond. The LoadSpec - which is a function of time - thus gets sampled at every tick to determine whether load should be sent or not. At each tick the load generator may decide to generate multiple calls, so it is - at least in theory - possible to produce arbitrary large amount of cps.

In practice however the load generator itself will become CPU limited above a point that depends on your CPU and/or OS. It is better to use two - or more - generators to leverage the power of multi-core CPUs if such high load is required.

There are other factors that may prevent a load generator to exactly reproduce the specified load pattern.

One of the factors is how timers are implemented in the Erlang VM: a timer may never fire early, but may get delayed indefinitely. This means when the load generator decides to sleep for 1 ms, it will usually end up sleeping for somewhat longer. This will reduce the actual sampling frequency of the LoadSpec. Even if the system is not otherwise overloaded you may not assume sampling frequencies above 500 Hz in practice.

The other limiting factor is that the load generator will favor higher intensities over lower ones when the LoadSpec is not constant. This effect is best explained via an example.

Consider a LoadSpec that for even seconds specifies intensity 0.5 and for odd seconds 4.0. This means ponos will try to trigger tasks at 1000, 1250, 1500, 1750, and 3000 milliseconds. The actual intensity will be therefore 4.0 for odd seconds, but for even seconds it will only fall to 0.8, not to 0.5. The low intensity interval is simply too short.

Task Runners

In essence, ponos gathers load generators under a supervisor and makes sure they get a chance to execute their task at the requested interval. Instead of executing the task itself, ponos hands over to a task runner at certain points, e.g. when it is time to trigger a task. For convenience, ponos provides two implementations of the ponos_task_runner_callbacks interface:

The ponos_task_runner_callbacks behaviour defines the following interface:

ponos_task_runner_callbacks behaviour
call(Name, Task, State) -> ok
concurrency_limit(Name, State::any()) -> ok
init(Name, Args) -> {ok, State::any()}
pause(Name, State) -> ok
start(Name, State) -> {ok, NewState::any()
terminate(Name, State) -> ok

The API allows for modifying the state of the task runner at two occasions: init and start. In all other instances, the task runner may view the state but has no possibility to modify it.

Please refer to ponos_default_task_runner.erl and ponos_file_task_runner. for further reference.

Versioning

Ponos is versioned according to semantic versioning.

Notes

<a name="ref1"></a>[1] The author is aware of the Russian meaning of the word ponos. I leave it to the Russian to fight this down with the Greek. Needless to say, a suitable name is a suitable name.