Passo 1
O ponto: três inteiros
Todo ponto é um tempo, um valor e um peso, cada um um inteiro de 64 bits. O valor é
escalado: com expoente 2, 10800000000 quer dizer 108.000.000,00, exatamente.
ti64
1788220800000000000
nanossegundos desde 1970: 2026-09-01 00:00:00 UTC
vi64
10800000000
× 10−2, o expoente da série: 108.000.000,00
wu64
3
um peso: um volume, uma contagem de amostras; 1 quando não há
Por que não floats
Perto de 2026, o menor passo entre dois tempos que um número consegue guardar:
float de 32 bits137 s
float de 64 bits256 ns
inteiro de 64 bits, Nonius1 ns
Os floats só aparecem no último momento, depois de subtraído o início da janela visível, quando os números que sobram são pequenos.
Passo 2
NSB1: colunas que começam num múltiplo de 64
Os pontos viajam em colunas: todos os tempos juntos, depois todos os valores, depois os pesos. Um contêiner é um cabeçalho de 64 bytes, uma entrada de 48 bytes por coluna dizendo onde ela começa, as próprias colunas e um bloco de JSON dizendo o que é o tile. Aqui está um tile bruto inteiro, o tipo que carrega os próprios pontos (passo 4), com dois pontos e nenhum vizinho: todos os seus 477 bytes.
- cabeçalho, 0–63
- diretório de colunas, 64–207
- preenchimento com zeros
- t em 256, v em 320, w em 384
- metadados, 448–476
O cabeçalho, 64 bytes
- 0
- número mágico, ASCII
NONIUSB1 - 8
- versão, 1
- 10
- flags: tempos em ordem, tempos únicos
- 12
- número de colunas
- 16
- número de linhas
- 24
- onde começam as colunas
- 32
- onde começam os metadados
- 40
- o tamanho dos metadados em bytes
- 48
- hash FNV-1a-64 dos bytes das colunas, preenchimento incluído
Posições em bytes. Todo inteiro do contêiner é little-endian; os campos entre estes são zero.
A entrada de uma coluna, 48 bytes
- 0
- nome, 16 bytes de ASCII completados com zeros
- 16
- tipo: 1 inteiro de 64 bits com sinal, 2 sem sinal, 3 float
- 17
- expoente: 9 para t, o da série para os valores, 0 para os pesos
- 24
- onde começam seus bytes
- 32
- seu tamanho: 8 bytes por linha, sem preenchimento
Assim o expoente de um valor viaja com o tile: neste, a entrada de v diz 2, e 10800000000 é 108.000.000,00.
A coluna t, no byte 256
0100: 00 00 f7 db 57 09 d1 18 dc 05 f7 db 57 09 d1 18
Oito bytes por linha, o menos significativo primeiro: 0x18d10957dbf70000
é 1788220800000000000, e a linha seguinte é 1.500 a mais, 1,5 µs depois.
Lido no lugar
const t = new BigInt64Array(bytes.buffer, 256, 2);
// 1788220800000000000n, 1788220800000001500n
Toda coluna começa num múltiplo de 64, então quem lê enxerga a coluna como um array onde ela está, sem interpretá-la: o layout dos buffers do Arrow. O pacote do navegador copia um tile uma vez, para a memória do gráfico, e o lê ali. Um hash das colunas deixa quem lê recusar um tile danificado.
Passo 3
A pirâmide: resumos em cada zoom
Um dia pode ter milhões de pontos, e uma tela tem cerca de duas mil colunas de pixels. Por isso o servidor guarda a série resumida em níveis, os buckets de cada nível quatro vezes mais largos que os do nível de baixo, todos alinhados a 1970.
Um bucket são oito números
- first_t
- first_v
- last_t
- last_v
- min
- max
- count
- weight
O primeiro e o último ponto, o menor e o maior valor, quantos são e o seu peso. Desenhada como um traço do menor ao maior, cada coluna ligada à seguinte, a linha é a mesma, pixel a pixel, que a linha desenhada com todos os pontos.
A visualização escolhe o nível
Com resumos a partir de 1 ms, um dia em 1.920 pixels pede colunas de 45 s. O nível mais grosso que ainda dá 16 buckets ou mais por coluna é o nível 5, 1,024 s: 43 dos seus buckets formam uma coluna, 1.964 colunas para o dia. Aproxime o bastante e nenhum nível é fino o suficiente: a visualização lê os pontos brutos.
Passo 4
Tiles por HTTP, como os de um mapa
O cliente pede só os tiles de que a sua visualização precisa. Um tile é uma sequência fixa de colunas de um nível, num endereço cuja resposta nunca muda, então o navegador e todo cache no caminho o guardam por um ano.
GET /v1/series/day/columns/0/16/27940950000
- day
- a série
- 0
- o nível
- 16
- buckets juntados numa coluna
- 27940950000
- qual sequência de colunas: aqui, quatro colunas por tile
Cada tile cobre um trecho fixo: um tile de colunas, uma sequência de colunas; um tile
bruto, um trecho de tempo: o tile bruto i cobre [i · R, (i + 1) · R)
nanossegundos desde 1970. Os metadados da série, em /v1/series/{id}, dão essas
larguras (R é o seu raw_tile_ns). Um tile também traz o vizinho
mais próximo de cada lado, uma coluna ou um ponto logo fora do seu trecho, quando existe,
para que a linha possa ser desenhada entrando e saindo da visualização. Eles contam entre
as suas linhas, e quem lê os distingue pelo tempo.
O que volta
200 OK
Content-Type: application/vnd.nonius.nsb1
ETag: "e490c413ec950166"
Cache-Control: public, max-age=31536000, immutable
Um contêiner NSB1 de oito colunas, uma por campo do bucket, e três linhas: as duas colunas do tile que têm pontos e a seguinte, que a linha precisa para sair da visualização.
A última linha diz o que ele é
{"tile":"columns","level":0,"k":16,"i":"27940950000"}
O cliente confere com o endereço que pediu. O ETag é o hash do cabeçalho, então pedir
de novo um tile que não mudou custa um 304. Bem de perto, os tiles brutos
em /raw/{i} trazem os próprios pontos: três colunas, t, v e w, como as do
passo 2.
Passo 5
O feed ao vivo: quadros numerados
Pontos novos chegam num WebSocket em quadros, cada um numerado e com as mesmas três colunas, 24 bytes por ponto. Redes perdem quadros; os números mostram onde.
NLS1início: a partir do 0NLF1 · 0aplicadoNLF1 · 1perdidoNLF1 · 2uma lacuna: reconectar a partir do 0NLF1 · 1, 2aplicados, em ordem
Um quadro
O número mágico, o número de linhas, o número do quadro, e então as colunas.
Nada é adivinhado
Um quadro fora de vez nunca é aplicado. O cliente reconecta a partir do último quadro que aplicou, e tudo antes dele continua certo. Quando o feed está parado, heartbeats dizem o próximo número, então até a perda do último quadro é notada.
O todo
As especificações são escritas para que se possa construir um cliente só a partir delas, e um foi construído: suas colunas batem com as do núcleo. Estão em inglês.
- O protocolo (em inglês): o contêiner, a pirâmide, tiles, metadados, HTTP, seleção de pontos e imagens.
- O arquivo de pirâmide (em inglês): uma série e seus resumos, lidos no lugar.
- O formato do feed ao vivo, no
nonius-live(em inglês).