# 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.