A pirâmide
Um ano de leituras uma por segundo são 31,5 milhões de pontos. Uma tela tem uns 2.000 pixels de largura. Ler os 31,5 milhões de pontos para desenhar 2.000 colunas, sessenta vezes por segundo enquanto a pessoa arrasta, é lento demais — e mandá-los pela rede, mais ainda. A pirâmide é como o Nonius desenha esse ano a partir de alguns milhares de números, e ainda desenha exatamente a linha que todos os pontos desenhariam.
Um resumo: o bucket
Um bucket resume os pontos de um trecho de tempo em oito números:
| Campo | O que guarda |
|---|---|
first_t, first_v | o tempo e o valor do primeiro ponto |
last_t, last_v | o tempo e o valor do último ponto |
min, max | o menor e o maior valor |
count | quantos pontos |
weight | a soma dos pesos |
Dois buckets vizinhos se juntam num só: o primeiro do primeiro, o último do segundo, o menor dos dois mínimos, o maior dos dois máximos, as contagens e os pesos somados. Assim um bucket de um minuto sai de quatro de quinze segundos, sem voltar aos pontos.
Níveis
A pirâmide é feita de níveis de buckets. Os buckets do nível 0 têm a largura da base — digamos, um segundo. Cada nível acima é quatro vezes mais largo: 4 s, 16 s, 64 s, e assim por diante. Os buckets são alinhados a 1970, então todo cliente e todo servidor os cortam nos mesmos lugares. Um trecho sem pontos não tem bucket.
| Nível | Largura do bucket, base 1 s |
|---|---|
| 0 | 1 s |
| 1 | 4 s |
| 2 | 16 s |
| 4 | 4 min 16 s |
| 8 | 18 h 12 min |
O que uma vista lê
Uma vista é um intervalo de tempo sobre um número de colunas de pixels. Cada coluna cobre um trecho de tempo; o gráfico escolhe o nível mais grosso cujos buckets cabem pelo menos 16 numa coluna, e os junta num resumo por coluna. Quando nem o nível 0 serve — menos de 16 larguras de base por coluna, no fundo de um zoom —, ele lê os pontos crus.
Então um quadro lê entre 16 e 64 buckets por coluna, alguns milhares de números para a tela inteira, seja qual for o tamanho do histórico. E a linha é exata: uma coluna desenhada do menor ao maior valor, ligada à seguinte do seu último valor ao primeiro da próxima, é a mesma, pixel por pixel, que a linha desenhada com todos os pontos. A pirâmide muda quanto se lê, nunca o que se desenha.
Escolher a base e os níveis
Dois números, definidos onde a pirâmide é construída:
-
A base: mais ou menos o espaçamento dos seus pontos onde eles são mais densos. Uma leitura por segundo, base de um segundo. Pontos em rajadas — mil por segundo durante dez segundos a cada hora — usam o espaçamento dentro da rajada, um milissegundo. Uma base muito mais fina que os pontos custa memória (64 bytes por bucket); muito mais grossa, e um zoom profundo lê mais pontos crus por coluna do que precisa.
-
Os níveis: o suficiente para a sua vista mais larga. Uma vista lê no máximo 64 buckets por coluna enquanto o nível do topo for fino o bastante; depois disso, os buckets do topo se acumulam. Use níveis suficientes para que
base × 4^(níveis − 1) ≥ intervalo mais largo ÷ (64 × largura do gráfico)com a largura do gráfico em pixels do dispositivo (pixels CSS × a razão de pixels). Use pelo menos 2, mesmo quando a regra dá 1: o servidor sempre mantém o nível do topo, então um nível 0 sozinho, com um ponto por bucket, fica inteiro (O que custa). Três dias de leituras a cada 10 segundos, base de 10 s, custam 88 bytes por ponto num arquivo de pirâmide com 1 nível e 40 com 2. Níveis a mais custam pouco — 44 bytes com 3, 46 com 9 —, então na dúvida use mais.
Exemplo: pontos a um segundo de distância, um ano mostrado em 2.000 pixels. O intervalo mais largo é 31.536.000 s; dividido por 64 × 2.000, dá uns 246 s. Com base de 1 s, o nível do topo precisa ter pelo menos 246 s: 4⁴ = 256 s é o nível 4, então 5 níveis.
Nenhum dos dois números é uma promessa que você não pode mudar: um arquivo de série guarda os pontos, não uma pirâmide, que é construída a partir deles quando o gráfico ou o servidor carrega o arquivo. (O arquivo de pirâmide de um servidor guarda uma já construída, e é construído de novo para mudá-la.)
Onde eles são definidos
| Onde | Como | Padrão |
|---|---|---|
| Navegador | createChart(canvas, { baseNs, levels }) | 1 s, 9 níveis |
| .NET | new Series(exponent, baseNs, levels) | 1 s, 9 níveis |
nonius-server | --base-ns N --levels L | 1 ms, 12 níveis |
| Rust | Pyramid::new(base, levels) | — |
Os padrões cobrem anos de pontos a um segundo de distância. A base de
um milissegundo do nonius-server serve para dados rápidos; para um
sensor que lê a cada poucos segundos, passe --base-ns. Um gráfico
alimentado por tiles usa a pirâmide do servidor, dos seus metadados:
os ajustes do próprio gráfico são para pontos que você entrega.
No Rust, construir uma pirâmide são duas chamadas:
#![allow(unused)]
fn main() {
// Summaries from one millisecond up, eight levels.
let mut pyramid = Pyramid::new(Width::new(1_000_000)?, 8)?;
pyramid.extend_sorted(series.iter())?;
}
O que custa
Um bucket tem 64 bytes. O nível 0 de uma base de um segundo sobre um
ano de pontos a cada segundo tem 31,5 milhões deles — tantos quanto os
pontos, com quase três vezes o tamanho. Esse nível não rende nada: um
bucket de um ponto é o ponto. Por isso o nonius-server libera um
nível cujos buckets têm em média menos de 4 pontos (--sparse 4, o
padrão) — qualquer nível menos o do topo, que ele sempre mantém — e
responde uma vista nessa profundidade com os próprios pontos. Cada
nível acima tem um quarto dos buckets do de baixo, então o resto da
pirâmide junto custa mais ou menos um terço do primeiro nível mantido.
No exemplo de Servir, isso dá: 24 bytes por ponto para os próprios pontos, 16 para o nível 1 (um bucket de 64 bytes a cada 4 pontos), e uns 5 para os níveis acima — 45 bytes por ponto.
Servir mostra um servidor construindo uma pirâmide e dizendo o que manteve.