package disk import ( "errors" "math" "sort" "strings" "time" ) type RawPerformanceSample struct { ObservedAt time.Time `json:"observedAt"` ReadBytesPerSecond *float64 `json:"readBytesPerSecond,omitempty"` WriteBytesPerSecond *float64 `json:"writeBytesPerSecond,omitempty"` ReadIOPS *float64 `json:"readIops,omitempty"` WriteIOPS *float64 `json:"writeIops,omitempty"` ReadLatencyMs *float64 `json:"readLatencyMs,omitempty"` WriteLatencyMs *float64 `json:"writeLatencyMs,omitempty"` } type PerformanceSample struct { ObservedAt time.Time `json:"observedAt"` ReadBytesPerSecond *float64 `json:"readBytesPerSecond,omitempty"` WriteBytesPerSecond *float64 `json:"writeBytesPerSecond,omitempty"` ReadIOPS *float64 `json:"readIops,omitempty"` WriteIOPS *float64 `json:"writeIops,omitempty"` ReadLatencyMs *float64 `json:"readLatencyMs,omitempty"` WriteLatencyMs *float64 `json:"writeLatencyMs,omitempty"` } type RawPerformance struct { Available bool `json:"available"` Current RawPerformanceSample `json:"current"` History []RawPerformanceSample `json:"history,omitempty"` } type Performance struct { State string `json:"state"` Current PerformanceSample `json:"current"` History []PerformanceSample `json:"history,omitempty"` } type RawTemperature struct { Available bool `json:"available"` Celsius float64 `json:"celsius"` ObservedAt time.Time `json:"observedAt"` } type Temperature struct { State string `json:"state"` Celsius *float64 `json:"celsius,omitempty"` Status string `json:"status"` ObservedAt *time.Time `json:"observedAt,omitempty"` } type RawSpin struct { Supported bool `json:"supported"` State string `json:"state"` } type Spin struct { State string `json:"state"` } type PerformancePolicy struct { MaxHistory int TemperatureWarningCelsius float64 TemperatureCriticalCelsius float64 TemperatureRecoveryCelsius float64 } func (p PerformancePolicy) withDefaults() PerformancePolicy { if p.MaxHistory == 0 { p.MaxHistory = 120 } if p.TemperatureWarningCelsius == 0 { p.TemperatureWarningCelsius = 50 } if p.TemperatureCriticalCelsius == 0 { p.TemperatureCriticalCelsius = 55 } if p.TemperatureRecoveryCelsius == 0 { p.TemperatureRecoveryCelsius = 45 } return p } func (p PerformancePolicy) Validate() error { if p.MaxHistory < 1 || p.MaxHistory > 1000 || p.TemperatureRecoveryCelsius >= p.TemperatureWarningCelsius || p.TemperatureWarningCelsius >= p.TemperatureCriticalCelsius { return errors.New("disk performance policy is outside safe bounds") } return nil } func normalizePerformance(raw *RawPerformance, now time.Time, policy PerformancePolicy) (*Performance, error) { if raw == nil { return nil, nil } policy = policy.withDefaults() if err := policy.Validate(); err != nil { return nil, err } result := &Performance{State: "unknown", History: []PerformanceSample{}} if !raw.Available { result.State = "unsupported" return result, nil } if len(raw.History) > policy.MaxHistory { return nil, errors.New("disk performance history exceeds bounds") } current, err := normalizeSample(raw.Current, now) if err != nil { return nil, err } result.State = "available" result.Current = current for _, item := range raw.History { sample, sampleErr := normalizeSample(item, now) if sampleErr != nil { return nil, sampleErr } result.History = append(result.History, sample) } sort.Slice(result.History, func(i, j int) bool { return result.History[i].ObservedAt.After(result.History[j].ObservedAt) }) return result, nil } func normalizeSample(raw RawPerformanceSample, now time.Time) (PerformanceSample, error) { observed := raw.ObservedAt if observed.IsZero() { observed = now } if observed.After(now.Add(time.Minute)) { return PerformanceSample{}, errors.New("disk performance observation is materially in the future") } for _, value := range []*float64{raw.ReadBytesPerSecond, raw.WriteBytesPerSecond, raw.ReadIOPS, raw.WriteIOPS, raw.ReadLatencyMs, raw.WriteLatencyMs} { if value != nil && (*value < 0 || math.IsNaN(*value) || math.IsInf(*value, 0)) { return PerformanceSample{}, errors.New("disk performance value is invalid") } } return PerformanceSample{ObservedAt: observed.UTC(), ReadBytesPerSecond: raw.ReadBytesPerSecond, WriteBytesPerSecond: raw.WriteBytesPerSecond, ReadIOPS: raw.ReadIOPS, WriteIOPS: raw.WriteIOPS, ReadLatencyMs: raw.ReadLatencyMs, WriteLatencyMs: raw.WriteLatencyMs}, nil } func normalizeTemperature(raw *RawTemperature, now time.Time, policy PerformancePolicy) (*Temperature, error) { if raw == nil { return nil, nil } result := &Temperature{State: "unknown", Status: "unknown"} if !raw.Available { return result, nil } if raw.Celsius < -50 || raw.Celsius > 150 || math.IsNaN(raw.Celsius) || math.IsInf(raw.Celsius, 0) { return nil, errors.New("disk temperature is invalid") } observed := raw.ObservedAt if observed.IsZero() { observed = now } if observed.After(now.Add(time.Minute)) { return nil, errors.New("disk temperature observation is materially in the future") } result.State = "available" result.Celsius = &raw.Celsius value := observed.UTC() result.ObservedAt = &value result.Status = temperatureStatus("normal", raw.Celsius, policy) return result, nil } func temperatureStatus(previous string, celsius float64, policy PerformancePolicy) string { policy = policy.withDefaults() if celsius >= policy.TemperatureCriticalCelsius { return "critical" } if celsius >= policy.TemperatureWarningCelsius { return "attention" } if (previous == "attention" || previous == "critical") && celsius >= policy.TemperatureRecoveryCelsius { return previous } return "normal" } func TemperatureStatus(previous string, celsius float64, policy PerformancePolicy) string { return temperatureStatus(strings.ToLower(previous), celsius, policy) } func normalizeSpin(raw *RawSpin) (*Spin, error) { if raw == nil { return nil, nil } if !raw.Supported { return &Spin{State: "unsupported"}, nil } state := strings.ToLower(strings.TrimSpace(raw.State)) switch state { case "spinning", "idle", "standby", "unknown": return &Spin{State: state}, nil default: return &Spin{State: "unknown"}, nil } }