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

167 lines
4.4 KiB
Markdown
Raw Normal View History

# Chapter III: starting to simulate a real torsf experiment
In this chapter we will improve upon what we did in the previous
chapter by creating runner code for the `torsf` experiment. We will
not, yet, run the real experiment, but we will instead write
simple code that pretends to run a `tor` bootstrap using snowflake.
(This file is auto-generated from the corresponding source file,
so make sure you don't edit it manually.)
### The TestKeys structure
Let us start by defining the `TestKeys` structure that contains
the experiment specific results. As we have already seen in
Chapter I, this structure must contain two fields. The bootstrap
time for the experiment and the failure.
```Go
type TestKeys struct {
BootstrapTime float64 `json:"bootstrap_time"`
Failure *string `json:"failure"`
}
```
### Rewriting the Run method
Next we will rewrite the Run method. We will arrange for this
method to fill the `measurement`, to setup the timeout, and to
print periodic updates via the `callbacks`. We will defer the
real work to a private function called `run`.
```Go
func (m *Measurer) Run(
ctx context.Context, sess model.ExperimentSession,
measurement *model.Measurement, callbacks model.ExperimentCallbacks,
) error {
```
Let's create an instance of `TestKeys` and let's modify
the `measurement` to refer to such an instance.
```Go
testkeys := &TestKeys{}
measurement.TestKeys = testkeys
```
Next, we record the current time and we modify the
context to have a timeout after 300 seconds. Because
Snowflake *may* take a long time to bootstrap, we
need to specify a generous timeout here.
```Go
start := time.Now()
const maxRuntime = 300 * time.Second
ctx, cancel := context.WithTimeout(ctx, maxRuntime)
defer cancel()
```
Okay, now we are ready to defer the real work to
the internal `run` function. We first create a
channel to receive the result of `run`. Then, we
create a ticker to emit periodic updates. We
emit an update every 250 milliseconds, which is
a reasonably smooth way of increasing a progress
bar (progress is indeed used to move progress bars
both in OONI Probe Desktop and mobile.)
```Go
errch := make(chan error)
ticker := time.NewTicker(250 * time.Millisecond)
defer ticker.Stop()
```
Now we defer the real work to `run`, which will
run in a background goroutine.
```Go
go m.run(ctx, sess, testkeys, errch)
```
While `run` is running, we loop and check which
channel has become ready.
If the `errch` channel is ready, it means that `run` is
terminated, so we return to the caller.
Instead, if `ticker.C` is ready, we emit a progress
update using the `callbacks`.
```Go
for {
select {
case err := <-errch:
callbacks.OnProgress(1.0, "torsf experiment is finished")
return err
case <-ticker.C:
progress := time.Since(start).Seconds() / maxRuntime.Seconds()
callbacks.OnProgress(progress, "torsf experiment is running")
}
}
}
```
### The run function
We will now implement the `run` function. For now, this function
will not do any real work, but it will just pretend to do work.
Note how we sleep for some time, set the `BootstrapTime` field
of the `TestKeys`, and then return using `errch`.
```Go
func (m *Measurer) run(ctx context.Context,
sess model.ExperimentSession, testkeys *TestKeys, errch chan<- error) {
fakeBootstrapTime := 10 * time.Second
time.Sleep(fakeBootstrapTime)
testkeys.BootstrapTime = fakeBootstrapTime.Seconds()
errch <- nil
}
```
## Running the code
It's now time to run the new code we've written:
```
$ go run ./experiment/torsf/chapter03 | jq
2021/06/21 21:21:18 info [ 0.1%] torsf experiment is running
2021/06/21 21:21:19 info [ 0.2%] torsf experiment is running
[...]
2021/06/21 21:21:28 info [ 3.3%] torsf experiment is running
2021/06/21 21:21:28 info [100.0%] torsf experiment is finished
{
"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": {
"bootstrap_time": 10,
"failure": null
},
"test_name": "",
"test_runtime": 0,
"test_start_time": "",
"test_version": ""
}
```
You see that now we're filling the bootstrap time and we're
also printing progress using `callbacks`.
In the next chapter, we'll replace the stub `run` implementation
with a real implementation using Snowflake.