disk-load: measure the node's data items and keep each reading and seven-day peak as its own state readings (issue 1955, ADR 0571 clause 3)
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
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"
|
||||
}
|
||||
Reference in New Issue
Block a user