add diagnostic endpoint
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:
150
server/handler/dailyCreationDiagnostics.go
Normal file
150
server/handler/dailyCreationDiagnostics.go
Normal file
@@ -0,0 +1,150 @@
|
||||
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
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
response := models.DailyCreationDiagnosticsResponse{
|
||||
Status: "OK",
|
||||
Date: parsed.Format("2006-01-02"),
|
||||
Table: tableName,
|
||||
TotalRows: totalRows,
|
||||
MissingCreationCount: missingTotal,
|
||||
MissingCreationPct: missingPct,
|
||||
MissingByVcenter: byVcenter,
|
||||
Samples: samples,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
Reference in New Issue
Block a user