Files
healthlog/internal/hae/mem_test.go
T
av 01d0de59df разбор метрик HAE: фикстуры, канонизация, парсер
- tmp/research/fixtures.py собирает фикстуры из архива, вычищая измерения и
  сохраняя порядок ключей, форму литералов, выравнивание меток и невидимые
  символы; шесть фикстур в internal/hae/testdata
- internal/canon — общий дом канонической формы, полноты и хеша: числа читаются
  литералом через json.Number, округление до 12 значащих цифр
- internal/hae — разбор секции metrics, вывод слоя по метрике, разделение схем
  сна, координаты интервалом; recover внутри Parse, фаззинг
- миграция bucket и docs/database.md
2026-08-01 17:31:41 +03:00

85 lines
2.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package hae_test
import (
"fmt"
"runtime"
"strings"
"testing"
"time"
"git.vakhrushev.me/av/healthlog/internal/hae"
)
// Тела доходили до 42 МиБ, и стратегия декодирования — часть контракта, а не
// деталь: разбор такого тела в map[string]any удерживает 197 МиБ кучи против
// 54 МиБ у формы с json.RawMessage. Вместе с самим телом и удвоением в чтении
// пик доходил бы до ~300 МиБ на доставку — это OOM ровно на пике потока, когда
// терять доставки дороже всего.
//
// Тест сторожит именно порядок величины, а не точное число: он обязан падать
// при возврате к map[string]any и не падать от смены версии Go.
func TestParseУдержаниеКучи(t *testing.T) {
if testing.Short() {
t.Skip("измерение кучи: не для -short")
}
body := largeBody(40, 4000)
t.Logf("тело %d МиБ", len(body)>>20)
var before, after runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&before)
res, err := hae.Parse(body, hae.Meta{Aggregation: "Minutes"})
if err != nil {
t.Fatalf("разбор: %v", err)
}
runtime.GC()
runtime.ReadMemStats(&after)
retained := int64(after.HeapAlloc) - int64(before.HeapAlloc)
limit := int64(len(body)) * 4
t.Logf("точек %d, удержано %d МиБ при теле %d МиБ",
len(res.Points), retained>>20, len(body)>>20)
if retained > limit {
t.Errorf("удержано %d МиБ при теле %d МиБ — больше четырёх тел; "+
"похоже, точки снова разбираются в map[string]any",
retained>>20, len(body)>>20)
}
// Результат обязан дожить до замера, иначе мерить нечего.
runtime.KeepAlive(res)
runtime.KeepAlive(body)
}
// largeBody собирает тело, похожее на настоящее: те же поля, тот же формат
// метки, минутное выравнивание.
func largeBody(metrics, points int) []byte {
var b strings.Builder
b.WriteString(`{"data":{"metrics":[`)
base := time.Date(2025, 6, 5, 0, 0, 0, 0, time.FixedZone("", 3*3600))
for m := range metrics {
if m > 0 {
b.WriteByte(',')
}
fmt.Fprintf(&b, `{"name":"metric_%d","units":"count","data":[`, m)
for p := range points {
if p > 0 {
b.WriteByte(',')
}
ts := base.Add(time.Duration(p) * time.Minute).Format("2006-01-02 15:04:05 -0700")
fmt.Fprintf(&b,
`{"date":"%s","qty":%d.09523182962471353,"source":"Device A|Device B"}`,
ts, p%97)
}
b.WriteString(`]}`)
}
b.WriteString(`]}}`)
return []byte(b.String())
}