From 8a3481b966e5306914aa0b7970bdf216a352be4b Mon Sep 17 00:00:00 2001
From: Nathan Coad
Date: Fri, 23 Jan 2026 07:29:59 +1100
Subject: [PATCH] fix creationtime in aggregations
---
README.md | 3 +-
components/core/header_templ.go | 2 +-
components/views/index.templ | 3 +-
components/views/index_templ.go | 8 +--
components/views/snapshots_templ.go | 100 ++++++++++++++--------------
components/views/vm_trace_templ.go | 88 ++++++++++++------------
db/helpers.go | 92 +++++++++++++------------
internal/tasks/dailyAggregate.go | 40 +++++------
internal/tasks/monthlyAggregate.go | 25 +++----
9 files changed, 183 insertions(+), 178 deletions(-)
diff --git a/README.md b/README.md
index b440990..101c6ad 100644
--- a/README.md
+++ b/README.md
@@ -15,8 +15,9 @@ Daily aggregation runs per VM using sample counts for the day:
- `SamplesPresent`: count of snapshot samples in which the VM appears.
- `TotalSamples`: count of unique snapshot timestamps for the vCenter in the day.
- `AvgIsPresent`: `SamplesPresent / TotalSamples` (0 when `TotalSamples` is 0).
-- `AvgVcpuCount`, `AvgRamGB`, `AvgProvisionedDisk` (daily): `AvgIsPresent * last_observed_value` for the VM that day. This prorates partial-day VMs by presence ratio.
+- `AvgVcpuCount`, `AvgRamGB`, `AvgProvisionedDisk` (daily): `sum(values_per_sample) / TotalSamples` to time‑weight config changes and prorate partial‑day VMs.
- `PoolTinPct`, `PoolBronzePct`, `PoolSilverPct`, `PoolGoldPct` (daily): `(pool_hits / SamplesPresent) * 100`, so pool percentages reflect only the time the VM existed.
+- `CreationTime`: only set when vCenter provides it or when a lifecycle cache `FirstSeen` is available; otherwise it remains `0`.
Monthly aggregation builds on daily summaries (or the daily rollup cache):
- For each VM, daily averages are converted to weighted sums: `daily_avg * daily_total_samples`.
diff --git a/components/core/header_templ.go b/components/core/header_templ.go
index 0d57eba..3849028 100644
--- a/components/core/header_templ.go
+++ b/components/core/header_templ.go
@@ -38,7 +38,7 @@ func Header() templ.Component {
var templ_7745c5c3_Var2 templ.SafeURL
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinURLErrs("/assets/css/output@" + version.Value + ".css")
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/header.templ`, Line: 15, Col: 61}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/core/header.templ`, Line: 15, Col: 61}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
diff --git a/components/views/index.templ b/components/views/index.templ
index 16d418e..2bb75fc 100644
--- a/components/views/index.templ
+++ b/components/views/index.templ
@@ -70,9 +70,10 @@ templ Index(info BuildInfo) {
- 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 = AvgIsPresent * last observed value for the day.
+ - 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 or when lifecycle FirstSeen is available; otherwise it remains 0.
diff --git a/components/views/index_templ.go b/components/views/index_templ.go
index 741a120..d9e69e0 100644
--- a/components/views/index_templ.go
+++ b/components/views/index_templ.go
@@ -54,7 +54,7 @@ func Index(info BuildInfo) templ.Component {
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(info.BuildTime)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/index.templ`, Line: 40, Col: 59}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/index.templ`, Line: 40, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
@@ -67,7 +67,7 @@ func Index(info BuildInfo) templ.Component {
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(info.SHA1Ver)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/index.templ`, Line: 44, Col: 57}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/index.templ`, Line: 44, Col: 57}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
@@ -80,13 +80,13 @@ func Index(info BuildInfo) templ.Component {
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(info.GoVersion)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/index.templ`, Line: 48, Col: 59}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/views/index.templ`, Line: 48, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
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
- 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.
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 = AvgIsPresent * last observed value for the day.
- 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.