Files
mesh-catalog/modules/disk-load/cmd/disk-load/readings.go
T

140 lines
4.2 KiB
Go

package main
// **Disk usage readings and peaks per data item** (novox/hq ADR 0571 clause 3, ADR 0233 §21, §27, §34): disk-load measures
// each data item the modules of its node declare and keeps the newest reading and the item's largest size in seven
// days as its own state `readings`, one key `<node>.<module>.<item>`, written only when it changes. The node-backup
// holder, mounts, nfs-server and the self-check's probes read it there; the controller keeps none of it.
import (
"encoding/json"
"reflect"
"time"
)
// Version of a reading's layout.
const ReadingVersion = 1
// PeakWindow is how far back the peak looks (ADR 0233 §34).
const PeakWindow = 7 * 24 * time.Hour
// StateReadings is the state disk-load keeps.
const StateReadings = "readings"
// The ways an item is measured (ADR 0233 §22 to §25).
const (
MeasureWalk = "walk"
MeasureDataset = "dataset"
MeasureShallow = "shallow"
ClassCache = "cache"
)
// Item is one data item a module declares.
type Item struct {
Measure string `json:"measure,omitempty"`
Module string `json:"module"`
ID string `json:"item"`
Class string `json:"class"`
Path string `json:"path"`
CoveredBy string `json:"covered_by,omitempty"`
Protection string `json:"protection,omitempty"`
}
// Redundancy is the redundant storage an item is on, as read.
type Redundancy struct {
Kind string `json:"kind"`
Where string `json:"where"`
Healthy *bool `json:"healthy"`
Said string `json:"said"`
}
// Measured is what one measurement found.
type Measured struct {
SizeBytes *int64 `json:"size_bytes,omitempty"`
LastWrite *time.Time `json:"last_write,omitempty"`
MeasuredAt *time.Time `json:"measured_at,omitempty"`
// Precision says what the size is: "exact", "dataset <name>: its whole size", "partial: ..." (a lower bound,
// never compared), or "no size: ...".
Precision string `json:"precision,omitempty"`
Error string `json:"error,omitempty"`
Redundancy *Redundancy `json:"redundancy,omitempty"`
}
// DaySize is the largest size measured on one UTC day.
type DaySize struct {
Day string `json:"day"`
Bytes int64 `json:"bytes"`
}
// Reading is the value of one key.
type Reading struct {
V int `json:"v"`
Node string `json:"node"`
Item
Measured
// Peak is the largest size measured in the seven days up to the reading, PeakAt the day it was measured; Days are
// the day maxima it is read from.
Peak *int64 `json:"peak_bytes,omitempty"`
PeakAt string `json:"peak_day,omitempty"`
Days []DaySize `json:"days,omitempty"`
}
// ReadingKey is the key of an item on a node.
func ReadingKey(node, module, item string) string { return node + "." + module + "." + item }
// DecodeReading reads a key's value.
func DecodeReading(raw []byte) (Reading, error) {
var r Reading
err := json.Unmarshal(raw, &r)
return r, err
}
// Next is the reading after a measurement, and whether it differs from the one before in anything but the time it was
// taken (a reading that did not change is not written).
func Next(prev *Reading, node string, it Item, m Measured, now time.Time) (Reading, bool) {
r := Reading{V: ReadingVersion, Node: node, Item: it, Measured: m}
var days []DaySize
if prev != nil {
days = prev.Days
}
// A reading with no size (an error, a shallow item) carries the last size's days on, so the peak outlives one failed
// measurement.
today := now.UTC().Format("2006-01-02")
cut := now.UTC().Add(-PeakWindow).Format("2006-01-02")
var kept []DaySize
for _, d := range days {
if d.Day >= cut && d.Day != today {
kept = append(kept, d)
}
}
todays := int64(-1)
for _, d := range days {
if d.Day == today {
todays = d.Bytes
}
}
if m.SizeBytes != nil && !partial(m) && *m.SizeBytes > todays {
todays = *m.SizeBytes
}
if todays >= 0 {
kept = append(kept, DaySize{Day: today, Bytes: todays})
}
r.Days = kept
for _, d := range kept {
if r.Peak == nil || d.Bytes >= *r.Peak {
b := d.Bytes
r.Peak, r.PeakAt = &b, d.Day
}
}
if prev == nil {
return r, true
}
a, b := *prev, r
a.MeasuredAt, b.MeasuredAt = nil, nil
return r, !reflect.DeepEqual(a, b)
}
// partial says a size is only a lower bound, which a peak is never made of.
func partial(m Measured) bool {
return len(m.Precision) >= 7 && m.Precision[:7] == "partial"
}