Developing new metrics plugins

It is possible to extend Optimizer Studio by writing plugins for specific hardware or software applications.

The supplied header file metricsPlugin.h can be used to develop a plugin to Optimizer Studio for sampling metrics. Optimizer Studio will call this plugin's sample_*() functions:

  1. sample_system() will be called for sampling system-wide events
  2. sample_socket() will be called for sampling socket-wide events. On a system with several physical chips, this function will be called concurrently for every socket. The calling thread will be affined by Optimizer Studio to a logical cpu on the respective socket.
  3. sample_cpu() will be called for sampling per-logical-cpu events. This function will be called concurrently for each logical cpu. The calling thread will be affined by Optimizer Studio to the respective logical cpu.

The sample*() functions will then send their sampled metrics to the MetricsPluginData object by using the ::insert() method of that class. The Metric struct, once passed to Optimizer Studio, should not be deleted as long as the plugin is alive.

Upon initialization, Optimizer Studio will pass metrics parameters that were defined in the knobs.yaml file by invoking calls to set_metric_parameter().

An example plugin

Below is an example of a plugin:

/*
 * examplePlugin.h
 *
 * This example plugin returns an aggregated counter normalized by time, 
 * and will appear under "example.my_counter" metric name
 *
 */

#include "metricsPlugin.h"

class examplePlugin : public MetricsPlugin {
public:
  examplePlugin();
  ~examplePlugin();
  void sample_system(MetricsPluginData *current_values, int phase) override;
  std::string get_name();
private:
  int m_counter;
  MetricsPlugin::Metric m_metric;
};

examplePlugin::examplePlugin() {
  m_metric.name="my_counter";
  m_metric.aggregated=true;
  m_metric.normalize_by="duration";
  m_counter=0;
}

void examplePlugin::sample_system(MetricsPluginData *current_values, int phase) {
  current_values->insert(m_metric,m_counter++);
}

std::string examplePlugin::get_name() {
  return "example";
}

examplePlugin::~examplePlugin() {
}

extern "C" MetricsPlugin* create_object() {
  return new examplePlugin;
}

extern "C" void destroy_object( MetricsPlugin* object ) {
  delete (examplePlugin *) object;
}

Compiling a plugin library

To compile the example in examplePlugin.cpp:

$ g++ -fpic -shared -o libexampleplugin.so examplePlugin.cpp

Using a custom plugin

This will create a shared object by the name of libexampleplugin.so In order to load it, it needs to be specified in knobs.yaml:

domains:
  common:
    plugins: [/path/to/libexampleplugin.so]
    include_metrics: [example.my_counter]

On startup, if the library was loaded successfully Optimizer Studio will report:

Metrics plugin /path/to/libexampleplugin.so loaded successfully with name: example

Passing parameters to a plugin

It is possible to configure a custom plugin via knobs.yaml as follows:

domains:
  common:
    metrics:
      metric-name:
      kind: example
      key: value

Each metrics is required, at least, to define its kind - kind specifies the metric plugin to be loaded, and can be file, shell or a user-defined plugin.
Additional optional parameters are
- aggregated - true if the metric value is aggregated, false if it represents the change since the last measurement.
- normalize_by - the name of a metric that can be used to normalize this metric. It can be duration, performance or any other available metric.

When the example plugin is loaded, Optimizer Studio invokes:

example_plugin->set_metric_parameter("metric-name", "key", "value");

The "file" plugin

Metrics whose value is read from a file can be defined using the included file plugin - libfile-metrics.so.
Below is an example of a metric that is read from a file:

domains:
  common:
    metrics:
      metric-name:
        kind: file
        type: plaintext
        path: /tmp/metric

Beyond the mandatory kind parameter, a metric must have, at least, path, that specifies the path to the file containing the metric value.
The optional parameter type can be either plaintext or json.
The defaults for the optional parameters are: aggregated: false, normalize_by="", type: plaintext.

The "shell" plugin

Metrics whose value can be calculated using a shell script can be defined using the included shell plugin - libshell-metrics.so. Below is an example of a metric that is read from a file:

domains:
  common:
    metrics:
      metric-name:
        kind: shell
        init_script: echo 0 > /tmp/metric
        sample_script: awk '{print $2}' /tmp/metric

Beyond the mandatory kind parameter, a metric must have, at least, sample_script, that defines the script whose (standard) output is fed to the metric value.
The optional parameter init_script can contain a script required as a prerequisite for the shell knob - it can define a variable or source an external code file. The init_script is run once upon knob initialization. Notice, that all the shell metrics reside in the same shell, so that init_script of one metric impacts every shell metric.
The defaults for the optional parameters are: aggregated: false, normalize_by="".