Tuning
After you have written your software, you will most likely want to tune it. Tuning is the process of modifying parameters that your code uses to optimize for your given challenge. Consider our Camera module from the running example: we would like to tune the brightness and contrast so that the module optimally detects the track edges.
One of the key principles in the tuning process is to separate your compile-time configuration from your runtime configuration. As you might have expected, tuning parameters are a prime example of a runtime configuration. This means that you can tune parameters in your code, without the need to recompile. In the ASE labs, we go one step further by allowing you to change runtime parameters during execution, Over the Air (OTA). We will explore this in detail in this section.
Why you should avoid compile-time configuration
We know, it is tempting when building a new service: just introduce one more parameter in your code. You're sure it will be fine. For example, in our Camera module from the running example, we might do this:
int cameraBrightness = 50;
// ... the rest of your code
But the problem is, there will always be a new parameter to introuce and at some point, you will lose overview of all tuning parameters that exist. Besides:
- You now need to recompile your entire service, even if you want to change just one parameter
- This also holds for interpreted languages, you still need to modify code
- Your teammates do not know of all tuning parameters that can be used, you need to create a convention that cannot be enforced by the compiler/interpreter
- You cannot "swap" configurations. In the best case, you can backtrack your git commits but more likely you will need to document tuning parameters and edit them in code manually
- You cannot build multiple configurations in parallel. The closest approach would be to uncomment lines, but this is not desired for obvious reasons:
// for use outside
int cameraBrightness = 50;
// uncomment if using LED lights
// cameraBrightness = 100;
// uncomment if in a dimly lit room
// cameraBrightness = 200;
// uncomment if in a really dark room
// cameraBrightness = 220;
// see the problem yet?
That being said, having your contribution fail due to invalid tuning parameters or related problems is not a valid excuse. On the other hand, using the tools provided by the ASE labs to incorporate tuning, will positively impact your contribution grade advice. We'll explore how this is done in the next section.
Runtime configurations using service.yaml
files
Recall that we have introduced service.yaml
files, which define outputs, dependencies and options for a service. They serve as the blueprint for a given service: in just a glance you can understand how a service behaves, where data is outputted and which options are available for tuning. Let's consider our Camera module service.yaml
again:
# Service identifying information
name: camera
description: camera module to convert RGB images to an array of track edge X,Y-coordinates
# No dependencies, this is the first service in the pipeline
dependencies: []
# Output streams by the Camera service
outputs:
- name: trackdata
address: tcp://localhost:5050
- name: metrics
address: ipc://dev/metrics
# Options that can be accessed during runtime and tuned Over the Air (OTA) when applicable
options:
# the camera device to use, coresponding to /dev/video1 in this case
- name: camera-index
type: int
mutable: false # don't allow OTA changes
default: 1
# brightness to capture images with
- name: brightness
type: int
mutable: true # allow tuning the brightness OTA
default: 50
# contrast to capture images with
- name: contrast
type: int
mutable: true # allow tuning the contrast OTA
default: 20
Let's focus on the options
part of this YAML file. You should already notice how this houses all runtime parameters. Because the service.yaml file is read at the start of a service run, you can modify configurations here and try them without recompiling your code.
This only applies if you use either pkg-ServiceRunner when building your service in Golang, or the mod-ServiceWrapper binary for any other language. This is highly recommended but not required.
Every "configuration field" has four properties:
name
(string) : this is the name of the tuning parameter. It is case-sensitive and referenced in your code. Names should be uniquetype
('int' | 'float' | 'string'): the data type of the primitive. Supported types areint
,float
andstring
. The value will be parsed according to this typemutable
(boolean): whether or not this value can be mutated during service executiondefault
(int | float | string | null): the default value of this parameter. Whenmutable
is set to false, this will be the actual value of the parameter during execution.
With this in mind, try to understand why the following two statements hold:
- When setting
mutable
tofalse
, it is forbidden to omit a default value - When setting
mutable
totrue
, it is allowed to omit a default value
Recall that (unless explicitly stated), a module will look for its service.yaml
file in the current working directory. In all services provided by the ASE framework, you will hence see the following file structure:
mod-Camera
├── mod-Camera
├── service.yaml
When starting the mod-Camera
module, it will thus use the service.yaml
file:
# no service.yaml was specified explicitly, so we just use service.yaml
./mod-Camera
But this behavior can be overriden by explicitly passing the -service-yaml
flag when starting your service:
./mod-Camera -service-yaml service.yaml
This allows you to have multiple configurations in parallel, which you can just swap as needed, depending on the environment you are running your code in. Consider different brightness configurations, depending on the lighting situation. We might now tackle this with the following file structure:
mod-Camera
├── mod-Camera
├── service.yaml
├── brightroom.yaml
├── dimlitroom.yaml
...
You can just start the different configurations by switching flags:
# we are in a bright room
./mod-Camera -service-yaml brightroom.yaml
# we are in a dim lit room
./mod-Camera -service-yaml dimlitroom.yaml
Naturally, it is always better to make your service as resillient as possible and writing calibration software to help in different lightning enviornments would be the best option. But when this is not feasible, using runtime tunings using service.yaml
files is the second-best option.
Acessing tuning parameters at runtime
When the options are defined in your service YAML, you will need to read them into your code at runtime. The ASE framework tackles this for you, depending on which option you choose to run your binaries.
- Using mod-ServiceWrapper
- using pkg-ServiceRunner
mod-ServiceWrapper will inject all values and tuning parameters into your shell environment. On this page you can read about how this is done and which values you can read at runtime.
pkg-ServiceRunner will give you a tuningState
Golang object that implements the getTuningInt()
, getTuningFloat()
and getTuningString()
methods. On this page you can read how to use these methods.