fix new-vm detection interval
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-01-21 09:36:19 +11:00
parent fd9cc185ce
commit 00805513c9
2 changed files with 61 additions and 27 deletions

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"strconv"
"strings" "strings"
"time" "time"
@@ -43,18 +44,47 @@ func boolStringFromInterface(value interface{}) string {
// latestHourlySnapshotBefore finds the most recent hourly snapshot table prior to the given time, skipping empty tables. // latestHourlySnapshotBefore finds the most recent hourly snapshot table prior to the given time, skipping empty tables.
func latestHourlySnapshotBefore(ctx context.Context, dbConn *sqlx.DB, cutoff time.Time) (string, error) { func latestHourlySnapshotBefore(ctx context.Context, dbConn *sqlx.DB, cutoff time.Time) (string, error) {
rows, err := dbConn.QueryxContext(ctx, ` tables, err := listLatestHourlyWithRows(ctx, dbConn, "", cutoff.Unix(), 1)
SELECT table_name, snapshot_time, snapshot_count
FROM snapshot_registry
WHERE snapshot_type = 'hourly' AND snapshot_time < ? AND snapshot_count > 0
ORDER BY snapshot_time DESC
LIMIT 50
`, cutoff.Unix())
if err != nil { if err != nil {
return "", err return "", err
} }
if len(tables) == 0 {
return "", nil
}
return tables[0].Table, nil
}
// parseSnapshotTime extracts the unix suffix from an inventory_hourly table name.
func parseSnapshotTime(table string) (int64, bool) {
const prefix = "inventory_hourly_"
if !strings.HasPrefix(table, prefix) {
return 0, false
}
ts, err := strconv.ParseInt(strings.TrimPrefix(table, prefix), 10, 64)
if err != nil {
return 0, false
}
return ts, true
}
// listLatestHourlyWithRows returns recent hourly snapshot tables (ordered desc by time) that have rows, optionally filtered by vcenter.
func listLatestHourlyWithRows(ctx context.Context, dbConn *sqlx.DB, vcenter string, beforeUnix int64, limit int) ([]snapshotTable, error) {
if limit <= 0 {
limit = 50
}
rows, err := dbConn.QueryxContext(ctx, `
SELECT table_name, snapshot_time, snapshot_count
FROM snapshot_registry
WHERE snapshot_type = 'hourly' AND snapshot_time < ?
ORDER BY snapshot_time DESC
LIMIT ?
`, beforeUnix, limit)
if err != nil {
return nil, err
}
defer rows.Close() defer rows.Close()
var out []snapshotTable
for rows.Next() { for rows.Next() {
var name string var name string
var ts int64 var ts int64
@@ -65,23 +95,34 @@ LIMIT 50
if err := db.ValidateTableName(name); err != nil { if err := db.ValidateTableName(name); err != nil {
continue continue
} }
// Rely on snapshot_count to avoid costly table scans; fall back to a cheap row check only if count is zero. // Use snapshot_count first; fall back to row check (and vcenter filter) only when needed.
if count > 0 { if count == 0 {
return name, nil if has, _ := db.TableHasRows(ctx, dbConn, name); !has {
continue
}
} }
if hasRows, _ := db.TableHasRows(ctx, dbConn, name); hasRows { if vcenter != "" {
return name, nil vrows, qerr := querySnapshotRows(ctx, dbConn, name, []string{"VmId"}, `"Vcenter" = ? LIMIT 1`, vcenter)
if qerr != nil {
continue
}
hasVcenter := vrows.Next()
vrows.Close()
if !hasVcenter {
continue
}
} }
out = append(out, snapshotTable{Table: name, Time: ts})
} }
return "", nil return out, nil
} }
// HasSnapshotGap reports whether the gap between prev and curr exceeds 2x the expected interval. // SnapshotTooSoon reports whether the gap between prev and curr is significantly shorter than expected (default: <50% interval).
func HasSnapshotGap(prevUnix, currUnix int64, expectedSeconds int64) bool { func SnapshotTooSoon(prevUnix, currUnix int64, expectedSeconds int64) bool {
if prevUnix == 0 || currUnix == 0 || expectedSeconds <= 0 { if prevUnix == 0 || currUnix == 0 || expectedSeconds <= 0 {
return false return false
} }
return currUnix-prevUnix > expectedSeconds*2 return currUnix-prevUnix < expectedSeconds/2
} }
// querySnapshotRows builds a SELECT with proper rebind for the given table/columns/where. // querySnapshotRows builds a SELECT with proper rebind for the given table/columns/where.

View File

@@ -1104,23 +1104,16 @@ func (c *CronTask) compareWithPreviousSnapshot(
c.Logger.Warn("failed to locate previous hourly snapshot for deletion comparison", "error", prevTableErr, "url", url) c.Logger.Warn("failed to locate previous hourly snapshot for deletion comparison", "error", prevTableErr, "url", url)
} }
prevSnapshotTime := int64(0) prevSnapshotTime, _ := parseSnapshotTime(prevTableName)
if prevTableName != "" {
if suffix := strings.TrimPrefix(prevTableName, "inventory_hourly_"); suffix != prevTableName {
if ts, err := strconv.ParseInt(suffix, 10, 64); err == nil {
prevSnapshotTime = ts
}
}
}
newCount := 0 newCount := 0
if prevTableName != "" { if prevTableName != "" {
moreMissing := c.markMissingFromPrevious(ctx, dbConn, prevTableName, url, startTime, presentSnapshots, presentByUuid, presentByName, inventoryByVmID, inventoryByUuid, inventoryByName) moreMissing := c.markMissingFromPrevious(ctx, dbConn, prevTableName, url, startTime, presentSnapshots, presentByUuid, presentByName, inventoryByVmID, inventoryByUuid, inventoryByName)
missingCount += moreMissing missingCount += moreMissing
expectedSeconds := int64(durationFromSeconds(c.Settings.Values.Settings.VcenterInventorySnapshotSeconds, time.Hour).Seconds()) expectedSeconds := int64(durationFromSeconds(c.Settings.Values.Settings.VcenterInventorySnapshotSeconds, time.Hour).Seconds())
// Allow runs as soon as half the normal interval; treat larger gaps as unreliable for "new" detection. // Skip only if snapshots are much closer together than the configured cadence (e.g., rerun inside half interval).
if HasSnapshotGap(prevSnapshotTime, startTime.Unix(), expectedSeconds/2) { if SnapshotTooSoon(prevSnapshotTime, startTime.Unix(), expectedSeconds) {
c.Logger.Info("skipping new-VM detection due to gap between snapshots", "prev_table", prevTableName, "prev_snapshot_unix", prevSnapshotTime, "current_snapshot_unix", startTime.Unix()) c.Logger.Info("skipping new-VM detection because snapshots are too close together", "prev_table", prevTableName, "prev_snapshot_unix", prevSnapshotTime, "current_snapshot_unix", startTime.Unix(), "expected_interval_seconds", expectedSeconds)
} else { } else {
newCount = countNewFromPrevious(ctx, dbConn, prevTableName, url, presentSnapshots) newCount = countNewFromPrevious(ctx, dbConn, prevTableName, url, presentSnapshots)
if newCount > 0 { if newCount > 0 {