diff --git a/internal/tasks/dailyAggregate.go b/internal/tasks/dailyAggregate.go index 738c705..6aac1c8 100644 --- a/internal/tasks/dailyAggregate.go +++ b/internal/tasks/dailyAggregate.go @@ -155,6 +155,11 @@ func (c *CronTask) aggregateDailySummary(ctx context.Context, targetTime time.Ti } else { c.Logger.Info("Daily aggregation deletion times", "source_lifecycle_cache", applied) } + if applied, err := db.ApplyLifecycleCreationToSummary(ctx, dbConn, summaryTable); err != nil { + c.Logger.Warn("failed to apply lifecycle creations to daily summary", "error", err, "table", summaryTable) + } else if applied > 0 { + c.Logger.Info("Daily aggregation creation times", "source_lifecycle_cache", applied) + } if err := db.RefineCreationDeletionFromUnion(ctx, dbConn, summaryTable, unionQuery); err != nil { c.Logger.Warn("failed to refine creation/deletion times", "error", err, "table", summaryTable) } @@ -406,6 +411,11 @@ LIMIT 1 } else { c.Logger.Debug("refined creation/deletion times", "table", summaryTable) } + if applied, err := db.ApplyLifecycleCreationToSummary(ctx, dbConn, summaryTable); err != nil { + c.Logger.Warn("failed to apply lifecycle creations to daily summary (Go path)", "error", err, "table", summaryTable) + } else if applied > 0 { + c.Logger.Info("Daily aggregation creation times (Go path)", "source_lifecycle_cache", applied) + } if err := db.UpdateSummaryPresenceByWindow(ctx, dbConn, summaryTable, dayStart.Unix(), dayEnd.Unix()); err != nil { c.Logger.Warn("failed to update daily AvgIsPresent from lifecycle window (Go path)", "error", err, "table", summaryTable) } diff --git a/internal/tasks/monthlyAggregate.go b/internal/tasks/monthlyAggregate.go index 302bee7..1f0571e 100644 --- a/internal/tasks/monthlyAggregate.go +++ b/internal/tasks/monthlyAggregate.go @@ -171,6 +171,11 @@ func (c *CronTask) aggregateMonthlySummary(ctx context.Context, targetMonth time } else { c.Logger.Info("Monthly aggregation deletion times", "source_lifecycle_cache", applied) } + if applied, err := db.ApplyLifecycleCreationToSummary(ctx, dbConn, monthlyTable); err != nil { + c.Logger.Warn("failed to apply lifecycle creations to monthly summary", "error", err, "table", monthlyTable) + } else if applied > 0 { + c.Logger.Info("Monthly aggregation creation times", "source_lifecycle_cache", applied) + } if err := db.UpdateSummaryPresenceByWindow(ctx, dbConn, monthlyTable, monthStart.Unix(), monthEnd.Unix()); err != nil { c.Logger.Warn("failed to update monthly AvgIsPresent from lifecycle window", "error", err, "table", monthlyTable) } @@ -293,6 +298,11 @@ func (c *CronTask) aggregateMonthlySummaryGoHourly(ctx context.Context, monthSta if err := c.insertDailyAggregates(ctx, summaryTable, aggMap, totalSamples, totalSamplesByVcenter); err != nil { return err } + if applied, err := db.ApplyLifecycleCreationToSummary(ctx, dbConn, summaryTable); err != nil { + c.Logger.Warn("failed to apply lifecycle creations to monthly summary (Go hourly)", "error", err, "table", summaryTable) + } else if applied > 0 { + c.Logger.Info("Monthly aggregation creation times (Go hourly)", "source_lifecycle_cache", applied) + } if err := db.UpdateSummaryPresenceByWindow(ctx, dbConn, summaryTable, monthStart.Unix(), monthEnd.Unix()); err != nil { c.Logger.Warn("failed to update monthly AvgIsPresent from lifecycle window (Go hourly)", "error", err, "table", summaryTable) } @@ -365,6 +375,11 @@ func (c *CronTask) aggregateMonthlySummaryGo(ctx context.Context, monthStart, mo } else { c.Logger.Info("Monthly aggregation deletion times", "source_lifecycle_cache", applied) } + if applied, err := db.ApplyLifecycleCreationToSummary(ctx, dbConn, summaryTable); err != nil { + c.Logger.Warn("failed to apply lifecycle creations to monthly summary (Go)", "error", err, "table", summaryTable) + } else if applied > 0 { + c.Logger.Info("Monthly aggregation creation times (Go)", "source_lifecycle_cache", applied) + } if err := db.RefineCreationDeletionFromUnion(ctx, dbConn, summaryTable, unionQuery); err != nil { c.Logger.Warn("failed to refine creation/deletion times (monthly Go)", "error", err, "table", summaryTable) diff --git a/server/handler/dailyCreationDiagnostics.go b/server/handler/dailyCreationDiagnostics.go index e3c15ba..2fef1dd 100644 --- a/server/handler/dailyCreationDiagnostics.go +++ b/server/handler/dailyCreationDiagnostics.go @@ -68,6 +68,22 @@ func (h *Handler) DailyCreationDiagnostics(w http.ResponseWriter, r *http.Reques return } + var avgIsPresentLtOne int64 + avgPresenceQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE "AvgIsPresent" IS NOT NULL AND "AvgIsPresent" < 0.999999`, tableName) + if err := dbConn.GetContext(ctx, &avgIsPresentLtOne, avgPresenceQuery); err != nil { + h.Logger.Warn("daily creation diagnostics avg-is-present count failed", "table", tableName, "error", err) + writeJSONError(w, http.StatusInternalServerError, "failed to read avg is present rows") + return + } + + var missingPartialCount int64 + missingPartialQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE ("CreationTime" IS NULL OR "CreationTime" = 0) AND "AvgIsPresent" IS NOT NULL AND "AvgIsPresent" < 0.999999`, tableName) + if err := dbConn.GetContext(ctx, &missingPartialCount, missingPartialQuery); err != nil { + h.Logger.Warn("daily creation diagnostics missing partial count failed", "table", tableName, "error", err) + writeJSONError(w, http.StatusInternalServerError, "failed to read missing partial rows") + return + } + missingPct := 0.0 if totalRows > 0 { missingPct = float64(missingTotal) * 100 / float64(totalRows) @@ -133,6 +149,42 @@ LIMIT %d rows.Close() } + partialSamples := make([]models.DailyCreationMissingSample, 0, sampleLimit) + partialSampleQuery := fmt.Sprintf(` +SELECT "Vcenter","VmId","VmUuid","Name","SamplesPresent","AvgIsPresent","SnapshotTime" +FROM %s +WHERE ("CreationTime" IS NULL OR "CreationTime" = 0) + AND "AvgIsPresent" IS NOT NULL + AND "AvgIsPresent" < 0.999999 +ORDER BY "SamplesPresent" DESC +LIMIT %d +`, tableName, sampleLimit) + if rows, err := dbConn.QueryxContext(ctx, partialSampleQuery); err != nil { + h.Logger.Warn("daily creation diagnostics partial sample failed", "table", tableName, "error", err) + } else { + for rows.Next() { + var ( + vcenter string + vmId, vmUuid, name sql.NullString + samplesPresent, snapshotTime sql.NullInt64 + avgIsPresent sql.NullFloat64 + ) + if err := rows.Scan(&vcenter, &vmId, &vmUuid, &name, &samplesPresent, &avgIsPresent, &snapshotTime); err != nil { + continue + } + partialSamples = append(partialSamples, models.DailyCreationMissingSample{ + Vcenter: vcenter, + VmId: vmId.String, + VmUuid: vmUuid.String, + Name: name.String, + SamplesPresent: samplesPresent.Int64, + AvgIsPresent: avgIsPresent.Float64, + SnapshotTime: snapshotTime.Int64, + }) + } + rows.Close() + } + response := models.DailyCreationDiagnosticsResponse{ Status: "OK", Date: parsed.Format("2006-01-02"), @@ -140,8 +192,11 @@ LIMIT %d TotalRows: totalRows, MissingCreationCount: missingTotal, MissingCreationPct: missingPct, + AvgIsPresentLtOneCount: avgIsPresentLtOne, + MissingCreationPartialCount: missingPartialCount, MissingByVcenter: byVcenter, Samples: samples, + MissingCreationPartialSamples: partialSamples, } w.Header().Set("Content-Type", "application/json") diff --git a/server/models/diagnostics.go b/server/models/diagnostics.go index 6ffb655..316b68b 100644 --- a/server/models/diagnostics.go +++ b/server/models/diagnostics.go @@ -25,6 +25,9 @@ type DailyCreationDiagnosticsResponse struct { TotalRows int64 `json:"total_rows"` MissingCreationCount int64 `json:"missing_creation_count"` MissingCreationPct float64 `json:"missing_creation_pct"` + AvgIsPresentLtOneCount int64 `json:"avg_is_present_lt_one_count"` + MissingCreationPartialCount int64 `json:"missing_creation_partial_count"` MissingByVcenter []DailyCreationMissingByVcenter `json:"missing_by_vcenter"` Samples []DailyCreationMissingSample `json:"samples"` + MissingCreationPartialSamples []DailyCreationMissingSample `json:"missing_creation_partial_samples"` } diff --git a/server/router/docs/swagger.json b/server/router/docs/swagger.json index a126b80..fc78561 100644 --- a/server/router/docs/swagger.json +++ b/server/router/docs/swagger.json @@ -1356,6 +1356,12 @@ "missing_creation_pct": { "type": "number" }, + "avg_is_present_lt_one_count": { + "type": "integer" + }, + "missing_creation_partial_count": { + "type": "integer" + }, "missing_by_vcenter": { "type": "array", "items": { @@ -1367,6 +1373,12 @@ "items": { "$ref": "#/definitions/models.DailyCreationMissingSample" } + }, + "missing_creation_partial_samples": { + "type": "array", + "items": { + "$ref": "#/definitions/models.DailyCreationMissingSample" + } } } }, diff --git a/server/router/docs/swagger.yaml b/server/router/docs/swagger.yaml index 84cd721..6b5390f 100644 --- a/server/router/docs/swagger.yaml +++ b/server/router/docs/swagger.yaml @@ -247,6 +247,10 @@ definitions: type: integer missing_creation_pct: type: number + avg_is_present_lt_one_count: + type: integer + missing_creation_partial_count: + type: integer missing_by_vcenter: items: $ref: '#/definitions/models.DailyCreationMissingByVcenter' @@ -255,6 +259,10 @@ definitions: items: $ref: '#/definitions/models.DailyCreationMissingSample' type: array + missing_creation_partial_samples: + items: + $ref: '#/definitions/models.DailyCreationMissingSample' + type: array type: object models.DailyCreationMissingByVcenter: properties: