diff --git a/README.md b/README.md index 871efc0..69dacbc 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,29 @@ Monthly aggregation builds on daily summaries (or the daily rollup cache): - Monthly averages are `sum(weighted_sums) / monthly_total_samples` (per vCenter). - Pool percentages are weighted the same way: `(daily_pool_pct / 100) * daily_total_samples`, summed, then divided by `monthly_total_samples` and multiplied by 100. +### Hourly Snapshot Fields +Each hourly snapshot row tracks: +- Identity: `InventoryId`, `Name`, `Vcenter`, `VmId`, `VmUuid`, `EventKey`, `CloudId` +- Lifecycle/timing: `CreationTime`, `DeletionTime`, `SnapshotTime` +- Placement: `ResourcePool`, `Datacenter`, `Cluster`, `Folder` +- Sizing/state: `ProvisionedDisk`, `VcpuCount`, `RamGB`, `IsTemplate`, `PoweredOn`, `SrmPlaceholder` + +### Daily Aggregate Fields +Daily summary rows retain identity/placement/sizing fields and add: +- Sample coverage: `SamplesPresent`, `TotalSamples`, `AvgIsPresent` +- Time-weighted sizing: `AvgVcpuCount`, `AvgRamGB`, `AvgProvisionedDisk` +- Pool distribution percentages: `PoolTinPct`, `PoolBronzePct`, `PoolSilverPct`, `PoolGoldPct` +- Chargeback totals columns: `Tin`, `Bronze`, `Silver`, `Gold` +- Lifecycle carry-forward used by reports and trace: `CreationTime`, `DeletionTime`, `SnapshotTime` + +### Monthly Aggregate Fields +Monthly summary rows keep the same aggregate fields as daily summaries and recompute them over the month: +- `SamplesPresent` is summed across days. +- Monthly averages (`AvgVcpuCount`, `AvgRamGB`, `AvgProvisionedDisk`) are weighted by each day's sample volume. +- Monthly presence (`AvgIsPresent`) is normalized by monthly total samples. +- Monthly pool percentages (`PoolTinPct`, `PoolBronzePct`, `PoolSilverPct`, `PoolGoldPct`) are weighted by each day’s sample volume before normalization. +- `Tin`, `Bronze`, `Silver`, `Gold` totals remain available for reporting output. + ## RPM Layout (summary) The RPM installs the service and defaults under `/usr/bin`, config under `/etc/dtms`, and data under `/var/lib/vctp`: - Binary: `/usr/bin/vctp-linux-amd64` diff --git a/components/views/index.templ b/components/views/index.templ index cd017f1..a876c66 100644 --- a/components/views/index.templ +++ b/components/views/index.templ @@ -56,29 +56,31 @@ templ Index(info BuildInfo) { 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.

+
+

Snapshots and Reports

+
+

Hourly snapshots capture inventory per vCenter (concurrency via hourly_snapshot_concurrency), then daily and monthly summaries are derived from those snapshots.

+

Hourly tracks: VM identity (InventoryId, Name, VmId, VmUuid, Vcenter, EventKey, CloudId), lifecycle (CreationTime, DeletionTime, SnapshotTime), placement (Datacenter, Cluster, Folder, ResourcePool), and sizing/state (VcpuCount, RamGB, ProvisionedDisk, PoweredOn, IsTemplate, SrmPlaceholder).

+

Daily tracks: SamplesPresent, TotalSamples, AvgIsPresent, AvgVcpuCount, AvgRamGB, AvgProvisionedDisk, PoolTinPct, PoolBronzePct, PoolSilverPct, PoolGoldPct, plus chargeback totals columns Tin, Bronze, Silver, Gold.

+

Monthly tracks: the same daily aggregate fields, with monthly values weighted by per-day sample volume so partial-day VMs and config changes stay proportional.

+

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.

+
+

Prorating and Aggregation

+
+

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

+

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

+

Daily AvgVcpuCount, AvgRamGB, and AvgProvisionedDisk are per-sample sums divided by TotalSamples (time-weighted).

+

Daily pool percentages (PoolTinPct/PoolBronzePct/PoolSilverPct/PoolGoldPct) use pool-hit counts divided by SamplesPresent.

+

Monthly aggregation converts each day into weighted sums using sample volume, then recomputes monthly averages and pool percentages from those weighted totals.

+

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

+
-
- + @core.Footer() diff --git a/components/views/index_templ.go b/components/views/index_templ.go index 6002134..dfc690c 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

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.

") + 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), then daily and monthly summaries are derived from those snapshots.

Hourly tracks: VM identity (InventoryId, Name, VmId, VmUuid, Vcenter, EventKey, CloudId), lifecycle (CreationTime, DeletionTime, SnapshotTime), placement (Datacenter, Cluster, Folder, ResourcePool), and sizing/state (VcpuCount, RamGB, ProvisionedDisk, PoweredOn, IsTemplate, SrmPlaceholder).

Daily tracks: SamplesPresent, TotalSamples, AvgIsPresent, AvgVcpuCount, AvgRamGB, AvgProvisionedDisk, PoolTinPct, PoolBronzePct, PoolSilverPct, PoolGoldPct, plus chargeback totals columns Tin, Bronze, Silver, Gold.

Monthly tracks: the same daily aggregate fields, with monthly values weighted by per-day sample volume so partial-day VMs and config changes stay proportional.

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 that vCenter/day.

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

Daily AvgVcpuCount, AvgRamGB, and AvgProvisionedDisk are per-sample sums divided by TotalSamples (time-weighted).

Daily pool percentages (PoolTinPct/PoolBronzePct/PoolSilverPct/PoolGoldPct) use pool-hit counts divided by SamplesPresent.

Monthly aggregation converts each day into weighted sums using sample volume, then recomputes monthly averages and pool percentages from those weighted totals.

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

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/server/router/docs/docs.go b/server/router/docs/docs.go index e6fd2a1..152cc14 100644 --- a/server/router/docs/docs.go +++ b/server/router/docs/docs.go @@ -185,6 +185,12 @@ const docTemplate = `{ "$ref": "#/definitions/models.StatusMessageResponse" } }, + "400": { + "description": "Invalid request", + "schema": { + "$ref": "#/definitions/models.ErrorResponse" + } + }, "500": { "description": "Server error", "schema": { @@ -201,7 +207,7 @@ const docTemplate = `{ "application/json" ], "produces": [ - "text/plain" + "application/json" ], "tags": [ "events" @@ -223,19 +229,19 @@ const docTemplate = `{ "200": { "description": "Create event processed", "schema": { - "type": "string" + "$ref": "#/definitions/models.StatusMessageResponse" } }, "400": { "description": "Invalid request", "schema": { - "type": "string" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Server error", "schema": { - "type": "string" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -248,7 +254,7 @@ const docTemplate = `{ "application/json" ], "produces": [ - "text/plain" + "application/json" ], "tags": [ "events" @@ -270,19 +276,19 @@ const docTemplate = `{ "200": { "description": "Delete event processed", "schema": { - "type": "string" + "$ref": "#/definitions/models.StatusMessageResponse" } }, "400": { "description": "Invalid request", "schema": { - "type": "string" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Server error", "schema": { - "type": "string" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -468,7 +474,7 @@ const docTemplate = `{ "post": { "description": "Queries vCenter and updates inventory records with missing details.", "produces": [ - "text/plain" + "application/json" ], "tags": [ "inventory" @@ -478,13 +484,13 @@ const docTemplate = `{ "200": { "description": "Update completed", "schema": { - "type": "string" + "$ref": "#/definitions/models.StatusMessageResponse" } }, "500": { "description": "Server error", "schema": { - "type": "string" + "$ref": "#/definitions/models.ErrorResponse" } } } diff --git a/server/router/docs/swagger.json b/server/router/docs/swagger.json index 0650a21..557d428 100644 --- a/server/router/docs/swagger.json +++ b/server/router/docs/swagger.json @@ -174,6 +174,12 @@ "$ref": "#/definitions/models.StatusMessageResponse" } }, + "400": { + "description": "Invalid request", + "schema": { + "$ref": "#/definitions/models.ErrorResponse" + } + }, "500": { "description": "Server error", "schema": { @@ -190,7 +196,7 @@ "application/json" ], "produces": [ - "text/plain" + "application/json" ], "tags": [ "events" @@ -212,19 +218,19 @@ "200": { "description": "Create event processed", "schema": { - "type": "string" + "$ref": "#/definitions/models.StatusMessageResponse" } }, "400": { "description": "Invalid request", "schema": { - "type": "string" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Server error", "schema": { - "type": "string" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -237,7 +243,7 @@ "application/json" ], "produces": [ - "text/plain" + "application/json" ], "tags": [ "events" @@ -259,19 +265,19 @@ "200": { "description": "Delete event processed", "schema": { - "type": "string" + "$ref": "#/definitions/models.StatusMessageResponse" } }, "400": { "description": "Invalid request", "schema": { - "type": "string" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Server error", "schema": { - "type": "string" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -457,7 +463,7 @@ "post": { "description": "Queries vCenter and updates inventory records with missing details.", "produces": [ - "text/plain" + "application/json" ], "tags": [ "inventory" @@ -467,13 +473,13 @@ "200": { "description": "Update completed", "schema": { - "type": "string" + "$ref": "#/definitions/models.StatusMessageResponse" } }, "500": { "description": "Server error", "schema": { - "type": "string" + "$ref": "#/definitions/models.ErrorResponse" } } } diff --git a/server/router/docs/swagger.yaml b/server/router/docs/swagger.yaml index a5509fa..1b30baa 100644 --- a/server/router/docs/swagger.yaml +++ b/server/router/docs/swagger.yaml @@ -405,6 +405,10 @@ paths: description: Ciphertext response schema: $ref: '#/definitions/models.StatusMessageResponse' + "400": + description: Invalid request + schema: + $ref: '#/definitions/models.ErrorResponse' "500": description: Server error schema: @@ -427,20 +431,20 @@ paths: schema: $ref: '#/definitions/models.CloudEventReceived' produces: - - text/plain + - application/json responses: "200": description: Create event processed schema: - type: string + $ref: '#/definitions/models.StatusMessageResponse' "400": description: Invalid request schema: - type: string + $ref: '#/definitions/models.ErrorResponse' "500": description: Server error schema: - type: string + $ref: '#/definitions/models.ErrorResponse' summary: Record VM create event (deprecated) tags: - events @@ -459,20 +463,20 @@ paths: schema: $ref: '#/definitions/models.CloudEventReceived' produces: - - text/plain + - application/json responses: "200": description: Delete event processed schema: - type: string + $ref: '#/definitions/models.StatusMessageResponse' "400": description: Invalid request schema: - type: string + $ref: '#/definitions/models.ErrorResponse' "500": description: Server error schema: - type: string + $ref: '#/definitions/models.ErrorResponse' summary: Record VM delete event (deprecated) tags: - events @@ -598,16 +602,16 @@ paths: post: description: Queries vCenter and updates inventory records with missing details. produces: - - text/plain + - application/json responses: "200": description: Update completed schema: - type: string + $ref: '#/definitions/models.StatusMessageResponse' "500": description: Server error schema: - type: string + $ref: '#/definitions/models.ErrorResponse' summary: Refresh VM details tags: - inventory