KapfenbergWhat went wrong?Network and measurementLocal over cloud

The regulator's client, self-built — and then there were three

A second measuring method alongside Ookla: the regulator’s own client, packed into a home-built add-on. And three instruments that falsified each other.

In part one I replaced my measuring tool because the bundled one was off by a factor of four. An Ookla client has been measuring every 30 minutes since, and the series is good.

It has one property you need to know about, though: it holds true in my house. A provider does not have to believe it, and that is not even unfair. Ookla measures against some server, picked by proximity.

So a second method was added. And with it a problem I did not see coming.

Two methods, two different questions

In Austria the regulator runs its own network test. It measures against their servers and reports the result to their control server.

Ookla the regulator’s test
measures against nearest server the regulator’s server
result stays with me there as well
interval every 30 minutes four times a day, in windows
works as an indicator a record

The difference is not accuracy. It is anchoring: afterwards the result does not live only in my database.

The command-line client for it is openly available. The small print says it is meant for development and verification purposes — not a prohibition, but a pointer to what these measurements are not. More on that below.

First look at what the neighbourhood gets

Before building anything I queried the same regulator’s open data. Every measurement from their network test is published, with location, provider and technology.

Within eight kilometres, 5G only, my provider only — 539 measurements:

Download
lower quartile 104.2 Mbit/s
median 189.9
upper quartile 341.7
best decile 487.7
below 90 Mbit/s 22 % of measurements

For the first time my own series was placed rather than judged. The 242 to 338 of the good days sat between median and upper quartile — entirely unremarkable. The 60 to 95 of the bad days sat below the lower quartile.

That weakens a thesis I had almost come to believe: if the cell were fundamentally maxed out, the whole neighbourhood would be down there. It is not.

What this data does not give

In the window of the last seven days there were two matching measurements, in thirty days eleven. About what has been happening since Tuesday they say nothing.

And they are phone measurements from varying places, indoors and out. They describe the order of magnitude of the area, not my cell.

Three traps in the interface, all stepped into personally

  1. The radius parameter is called radius, not distance. With the wrong name you get an empty list back — which looks like “no data in this area”.
  2. The results field can be null instead of an empty list. Iterate over it and you crash in the middle of the evaluation.
  3. The most expensive one: the interface occasionally serves an empty page even though there is more to come. My loop stopped there. Two identical queries returned 5000 measurements once and 1000 the other time.

Number three comes with an admission: within a few minutes I reported first “only five measurements, too few to say anything” and then “118”. Same area, just queried to different depths. The script now stops only after three empty pages in a row — and returns 14,500 instead of 1000.

A stopping condition that listens to the first silence turns a gap in the data into a statement of fact.

What was built

Add-on bare Alpine, the C client pinned to one commit
Service HTTP inside the house; one call measures and answers as JSON
Schedule every 15 minutes in four windows: 06–08, 11–13, 17–19, 23–01

Four windows instead of round the clock, because every measurement really does occupy the line, and because the daily pattern only stays comparable if you always measure at the same times.

The client is pinned to a specific state, not to “latest release”. For a measuring instrument that is not pedantry: if the tool changes mid-series, you are afterwards comparing two different things.

The position had to be retrofitted

This is where it got interesting. The client sends its location as an empty list — hard-coded, with no switch. For a measurement that is supposed to record something later, that is the wrong default: without a location it is a measurement from somewhere.

So a small script patches three places in the source at build time and inserts the real fields.

The part that matters: it aborts if any one of the three places does not match exactly.

A half-applied patch would be worse than none. It would build, run and measure — and nobody would know what is in there.

And the field names do not come from memory, they come from the source of the control server that receives them. That is the difference between “should be called that” and “is called that”.

Honestly: whether the position really arrives was not yet confirmed that day. The open data lags one to two days behind.

Four build failures

  1. The base image was undefined, because a file was missing. Without it, what you are building on is not specified.
  2. A package conflict inside the ready-made base image. It pins a package archive in which two matching libraries do not match. An upgrade did not help — the pin does not even offer the newer version. Solution: bare Alpine, and therefore without the helper library; the service reads its own settings.
  3. A replacement in the Dockerfile that did not apply — and I reported “adjusted” without checking. The build then failed twice with the same error. Since then: rewrite the file instead of whittling at it, and look afterwards.
  4. The control server rejected the first request with “your client version is not supported”. That was a false trail: it was not the version, it was two mandatory fields that were never sent. With them it answers politely at any version string.

Number four is the instructive one. The error message was true and misleading at the same time — it described what the server could not recognise, not why.

What this is not

Not a certified measurement. Under the regulator’s rules that only works through a browser on a computer, on an otherwise idle connection, in three cycles on three different days — and then establishes prima facie evidence.

