This commit is contained in:
henrygd
2026-04-26 19:24:12 -04:00
parent 788483ac56
commit df249b24f6
7 changed files with 40 additions and 26 deletions

View File

@@ -393,6 +393,7 @@ func (task *probeTask) aggregateLocked(duration time.Duration, now time.Time) pr
return aggregateBucketsSince(task.buckets[:], cutoff, now) return aggregateBucketsSince(task.buckets[:], cutoff, now)
} }
// resultLocked returns the aggregated probe result for the requested duration along with a bool indicating whether any data was available.
func (task *probeTask) resultLocked(duration time.Duration, now time.Time) (probe.Result, bool) { func (task *probeTask) resultLocked(duration time.Duration, now time.Time) (probe.Result, bool) {
agg := task.aggregateLocked(duration, now) agg := task.aggregateLocked(duration, now)
hourAgg := task.aggregateLocked(time.Hour, now) hourAgg := task.aggregateLocked(time.Hour, now)
@@ -401,18 +402,20 @@ func (task *probeTask) resultLocked(duration time.Duration, now time.Time) (prob
} }
result := agg.result() result := agg.result()
hourAvg := hourAgg.avgResponse() loss1m := result[3]
hourLoss := hourAgg.lossPercentage() response1h := hourAgg.avgResponse()
loss1h := hourAgg.lossPercentage()
if hourAgg.successCount > 0 { if hourAgg.successCount > 0 {
return probe.Result{ return probe.Result{
result[0], result[0],
hourAvg, response1h,
float64(hourAgg.minUs), float64(hourAgg.minUs),
float64(hourAgg.maxUs), float64(hourAgg.maxUs),
hourLoss, loss1m,
loss1h,
}, true }, true
} }
return probe.Result{result[0], hourAvg, 0, 0, hourLoss}, true return probe.Result{result[0], response1h, 0, 0, loss1m, loss1h}, true
} }
// aggregateSamplesSince aggregates raw samples newer than the cutoff. // aggregateSamplesSince aggregates raw samples newer than the cutoff.

View File

