How to tune applications with Concertio Optimizer

Seldomly do application developers know exactly how users will use their applications. In order to maximize performance, developers tune their applications according to synthetic loads and use cases.

Some applications have dynamic tuning capabilities, such as in databases, where the application behavior can change in response to the applied load. While this technique is beneficial, it is time-consuming to develop and may not capture all the important use-cases.

Starting with version 0.10 of Concertio Optimizer, application developers can perform dynamic tuning automatically.

In this post, we’ll show how this can be done using an example application written in bash.

Tuning a simple application – one knob and one metric

Below is an example application that has one knob that controls its behavior and one metric that describes its performance. The knob integer value is communicated via a file named “knob”, and the metric integer value is communicated via a file named “metric”.


#!/bin/bash
while true ; do
        knob=`cat knob`
        metric=$(( 50 - (knob-7) * (knob-7) ))
        echo "$metric" | tee metric.tmp
        mv metric.tmp metric
        sleep 0.2
done

Passing knob and metric values via files as we did above is not recommended unless proper lock mechanisms are used. We did it here only to keep the example simple.

We’ll now write a plugin to Concertio Optimizer that will sample “metric” and feed it into Optimizer as a metric with the name: “example_app.my_target_metric”. To do this, we’ll write a new C++ class that inherits from MetricsPlugin defined in metricsPlugin.h provided with Optimizer. The constructor defines the metrics, and sample_system() samples the metrics and feeds them to Optimizer.


#include 
#include "metricsPlugin.h"

class examplePlugin : public MetricsPlugin {
public:
        examplePlugin();
        ~examplePlugin() {};
        void sample_system(MetricsPluginData *current_values);
        std::string get_name() {return "example_app";};
private:
        MetricsPlugin::Metric m_target_metric;
};

examplePlugin::examplePlugin() {
        m_target_metric.name="my_target_metric";
        m_target_metric.aggregated=false;
}

void examplePlugin::sample_system(MetricsPluginData *current_values) {
        std::ifstream f("metric");
        int metric_val;
        f >> metric_val;
        current_values->insert(m_target_metric,metric_val);
}

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

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

We then compile the plugin as follows:


$ gcc -c -fpic example_app_plugin.cpp
$ gcc -shared -o libexampleplugin.so example_app_plugin.o

Putting everything together via the knobs.yaml configuration file:


domain:
  common:
    knobs:
      my_knob:
        options: [0,4,8,12]
        get_script: "cat knob"
        set_script: "echo $value > knob"
    metrics:
      include: [example_app.*]
      target: example_app.my_target_metric
      plugins: [libexampleplugin.so]

We’ll start with a knob value of 0:


$ echo 0 > knob

It took Optimizer around 90 seconds to properly tune our simple application and give it a 49x boost in its target metric.

Applications with phases

We’ve shown above a very simplistic example of an application. In reality, applications have phases, and in each phase the behavior of the application may be different. We’ll add phases to our simple bash application:


#!/bin/bash
    phase=0
    counter=0
    while true ; do
            ((counter++))
            if [ $counter -ge 150 ] ; then
                    phase=$(( (phase + 1) % 2 ))
                    counter=0
            fi
            max=$((5+phase*2))
            knob=`cat knob`
            metric=$(( 50 - (knob-max) * (knob-max) ))
            echo -e "${metric}\n${phase}" | tee metric.tmp
            mv metric.tmp metric
            sleep 0.2
    done

In the revised application, we have two phases, as follows:

PhaseTarget metric (x = knob value)
050-(x-5)^2
150-(x-7)^2

The revised application will spend 30 seconds in each phase, and then switch. In each phase, the target metric behaves differently, depending on the knob setting. This behavior resembles real applications with different phases of execution. In order to feed the new metric to Optimizer, we’ll alter the plugin:


class examplePlugin : public MetricsPlugin {
public:
        examplePlugin();
        ~examplePlugin() {};
        void sample_system(MetricsPluginData *current_values);
        std::string get_name() {return "example_app";};
private:
        MetricsPlugin::Metric m_target_metric, m_phase_metric;
};

examplePlugin::examplePlugin() {
        m_target_metric.name="my_target_metric";
        m_target_metric.aggregated=false;
        m_phase_metric.name="phase";
        m_phase_metric.aggregated=false;
}

void examplePlugin::sample_system(MetricsPluginData *current_values) {
        std::ifstream f("metric");
        int metric_val;
        f >> metric_val;
        current_values->insert(m_target_metric,metric_val);
        f >> metric_val;
        current_values->insert(m_phase_metric,metric_val);
}

class examplePlugin : public MetricsPlugin { public: examplePlugin(); ~examplePlugin() {}; void sample_system(MetricsPluginData *current_values); std::string get_name() {return "example_app";}; private: MetricsPlugin::Metric m_target_metric, m_phase_metric; }; examplePlugin::examplePlugin() { m_target_metric.name="my_target_metric"; m_target_metric.aggregated=false; m_phase_metric.name="phase"; m_phase_metric.aggregated=false; } void examplePlugin::sample_system(MetricsPluginData *current_values) { std::ifstream f("metric"); int metric_val; f >> metric_val; current_values->insert(m_target_metric,metric_val); f >> metric_val; current_values->insert(m_phase_metric,metric_val); }

We’ll now tune the application again:

In the first 3 minutes, Optimizer learned about the workload, which switched phases every 30 seconds. When enough data was gathered, Optimizer attempted to tune each phase separately. Approximately 6 minutes into the run, Optimizer found the best knob setting for each of the two phases, and switched to the correct setting after each phase change.

Optimizer can work with hundreds of application metrics in order to determine the phase of the application and to tune for each specific phase. Moreover, new knobs can be easily added in knobs.yaml.

The big picture

Application tuning is a complex task, involving many moving parts and use of advanced algorithms. Concertio Optimizer can be used to automate application tuning, freeing application developers to focus on the business logic.

Application tuning can be employed in conjunction with Optimizer’s CPU and OS tuning features for even greater gains.

Stay tuned for announcements of support for specific applications in the coming months!

Contact us

Have any lingering questions or need a walkthrough? Send us a message and we’ll get back to you.

Please Contact To Download Concertio HFT One Pager

Download Mellanox Solution Brief

Schedule A Demo

Let’s set-up a time to show how to unleash your system’s full potential.