diff --git a/README.md b/README.md index 93fa15f..c6ad12d 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ vCTP is a vSphere Chargeback Tracking Platform, designed for a specific customer - Snapshots are registered in `snapshot_registry` so regeneration via `/api/snapshots/aggregate` can locate the correct tables (fallback scanning is also supported). - Reports (XLSX with totals/charts) are generated automatically after hourly, daily, and monthly jobs and written to a reports directory. - Hourly totals in reports are interval-based: each row represents `[HH:00, HH+1:00)` and uses the first snapshot at or after the hour end (including cross-day snapshots) to prorate VM presence by creation/deletion overlap. +- Monthly aggregation reports include a Daily Totals sheet with full-day interval labels (`YYYY-MM-DD to YYYY-MM-DD`) and prorated totals derived from daily summaries. - Prometheus metrics are exposed at `/metrics`: - Snapshots/aggregations: `vctp_hourly_snapshots_total`, `vctp_hourly_snapshots_failed_total`, `vctp_hourly_snapshot_last_unix`, `vctp_hourly_snapshot_last_rows`, `vctp_daily_aggregations_total`, `vctp_daily_aggregations_failed_total`, `vctp_daily_aggregation_duration_seconds`, `vctp_monthly_aggregations_total`, `vctp_monthly_aggregations_failed_total`, `vctp_monthly_aggregation_duration_seconds`, `vctp_reports_available` - vCenter health/perf: `vctp_vcenter_connect_failures_total{vcenter}`, `vctp_vcenter_snapshot_duration_seconds{vcenter}`, `vctp_vcenter_inventory_size{vcenter}` diff --git a/components/views/index.templ b/components/views/index.templ index 5d9b2a8..cd017f1 100644 --- a/components/views/index.templ +++ b/components/views/index.templ @@ -51,31 +51,32 @@ templ Index(info BuildInfo) {
-

Overview

+

Overview

vCTP is a vSphere Chargeback Tracking Platform.

-

Snapshots and Reports

- +

Snapshots and Reports

+
+

Hourly snapshots capture inventory per vCenter (concurrency via hourly_snapshot_concurrency).

+

Daily summaries aggregate the hourly snapshots for the day; monthly summaries aggregate daily summaries for the month (or hourly snapshots if configured).

+

Snapshots are registered in snapshot_registry so regeneration via /api/snapshots/aggregate can locate the correct tables (fallback scanning is also supported).

+

Reports (XLSX with totals/charts) are generated automatically after hourly, daily, and monthly jobs and written to a reports directory.

+

Hourly totals are interval-based: each row represents [HH:00, HH+1:00) and uses the first snapshot at or after the hour end (including cross-day snapshots) to prorate VM presence.

+

Monthly aggregation reports include a Daily Totals sheet with full-day interval labels (YYYY-MM-DD to YYYY-MM-DD) and prorated totals.

+
-

Prorating and Aggregation

- +

Prorating and Aggregation

+
+

SamplesPresent is the count of snapshots in which the VM appears; TotalSamples is the count of unique snapshot times for the vCenter.

+

AvgIsPresent = SamplesPresent / TotalSamples (0 when TotalSamples is 0).

+

Daily AvgVcpuCount/AvgRamGB/AvgProvisionedDisk = sum of per-sample values divided by TotalSamples (time-weighted).

+

Daily pool percentages use pool hits divided by SamplesPresent, so they reflect only the time the VM existed.

+

Monthly aggregation weights daily averages by daily total samples, then divides by monthly total samples.

+

CreationTime is only set when vCenter provides it; otherwise it remains 0.

+
diff --git a/components/views/index_templ.go b/components/views/index_templ.go index 9eae4b0..6002134 100644 --- a/components/views/index_templ.go +++ b/components/views/index_templ.go @@ -86,7 +86,7 @@ func Index(info BuildInfo) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

Overview

vCTP is a vSphere Chargeback Tracking Platform.

Snapshots and Reports

Prorating and Aggregation

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

Overview

vCTP is a vSphere Chargeback Tracking Platform.

Snapshots and Reports

Hourly snapshots capture inventory per vCenter (concurrency via hourly_snapshot_concurrency).

Daily summaries aggregate the hourly snapshots for the day; monthly summaries aggregate daily summaries for the month (or hourly snapshots if configured).

Snapshots are registered in snapshot_registry so regeneration via /api/snapshots/aggregate can locate the correct tables (fallback scanning is also supported).

Reports (XLSX with totals/charts) are generated automatically after hourly, daily, and monthly jobs and written to a reports directory.

Hourly totals are interval-based: each row represents [HH:00, HH+1:00) and uses the first snapshot at or after the hour end (including cross-day snapshots) to prorate VM presence.

Monthly aggregation reports include a Daily Totals sheet with full-day interval labels (YYYY-MM-DD to YYYY-MM-DD) and prorated totals.

Prorating and Aggregation

SamplesPresent is the count of snapshots in which the VM appears; TotalSamples is the count of unique snapshot times for the vCenter.

AvgIsPresent = SamplesPresent / TotalSamples (0 when TotalSamples is 0).

Daily AvgVcpuCount/AvgRamGB/AvgProvisionedDisk = sum of per-sample values divided by TotalSamples (time-weighted).

Daily pool percentages use pool hits divided by SamplesPresent, so they reflect only the time the VM existed.

Monthly aggregation weights daily averages by daily total samples, then divides by monthly total samples.

CreationTime is only set when vCenter provides it; otherwise it remains 0.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/dist/assets/css/web3.css b/dist/assets/css/web3.css index e0a8388..bcfe36e 100644 --- a/dist/assets/css/web3.css +++ b/dist/assets/css/web3.css @@ -61,6 +61,18 @@ body { font-size: 0.85rem; letter-spacing: 0.02em; } +.web2-code { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + background: #f1f5f9; + border: 1px solid var(--web2-border); + border-radius: 3px; + padding: 0.1rem 0.35rem; + font-size: 0.85em; + color: #0f172a; +} +.web2-paragraphs p + p { + margin-top: 0.85rem; +} .web2-link { color: var(--web2-blue); text-decoration: none; diff --git a/internal/report/snapshots.go b/internal/report/snapshots.go index 862c6f3..88ce141 100644 --- a/internal/report/snapshots.go +++ b/internal/report/snapshots.go @@ -1319,6 +1319,10 @@ func formatHourIntervalLabel(start, end time.Time) string { return fmt.Sprintf("%s to %s", startLabel, end.Format("2006-01-02 15:04")) } +func formatDayIntervalLabel(start, end time.Time) string { + return fmt.Sprintf("%s to %s", start.Format("2006-01-02"), end.Format("2006-01-02")) +} + func buildDailyTotals(ctx context.Context, dbConn *sqlx.DB, records []SnapshotRecord, prorateByAvg bool) ([]totalsPoint, error) { points := make([]totalsPoint, 0, len(records)) tinExpr := `COALESCE(SUM(CASE WHEN "Tin" IS NOT NULL THEN "Tin" ELSE 0 END) / 100.0, 0)` @@ -1374,8 +1378,11 @@ WHERE %s if err := dbConn.GetContext(ctx, &row, query); err != nil { return nil, err } + dayTime := record.SnapshotTime.Local() + dayStart := time.Date(dayTime.Year(), dayTime.Month(), dayTime.Day(), 0, 0, 0, 0, dayTime.Location()) + dayEnd := dayStart.AddDate(0, 0, 1) points = append(points, totalsPoint{ - Label: record.SnapshotTime.Local().Format("2006-01-02"), + Label: formatDayIntervalLabel(dayStart, dayEnd), VmCount: float64(row.VmCount), VcpuTotal: row.VcpuTotal, RamTotal: row.RamTotal,