@@ -22,8 +22,8 @@ func TestProbeTaskAggregateLockedUsesRawSamplesForShortWindows(t *testing.T) {
agg := task.aggregateLocked(time.Minute, now) agg := task.aggregateLocked(time.Minute, now)
require.True(t, agg.hasData()) require.True(t, agg.hasData())
assert.Equal(t, 2, agg.totalCount) assert.Equal(t, int64(2), agg.totalCount)
assert.Equal(t, 1, agg.successCount) assert.Equal(t, int64(1), agg.successCount)
assert.Equal(t, 20.0, agg.result()[0]) assert.Equal(t, 20.0, agg.result()[0])
assert.Equal(t, 20.0, agg.result()[1]) assert.Equal(t, 20.0, agg.result()[1])
assert.Equal(t, 20.0, agg.result()[2]) assert.Equal(t, 20.0, agg.result()[2])
@@ -42,8 +42,8 @@ func TestProbeTaskAggregateLockedUsesMinuteBucketsForLongWindows(t *testing.T) {
agg := task.aggregateLocked(10*time.Minute, now) agg := task.aggregateLocked(10*time.Minute, now)
require.True(t, agg.hasData()) require.True(t, agg.hasData())
assert.Equal(t, 4, agg.totalCount) assert.Equal(t, int64(4), agg.totalCount)
assert.Equal(t, 3, agg.successCount) assert.Equal(t, int64(3), agg.successCount)
assert.Equal(t, 30.0, agg.result()[0]) assert.Equal(t, 30.0, agg.result()[0])
assert.Equal(t, 20.0, agg.result()[1]) assert.Equal(t, 20.0, agg.result()[1])
assert.Equal(t, 40.0, agg.result()[2]) assert.Equal(t, 40.0, agg.result()[2])
@@ -62,8 +62,8 @@ func TestProbeTaskAddSampleLockedTrimsRawSamplesButKeepsBucketHistory(t *testing
agg := task.aggregateLocked(10*time.Minute, now) agg := task.aggregateLocked(10*time.Minute, now)
require.True(t, agg.hasData()) require.True(t, agg.hasData())
assert.Equal(t, 2, agg.totalCount) assert.Equal(t, int64(2), agg.totalCount)
assert.Equal(t, 2, agg.successCount) assert.Equal(t, int64(2), agg.successCount)
assert.Equal(t, 15.0, agg.result()[0]) assert.Equal(t, 15.0, agg.result()[0])
assert.Equal(t, 10.0, agg.result()[1]) assert.Equal(t, 10.0, agg.result()[1])
assert.Equal(t, 20.0, agg.result()[2]) assert.Equal(t, 20.0, agg.result()[2])
@@ -76,20 +76,21 @@ func TestProbeManagerGetResultsIncludesHourResponseRange(t *testing.T) {
task.addSampleLocked(probeSample{responseUs: 10, timestamp: now.Add(-30 * time.Minute)}) task.addSampleLocked(probeSample{responseUs: 10, timestamp: now.Add(-30 * time.Minute)})
task.addSampleLocked(probeSample{responseUs: 20, timestamp: now.Add(-9 * time.Minute)}) task.addSampleLocked(probeSample{responseUs: 20, timestamp: now.Add(-9 * time.Minute)})
task.addSampleLocked(probeSample{responseUs: 40, timestamp: now.Add(-5 * time.Minute)}) task.addSampleLocked(probeSample{responseUs: 40, timestamp: now.Add(-5 * time.Minute)})
task.addSampleLocked(probeSample{responseUs: -1, timestamp: now.Add(-90 * time.Second)}) task.addSampleLocked(probeSample{responseUs: 30, timestamp: now.Add(-50 * time.Second)})
task.addSampleLocked(probeSample{responseUs: 30, timestamp: now.Add(-30 * time.Second)}) task.addSampleLocked(probeSample{responseUs: -1, timestamp: now.Add(-30 * time.Second)})
pm := &ProbeManager{probes: map[string]*probeTask{"icmp:example.com": task}} pm := &ProbeManager{probes: map[string]*probeTask{"icmp:example.com": task}}
results := pm.GetResults(uint16(time.Minute / time.Millisecond)) results := pm.GetResults(uint16(time.Minute / time.Millisecond))
result, ok := results["probe-1"] result, ok := results["probe-1"]
require.True(t, ok) require.True(t, ok)
require.Len(t, result, 5) require.Len(t, result, 6)
assert.Equal(t, 30.0, result[0]) assert.Equal(t, 30.0, result[0])
assert.Equal(t, 25.0, result[1]) assert.Equal(t, 25.0, result[1])
assert.Equal(t, 10.0, result[2]) assert.Equal(t, 10.0, result[2])
assert.Equal(t, 40.0, result[3]) assert.Equal(t, 40.0, result[3])
assert.Equal(t, 20.0, result[4]) assert.Equal(t, 50.0, result[4])
assert.Equal(t, 20.0, result[5])
} }
func TestProbeManagerGetResultsIncludesLossOnlyHourData(t *testing.T) { func TestProbeManagerGetResultsIncludesLossOnlyHourData(t *testing.T) {
@@ -103,12 +104,13 @@ func TestProbeManagerGetResultsIncludesLossOnlyHourData(t *testing.T) {
results := pm.GetResults(uint16(time.Minute / time.Millisecond)) results := pm.GetResults(uint16(time.Minute / time.Millisecond))
result, ok := results["probe-1"] result, ok := results["probe-1"]
require.True(t, ok) require.True(t, ok)
require.Len(t, result, 5) require.Len(t, result, 6)
assert.Equal(t, 0.0, result[0]) assert.Equal(t, 0.0, result[0])
assert.Equal(t, 0.0, result[1]) assert.Equal(t, 0.0, result[1])
assert.Equal(t, 0.0, result[2]) assert.Equal(t, 0.0, result[2])
assert.Equal(t, 0.0, result[3]) assert.Equal(t, 0.0, result[3])
assert.Equal(t, 100.0, result[4]) assert.Equal(t, 100.0, result[4])
assert.Equal(t, 100.0, result[5])
} }
func TestProbeConfigResultKeyUsesSyncedID(t *testing.T) { func TestProbeConfigResultKeyUsesSyncedID(t *testing.T) {
@@ -205,9 +207,10 @@ func TestProbeManagerApplySyncUpsertRunsImmediatelyAndReturnsResult(t *testing.T
defer pm.Stop() defer pm.Stop()
require.NoError(t, err) require.NoError(t, err)
require.Len(t, resp.Result, 5) require.Len(t, resp.Result, 6)
assert.GreaterOrEqual(t, resp.Result[0], 0.0) assert.GreaterOrEqual(t, resp.Result[0], 0.0)
assert.Equal(t, 0.0, resp.Result[4]) assert.Equal(t, 0.0, resp.Result[4])
assert.Equal(t, 0.0, resp.Result[5])
task := pm.probes["probe-1"] task := pm.probes["probe-1"]
require.NotNil(t, task) require.NotNil(t, task)
@@ -247,8 +250,8 @@ func TestProbeManagerUpsertProbeKeepsHistoryWhenOnlyIntervalChanges(t *testing.T
agg := updatedTask.aggregateLocked(time.Hour, now) agg := updatedTask.aggregateLocked(time.Hour, now)
require.True(t, agg.hasData()) require.True(t, agg.hasData())
assert.Equal(t, 2, agg.totalCount) assert.Equal(t, int64(2), agg.totalCount)
assert.Equal(t, 2, agg.successCount) assert.Equal(t, int64(2), agg.successCount)
assert.Equal(t, 18.0, agg.avgResponse()) assert.Equal(t, 18.0, agg.avgResponse())
select { select {

View File

@@ -44,7 +44,9 @@ type SyncResponse struct {
// //
// 3: max response over the last hour in microseconds // 3: max response over the last hour in microseconds
// //
// 4: packet loss percentage over the last hour (0-100) // 4: packet loss percentage (0-100)
//
// 5: packet loss percentage over the last hour (0-100)
type Result []float64 type Result []float64
// Get returns the value at the specified index or 0 if the index is out of range. // Get returns the value at the specified index or 0 if the index is out of range.

View File

@@ -117,7 +117,8 @@ func setProbeResultFields(record *core.Record, result probe.Result) {
record.Set("resAvg1h", result.Get(1)) record.Set("resAvg1h", result.Get(1))
record.Set("resMin1h", result.Get(2)) record.Set("resMin1h", result.Get(2))
record.Set("resMax1h", result.Get(3)) record.Set("resMax1h", result.Get(3))
record.Set("loss1h", result.Get(4)) record.Set("loss", result.Get(4))
record.Set("loss1h", result.Get(5))
record.Set("updated", nowString) record.Set("updated", nowString)
} }

View File

@@ -334,7 +334,7 @@ func updateNetworkProbesRecords(app core.App, data map[string]probe.Result, syst
var updateQuery *dbx.Query var updateQuery *dbx.Query
if !realtimeActive { if !realtimeActive {
db = app.DB() db = app.DB()
sql := fmt.Sprintf("UPDATE %s SET res={:res}, resMin1h={:resMin1h}, resMax1h={:resMax1h}, resAvg1h={:resAvg1h}, loss1h={:loss1h}, updated={:updated} WHERE id={:id}", collectionName) sql := fmt.Sprintf("UPDATE %s SET res={:res}, resMin1h={:resMin1h}, resMax1h={:resMax1h}, resAvg1h={:resAvg1h}, loss={:loss}, loss1h={:loss1h}, updated={:updated} WHERE id={:id}", collectionName)
updateQuery = db.NewQuery(sql) updateQuery = db.NewQuery(sql)
} }
@@ -349,7 +349,8 @@ func updateNetworkProbesRecords(app core.App, data map[string]probe.Result, syst
record.Set("resAvg1h", values.Get(1)) record.Set("resAvg1h", values.Get(1))
record.Set("resMin1h", values.Get(2)) record.Set("resMin1h", values.Get(2))
record.Set("resMax1h", values.Get(3)) record.Set("resMax1h", values.Get(3))
record.Set("loss1h", values.Get(4)) record.Set("loss", values.Get(4))
record.Set("loss1h", values.Get(5))
record.Set("updated", nowString) record.Set("updated", nowString)
err = app.SaveNoValidate(record) err = app.SaveNoValidate(record)
} }
@@ -360,7 +361,8 @@ func updateNetworkProbesRecords(app core.App, data map[string]probe.Result, syst
"resAvg1h": values.Get(1), "resAvg1h": values.Get(1),
"resMin1h": values.Get(2), "resMin1h": values.Get(2),
"resMax1h": values.Get(3), "resMax1h": values.Get(3),
"loss1h": values.Get(4), "loss": values.Get(4),
"loss1h": values.Get(5),
"updated": nowString, "updated": nowString,
}).Execute() }).Execute()
} }

View File

@@ -248,7 +248,7 @@ export function useNetworkProbesData(props: UseNetworkProbesProps) {
// const stats: NetworkProbeStatsRecord["stats"] = {} // const stats: NetworkProbeStatsRecord["stats"] = {}
// for (const probe of probes) { // for (const probe of probes) {
// // TODO: include only if probe.updated < charttime // // TODO: include only if probe.updated < charttime
// stats[probe.id] = [probe.res, probe.resAvg1h, probe.resMin1h, probe.resMax1h, probe.loss1h] // stats[probe.id] = [probe.res, probe.resAvg1h, probe.resMin1h, probe.resMax1h, probe.loss, probe.loss1h]
// } // }
// return stats // return stats
// } // }

View File

@@ -556,6 +556,7 @@ export interface NetworkProbeRecord {
resMin1h: number resMin1h: number
resMax1h: number resMax1h: number
resAvg1h: number resAvg1h: number
loss: number
loss1h: number loss1h: number
interval: number interval: number
enabled: boolean enabled: boolean
@@ -571,7 +572,9 @@ export interface NetworkProbeRecord {
* *
* 3: max response over the last hour in microseconds * 3: max response over the last hour in microseconds
* *
* 4: packet loss over 1 hour in % * 4: packet loss %
*
* 5: packet loss over the last hour in %
*/ */
type ProbeResult = number[] type ProbeResult = number[]