add record size to hourly snapshot page
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:
@@ -86,9 +86,6 @@ func (c *CronTask) RunVcenterSnapshotHourly(ctx context.Context, logger *slog.Lo
|
||||
if err := ensureDailyInventoryTable(ctx, dbConn, tableName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := report.RegisterSnapshot(ctx, c.Database, "hourly", tableName, startTime); err != nil {
|
||||
c.Logger.Warn("failed to register hourly snapshot", "error", err, "table", tableName)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var errCount int64
|
||||
@@ -102,6 +99,7 @@ func (c *CronTask) RunVcenterSnapshotHourly(ctx context.Context, logger *slog.Lo
|
||||
wg.Add(1)
|
||||
go func(url string) {
|
||||
defer wg.Done()
|
||||
vcStart := time.Now()
|
||||
if sem != nil {
|
||||
sem <- struct{}{}
|
||||
defer func() { <-sem }()
|
||||
@@ -110,6 +108,8 @@ func (c *CronTask) RunVcenterSnapshotHourly(ctx context.Context, logger *slog.Lo
|
||||
if err := c.captureHourlySnapshotForVcenter(ctx, startTime, tableName, url); err != nil {
|
||||
atomic.AddInt64(&errCount, 1)
|
||||
c.Logger.Error("hourly snapshot failed", "error", err, "url", url)
|
||||
} else {
|
||||
c.Logger.Info("Finished hourly snapshot for vcenter", "url", url, "duration", time.Since(vcStart))
|
||||
}
|
||||
}(url)
|
||||
}
|
||||
@@ -118,7 +118,15 @@ func (c *CronTask) RunVcenterSnapshotHourly(ctx context.Context, logger *slog.Lo
|
||||
return fmt.Errorf("hourly snapshot failed for %d vcenter(s)", errCount)
|
||||
}
|
||||
|
||||
c.Logger.Debug("Finished hourly vcenter snapshot")
|
||||
rowCount, err := db.TableRowCount(ctx, dbConn, tableName)
|
||||
if err != nil {
|
||||
c.Logger.Warn("unable to count hourly snapshot rows", "error", err, "table", tableName)
|
||||
}
|
||||
if err := report.RegisterSnapshot(ctx, c.Database, "hourly", tableName, startTime, rowCount); err != nil {
|
||||
c.Logger.Warn("failed to register hourly snapshot", "error", err, "table", tableName)
|
||||
}
|
||||
|
||||
c.Logger.Debug("Finished hourly vcenter snapshot", "vcenter_count", len(c.Settings.Values.Settings.VcenterAddresses), "table", tableName, "row_count", rowCount)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -161,12 +169,6 @@ func (c *CronTask) aggregateDailySummary(ctx context.Context, targetTime time.Ti
|
||||
return err
|
||||
}
|
||||
}
|
||||
if rowsExist, err := db.TableHasRows(ctx, dbConn, summaryTable); err != nil {
|
||||
return err
|
||||
} else if rowsExist {
|
||||
c.Logger.Debug("Daily summary already exists, skipping aggregation", "summary_table", summaryTable)
|
||||
return nil
|
||||
}
|
||||
|
||||
hourlySnapshots, err := report.ListSnapshotsByRange(ctx, c.Database, "hourly", dayStart, dayEnd)
|
||||
if err != nil {
|
||||
@@ -181,12 +183,10 @@ func (c *CronTask) aggregateDailySummary(ctx context.Context, targetTime time.Ti
|
||||
for _, snapshot := range hourlySnapshots {
|
||||
hourlyTables = append(hourlyTables, snapshot.TableName)
|
||||
}
|
||||
unionQuery := buildUnionQuery(hourlyTables, []string{
|
||||
`"InventoryId"`, `"Name"`, `"Vcenter"`, `"VmId"`, `"EventKey"`, `"CloudId"`, `"CreationTime"`,
|
||||
`"DeletionTime"`, `"ResourcePool"`, `"Datacenter"`, `"Cluster"`, `"Folder"`,
|
||||
`"ProvisionedDisk"`, `"VcpuCount"`, `"RamGB"`, `"IsTemplate"`, `"PoweredOn"`,
|
||||
`"SrmPlaceholder"`, `"VmUuid"`, `"SnapshotTime"`, `"IsPresent"`,
|
||||
}, templateExclusionFilter())
|
||||
unionQuery, err := buildUnionQuery(hourlyTables, summaryUnionColumns, templateExclusionFilter())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentTotals, err := db.SnapshotTotalsForUnion(ctx, dbConn, unionQuery)
|
||||
if err != nil {
|
||||
@@ -210,24 +210,23 @@ func (c *CronTask) aggregateDailySummary(ctx context.Context, targetTime time.Ti
|
||||
for _, snapshot := range prevSnapshots {
|
||||
prevTables = append(prevTables, snapshot.TableName)
|
||||
}
|
||||
prevUnion := buildUnionQuery(prevTables, []string{
|
||||
`"InventoryId"`, `"Name"`, `"Vcenter"`, `"VmId"`, `"EventKey"`, `"CloudId"`, `"CreationTime"`,
|
||||
`"DeletionTime"`, `"ResourcePool"`, `"Datacenter"`, `"Cluster"`, `"Folder"`,
|
||||
`"ProvisionedDisk"`, `"VcpuCount"`, `"RamGB"`, `"IsTemplate"`, `"PoweredOn"`,
|
||||
`"SrmPlaceholder"`, `"VmUuid"`, `"SnapshotTime"`, `"IsPresent"`,
|
||||
}, templateExclusionFilter())
|
||||
prevTotals, err := db.SnapshotTotalsForUnion(ctx, dbConn, prevUnion)
|
||||
if err != nil {
|
||||
c.Logger.Warn("unable to calculate previous day totals", "error", err, "date", prevStart.Format("2006-01-02"))
|
||||
prevUnion, err := buildUnionQuery(prevTables, summaryUnionColumns, templateExclusionFilter())
|
||||
if err == nil {
|
||||
prevTotals, err := db.SnapshotTotalsForUnion(ctx, dbConn, prevUnion)
|
||||
if err != nil {
|
||||
c.Logger.Warn("unable to calculate previous day totals", "error", err, "date", prevStart.Format("2006-01-02"))
|
||||
} else {
|
||||
c.Logger.Info("Daily snapshot comparison",
|
||||
"current_date", dayStart.Format("2006-01-02"),
|
||||
"previous_date", prevStart.Format("2006-01-02"),
|
||||
"vm_delta", currentTotals.VmCount-prevTotals.VmCount,
|
||||
"vcpu_delta", currentTotals.VcpuTotal-prevTotals.VcpuTotal,
|
||||
"ram_delta_gb", currentTotals.RamTotal-prevTotals.RamTotal,
|
||||
"disk_delta_gb", currentTotals.DiskTotal-prevTotals.DiskTotal,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
c.Logger.Info("Daily snapshot comparison",
|
||||
"current_date", dayStart.Format("2006-01-02"),
|
||||
"previous_date", prevStart.Format("2006-01-02"),
|
||||
"vm_delta", currentTotals.VmCount-prevTotals.VmCount,
|
||||
"vcpu_delta", currentTotals.VcpuTotal-prevTotals.VcpuTotal,
|
||||
"ram_delta_gb", currentTotals.RamTotal-prevTotals.RamTotal,
|
||||
"disk_delta_gb", currentTotals.DiskTotal-prevTotals.DiskTotal,
|
||||
)
|
||||
c.Logger.Warn("unable to build previous day union", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +239,11 @@ func (c *CronTask) aggregateDailySummary(ctx context.Context, targetTime time.Ti
|
||||
c.Logger.Error("failed to aggregate daily inventory", "error", err, "date", dayStart.Format("2006-01-02"))
|
||||
return err
|
||||
}
|
||||
if err := report.RegisterSnapshot(ctx, c.Database, "daily", summaryTable, dayStart); err != nil {
|
||||
rowCount, err := db.TableRowCount(ctx, dbConn, summaryTable)
|
||||
if err != nil {
|
||||
c.Logger.Warn("unable to count daily summary rows", "error", err, "table", summaryTable)
|
||||
}
|
||||
if err := report.RegisterSnapshot(ctx, c.Database, "daily", summaryTable, dayStart, rowCount); err != nil {
|
||||
c.Logger.Warn("failed to register daily snapshot", "error", err, "table", summaryTable)
|
||||
}
|
||||
|
||||
@@ -300,25 +303,14 @@ func (c *CronTask) aggregateMonthlySummary(ctx context.Context, targetMonth time
|
||||
return err
|
||||
}
|
||||
}
|
||||
if rowsExist, err := db.TableHasRows(ctx, dbConn, monthlyTable); err != nil {
|
||||
return err
|
||||
} else if rowsExist {
|
||||
c.Logger.Debug("Monthly summary already exists, skipping aggregation", "summary_table", monthlyTable)
|
||||
return nil
|
||||
}
|
||||
|
||||
dailyTables := make([]string, 0, len(dailySnapshots))
|
||||
for _, snapshot := range dailySnapshots {
|
||||
dailyTables = append(dailyTables, snapshot.TableName)
|
||||
}
|
||||
unionQuery := buildUnionQuery(dailyTables, []string{
|
||||
`"InventoryId"`, `"Name"`, `"Vcenter"`, `"VmId"`, `"EventKey"`, `"CloudId"`, `"CreationTime"`,
|
||||
`"DeletionTime"`, `"ResourcePool"`, `"Datacenter"`, `"Cluster"`, `"Folder"`,
|
||||
`"ProvisionedDisk"`, `"VcpuCount"`, `"RamGB"`, `"IsTemplate"`, `"PoweredOn"`,
|
||||
`"SrmPlaceholder"`, `"VmUuid"`, `"SnapshotTime"`, `"IsPresent"`,
|
||||
}, templateExclusionFilter())
|
||||
if strings.TrimSpace(unionQuery) == "" {
|
||||
return fmt.Errorf("no valid daily snapshot tables found for %s", targetMonth.Format("2006-01"))
|
||||
unionQuery, err := buildUnionQuery(dailyTables, summaryUnionColumns, templateExclusionFilter())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
monthlyTotals, err := db.SnapshotTotalsForUnion(ctx, dbConn, unionQuery)
|
||||
@@ -343,7 +335,11 @@ func (c *CronTask) aggregateMonthlySummary(ctx context.Context, targetMonth time
|
||||
c.Logger.Error("failed to aggregate monthly inventory", "error", err, "month", targetMonth.Format("2006-01"))
|
||||
return err
|
||||
}
|
||||
if err := report.RegisterSnapshot(ctx, c.Database, "monthly", monthlyTable, targetMonth); err != nil {
|
||||
rowCount, err := db.TableRowCount(ctx, dbConn, monthlyTable)
|
||||
if err != nil {
|
||||
c.Logger.Warn("unable to count monthly summary rows", "error", err, "table", monthlyTable)
|
||||
}
|
||||
if err := report.RegisterSnapshot(ctx, c.Database, "monthly", monthlyTable, targetMonth, rowCount); err != nil {
|
||||
c.Logger.Warn("failed to register monthly snapshot", "error", err, "table", monthlyTable)
|
||||
}
|
||||
|
||||
@@ -443,25 +439,36 @@ func ensureDailyInventoryTable(ctx context.Context, dbConn *sqlx.DB, tableName s
|
||||
return err
|
||||
}
|
||||
|
||||
return ensureSnapshotColumns(ctx, dbConn, tableName, []columnDef{
|
||||
return db.EnsureColumns(ctx, dbConn, tableName, []db.ColumnDef{
|
||||
{Name: "VcpuCount", Type: "BIGINT"},
|
||||
{Name: "RamGB", Type: "BIGINT"},
|
||||
})
|
||||
}
|
||||
func buildUnionQuery(tables []string, columns []string, whereClause string) string {
|
||||
func buildUnionQuery(tables []string, columns []string, whereClause string) (string, error) {
|
||||
if len(tables) == 0 {
|
||||
return "", fmt.Errorf("no tables provided for union")
|
||||
}
|
||||
if len(columns) == 0 {
|
||||
return "", fmt.Errorf("no columns provided for union")
|
||||
}
|
||||
|
||||
queries := make([]string, 0, len(tables))
|
||||
columnList := strings.Join(columns, ", ")
|
||||
for _, table := range tables {
|
||||
if _, err := db.SafeTableName(table); err != nil {
|
||||
continue
|
||||
safeName, err := db.SafeTableName(table)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
query := fmt.Sprintf("SELECT %s FROM %s", columnList, table)
|
||||
query := fmt.Sprintf("SELECT %s FROM %s", columnList, safeName)
|
||||
if whereClause != "" {
|
||||
query = fmt.Sprintf("%s WHERE %s", query, whereClause)
|
||||
}
|
||||
queries = append(queries, query)
|
||||
}
|
||||
return strings.Join(queries, "\nUNION ALL\n")
|
||||
if len(queries) == 0 {
|
||||
return "", fmt.Errorf("no valid tables provided for union")
|
||||
}
|
||||
return strings.Join(queries, "\nUNION ALL\n"), nil
|
||||
}
|
||||
|
||||
func templateExclusionFilter() string {
|
||||
@@ -525,70 +532,11 @@ type columnDef struct {
|
||||
Type string
|
||||
}
|
||||
|
||||
func summaryMetricColumns() []columnDef {
|
||||
return []columnDef{
|
||||
{Name: "InventoryId", Type: "BIGINT"},
|
||||
{Name: "Name", Type: "TEXT"},
|
||||
{Name: "Vcenter", Type: "TEXT"},
|
||||
{Name: "VmId", Type: "TEXT"},
|
||||
{Name: "EventKey", Type: "TEXT"},
|
||||
{Name: "CloudId", Type: "TEXT"},
|
||||
{Name: "CreationTime", Type: "BIGINT"},
|
||||
{Name: "DeletionTime", Type: "BIGINT"},
|
||||
{Name: "ResourcePool", Type: "TEXT"},
|
||||
{Name: "Datacenter", Type: "TEXT"},
|
||||
{Name: "Cluster", Type: "TEXT"},
|
||||
{Name: "Folder", Type: "TEXT"},
|
||||
{Name: "ProvisionedDisk", Type: "REAL"},
|
||||
{Name: "VcpuCount", Type: "BIGINT"},
|
||||
{Name: "RamGB", Type: "BIGINT"},
|
||||
{Name: "IsTemplate", Type: "TEXT"},
|
||||
{Name: "PoweredOn", Type: "TEXT"},
|
||||
{Name: "SrmPlaceholder", Type: "TEXT"},
|
||||
{Name: "VmUuid", Type: "TEXT"},
|
||||
{Name: "SamplesPresent", Type: "BIGINT"},
|
||||
}
|
||||
}
|
||||
|
||||
func summaryAvgColumns() []columnDef {
|
||||
return []columnDef{
|
||||
{Name: "AvgVcpuCount", Type: "REAL"},
|
||||
{Name: "AvgRamGB", Type: "REAL"},
|
||||
{Name: "AvgProvisionedDisk", Type: "REAL"},
|
||||
{Name: "AvgIsPresent", Type: "REAL"},
|
||||
{Name: "PoolTinPct", Type: "REAL"},
|
||||
{Name: "PoolBronzePct", Type: "REAL"},
|
||||
{Name: "PoolSilverPct", Type: "REAL"},
|
||||
{Name: "PoolGoldPct", Type: "REAL"},
|
||||
{Name: "Tin", Type: "REAL"},
|
||||
{Name: "Bronze", Type: "REAL"},
|
||||
{Name: "Silver", Type: "REAL"},
|
||||
{Name: "Gold", Type: "REAL"},
|
||||
}
|
||||
}
|
||||
|
||||
func ensureSnapshotColumns(ctx context.Context, dbConn *sqlx.DB, tableName string, columns []columnDef) error {
|
||||
if _, err := db.SafeTableName(tableName); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, column := range columns {
|
||||
if err := addColumnIfMissing(ctx, dbConn, tableName, column); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addColumnIfMissing(ctx context.Context, dbConn *sqlx.DB, tableName string, column columnDef) error {
|
||||
query := fmt.Sprintf(`ALTER TABLE %s ADD COLUMN "%s" %s`, tableName, column.Name, column.Type)
|
||||
if _, err := dbConn.ExecContext(ctx, query); err != nil {
|
||||
errText := strings.ToLower(err.Error())
|
||||
if strings.Contains(errText, "duplicate column") || strings.Contains(errText, "already exists") {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
var summaryUnionColumns = []string{
|
||||
`"InventoryId"`, `"Name"`, `"Vcenter"`, `"VmId"`, `"EventKey"`, `"CloudId"`, `"CreationTime"`,
|
||||
`"DeletionTime"`, `"ResourcePool"`, `"Datacenter"`, `"Cluster"`, `"Folder"`,
|
||||
`"ProvisionedDisk"`, `"VcpuCount"`, `"RamGB"`, `"IsTemplate"`, `"PoweredOn"`,
|
||||
`"SrmPlaceholder"`, `"VmUuid"`, `"SnapshotTime"`, `"IsPresent"`,
|
||||
}
|
||||
|
||||
func ensureSnapshotRowID(ctx context.Context, dbConn *sqlx.DB, tableName string) error {
|
||||
@@ -600,7 +548,7 @@ func ensureSnapshotRowID(ctx context.Context, dbConn *sqlx.DB, tableName string)
|
||||
return err
|
||||
}
|
||||
if !hasColumn {
|
||||
if err := addColumnIfMissing(ctx, dbConn, tableName, columnDef{Name: "RowId", Type: "BIGSERIAL"}); err != nil {
|
||||
if err := db.AddColumnIfMissing(ctx, dbConn, tableName, db.ColumnDef{Name: "RowId", Type: "BIGSERIAL"}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -851,7 +799,11 @@ func (c *CronTask) captureHourlySnapshotForVcenter(ctx context.Context, startTim
|
||||
if err := vc.Login(url); err != nil {
|
||||
return fmt.Errorf("unable to connect to vcenter: %w", err)
|
||||
}
|
||||
defer vc.Logout()
|
||||
defer func() {
|
||||
if err := vc.Logout(); err != nil {
|
||||
c.Logger.Warn("vcenter logout failed", "url", url, "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
vcVms, err := vc.GetAllVmReferences()
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user