206 lines
7.3 KiB
Go
206 lines
7.3 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
"vctp/db"
|
|
"vctp/server/models"
|
|
)
|
|
|
|
// DailyCreationDiagnostics returns missing CreationTime diagnostics for a daily summary table.
|
|
// @Summary Daily summary CreationTime diagnostics
|
|
// @Description Returns counts of daily summary rows missing CreationTime and sample rows for the given date.
|
|
// @Tags diagnostics
|
|
// @Produce json
|
|
// @Param date query string true "Daily date (YYYY-MM-DD)"
|
|
// @Success 200 {object} models.DailyCreationDiagnosticsResponse "Diagnostics result"
|
|
// @Failure 400 {object} models.ErrorResponse "Invalid request"
|
|
// @Failure 404 {object} models.ErrorResponse "Summary not found"
|
|
// @Failure 500 {object} models.ErrorResponse "Server error"
|
|
// @Router /api/diagnostics/daily-creation [get]
|
|
func (h *Handler) DailyCreationDiagnostics(w http.ResponseWriter, r *http.Request) {
|
|
dateValue := strings.TrimSpace(r.URL.Query().Get("date"))
|
|
if dateValue == "" {
|
|
writeJSONError(w, http.StatusBadRequest, "date is required")
|
|
return
|
|
}
|
|
|
|
loc := time.Now().Location()
|
|
parsed, err := time.ParseInLocation("2006-01-02", dateValue, loc)
|
|
if err != nil {
|
|
writeJSONError(w, http.StatusBadRequest, "date must be YYYY-MM-DD")
|
|
return
|
|
}
|
|
|
|
tableName := fmt.Sprintf("inventory_daily_summary_%s", parsed.Format("20060102"))
|
|
if _, err := db.SafeTableName(tableName); err != nil {
|
|
writeJSONError(w, http.StatusBadRequest, "invalid summary table name")
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
dbConn := h.Database.DB()
|
|
if !db.TableExists(ctx, dbConn, tableName) {
|
|
writeJSONError(w, http.StatusNotFound, "daily summary table not found")
|
|
return
|
|
}
|
|
|
|
var totalRows int64
|
|
countQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, tableName)
|
|
if err := dbConn.GetContext(ctx, &totalRows, countQuery); err != nil {
|
|
h.Logger.Warn("daily creation diagnostics count failed", "table", tableName, "error", err)
|
|
writeJSONError(w, http.StatusInternalServerError, "failed to read summary rows")
|
|
return
|
|
}
|
|
|
|
var missingTotal int64
|
|
missingQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE "CreationTime" IS NULL OR "CreationTime" = 0`, tableName)
|
|
if err := dbConn.GetContext(ctx, &missingTotal, missingQuery); err != nil {
|
|
h.Logger.Warn("daily creation diagnostics missing count failed", "table", tableName, "error", err)
|
|
writeJSONError(w, http.StatusInternalServerError, "failed to read missing creation rows")
|
|
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)
|
|
}
|
|
|
|
byVcenter := make([]models.DailyCreationMissingByVcenter, 0)
|
|
byVcenterQuery := fmt.Sprintf(`
|
|
SELECT "Vcenter", COUNT(*) AS missing_count
|
|
FROM %s
|
|
WHERE "CreationTime" IS NULL OR "CreationTime" = 0
|
|
GROUP BY "Vcenter"
|
|
ORDER BY missing_count DESC
|
|
`, tableName)
|
|
if rows, err := dbConn.QueryxContext(ctx, byVcenterQuery); err != nil {
|
|
h.Logger.Warn("daily creation diagnostics by-vcenter failed", "table", tableName, "error", err)
|
|
} else {
|
|
for rows.Next() {
|
|
var vcenter string
|
|
var count int64
|
|
if err := rows.Scan(&vcenter, &count); err != nil {
|
|
continue
|
|
}
|
|
byVcenter = append(byVcenter, models.DailyCreationMissingByVcenter{
|
|
Vcenter: vcenter,
|
|
MissingCount: count,
|
|
})
|
|
}
|
|
rows.Close()
|
|
}
|
|
|
|
const sampleLimit = 10
|
|
samples := make([]models.DailyCreationMissingSample, 0, sampleLimit)
|
|
sampleQuery := fmt.Sprintf(`
|
|
SELECT "Vcenter","VmId","VmUuid","Name","SamplesPresent","AvgIsPresent","SnapshotTime"
|
|
FROM %s
|
|
WHERE "CreationTime" IS NULL OR "CreationTime" = 0
|
|
ORDER BY "SamplesPresent" DESC
|
|
LIMIT %d
|
|
`, tableName, sampleLimit)
|
|
if rows, err := dbConn.QueryxContext(ctx, sampleQuery); err != nil {
|
|
h.Logger.Warn("daily creation diagnostics 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
|
|
}
|
|
samples = append(samples, models.DailyCreationMissingSample{
|
|
Vcenter: vcenter,
|
|
VmId: vmId.String,
|
|
VmUuid: vmUuid.String,
|
|
Name: name.String,
|
|
SamplesPresent: samplesPresent.Int64,
|
|
AvgIsPresent: avgIsPresent.Float64,
|
|
SnapshotTime: snapshotTime.Int64,
|
|
})
|
|
}
|
|
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"),
|
|
Table: tableName,
|
|
TotalRows: totalRows,
|
|
MissingCreationCount: missingTotal,
|
|
MissingCreationPct: missingPct,
|
|
AvgIsPresentLtOneCount: avgIsPresentLtOne,
|
|
MissingCreationPartialCount: missingPartialCount,
|
|
MissingByVcenter: byVcenter,
|
|
Samples: samples,
|
|
MissingCreationPartialSamples: partialSamples,
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|