ooni-probe-cli/internal/tutorial/experiment/torsf/chapter02/README.md

205 lines
5.5 KiB
Markdown
Raw Normal View History

# Chapter II: creating an empty experiment
In this chapter we will create an empty experiment and replace
the code calling the real `torsf` experiment in `main.go` to
call our empty experiment instead.
(This file is auto-generated from the corresponding source file,
so make sure you don't edit it manually.)
## Changes in main.go
In `main.go` we will simply replace the call to the
`torsf.NewExperimentMeasurer` function with a call to
a `NewExperimentMeasurer` function that we are going
to implement as part of this chapter.
After you do this, you also need to remove the now-unneded
import of the `torsf` package.
There are no additional changes to `main.go`.
```Go
m := NewExperimentMeasurer(Config{})
```
## The torsf.go file
This file will contain the implementation of the
`NewExperimentMeasurer` function.
As usual we start with the `package` declaration and
with the few imports we need to add.
```Go
package main
import (
"context"
"time"
"github.com/ooni/probe-cli/v3/internal/engine/model"
)
```
### Data structures
Next, we define data structures.
Config contains config for the torsf experiment. As for the real
`torsf` experiment, we don't have any specific config, so we keep
the structure empty. We still need to define a `Config` struct
here, because, by convention, all OONI experiments have a `Config`.
```Go
type Config struct{}
```
Measurer is the torsf measurer. This structure implements the
`model.ExperimentMeasurer` interface, as we will see below.
Most OONI experiments have a measurer that contains as its unique
field the specific configuration. Here we do the same.
```Go
type Measurer struct {
config Config
}
```
NewExperimentMeasurer creates a new model.ExperimentMeasurer
instance for performing `torsf` measurements. This function
will just assemble a new instance of `Measurer` with the `config`
that was passed as an argument.
```Go
func NewExperimentMeasurer(config Config) model.ExperimentMeasurer {
return &Measurer{config: config}
}
```
### Implementing the model.ExperimentMeasurer.
Now it's time to implement the methods required by the `model`'s
`ExperimentMeasurer` interface.
ExperimentName implements ExperimentMeasurer.ExperimentName. This function
returns the name of the experiment. This code is used by generic code
manipulating the experiment to print the experiment name.
```Go
func (m *Measurer) ExperimentName() string {
return "torsf"
}
```
ExperimentVersion implements ExperimentMeasurer.ExperimentVersion. This
function returns the version of the experiment. This code is also used by
generic code manipulating the experiment to print the experiment version.
```Go
func (m *Measurer) ExperimentVersion() string {
return "0.1.0"
}
```
Run implements ExperimentMeasurer.Run. This is the most interesting
function, where we run the experiment proper. In the previous chapter
we learned how to call this function from a `main.go` file. Here,
instead, we're going to create a minimal stub. In the subsequent
chapters, finally, we will modify this function until it is a
minimal implementation of the `torsf` experiment.
```Go
func (m *Measurer) Run(
ctx context.Context, sess model.ExperimentSession,
measurement *model.Measurement, callbacks model.ExperimentCallbacks,
) error {
```
As you can see, this is just a stub implementation that sleeps
for one second and prints a logging message.
```Go
time.Sleep(time.Second)
sess.Logger().Info("hello from the torsf experiment!")
return nil
}
```
### Summary keys
Before concluding this chapter, we also need to create the `SummaryKeys`
for this experiment. For historical reasons, the `TestKeys` of each
experiment is an `interface{}`. Every experiment also defines a `SummaryKeys`
data structure and a `GetSummaryKeys` method to convert the opaque
result of a measurement to the summary for such an experiment.
The experiment summary is *only* used by the OONI Probe CLI.
SummaryKeys contains summary keys for this experiment. Because this is
just an illustrative tutorial, we will just include a single key, named
`IsAnomaly`. This key is not exported as JSON and is used by the OONI
Probe CLI to inform the user of whether this measurement is ordinary or
anomalous. All OONI experiments' `SummaryKeys` contain such a field.
```Go
type SummaryKeys struct {
IsAnomaly bool `json:"-"`
}
```
GetSummaryKeys implements model.ExperimentMeasurer.GetSummaryKeys. This
method just converts the `TestKeys` inside `measurement` to an instance of
the `SummaryKeys` structure. For now, we'll just implement a stub returning
fake `SummaryKeys` declaring there was no anomaly.
```Go
func (m *Measurer) GetSummaryKeys(measurement *model.Measurement) (interface{}, error) {
return &SummaryKeys{IsAnomaly: false}, nil
}
```
## Running the code
We can run the code written in this chapter as follows:
```
$ go run ./experiment/torsf/chapter02
2021/06/21 20:48:32 info hello from the torsf experiment!
{
"data_format_version": "",
"input": null,
"measurement_start_time": "",
"probe_asn": "",
"probe_cc": "",
"probe_network_name": "",
"report_id": "",
"resolver_asn": "",
"resolver_ip": "",
"resolver_network_name": "",
"software_name": "",
"software_version": "",
"test_keys": null,
"test_name": "",
"test_runtime": 0,
"test_start_time": "",
"test_version": ""
}
```
Here you see that we're printing the log message and
that the `test_keys` are `null`.
The OONI data processing popeline will not be so happy
if we pass it a `null` settings, because there is not
much interesting data in there. We will thus start filling
it in the next chapter.