My series is something else and something more modest: a record made with a recognised tool, one that keeps a conversation factual. It does not replace evidence. Confuse the two and you end up with 300 measurements that formally count for nothing.

Timestamps in UTC, and why that is not a detail

The add-on’s container has no timezone, and it does not get one passed through either. So the table read 05:15 while the house clock said 07:15.

Now everything is UTC with an explicit Z. That costs two seconds of mental arithmetic when reading and saves two things: a discontinuity at the end of October when the clocks change — and a recipient who has to guess the zone.

How expensive a two-hour offset can get, I have paid for elsewhere: it cost me the entire attribution of blame.

The trap when extending the table

When a new column was added, the entire existing series almost disappeared. The reader discarded rows whose column count did not match the header — without an error message, just a suddenly much shorter table.

Missing fields are now padded. Only rows that are too long get discarded, because those point to a separator inside the text, and then everything would sit shifted by one.

And then there were three

The observation did not come out of an evaluation, it came from looking: at 5:00 and 5:30 the throughput is low, and Ookla is measuring at the same time as the regulator’s test.

That was right. By then three systems were measuring on this line: the regulator’s client in its windows, Ookla every 30 minutes round the clock, and the antenna itself twice a day.

Each pulls the line flat out for 20 to 45 seconds. Run two at once, and they share the bandwidth — both report half.

The evidence

107 measurements from the regulator’s client, grouped by their time distance to the nearest Ookla run:

Distance n down, median up, median
truly simultaneous, under 20 s 3 75 29
just beside it, 20–90 s 8 208 98
separated, 90–300 s 25 112 94
far apart, over 300 s 71 126 98

And now the sentence it turns on: the lowest download of the whole series and the lowest upload of the whole series both sit in those three rows — out of 107. The chance of both extremes landing in the same three by accident is under one in a thousand.

Those three results do not show what the line can do. They show what happens when you occupy it three times over.

Two wrong moves on the way there

First grouped by the minute — :00 and :30 against :15 and :45 — and came out with “no effect”.

Wrong grouping. Ookla does not run fixed to the half hour, it drifts by about 25 seconds per run and slowly wanders through the clock. Of 43 apparent collisions only three were real; the other 40 buried the effect.

Then “overlaps yes or no” with a 90-second tolerance — and again found nothing, this time even the other way round. Also wrong: at 90 seconds apart both measurements are long finished. Only the graded distance shows it.

Twice I asked the same question wrongly and twice got a clean answer. An evaluation does not tell you the grouping was wrong. It computes what you hand it.

And a sentence that had been false for four days

Three files had said since the build: “During a window, the house’s own sensor stays quiet.”

That was never built. The condition that should have done it appeared in no automation outside its own file. The intention was written down correctly and never implemented — and because it stood there, nobody looked again.

That is the most uncomfortable line in this article. Documentation that describes an intention as a state is worse than none.

The fixed schedule

An offset alone would not have been enough: the Ookla integration measures on an interval, not at fixed times, and therefore inevitably drifts back into the other measurement’s quarter hours.

So its own polling was switched off and the schedule set from outside:

regulator's client   :00  :15  :30  :45     only in the four windows
Ookla                :07       :37          round the clock, as before
antenna              08:07 and 20:07        outside all windows

Seven minutes apart, for measurements lasting under a minute.

Two pieces of aftercare belong to this:

Verified that Ookla still measures at all without its own polling. A disabled poll is a fine way to end a measurement series quietly. So it was triggered, and 30 seconds later the new value appeared.

And an alert if the schedule fails. If no new value arrives for three hours, the system says so. A schedule that silently stops is exactly the kind of fault you notice only when evaluating.

As an aside, the morning report moved from 07:30 to 08:15 and no longer measures itself, it reads out the 08:07 value. My first attempt would have removed only the measurement and left the report where it was — it would then have read out the previous evening’s value, eleven hours old.

What I would advise

  1. Add a second method if you want to prove something. Your own series is an indicator. A recognised tool is an argument.
  2. Look at what the neighbourhood gets before you suspect the provider. Open data exists; it places your numbers.
  3. Pin your measuring tool to one version. A tool that changes mid-series turns one series into two.
  4. Make a patch abort if it does not match exactly. Half applied is worse than not at all — because it runs.
  5. Count your instruments. Each one occupies the line. Three on one line measure each other.
  6. Only write down what is built. An intention that reads like a state ends every further check.

So what?

I set out to make my measurements more solid and first made them less usable. The third instrument produced the three worst values of the entire series — precisely the values I would otherwise have held up the loudest.

What remains is two things. A record made with a tool that is not mine. And a schedule in which the three no longer meet.

The rest is the old lesson in new clothing: an observer standing in the way measures itself as well.

What the clean series then showed is in part three: The weaker mast was the better one.