fix new-vm detection interval
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -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 {
|
|
||||||
return name, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", nil
|
if vcenter != "" {
|
||||||
|
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 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.
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user