mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
Preserve independent ARC and stop cancelled CORE reporting before I/O
Complete the native CORE #2077 repair after its whole-suite adverse result: retain ARC without inventing free RAM, enforce live-window freshness even with coarse RRD steps, and assert bounded graph splitting including CPU temperature. Keep empty/zero, aligned ARC and transport boundaries intact. Change-source: pulse-maintainer
This commit is contained in:
@@ -291,13 +291,16 @@ row timestamps and supported object aliases retain compatibility. A metric is
|
||||
never a timestamp, a null is never zero and a window mean is never a replacement
|
||||
for a missing current row. History retains measured buckets; native current
|
||||
readings accept only the last two steps before the requested end, preserving
|
||||
sample time rather than stamping an old value as newly measured. The five-state
|
||||
sample time rather than stamping an old value as newly measured. This allowance
|
||||
never exceeds the live query window, even with a coarse returned step. The five-state
|
||||
CORE CPU vector is normalized by its sum (RRD state rates need not sum to 100).
|
||||
All-zero CPU or complete memory-class vectors are empty buckets, not 100% usage;
|
||||
explicit zero usage, all-idle CPU, zero-free RAM with used pages and zero I/O
|
||||
remain measured zeros. Free memory comes from `memory-free_value`, not active
|
||||
pages or the sum of memory classes. ARC subtraction uses the matching free-RAM
|
||||
bucket. Reporting I/O values remain rates in the existing bytes/s contract;
|
||||
bucket when free RAM is present; independently reported ARC remains available
|
||||
without claiming measured memory usage. Pre-cancelled collection stops before
|
||||
reading the catalogue or posting a reporting query. Reporting I/O values remain rates in the existing bytes/s contract;
|
||||
step supplies time, not a second rate division; `overlap` is not extra traffic.
|
||||
|
||||
Legacy REST reads `/reporting/graphs` once per live/History collection and sends
|
||||
|
||||
@@ -342,6 +342,14 @@ func systemInfoFromMetricHistory(history *SystemMetricHistory) *SystemInfo {
|
||||
latestFree = free.Timestamp
|
||||
}
|
||||
}
|
||||
// ARC can be observed independently when no free-RAM sample exists;
|
||||
// retain it without implying known memory usage. With free RAM, require
|
||||
// cache and free to describe the same bucket before deriving usage.
|
||||
if latestFree.IsZero() {
|
||||
if value, ok := latestTimeSeriesValue(history.ARCSizeBytes); ok {
|
||||
system.ARCSizeBytes = int64(value)
|
||||
}
|
||||
}
|
||||
for _, arc := range history.ARCSizeBytes {
|
||||
if arc.Timestamp.Equal(latestFree) {
|
||||
system.ARCSizeBytes = int64(arc.Value)
|
||||
|
||||
@@ -2195,8 +2195,8 @@ func TestRESTReportingGraphFailurePreservesSnapshotTelemetry(t *testing.T) {
|
||||
t.Fatal("inventory lost")
|
||||
}
|
||||
}
|
||||
if transport.calls != 12 {
|
||||
t.Fatalf("reporting calls = %d, want bounded batch + five graphs per snapshot", transport.calls)
|
||||
if transport.calls != 2*(1+len(legacyRESTReportingGraphs())) {
|
||||
t.Fatalf("reporting calls = %d, want bounded batch + selected graphs per snapshot", transport.calls)
|
||||
}
|
||||
history, err := client.GetSystemMetricHistory(context.Background(), time.Hour)
|
||||
if err != nil {
|
||||
@@ -2464,7 +2464,7 @@ func TestRESTSnapshotRetainsSanitizedTelemetryFailure(t *testing.T) {
|
||||
}
|
||||
wantCalls := 1
|
||||
if tc.status == 422 {
|
||||
wantCalls = 6
|
||||
wantCalls = 1 + len(legacyRESTReportingGraphs())
|
||||
}
|
||||
if transport.calls != wantCalls {
|
||||
t.Fatalf("reporting calls=%d, want %d", transport.calls, wantCalls)
|
||||
|
||||
@@ -16,6 +16,9 @@ import (
|
||||
// Older REST bridges without the catalogue retain the unscoped compatibility
|
||||
// request. Authentication, transport and malformed replies are never retried.
|
||||
func (c *Client) legacyReportingGraphs(ctx context.Context) ([]map[string]any, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var catalogue []struct {
|
||||
Name string `json:"name"`
|
||||
Identifiers []string `json:"identifiers"`
|
||||
@@ -97,7 +100,9 @@ func reportingRow(response trueNASReportingGetDataResponse, index int) (time.Tim
|
||||
|
||||
// Keep native History intact, but do not label an old RRD point as a current
|
||||
// reading. Two returned steps allow the usual unfinished/null RRD bucket;
|
||||
// older points stay available only through History. Preserve row indices.
|
||||
// older points stay available only through History. Never exceed the live
|
||||
// query window even if a coarse/malformed step allows older buckets. Preserve
|
||||
// row indices.
|
||||
func liveReportingResponses(responses []trueNASReportingGetDataResponse, end int64) []trueNASReportingGetDataResponse {
|
||||
live := append([]trueNASReportingGetDataResponse(nil), responses...)
|
||||
for i, response := range live {
|
||||
@@ -108,7 +113,7 @@ func liveReportingResponses(responses []trueNASReportingGetDataResponse, end int
|
||||
for index := range response.Data {
|
||||
timestamp, _, ok := reportingRow(response, index)
|
||||
gap := end - timestamp.Unix()
|
||||
if !ok || timestamp.Unix() > end || (gap > response.Step && gap-response.Step > response.Step) {
|
||||
if !ok || timestamp.Unix() > end || gap > legacyRESTTelemetryWindowSeconds || (gap > response.Step && gap-response.Step > response.Step) {
|
||||
live[i].Data[index] = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,6 +417,28 @@ func TestCORE13ARCAlignedWithFreeMemory(t *testing.T) {
|
||||
assertCOREPoint(t, points, 0, 1789000000, 0)
|
||||
}
|
||||
|
||||
func TestCORE13CoarseStepDoesNotMakeOldRowsCurrent(t *testing.T) {
|
||||
response := trueNASReportingGetDataResponse{Name: "cpu", Legend: []string{"usage"}, Start: 1789000000, End: 1789000000, Step: 3600, Data: []any{[]any{12.}}}
|
||||
if history := parseSystemMetricHistory(liveReportingResponses([]trueNASReportingGetDataResponse{response}, 1789000400)); history != nil {
|
||||
t.Fatal("coarse step made an old CPU sample current")
|
||||
}
|
||||
if history := parseSystemMetricHistory([]trueNASReportingGetDataResponse{response}); history == nil {
|
||||
t.Fatal("live age filtering destroyed native History")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCORE13ARCOnlyDoesNotInventMemoryUsage(t *testing.T) {
|
||||
history := &SystemMetricHistory{ARCSizeBytes: []TimeSeriesPoint{{Timestamp: time.Unix(1789000000, 0), Value: 8 << 30}}}
|
||||
system := systemInfoFromMetricHistory(history)
|
||||
if system.ARCSizeBytes != 8<<30 || system.Telemetry.Memory {
|
||||
t.Fatal("ARC-only telemetry lost or claimed free RAM")
|
||||
}
|
||||
system.MemoryTotalBytes = 32 << 30
|
||||
if metricsFromTrueNASSystem(*system, 0, 0).Memory != nil {
|
||||
t.Fatal("ARC-only reading invented usage")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCORE13DiskTemperatureHistoryExternalTiming(t *testing.T) {
|
||||
response := trueNASReportingGetDataResponse{Name: "disktemp", Identifier: "disk-a", Legend: []string{"temperature"}, Start: 1789000000, End: 1789000020, Step: 10, Data: []any{[]any{40.}, []any{nil}, []any{42.}}}
|
||||
points := parseReportingDiskTemperatureHistory([]trueNASReportingGetDataResponse{response})["disk-a"]
|
||||
|
||||
Reference in New Issue
Block a user