@oneuptime/common 11.0.1 → 11.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/Alert.ts +110 -0
- package/Models/DatabaseModels/CephCluster.ts +964 -0
- package/Models/DatabaseModels/CephClusterLabelRule.ts +514 -0
- package/Models/DatabaseModels/CephClusterOwnerRule.ts +596 -0
- package/Models/DatabaseModels/CephClusterOwnerTeam.ts +487 -0
- package/Models/DatabaseModels/CephClusterOwnerUser.ts +486 -0
- package/Models/DatabaseModels/CephResource.ts +809 -0
- package/Models/DatabaseModels/Host.ts +64 -0
- package/Models/DatabaseModels/Incident.ts +110 -0
- package/Models/DatabaseModels/Index.ts +24 -0
- package/Models/DatabaseModels/ProxmoxCluster.ts +943 -0
- package/Models/DatabaseModels/ProxmoxClusterLabelRule.ts +514 -0
- package/Models/DatabaseModels/ProxmoxClusterOwnerRule.ts +596 -0
- package/Models/DatabaseModels/ProxmoxClusterOwnerTeam.ts +487 -0
- package/Models/DatabaseModels/ProxmoxClusterOwnerUser.ts +486 -0
- package/Models/DatabaseModels/ProxmoxResource.ts +726 -0
- package/Models/DatabaseModels/ScheduledMaintenance.ts +110 -0
- package/Server/API/BillingInvoiceAPI.ts +47 -7
- package/Server/API/CephResourceAPI.ts +134 -0
- package/Server/API/DashboardAPI.ts +46 -0
- package/Server/API/ProjectAPI.ts +15 -0
- package/Server/API/ProxmoxResourceAPI.ts +132 -0
- package/Server/API/ResellerPlanAPI.ts +17 -0
- package/Server/Infrastructure/GlobalCache.ts +8 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1781500000000-AddProxmoxAndCephClusterTables.ts +163 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1781600000000-AddProxmoxCephV2Columns.ts +211 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1781600000001-AddProxmoxCephActivityAndRules.ts +590 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1781700000000-AddProxmoxCephV3Columns.ts +64 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Infrastructure/Redis.ts +40 -12
- package/Server/Services/AnalyticsDatabaseService.ts +1 -1
- package/Server/Services/BillingService.ts +109 -21
- package/Server/Services/CephClusterLabelRuleEngineService.ts +200 -0
- package/Server/Services/CephClusterLabelRuleService.ts +14 -0
- package/Server/Services/CephClusterOwnerRuleEngineService.ts +218 -0
- package/Server/Services/CephClusterOwnerRuleService.ts +14 -0
- package/Server/Services/CephClusterOwnerTeamService.ts +10 -0
- package/Server/Services/CephClusterOwnerUserService.ts +10 -0
- package/Server/Services/CephClusterService.ts +401 -0
- package/Server/Services/CephResourceService.ts +383 -0
- package/Server/Services/CloudResourceService.ts +11 -3
- package/Server/Services/DockerHostService.ts +11 -3
- package/Server/Services/ExceptionAggregationService.ts +2 -0
- package/Server/Services/HostService.ts +11 -3
- package/Server/Services/Index.ts +24 -0
- package/Server/Services/KubernetesClusterService.ts +11 -3
- package/Server/Services/LogAggregationService.ts +2 -0
- package/Server/Services/MetricAggregationService.ts +2 -0
- package/Server/Services/OpenTelemetryIngestService.ts +36 -0
- package/Server/Services/ProxmoxClusterLabelRuleEngineService.ts +204 -0
- package/Server/Services/ProxmoxClusterLabelRuleService.ts +14 -0
- package/Server/Services/ProxmoxClusterOwnerRuleEngineService.ts +222 -0
- package/Server/Services/ProxmoxClusterOwnerRuleService.ts +14 -0
- package/Server/Services/ProxmoxClusterOwnerTeamService.ts +10 -0
- package/Server/Services/ProxmoxClusterOwnerUserService.ts +10 -0
- package/Server/Services/ProxmoxClusterService.ts +382 -0
- package/Server/Services/ProxmoxResourceService.ts +404 -0
- package/Server/Services/RumApplicationService.ts +11 -3
- package/Server/Services/ServerlessFunctionService.ts +11 -3
- package/Server/Services/TelemetryUsageBillingService.ts +41 -3
- package/Server/Services/TraceAggregationService.ts +2 -0
- package/Server/Types/AnalyticsDatabase/AggregateBy.ts +8 -23
- package/Server/Utils/Monitor/MonitorAlert.ts +45 -0
- package/Server/Utils/Monitor/MonitorClusterContext.ts +129 -0
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +344 -4
- package/Server/Utils/Monitor/MonitorIncident.ts +130 -7
- package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +39 -6
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +3 -1
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +33 -0
- package/Server/Utils/Profiling.ts +37 -2
- package/Server/Utils/Telemetry/EntityRegistry.ts +4 -0
- package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +1096 -0
- package/Server/Utils/Telemetry/TelemetryEntity.ts +85 -0
- package/Server/Utils/Telemetry.ts +8 -19
- package/Tests/Server/API/BillingInvoiceAPI.test.ts +194 -0
- package/Tests/Server/API/ProjectAPI.test.ts +91 -0
- package/Tests/Server/API/ResellerPlanAPI.test.ts +207 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +100 -0
- package/Tests/Server/Services/BillingService.test.ts +323 -0
- package/Tests/Server/Services/CephResourceService.test.ts +264 -0
- package/Tests/Server/Services/ProxmoxResourceService.test.ts +326 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +322 -0
- package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +13 -0
- package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +879 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +196 -0
- package/Tests/Types/Monitor/CephAlertTemplates.test.ts +1231 -0
- package/Tests/Types/Monitor/ProxmoxAlertTemplates.test.ts +732 -0
- package/Tests/Utils/Telemetry/EntityRelationship.test.ts +49 -0
- package/Tests/Utils/Telemetry/HeartbeatAvailability.test.ts +423 -0
- package/Types/BaseDatabase/AggregationIntervalUtil.ts +74 -0
- package/Types/Dashboard/DashboardComponentType.ts +4 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +2 -0
- package/Types/Dashboard/DashboardComponents/DashboardCephOsdListComponent.ts +15 -0
- package/Types/Dashboard/DashboardComponents/DashboardCephPoolListComponent.ts +14 -0
- package/Types/Dashboard/DashboardComponents/DashboardProxmoxGuestListComponent.ts +17 -0
- package/Types/Dashboard/DashboardComponents/DashboardProxmoxNodeListComponent.ts +16 -0
- package/Types/Dashboard/DashboardTemplates.ts +446 -0
- package/Types/Icon/IconProp.ts +2 -0
- package/Types/Monitor/CephAlertTemplates.ts +1647 -0
- package/Types/Monitor/CephMetricCatalog.ts +409 -0
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +44 -0
- package/Types/Monitor/MonitorStep.ts +64 -0
- package/Types/Monitor/MonitorStepCephMonitor.ts +57 -0
- package/Types/Monitor/MonitorStepProxmoxMonitor.ts +81 -0
- package/Types/Monitor/MonitorType.ts +29 -1
- package/Types/Monitor/ProxmoxAlertTemplates.ts +899 -0
- package/Types/Monitor/ProxmoxMetricCatalog.ts +382 -0
- package/Types/Permission.ts +464 -0
- package/Types/Telemetry/EntityType.ts +11 -0
- package/Types/Telemetry/ServiceType.ts +2 -0
- package/UI/Components/Icon/Icon.tsx +84 -0
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +9 -5
- package/UI/Utils/Telemetry/Telemetry.ts +16 -21
- package/UI/Utils/TelemetryService.ts +7 -3
- package/Utils/Dashboard/Components/DashboardCephOsdListComponent.ts +63 -0
- package/Utils/Dashboard/Components/DashboardCephPoolListComponent.ts +32 -0
- package/Utils/Dashboard/Components/DashboardCephResourceListShared.ts +61 -0
- package/Utils/Dashboard/Components/DashboardProxmoxGuestListComponent.ts +69 -0
- package/Utils/Dashboard/Components/DashboardProxmoxNodeListComponent.ts +55 -0
- package/Utils/Dashboard/Components/DashboardProxmoxResourceListShared.ts +61 -0
- package/Utils/Dashboard/Components/Index.ts +28 -0
- package/Utils/Telemetry/EntityKey.ts +35 -0
- package/Utils/Telemetry/EntityRelationship.ts +6 -0
- package/Utils/Telemetry/HeartbeatAvailability.ts +262 -0
- package/build/dist/Models/DatabaseModels/Alert.js +108 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CephCluster.js +992 -0
- package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -0
- package/build/dist/Models/DatabaseModels/CephClusterLabelRule.js +522 -0
- package/build/dist/Models/DatabaseModels/CephClusterLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/CephClusterOwnerRule.js +603 -0
- package/build/dist/Models/DatabaseModels/CephClusterOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/CephClusterOwnerTeam.js +503 -0
- package/build/dist/Models/DatabaseModels/CephClusterOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/CephClusterOwnerUser.js +502 -0
- package/build/dist/Models/DatabaseModels/CephClusterOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/CephResource.js +846 -0
- package/build/dist/Models/DatabaseModels/CephResource.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Host.js +63 -0
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +108 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +24 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +967 -0
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ProxmoxClusterLabelRule.js +522 -0
- package/build/dist/Models/DatabaseModels/ProxmoxClusterLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerRule.js +603 -0
- package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerTeam.js +503 -0
- package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerUser.js +502 -0
- package/build/dist/Models/DatabaseModels/ProxmoxClusterOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ProxmoxResource.js +761 -0
- package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +108 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Server/API/BillingInvoiceAPI.js +35 -5
- package/build/dist/Server/API/BillingInvoiceAPI.js.map +1 -1
- package/build/dist/Server/API/CephResourceAPI.js +98 -0
- package/build/dist/Server/API/CephResourceAPI.js.map +1 -0
- package/build/dist/Server/API/DashboardAPI.js +46 -0
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/ProjectAPI.js +11 -0
- package/build/dist/Server/API/ProjectAPI.js.map +1 -1
- package/build/dist/Server/API/ProxmoxResourceAPI.js +95 -0
- package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -0
- package/build/dist/Server/API/ResellerPlanAPI.js +17 -3
- package/build/dist/Server/API/ResellerPlanAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +7 -2
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781500000000-AddProxmoxAndCephClusterTables.js +76 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781500000000-AddProxmoxAndCephClusterTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781600000000-AddProxmoxCephV2Columns.js +108 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781600000000-AddProxmoxCephV2Columns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781600000001-AddProxmoxCephActivityAndRules.js +253 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781600000001-AddProxmoxCephActivityAndRules.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781700000000-AddProxmoxCephV3Columns.js +43 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1781700000000-AddProxmoxCephV3Columns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Redis.js +31 -8
- package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +85 -23
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js +166 -0
- package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/CephClusterLabelRuleService.js +13 -0
- package/build/dist/Server/Services/CephClusterLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js +186 -0
- package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/CephClusterOwnerRuleService.js +13 -0
- package/build/dist/Server/Services/CephClusterOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/CephClusterOwnerTeamService.js +9 -0
- package/build/dist/Server/Services/CephClusterOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/CephClusterOwnerUserService.js +9 -0
- package/build/dist/Server/Services/CephClusterOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/CephClusterService.js +353 -0
- package/build/dist/Server/Services/CephClusterService.js.map +1 -0
- package/build/dist/Server/Services/CephResourceService.js +257 -0
- package/build/dist/Server/Services/CephResourceService.js.map +1 -0
- package/build/dist/Server/Services/CloudResourceService.js +10 -2
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +10 -2
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/ExceptionAggregationService.js +2 -0
- package/build/dist/Server/Services/ExceptionAggregationService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +10 -2
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +24 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +10 -2
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +2 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricAggregationService.js +2 -0
- package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +37 -7
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js +166 -0
- package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ProxmoxClusterLabelRuleService.js +13 -0
- package/build/dist/Server/Services/ProxmoxClusterLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js +186 -0
- package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ProxmoxClusterOwnerRuleService.js +13 -0
- package/build/dist/Server/Services/ProxmoxClusterOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/ProxmoxClusterOwnerTeamService.js +9 -0
- package/build/dist/Server/Services/ProxmoxClusterOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/ProxmoxClusterOwnerUserService.js +9 -0
- package/build/dist/Server/Services/ProxmoxClusterOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/ProxmoxClusterService.js +337 -0
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -0
- package/build/dist/Server/Services/ProxmoxResourceService.js +285 -0
- package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -0
- package/build/dist/Server/Services/RumApplicationService.js +10 -2
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionService.js +10 -2
- package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +30 -3
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +2 -0
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Types/AnalyticsDatabase/AggregateBy.js +8 -25
- package/build/dist/Server/Types/AnalyticsDatabase/AggregateBy.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +36 -0
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js +90 -0
- package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +228 -4
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +103 -8
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +23 -6
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +3 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +23 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
- package/build/dist/Server/Utils/Profiling.js +24 -3
- package/build/dist/Server/Utils/Profiling.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +4 -0
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +854 -0
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +62 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry.js +8 -10
- package/build/dist/Server/Utils/Telemetry.js.map +1 -1
- package/build/dist/Types/BaseDatabase/AggregationIntervalUtil.js +69 -0
- package/build/dist/Types/BaseDatabase/AggregationIntervalUtil.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js +4 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardCephOsdListComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardCephOsdListComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardCephPoolListComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardCephPoolListComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardProxmoxGuestListComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardProxmoxGuestListComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardProxmoxNodeListComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardProxmoxNodeListComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js +394 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Icon/IconProp.js +2 -0
- package/build/dist/Types/Icon/IconProp.js.map +1 -1
- package/build/dist/Types/Monitor/CephAlertTemplates.js +1379 -0
- package/build/dist/Types/Monitor/CephAlertTemplates.js.map +1 -0
- package/build/dist/Types/Monitor/CephMetricCatalog.js +353 -0
- package/build/dist/Types/Monitor/CephMetricCatalog.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorStep.js +46 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepCephMonitor.js +34 -0
- package/build/dist/Types/Monitor/MonitorStepCephMonitor.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorStepProxmoxMonitor.js +36 -0
- package/build/dist/Types/Monitor/MonitorStepProxmoxMonitor.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorType.js +27 -1
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +743 -0
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -0
- package/build/dist/Types/Monitor/ProxmoxMetricCatalog.js +320 -0
- package/build/dist/Types/Monitor/ProxmoxMetricCatalog.js.map +1 -0
- package/build/dist/Types/Permission.js +408 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Telemetry/EntityType.js +11 -0
- package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
- package/build/dist/Types/Telemetry/ServiceType.js +2 -0
- package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +33 -0
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +5 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
- package/build/dist/UI/Utils/Telemetry/Telemetry.js +11 -10
- package/build/dist/UI/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/UI/Utils/TelemetryService.js +5 -2
- package/build/dist/UI/Utils/TelemetryService.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardCephOsdListComponent.js +50 -0
- package/build/dist/Utils/Dashboard/Components/DashboardCephOsdListComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardCephPoolListComponent.js +27 -0
- package/build/dist/Utils/Dashboard/Components/DashboardCephPoolListComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardCephResourceListShared.js +46 -0
- package/build/dist/Utils/Dashboard/Components/DashboardCephResourceListShared.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardProxmoxGuestListComponent.js +55 -0
- package/build/dist/Utils/Dashboard/Components/DashboardProxmoxGuestListComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardProxmoxNodeListComponent.js +42 -0
- package/build/dist/Utils/Dashboard/Components/DashboardProxmoxNodeListComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardProxmoxResourceListShared.js +46 -0
- package/build/dist/Utils/Dashboard/Components/DashboardProxmoxResourceListShared.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/Index.js +16 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/build/dist/Utils/Telemetry/EntityKey.js +27 -0
- package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
- package/build/dist/Utils/Telemetry/EntityRelationship.js +3 -0
- package/build/dist/Utils/Telemetry/EntityRelationship.js.map +1 -1
- package/build/dist/Utils/Telemetry/HeartbeatAvailability.js +174 -0
- package/build/dist/Utils/Telemetry/HeartbeatAvailability.js.map +1 -0
- package/package.json +29 -21
|
@@ -0,0 +1,1096 @@
|
|
|
1
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
2
|
+
import { JSONArray, JSONObject, JSONValue } from "../../../Types/JSON";
|
|
3
|
+
import logger from "../Logger";
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* ------------------------------------------------------------------
|
|
7
|
+
* Proxmox / Ceph snapshot scan — pure fold & derive helpers
|
|
8
|
+
* ------------------------------------------------------------------
|
|
9
|
+
*
|
|
10
|
+
* Extracted from App/FeatureSet/Telemetry/Services/
|
|
11
|
+
* OtelMetricsIngestService.ts (WI-21 refactor-for-testability, no
|
|
12
|
+
* behavior change). The ingest service owns the I/O: it walks the
|
|
13
|
+
* OTLP payload, calls bufferProxmoxSnapshotMetric /
|
|
14
|
+
* bufferCephSnapshotMetric per datapoint, and at flush time maps the
|
|
15
|
+
* folded buffers through computeProxmoxGuestBackedUp /
|
|
16
|
+
* deriveProxmoxClusterSnapshotExtras / deriveCephClusterSnapshotExtras
|
|
17
|
+
* before handing the results to ProxmoxResourceService /
|
|
18
|
+
* CephResourceService / *ClusterService. Everything in this module is
|
|
19
|
+
* pure (Map/object mutation only — no DB, no network), which is what
|
|
20
|
+
* makes the snapshot-scan semantics unit-testable:
|
|
21
|
+
*
|
|
22
|
+
* - identity labels fold first-non-null-wins; status/metric fields
|
|
23
|
+
* fold newest-observedAt-wins,
|
|
24
|
+
* - count columns are only derived when the batch carried the
|
|
25
|
+
* matching identity series (never zero a count on a partial
|
|
26
|
+
* batch — the COALESCE-per-column contract),
|
|
27
|
+
* - non-allow-listed metric names are skipped via the exported
|
|
28
|
+
* PVE_/CEPH_SNAPSHOT_METRIC_NAMES sets.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* Proxmox snapshot metrics — emitted by pve-exporter (prometheus
|
|
33
|
+
* receiver), identity in the `id` datapoint label (node/pve1,
|
|
34
|
+
* qemu/100, lxc/101, storage/local) plus the pve.scope / pve.type /
|
|
35
|
+
* pve.id attributes the agent's transform processor derives from it.
|
|
36
|
+
* Unlike K8s there is no separate object stream: identity, status AND
|
|
37
|
+
* the latest-metric mirror all arrive on every scrape, so the same
|
|
38
|
+
* scan feeds the ProxmoxResource inventory upsert and the
|
|
39
|
+
* ProxmoxCluster count/version snapshot columns (single source — the
|
|
40
|
+
* list-page counts and the sidebar badges can never drift).
|
|
41
|
+
*
|
|
42
|
+
* pve_cpu_usage_ratio is already a true 0..1 ratio — no allocatable-
|
|
43
|
+
* denominator cache is needed, unlike K8s cpuCoresToPercent.
|
|
44
|
+
*/
|
|
45
|
+
export const PVE_SNAPSHOT_METRIC_NAMES: ReadonlySet<string> = new Set([
|
|
46
|
+
// Identity / status (WI-3 cluster counts derive from these)
|
|
47
|
+
"pve_up",
|
|
48
|
+
"pve_node_info",
|
|
49
|
+
"pve_guest_info",
|
|
50
|
+
"pve_storage_info",
|
|
51
|
+
"pve_version_info",
|
|
52
|
+
"pve_ha_state",
|
|
53
|
+
"pve_onboot_status",
|
|
54
|
+
// Latest-metric mirror (WI-6 inventory columns)
|
|
55
|
+
"pve_uptime_seconds",
|
|
56
|
+
"pve_cpu_usage_ratio",
|
|
57
|
+
"pve_memory_usage_bytes",
|
|
58
|
+
"pve_memory_size_bytes",
|
|
59
|
+
"pve_disk_usage_bytes",
|
|
60
|
+
"pve_disk_size_bytes",
|
|
61
|
+
/*
|
|
62
|
+
* Backup coverage (WI-24) — cluster-level backup-info collector.
|
|
63
|
+
* pve_not_backed_up_total = count of guests not covered by ANY
|
|
64
|
+
* backup job; pve_not_backed_up_info = one series per uncovered
|
|
65
|
+
* guest, labeled only `id`. "Covered by a job" ≠ "backed up
|
|
66
|
+
* recently/successfully" — freshness needs the PVE task log or PBS
|
|
67
|
+
* API (v4 API-agent track).
|
|
68
|
+
*/
|
|
69
|
+
"pve_not_backed_up_total",
|
|
70
|
+
"pve_not_backed_up_info",
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
* Ceph snapshot metrics — emitted by the ceph-mgr prometheus module
|
|
75
|
+
* (honor_labels), identity in the `ceph_daemon` (osd.3, mon.a, …) or
|
|
76
|
+
* `pool_id` datapoint labels. Same single-source rule as Proxmox: one
|
|
77
|
+
* scan feeds the CephResource inventory and the CephCluster
|
|
78
|
+
* count/health/capacity snapshot columns.
|
|
79
|
+
*/
|
|
80
|
+
export const CEPH_SNAPSHOT_METRIC_NAMES: ReadonlySet<string> = new Set([
|
|
81
|
+
// Cluster-level health / capacity (WI-3 columns)
|
|
82
|
+
"ceph_health_status",
|
|
83
|
+
"ceph_cluster_total_bytes",
|
|
84
|
+
"ceph_cluster_total_used_bytes",
|
|
85
|
+
// Daemon identity / status
|
|
86
|
+
"ceph_mon_quorum_status",
|
|
87
|
+
"ceph_mon_metadata",
|
|
88
|
+
"ceph_osd_up",
|
|
89
|
+
"ceph_osd_in",
|
|
90
|
+
"ceph_osd_metadata",
|
|
91
|
+
"ceph_mgr_metadata",
|
|
92
|
+
"ceph_mds_metadata",
|
|
93
|
+
"ceph_rgw_metadata",
|
|
94
|
+
// OSD latest-metric mirror (WI-6 inventory columns)
|
|
95
|
+
"ceph_osd_stat_bytes",
|
|
96
|
+
"ceph_osd_stat_bytes_used",
|
|
97
|
+
"ceph_osd_apply_latency_ms",
|
|
98
|
+
"ceph_osd_commit_latency_ms",
|
|
99
|
+
"ceph_osd_numpg",
|
|
100
|
+
// Pool identity + latest-metric mirror
|
|
101
|
+
"ceph_pool_metadata",
|
|
102
|
+
"ceph_pool_stored",
|
|
103
|
+
"ceph_pool_max_avail",
|
|
104
|
+
"ceph_pool_objects",
|
|
105
|
+
"ceph_pool_rd",
|
|
106
|
+
"ceph_pool_wr",
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
/*
|
|
110
|
+
* One Proxmox resource (Node / Guest / Storage) folded across a batch.
|
|
111
|
+
* Identity labels are first-non-null-wins (stable for the lifetime of
|
|
112
|
+
* the resource); status / metric fields are newest-observedAt-wins.
|
|
113
|
+
*/
|
|
114
|
+
export interface ProxmoxResourceBufferEntry {
|
|
115
|
+
kind: string; // Node | Guest | Storage
|
|
116
|
+
externalId: string; // raw `id` label
|
|
117
|
+
name: string | null;
|
|
118
|
+
vmid: number | null;
|
|
119
|
+
guestType: string | null;
|
|
120
|
+
parentNodeName: string | null;
|
|
121
|
+
isUp: boolean | null;
|
|
122
|
+
haState: string | null;
|
|
123
|
+
onboot: boolean | null;
|
|
124
|
+
uptimeSeconds: number | null;
|
|
125
|
+
latestCpuPercent: number | null;
|
|
126
|
+
latestMemoryBytes: number | null;
|
|
127
|
+
maxMemoryBytes: number | null;
|
|
128
|
+
latestDiskBytes: number | null;
|
|
129
|
+
maxDiskBytes: number | null;
|
|
130
|
+
observedAt: Date;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/*
|
|
134
|
+
* Per-cluster Proxmox snapshot state. The saw* flags implement the
|
|
135
|
+
* never-zero-a-count-on-a-partial-batch contract: a count column is
|
|
136
|
+
* only written when the batch carried the matching identity series.
|
|
137
|
+
*/
|
|
138
|
+
export interface ProxmoxClusterSnapshotBufferEntry {
|
|
139
|
+
sawNodeIdentity: boolean; // pve_node_info, or pve_up on a node id
|
|
140
|
+
sawNodeUp: boolean; // pve_up on a node id
|
|
141
|
+
sawGuestIdentity: boolean; // pve_guest_info
|
|
142
|
+
sawStorageIdentity: boolean; // pve_storage_info
|
|
143
|
+
pveVersion: string | null; // pve_version_info `version` label
|
|
144
|
+
/*
|
|
145
|
+
* WI-24 backup coverage. sawBackupInfo flips when the batch carried
|
|
146
|
+
* the backup-info collector output at all (either series) — the
|
|
147
|
+
* per-guest isBackedUp flag and the cluster count are only written
|
|
148
|
+
* then (never-zero-on-partial-batch guard: a batch without the
|
|
149
|
+
* collector must not mark every guest "covered").
|
|
150
|
+
*/
|
|
151
|
+
sawBackupInfo: boolean;
|
|
152
|
+
guestsWithoutBackupCount: number | null; // pve_not_backed_up_total value
|
|
153
|
+
notBackedUpIds: Set<string>; // raw `id` labels of pve_not_backed_up_info
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/*
|
|
157
|
+
* One Ceph resource (Osd / Pool / Mon / Mgr / Mds / Rgw) folded across
|
|
158
|
+
* a batch. Same merge semantics as ProxmoxResourceBufferEntry.
|
|
159
|
+
*/
|
|
160
|
+
export interface CephResourceBufferEntry {
|
|
161
|
+
kind: string;
|
|
162
|
+
externalId: string; // ceph_daemon or pool_id
|
|
163
|
+
name: string | null; // pool name
|
|
164
|
+
hostname: string | null;
|
|
165
|
+
daemonVersion: string | null;
|
|
166
|
+
deviceClass: string | null;
|
|
167
|
+
isUp: boolean | null;
|
|
168
|
+
isIn: boolean | null;
|
|
169
|
+
inQuorum: boolean | null;
|
|
170
|
+
statBytes: number | null;
|
|
171
|
+
statBytesUsed: number | null;
|
|
172
|
+
applyLatencyMs: number | null;
|
|
173
|
+
commitLatencyMs: number | null;
|
|
174
|
+
pgCount: number | null;
|
|
175
|
+
storedBytes: number | null;
|
|
176
|
+
maxAvailBytes: number | null;
|
|
177
|
+
objects: number | null;
|
|
178
|
+
readOpsCounter: number | null;
|
|
179
|
+
writeOpsCounter: number | null;
|
|
180
|
+
observedAt: Date;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Per-cluster Ceph snapshot state — same saw* contract as Proxmox.
|
|
184
|
+
export interface CephClusterSnapshotBufferEntry {
|
|
185
|
+
sawMonMetadata: boolean;
|
|
186
|
+
sawOsdMetadata: boolean;
|
|
187
|
+
sawOsdUp: boolean;
|
|
188
|
+
sawOsdIn: boolean;
|
|
189
|
+
sawPoolMetadata: boolean;
|
|
190
|
+
healthStatus: number | null; // 0 OK / 1 WARN / 2 ERR
|
|
191
|
+
totalBytes: number | null;
|
|
192
|
+
totalUsedBytes: number | null;
|
|
193
|
+
// ceph_version label occurrences across ceph_mon_metadata — modal wins.
|
|
194
|
+
cephVersionCounts: Map<string, number>;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// The ProxmoxCluster snapshot columns derived from one folded batch.
|
|
198
|
+
export interface ProxmoxClusterSnapshotExtras {
|
|
199
|
+
pveVersion?: string | undefined;
|
|
200
|
+
nodeCount?: number | undefined;
|
|
201
|
+
onlineNodeCount?: number | undefined;
|
|
202
|
+
guestCount?: number | undefined;
|
|
203
|
+
storageCount?: number | undefined;
|
|
204
|
+
guestsWithoutBackupCount?: number | undefined;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// The CephCluster snapshot columns derived from one folded batch.
|
|
208
|
+
export interface CephClusterSnapshotExtras {
|
|
209
|
+
cephVersion?: string | undefined;
|
|
210
|
+
monCount?: number | undefined;
|
|
211
|
+
osdCount?: number | undefined;
|
|
212
|
+
osdUpCount?: number | undefined;
|
|
213
|
+
osdInCount?: number | undefined;
|
|
214
|
+
poolCount?: number | undefined;
|
|
215
|
+
healthStatus?: number | undefined;
|
|
216
|
+
capacityUsedPercent?: number | undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/*
|
|
220
|
+
* Same finite-or-null coercion contract as the ingest service's
|
|
221
|
+
* toNumberOrNull (NaN / ±Infinity fold to null so a malformed
|
|
222
|
+
* datapoint is skipped rather than poisoning a snapshot column).
|
|
223
|
+
*/
|
|
224
|
+
function toNumberOrNull(value: unknown): number | null {
|
|
225
|
+
if (typeof value === "number") {
|
|
226
|
+
return Number.isFinite(value) ? value : null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (typeof value === "string") {
|
|
230
|
+
const parsed: number = Number.parseFloat(value);
|
|
231
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/*
|
|
238
|
+
* Same fall-back-to-now parse contract as the ingest service's
|
|
239
|
+
* safeParseUnixNano — only the Date is needed on the snapshot path.
|
|
240
|
+
*/
|
|
241
|
+
function parseUnixNanoToDate(
|
|
242
|
+
value: string | number | undefined,
|
|
243
|
+
context: string,
|
|
244
|
+
): Date {
|
|
245
|
+
let numericValue: number = OneUptimeDate.getCurrentDateAsUnixNano();
|
|
246
|
+
|
|
247
|
+
if (value !== undefined && value !== null) {
|
|
248
|
+
try {
|
|
249
|
+
if (typeof value === "string") {
|
|
250
|
+
const parsed: number = Number.parseFloat(value);
|
|
251
|
+
if (isNaN(parsed)) {
|
|
252
|
+
throw new Error(`Invalid timestamp string: ${value}`);
|
|
253
|
+
}
|
|
254
|
+
numericValue = parsed;
|
|
255
|
+
} else if (typeof value === "number") {
|
|
256
|
+
if (!Number.isFinite(value)) {
|
|
257
|
+
throw new Error(`Invalid timestamp number: ${value}`);
|
|
258
|
+
}
|
|
259
|
+
numericValue = value;
|
|
260
|
+
}
|
|
261
|
+
} catch (error) {
|
|
262
|
+
logger.warn(
|
|
263
|
+
`Error processing ${context}: ${error instanceof Error ? error.message : String(error)}, using current time`,
|
|
264
|
+
);
|
|
265
|
+
numericValue = OneUptimeDate.getCurrentDateAsUnixNano();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return OneUptimeDate.fromUnixNano(numericValue);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Same trim-or-null read contract as OtelIngestBaseService.getStringAttribute.
|
|
273
|
+
function getStringAttribute(attributes: JSONArray, key: string): string | null {
|
|
274
|
+
for (const attribute of attributes) {
|
|
275
|
+
if (
|
|
276
|
+
attribute["key"] === key &&
|
|
277
|
+
attribute["value"] &&
|
|
278
|
+
(attribute["value"] as JSONObject)["stringValue"]
|
|
279
|
+
) {
|
|
280
|
+
const value: JSONValue = (attribute["value"] as JSONObject)[
|
|
281
|
+
"stringValue"
|
|
282
|
+
];
|
|
283
|
+
if (typeof value === "string" && value.trim()) {
|
|
284
|
+
return value.trim();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function getOrCreateProxmoxClusterSnapshot(
|
|
292
|
+
buffer: Map<string, ProxmoxClusterSnapshotBufferEntry>,
|
|
293
|
+
clusterIdStr: string,
|
|
294
|
+
): ProxmoxClusterSnapshotBufferEntry {
|
|
295
|
+
let entry: ProxmoxClusterSnapshotBufferEntry | undefined =
|
|
296
|
+
buffer.get(clusterIdStr);
|
|
297
|
+
if (!entry) {
|
|
298
|
+
entry = {
|
|
299
|
+
sawNodeIdentity: false,
|
|
300
|
+
sawNodeUp: false,
|
|
301
|
+
sawGuestIdentity: false,
|
|
302
|
+
sawStorageIdentity: false,
|
|
303
|
+
pveVersion: null,
|
|
304
|
+
sawBackupInfo: false,
|
|
305
|
+
guestsWithoutBackupCount: null,
|
|
306
|
+
notBackedUpIds: new Set(),
|
|
307
|
+
};
|
|
308
|
+
buffer.set(clusterIdStr, entry);
|
|
309
|
+
}
|
|
310
|
+
return entry;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/*
|
|
314
|
+
* Fold one pve_* datapoint into the per-cluster buffers. Identity
|
|
315
|
+
* lives in the `id` DATAPOINT label (prometheus receiver), so this
|
|
316
|
+
* reads the raw datapoint attribute array — not the merged
|
|
317
|
+
* resource-prefixed map the K8s scan uses.
|
|
318
|
+
*/
|
|
319
|
+
export function bufferProxmoxSnapshotMetric(data: {
|
|
320
|
+
clusterIdStr: string;
|
|
321
|
+
metricName: string;
|
|
322
|
+
datapoint: JSONObject;
|
|
323
|
+
resourceBuffer: Map<string, Map<string, ProxmoxResourceBufferEntry>>;
|
|
324
|
+
clusterBuffer: Map<string, ProxmoxClusterSnapshotBufferEntry>;
|
|
325
|
+
}): void {
|
|
326
|
+
const valueFromInt: number | null = toNumberOrNull(data.datapoint["asInt"]);
|
|
327
|
+
const valueFromDouble: number | null = toNumberOrNull(
|
|
328
|
+
data.datapoint["asDouble"],
|
|
329
|
+
);
|
|
330
|
+
const rawValue: number | null = valueFromDouble ?? valueFromInt;
|
|
331
|
+
if (rawValue === null) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const observedAt: Date = parseUnixNanoToDate(
|
|
336
|
+
data.datapoint["timeUnixNano"] as string | number | undefined,
|
|
337
|
+
"pve snapshot timeUnixNano",
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
const dpAttributes: JSONArray =
|
|
341
|
+
(data.datapoint["attributes"] as JSONArray) || [];
|
|
342
|
+
|
|
343
|
+
const cluster: ProxmoxClusterSnapshotBufferEntry =
|
|
344
|
+
getOrCreateProxmoxClusterSnapshot(data.clusterBuffer, data.clusterIdStr);
|
|
345
|
+
|
|
346
|
+
/*
|
|
347
|
+
* pve_version_info carries no `id` label — it is the cluster-level
|
|
348
|
+
* PVE version (this is what populates ProxmoxCluster.pveVersion).
|
|
349
|
+
*/
|
|
350
|
+
if (data.metricName === "pve_version_info") {
|
|
351
|
+
const version: string | null = getStringAttribute(dpAttributes, "version");
|
|
352
|
+
if (version) {
|
|
353
|
+
cluster.pveVersion = version;
|
|
354
|
+
}
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/*
|
|
359
|
+
* WI-24 backup coverage — cluster-level backup-info collector
|
|
360
|
+
* series, handled before the generic scope parse:
|
|
361
|
+
* pve_not_backed_up_total carries no `id` label, and the
|
|
362
|
+
* pve_not_backed_up_info `id` label format is unverified upstream
|
|
363
|
+
* (`qemu/100` vs bare vmid — §3.9 spike), so neither goes through
|
|
364
|
+
* the inventory-row path. The ids are folded into the cluster
|
|
365
|
+
* buffer and joined to the buffered Guest rows at flush time.
|
|
366
|
+
*/
|
|
367
|
+
if (data.metricName === "pve_not_backed_up_total") {
|
|
368
|
+
cluster.sawBackupInfo = true;
|
|
369
|
+
cluster.guestsWithoutBackupCount = Math.max(0, Math.trunc(rawValue));
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (data.metricName === "pve_not_backed_up_info") {
|
|
373
|
+
cluster.sawBackupInfo = true;
|
|
374
|
+
const uncoveredId: string | null = getStringAttribute(dpAttributes, "id");
|
|
375
|
+
if (uncoveredId) {
|
|
376
|
+
cluster.notBackedUpIds.add(uncoveredId);
|
|
377
|
+
}
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const id: string | null = getStringAttribute(dpAttributes, "id");
|
|
382
|
+
if (!id) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/*
|
|
387
|
+
* Scope/type: prefer the pve.scope / pve.type attributes stamped
|
|
388
|
+
* by the agent's transform/pve-identity processor; fall back to
|
|
389
|
+
* parsing the id prefix so inventory still populates on a
|
|
390
|
+
* hand-rolled collector config without the transform.
|
|
391
|
+
*/
|
|
392
|
+
const slashIndex: number = id.indexOf("/");
|
|
393
|
+
const idPrefix: string = slashIndex > 0 ? id.substring(0, slashIndex) : "";
|
|
394
|
+
const pveType: string =
|
|
395
|
+
getStringAttribute(dpAttributes, "pve.type") || idPrefix;
|
|
396
|
+
let scope: string | null = getStringAttribute(dpAttributes, "pve.scope");
|
|
397
|
+
if (!scope) {
|
|
398
|
+
scope = idPrefix === "qemu" || idPrefix === "lxc" ? "guest" : idPrefix;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
let kind: string;
|
|
402
|
+
if (scope === "node") {
|
|
403
|
+
kind = "Node";
|
|
404
|
+
} else if (scope === "guest") {
|
|
405
|
+
kind = "Guest";
|
|
406
|
+
} else if (scope === "storage") {
|
|
407
|
+
kind = "Storage";
|
|
408
|
+
} else {
|
|
409
|
+
// cluster/* series and unknown scopes aren't inventory rows.
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const patch: ProxmoxResourceBufferEntry = {
|
|
414
|
+
kind,
|
|
415
|
+
externalId: id,
|
|
416
|
+
name: null,
|
|
417
|
+
vmid: null,
|
|
418
|
+
guestType: null,
|
|
419
|
+
parentNodeName: null,
|
|
420
|
+
isUp: null,
|
|
421
|
+
haState: null,
|
|
422
|
+
onboot: null,
|
|
423
|
+
uptimeSeconds: null,
|
|
424
|
+
latestCpuPercent: null,
|
|
425
|
+
latestMemoryBytes: null,
|
|
426
|
+
maxMemoryBytes: null,
|
|
427
|
+
latestDiskBytes: null,
|
|
428
|
+
maxDiskBytes: null,
|
|
429
|
+
observedAt,
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
if (kind === "Guest") {
|
|
433
|
+
patch.guestType = pveType === "qemu" || pveType === "lxc" ? pveType : null;
|
|
434
|
+
if (slashIndex > 0) {
|
|
435
|
+
const vmidParsed: number = parseInt(id.substring(slashIndex + 1), 10);
|
|
436
|
+
patch.vmid = isNaN(vmidParsed) ? null : vmidParsed;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// `node` label: present on the info series and pve_onboot_status.
|
|
441
|
+
const nodeLabel: string | null = getStringAttribute(dpAttributes, "node");
|
|
442
|
+
if (nodeLabel && kind !== "Node") {
|
|
443
|
+
patch.parentNodeName = nodeLabel;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
switch (data.metricName) {
|
|
447
|
+
case "pve_up": {
|
|
448
|
+
patch.isUp = rawValue >= 1;
|
|
449
|
+
if (kind === "Node") {
|
|
450
|
+
// pve_up doubles as the node-identity fallback per the spec.
|
|
451
|
+
cluster.sawNodeIdentity = true;
|
|
452
|
+
cluster.sawNodeUp = true;
|
|
453
|
+
}
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
case "pve_node_info": {
|
|
457
|
+
patch.name = getStringAttribute(dpAttributes, "name");
|
|
458
|
+
cluster.sawNodeIdentity = true;
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
case "pve_guest_info": {
|
|
462
|
+
patch.name = getStringAttribute(dpAttributes, "name");
|
|
463
|
+
cluster.sawGuestIdentity = true;
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
case "pve_storage_info": {
|
|
467
|
+
patch.name = getStringAttribute(dpAttributes, "storage");
|
|
468
|
+
cluster.sawStorageIdentity = true;
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
case "pve_ha_state": {
|
|
472
|
+
/*
|
|
473
|
+
* Enum-series: one row per possible state, value 1 marks the
|
|
474
|
+
* current one. Only the active row carries signal — skip the
|
|
475
|
+
* zero-valued rows entirely so they don't create empty patches.
|
|
476
|
+
*/
|
|
477
|
+
if (rawValue < 1) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
patch.haState = getStringAttribute(dpAttributes, "state");
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
case "pve_onboot_status": {
|
|
484
|
+
patch.onboot = rawValue >= 1;
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
case "pve_uptime_seconds": {
|
|
488
|
+
patch.uptimeSeconds = Math.max(0, Math.trunc(rawValue));
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
case "pve_cpu_usage_ratio": {
|
|
492
|
+
/*
|
|
493
|
+
* Already a true 0..1 ratio — no allocatable-denominator cache
|
|
494
|
+
* needed, unlike K8s cpuCoresToPercent.
|
|
495
|
+
*/
|
|
496
|
+
patch.latestCpuPercent = rawValue * 100;
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
case "pve_memory_usage_bytes": {
|
|
500
|
+
patch.latestMemoryBytes = Math.max(0, Math.trunc(rawValue));
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
case "pve_memory_size_bytes": {
|
|
504
|
+
patch.maxMemoryBytes = Math.max(0, Math.trunc(rawValue));
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
case "pve_disk_usage_bytes": {
|
|
508
|
+
patch.latestDiskBytes = Math.max(0, Math.trunc(rawValue));
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
case "pve_disk_size_bytes": {
|
|
512
|
+
patch.maxDiskBytes = Math.max(0, Math.trunc(rawValue));
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
default: {
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
foldProxmoxResourceSnapshot({
|
|
521
|
+
buffer: data.resourceBuffer,
|
|
522
|
+
clusterIdStr: data.clusterIdStr,
|
|
523
|
+
patch,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/*
|
|
528
|
+
* Merge a patch into the per-cluster Proxmox buffer: identity labels
|
|
529
|
+
* are first-non-null-wins (stable, and a batch missing an info
|
|
530
|
+
* series must not blank them), status/metric fields are
|
|
531
|
+
* newest-observedAt-wins (K8s buffer semantics).
|
|
532
|
+
*/
|
|
533
|
+
export function foldProxmoxResourceSnapshot(data: {
|
|
534
|
+
buffer: Map<string, Map<string, ProxmoxResourceBufferEntry>>;
|
|
535
|
+
clusterIdStr: string;
|
|
536
|
+
patch: ProxmoxResourceBufferEntry;
|
|
537
|
+
}): void {
|
|
538
|
+
let perCluster: Map<string, ProxmoxResourceBufferEntry> | undefined =
|
|
539
|
+
data.buffer.get(data.clusterIdStr);
|
|
540
|
+
if (!perCluster) {
|
|
541
|
+
perCluster = new Map();
|
|
542
|
+
data.buffer.set(data.clusterIdStr, perCluster);
|
|
543
|
+
}
|
|
544
|
+
const key: string = `${data.patch.kind}|${data.patch.externalId}`;
|
|
545
|
+
const existing: ProxmoxResourceBufferEntry | undefined = perCluster.get(key);
|
|
546
|
+
if (!existing) {
|
|
547
|
+
perCluster.set(key, data.patch);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const patch: ProxmoxResourceBufferEntry = data.patch;
|
|
552
|
+
const newer: boolean = patch.observedAt >= existing.observedAt;
|
|
553
|
+
|
|
554
|
+
// Identity: first-non-null wins.
|
|
555
|
+
if (existing.name === null && patch.name !== null) {
|
|
556
|
+
existing.name = patch.name;
|
|
557
|
+
}
|
|
558
|
+
if (existing.vmid === null && patch.vmid !== null) {
|
|
559
|
+
existing.vmid = patch.vmid;
|
|
560
|
+
}
|
|
561
|
+
if (existing.guestType === null && patch.guestType !== null) {
|
|
562
|
+
existing.guestType = patch.guestType;
|
|
563
|
+
}
|
|
564
|
+
if (existing.parentNodeName === null && patch.parentNodeName !== null) {
|
|
565
|
+
existing.parentNodeName = patch.parentNodeName;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Status / metrics: newest observation wins.
|
|
569
|
+
if (patch.isUp !== null && newer) {
|
|
570
|
+
existing.isUp = patch.isUp;
|
|
571
|
+
}
|
|
572
|
+
if (patch.haState !== null && newer) {
|
|
573
|
+
existing.haState = patch.haState;
|
|
574
|
+
}
|
|
575
|
+
if (patch.onboot !== null && newer) {
|
|
576
|
+
existing.onboot = patch.onboot;
|
|
577
|
+
}
|
|
578
|
+
if (patch.uptimeSeconds !== null && newer) {
|
|
579
|
+
existing.uptimeSeconds = patch.uptimeSeconds;
|
|
580
|
+
}
|
|
581
|
+
if (patch.latestCpuPercent !== null && newer) {
|
|
582
|
+
existing.latestCpuPercent = patch.latestCpuPercent;
|
|
583
|
+
}
|
|
584
|
+
if (patch.latestMemoryBytes !== null && newer) {
|
|
585
|
+
existing.latestMemoryBytes = patch.latestMemoryBytes;
|
|
586
|
+
}
|
|
587
|
+
if (patch.maxMemoryBytes !== null && newer) {
|
|
588
|
+
existing.maxMemoryBytes = patch.maxMemoryBytes;
|
|
589
|
+
}
|
|
590
|
+
if (patch.latestDiskBytes !== null && newer) {
|
|
591
|
+
existing.latestDiskBytes = patch.latestDiskBytes;
|
|
592
|
+
}
|
|
593
|
+
if (patch.maxDiskBytes !== null && newer) {
|
|
594
|
+
existing.maxDiskBytes = patch.maxDiskBytes;
|
|
595
|
+
}
|
|
596
|
+
if (patch.observedAt > existing.observedAt) {
|
|
597
|
+
existing.observedAt = patch.observedAt;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/*
|
|
602
|
+
* WI-24: a guest is "covered by a backup job" exactly when the batch
|
|
603
|
+
* carried the backup-info collector output at all AND no
|
|
604
|
+
* pve_not_backed_up_info series carries its id (series present =
|
|
605
|
+
* NOT covered — the exporter emits one info row per uncovered
|
|
606
|
+
* guest). A batch without the collector output yields null, so the
|
|
607
|
+
* upsert COALESCE keeps the last-known value (the same
|
|
608
|
+
* never-zero-on-partial-batch guard the count columns use).
|
|
609
|
+
*
|
|
610
|
+
* The info `id` label's value format is unverified upstream
|
|
611
|
+
* (`qemu/100` vs bare vmid — §3.9 spike), so match both the full
|
|
612
|
+
* externalId and the bare vmid.
|
|
613
|
+
*/
|
|
614
|
+
export function computeProxmoxGuestBackedUp(
|
|
615
|
+
entry: ProxmoxResourceBufferEntry,
|
|
616
|
+
snap: ProxmoxClusterSnapshotBufferEntry | undefined,
|
|
617
|
+
): boolean | null {
|
|
618
|
+
if (entry.kind !== "Guest" || !snap?.sawBackupInfo) {
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
if (snap.notBackedUpIds.has(entry.externalId)) {
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
if (entry.vmid !== null && snap.notBackedUpIds.has(String(entry.vmid))) {
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
return true;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/*
|
|
631
|
+
* Derive the ProxmoxCluster snapshot columns from one folded batch.
|
|
632
|
+
* Counts are only set when the batch carried the matching identity
|
|
633
|
+
* series — never zero a count on a partial batch. Returns an object
|
|
634
|
+
* whose keys are exactly the columns to write (empty object = nothing
|
|
635
|
+
* to write).
|
|
636
|
+
*/
|
|
637
|
+
export function deriveProxmoxClusterSnapshotExtras(
|
|
638
|
+
entries: Array<ProxmoxResourceBufferEntry>,
|
|
639
|
+
snap: ProxmoxClusterSnapshotBufferEntry | undefined,
|
|
640
|
+
): ProxmoxClusterSnapshotExtras {
|
|
641
|
+
const extras: ProxmoxClusterSnapshotExtras = {};
|
|
642
|
+
|
|
643
|
+
if (snap?.pveVersion) {
|
|
644
|
+
extras.pveVersion = snap.pveVersion;
|
|
645
|
+
}
|
|
646
|
+
if (snap?.sawNodeIdentity) {
|
|
647
|
+
extras.nodeCount = entries.filter((e: ProxmoxResourceBufferEntry) => {
|
|
648
|
+
return e.kind === "Node";
|
|
649
|
+
}).length;
|
|
650
|
+
}
|
|
651
|
+
if (snap?.sawNodeUp) {
|
|
652
|
+
extras.onlineNodeCount = entries.filter((e: ProxmoxResourceBufferEntry) => {
|
|
653
|
+
return e.kind === "Node" && e.isUp === true;
|
|
654
|
+
}).length;
|
|
655
|
+
}
|
|
656
|
+
if (snap?.sawGuestIdentity) {
|
|
657
|
+
extras.guestCount = entries.filter((e: ProxmoxResourceBufferEntry) => {
|
|
658
|
+
return e.kind === "Guest";
|
|
659
|
+
}).length;
|
|
660
|
+
}
|
|
661
|
+
if (snap?.sawStorageIdentity) {
|
|
662
|
+
extras.storageCount = entries.filter((e: ProxmoxResourceBufferEntry) => {
|
|
663
|
+
return e.kind === "Storage";
|
|
664
|
+
}).length;
|
|
665
|
+
}
|
|
666
|
+
/*
|
|
667
|
+
* WI-24: written only when the batch carried pve_not_backed_up_total
|
|
668
|
+
* itself — NULL in Postgres stays "collector not reporting",
|
|
669
|
+
* distinct from 0 uncovered guests.
|
|
670
|
+
*/
|
|
671
|
+
if (
|
|
672
|
+
snap &&
|
|
673
|
+
snap.guestsWithoutBackupCount !== null &&
|
|
674
|
+
snap.guestsWithoutBackupCount !== undefined
|
|
675
|
+
) {
|
|
676
|
+
extras.guestsWithoutBackupCount = snap.guestsWithoutBackupCount;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return extras;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
export function getOrCreateCephClusterSnapshot(
|
|
683
|
+
buffer: Map<string, CephClusterSnapshotBufferEntry>,
|
|
684
|
+
clusterIdStr: string,
|
|
685
|
+
): CephClusterSnapshotBufferEntry {
|
|
686
|
+
let entry: CephClusterSnapshotBufferEntry | undefined =
|
|
687
|
+
buffer.get(clusterIdStr);
|
|
688
|
+
if (!entry) {
|
|
689
|
+
entry = {
|
|
690
|
+
sawMonMetadata: false,
|
|
691
|
+
sawOsdMetadata: false,
|
|
692
|
+
sawOsdUp: false,
|
|
693
|
+
sawOsdIn: false,
|
|
694
|
+
sawPoolMetadata: false,
|
|
695
|
+
healthStatus: null,
|
|
696
|
+
totalBytes: null,
|
|
697
|
+
totalUsedBytes: null,
|
|
698
|
+
cephVersionCounts: new Map(),
|
|
699
|
+
};
|
|
700
|
+
buffer.set(clusterIdStr, entry);
|
|
701
|
+
}
|
|
702
|
+
return entry;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
export function emptyCephResourceEntry(
|
|
706
|
+
kind: string,
|
|
707
|
+
externalId: string,
|
|
708
|
+
observedAt: Date,
|
|
709
|
+
): CephResourceBufferEntry {
|
|
710
|
+
return {
|
|
711
|
+
kind,
|
|
712
|
+
externalId,
|
|
713
|
+
name: null,
|
|
714
|
+
hostname: null,
|
|
715
|
+
daemonVersion: null,
|
|
716
|
+
deviceClass: null,
|
|
717
|
+
isUp: null,
|
|
718
|
+
isIn: null,
|
|
719
|
+
inQuorum: null,
|
|
720
|
+
statBytes: null,
|
|
721
|
+
statBytesUsed: null,
|
|
722
|
+
applyLatencyMs: null,
|
|
723
|
+
commitLatencyMs: null,
|
|
724
|
+
pgCount: null,
|
|
725
|
+
storedBytes: null,
|
|
726
|
+
maxAvailBytes: null,
|
|
727
|
+
objects: null,
|
|
728
|
+
readOpsCounter: null,
|
|
729
|
+
writeOpsCounter: null,
|
|
730
|
+
observedAt,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/*
|
|
735
|
+
* Fold one ceph_* datapoint into the per-cluster buffers. Pool
|
|
736
|
+
* series are keyed by the `pool_id` datapoint label, daemon series
|
|
737
|
+
* by `ceph_daemon` (osd.3, mon.a, mgr.x, …).
|
|
738
|
+
*/
|
|
739
|
+
export function bufferCephSnapshotMetric(data: {
|
|
740
|
+
clusterIdStr: string;
|
|
741
|
+
metricName: string;
|
|
742
|
+
datapoint: JSONObject;
|
|
743
|
+
resourceBuffer: Map<string, Map<string, CephResourceBufferEntry>>;
|
|
744
|
+
clusterBuffer: Map<string, CephClusterSnapshotBufferEntry>;
|
|
745
|
+
}): void {
|
|
746
|
+
const valueFromInt: number | null = toNumberOrNull(data.datapoint["asInt"]);
|
|
747
|
+
const valueFromDouble: number | null = toNumberOrNull(
|
|
748
|
+
data.datapoint["asDouble"],
|
|
749
|
+
);
|
|
750
|
+
const rawValue: number | null = valueFromDouble ?? valueFromInt;
|
|
751
|
+
if (rawValue === null) {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const observedAt: Date = parseUnixNanoToDate(
|
|
756
|
+
data.datapoint["timeUnixNano"] as string | number | undefined,
|
|
757
|
+
"ceph snapshot timeUnixNano",
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
const dpAttributes: JSONArray =
|
|
761
|
+
(data.datapoint["attributes"] as JSONArray) || [];
|
|
762
|
+
|
|
763
|
+
const cluster: CephClusterSnapshotBufferEntry =
|
|
764
|
+
getOrCreateCephClusterSnapshot(data.clusterBuffer, data.clusterIdStr);
|
|
765
|
+
|
|
766
|
+
// Cluster-level series — no per-resource row.
|
|
767
|
+
if (data.metricName === "ceph_health_status") {
|
|
768
|
+
// 0 = HEALTH_OK, 1 = HEALTH_WARN, 2 = HEALTH_ERR.
|
|
769
|
+
cluster.healthStatus = Math.max(0, Math.trunc(rawValue));
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
if (data.metricName === "ceph_cluster_total_bytes") {
|
|
773
|
+
cluster.totalBytes = Math.max(0, rawValue);
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (data.metricName === "ceph_cluster_total_used_bytes") {
|
|
777
|
+
cluster.totalUsedBytes = Math.max(0, rawValue);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (data.metricName.startsWith("ceph_pool_")) {
|
|
782
|
+
const poolId: string | null = getStringAttribute(dpAttributes, "pool_id");
|
|
783
|
+
if (!poolId) {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const patch: CephResourceBufferEntry = emptyCephResourceEntry(
|
|
788
|
+
"Pool",
|
|
789
|
+
poolId,
|
|
790
|
+
observedAt,
|
|
791
|
+
);
|
|
792
|
+
|
|
793
|
+
switch (data.metricName) {
|
|
794
|
+
case "ceph_pool_metadata": {
|
|
795
|
+
// The only pool series that carries the human-readable name.
|
|
796
|
+
patch.name = getStringAttribute(dpAttributes, "name");
|
|
797
|
+
cluster.sawPoolMetadata = true;
|
|
798
|
+
break;
|
|
799
|
+
}
|
|
800
|
+
case "ceph_pool_stored": {
|
|
801
|
+
patch.storedBytes = Math.max(0, Math.trunc(rawValue));
|
|
802
|
+
break;
|
|
803
|
+
}
|
|
804
|
+
case "ceph_pool_max_avail": {
|
|
805
|
+
patch.maxAvailBytes = Math.max(0, Math.trunc(rawValue));
|
|
806
|
+
break;
|
|
807
|
+
}
|
|
808
|
+
case "ceph_pool_objects": {
|
|
809
|
+
patch.objects = Math.max(0, Math.trunc(rawValue));
|
|
810
|
+
break;
|
|
811
|
+
}
|
|
812
|
+
/*
|
|
813
|
+
* Latest RAW cumulative counters — the Pools list computes
|
|
814
|
+
* IOPS rates on read from ClickHouse, never from these.
|
|
815
|
+
*/
|
|
816
|
+
case "ceph_pool_rd": {
|
|
817
|
+
patch.readOpsCounter = Math.max(0, Math.trunc(rawValue));
|
|
818
|
+
break;
|
|
819
|
+
}
|
|
820
|
+
case "ceph_pool_wr": {
|
|
821
|
+
patch.writeOpsCounter = Math.max(0, Math.trunc(rawValue));
|
|
822
|
+
break;
|
|
823
|
+
}
|
|
824
|
+
default: {
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
foldCephResourceSnapshot({
|
|
830
|
+
buffer: data.resourceBuffer,
|
|
831
|
+
clusterIdStr: data.clusterIdStr,
|
|
832
|
+
patch,
|
|
833
|
+
});
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const cephDaemon: string | null = getStringAttribute(
|
|
838
|
+
dpAttributes,
|
|
839
|
+
"ceph_daemon",
|
|
840
|
+
);
|
|
841
|
+
if (!cephDaemon) {
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const dotIndex: number = cephDaemon.indexOf(".");
|
|
846
|
+
const daemonPrefix: string =
|
|
847
|
+
dotIndex > 0 ? cephDaemon.substring(0, dotIndex) : "";
|
|
848
|
+
let kind: string;
|
|
849
|
+
if (daemonPrefix === "osd") {
|
|
850
|
+
kind = "Osd";
|
|
851
|
+
} else if (daemonPrefix === "mon") {
|
|
852
|
+
kind = "Mon";
|
|
853
|
+
} else if (daemonPrefix === "mgr") {
|
|
854
|
+
kind = "Mgr";
|
|
855
|
+
} else if (daemonPrefix === "mds") {
|
|
856
|
+
kind = "Mds";
|
|
857
|
+
} else if (daemonPrefix === "rgw") {
|
|
858
|
+
kind = "Rgw";
|
|
859
|
+
} else {
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
const patch: CephResourceBufferEntry = emptyCephResourceEntry(
|
|
864
|
+
kind,
|
|
865
|
+
cephDaemon,
|
|
866
|
+
observedAt,
|
|
867
|
+
);
|
|
868
|
+
|
|
869
|
+
switch (data.metricName) {
|
|
870
|
+
case "ceph_mon_quorum_status": {
|
|
871
|
+
patch.inQuorum = rawValue >= 1;
|
|
872
|
+
break;
|
|
873
|
+
}
|
|
874
|
+
case "ceph_mon_metadata": {
|
|
875
|
+
patch.hostname = getStringAttribute(dpAttributes, "hostname");
|
|
876
|
+
patch.daemonVersion = getStringAttribute(dpAttributes, "ceph_version");
|
|
877
|
+
cluster.sawMonMetadata = true;
|
|
878
|
+
// CephCluster.cephVersion = modal mon version across the batch.
|
|
879
|
+
if (patch.daemonVersion) {
|
|
880
|
+
cluster.cephVersionCounts.set(
|
|
881
|
+
patch.daemonVersion,
|
|
882
|
+
(cluster.cephVersionCounts.get(patch.daemonVersion) || 0) + 1,
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
break;
|
|
886
|
+
}
|
|
887
|
+
case "ceph_osd_up": {
|
|
888
|
+
patch.isUp = rawValue >= 1;
|
|
889
|
+
cluster.sawOsdUp = true;
|
|
890
|
+
break;
|
|
891
|
+
}
|
|
892
|
+
case "ceph_osd_in": {
|
|
893
|
+
patch.isIn = rawValue >= 1;
|
|
894
|
+
cluster.sawOsdIn = true;
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
case "ceph_osd_metadata": {
|
|
898
|
+
patch.hostname = getStringAttribute(dpAttributes, "hostname");
|
|
899
|
+
patch.deviceClass = getStringAttribute(dpAttributes, "device_class");
|
|
900
|
+
patch.daemonVersion = getStringAttribute(dpAttributes, "ceph_version");
|
|
901
|
+
cluster.sawOsdMetadata = true;
|
|
902
|
+
break;
|
|
903
|
+
}
|
|
904
|
+
case "ceph_mgr_metadata":
|
|
905
|
+
case "ceph_mds_metadata":
|
|
906
|
+
case "ceph_rgw_metadata": {
|
|
907
|
+
patch.hostname = getStringAttribute(dpAttributes, "hostname");
|
|
908
|
+
patch.daemonVersion = getStringAttribute(dpAttributes, "ceph_version");
|
|
909
|
+
break;
|
|
910
|
+
}
|
|
911
|
+
case "ceph_osd_stat_bytes": {
|
|
912
|
+
patch.statBytes = Math.max(0, Math.trunc(rawValue));
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
case "ceph_osd_stat_bytes_used": {
|
|
916
|
+
patch.statBytesUsed = Math.max(0, Math.trunc(rawValue));
|
|
917
|
+
break;
|
|
918
|
+
}
|
|
919
|
+
case "ceph_osd_apply_latency_ms": {
|
|
920
|
+
patch.applyLatencyMs = Math.max(0, rawValue);
|
|
921
|
+
break;
|
|
922
|
+
}
|
|
923
|
+
case "ceph_osd_commit_latency_ms": {
|
|
924
|
+
patch.commitLatencyMs = Math.max(0, rawValue);
|
|
925
|
+
break;
|
|
926
|
+
}
|
|
927
|
+
case "ceph_osd_numpg": {
|
|
928
|
+
patch.pgCount = Math.max(0, Math.trunc(rawValue));
|
|
929
|
+
break;
|
|
930
|
+
}
|
|
931
|
+
default: {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
foldCephResourceSnapshot({
|
|
937
|
+
buffer: data.resourceBuffer,
|
|
938
|
+
clusterIdStr: data.clusterIdStr,
|
|
939
|
+
patch,
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/*
|
|
944
|
+
* Merge a patch into the per-cluster Ceph buffer — same semantics
|
|
945
|
+
* as foldProxmoxResourceSnapshot (identity first-non-null-wins,
|
|
946
|
+
* status/metrics newest-observedAt-wins).
|
|
947
|
+
*/
|
|
948
|
+
export function foldCephResourceSnapshot(data: {
|
|
949
|
+
buffer: Map<string, Map<string, CephResourceBufferEntry>>;
|
|
950
|
+
clusterIdStr: string;
|
|
951
|
+
patch: CephResourceBufferEntry;
|
|
952
|
+
}): void {
|
|
953
|
+
let perCluster: Map<string, CephResourceBufferEntry> | undefined =
|
|
954
|
+
data.buffer.get(data.clusterIdStr);
|
|
955
|
+
if (!perCluster) {
|
|
956
|
+
perCluster = new Map();
|
|
957
|
+
data.buffer.set(data.clusterIdStr, perCluster);
|
|
958
|
+
}
|
|
959
|
+
const key: string = `${data.patch.kind}|${data.patch.externalId}`;
|
|
960
|
+
const existing: CephResourceBufferEntry | undefined = perCluster.get(key);
|
|
961
|
+
if (!existing) {
|
|
962
|
+
perCluster.set(key, data.patch);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
const patch: CephResourceBufferEntry = data.patch;
|
|
967
|
+
const newer: boolean = patch.observedAt >= existing.observedAt;
|
|
968
|
+
|
|
969
|
+
// Identity: first-non-null wins.
|
|
970
|
+
if (existing.name === null && patch.name !== null) {
|
|
971
|
+
existing.name = patch.name;
|
|
972
|
+
}
|
|
973
|
+
if (existing.hostname === null && patch.hostname !== null) {
|
|
974
|
+
existing.hostname = patch.hostname;
|
|
975
|
+
}
|
|
976
|
+
if (existing.daemonVersion === null && patch.daemonVersion !== null) {
|
|
977
|
+
existing.daemonVersion = patch.daemonVersion;
|
|
978
|
+
}
|
|
979
|
+
if (existing.deviceClass === null && patch.deviceClass !== null) {
|
|
980
|
+
existing.deviceClass = patch.deviceClass;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// Status / metrics: newest observation wins.
|
|
984
|
+
if (patch.isUp !== null && newer) {
|
|
985
|
+
existing.isUp = patch.isUp;
|
|
986
|
+
}
|
|
987
|
+
if (patch.isIn !== null && newer) {
|
|
988
|
+
existing.isIn = patch.isIn;
|
|
989
|
+
}
|
|
990
|
+
if (patch.inQuorum !== null && newer) {
|
|
991
|
+
existing.inQuorum = patch.inQuorum;
|
|
992
|
+
}
|
|
993
|
+
if (patch.statBytes !== null && newer) {
|
|
994
|
+
existing.statBytes = patch.statBytes;
|
|
995
|
+
}
|
|
996
|
+
if (patch.statBytesUsed !== null && newer) {
|
|
997
|
+
existing.statBytesUsed = patch.statBytesUsed;
|
|
998
|
+
}
|
|
999
|
+
if (patch.applyLatencyMs !== null && newer) {
|
|
1000
|
+
existing.applyLatencyMs = patch.applyLatencyMs;
|
|
1001
|
+
}
|
|
1002
|
+
if (patch.commitLatencyMs !== null && newer) {
|
|
1003
|
+
existing.commitLatencyMs = patch.commitLatencyMs;
|
|
1004
|
+
}
|
|
1005
|
+
if (patch.pgCount !== null && newer) {
|
|
1006
|
+
existing.pgCount = patch.pgCount;
|
|
1007
|
+
}
|
|
1008
|
+
if (patch.storedBytes !== null && newer) {
|
|
1009
|
+
existing.storedBytes = patch.storedBytes;
|
|
1010
|
+
}
|
|
1011
|
+
if (patch.maxAvailBytes !== null && newer) {
|
|
1012
|
+
existing.maxAvailBytes = patch.maxAvailBytes;
|
|
1013
|
+
}
|
|
1014
|
+
if (patch.objects !== null && newer) {
|
|
1015
|
+
existing.objects = patch.objects;
|
|
1016
|
+
}
|
|
1017
|
+
if (patch.readOpsCounter !== null && newer) {
|
|
1018
|
+
existing.readOpsCounter = patch.readOpsCounter;
|
|
1019
|
+
}
|
|
1020
|
+
if (patch.writeOpsCounter !== null && newer) {
|
|
1021
|
+
existing.writeOpsCounter = patch.writeOpsCounter;
|
|
1022
|
+
}
|
|
1023
|
+
if (patch.observedAt > existing.observedAt) {
|
|
1024
|
+
existing.observedAt = patch.observedAt;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/*
|
|
1029
|
+
* Derive the CephCluster snapshot columns from one folded batch —
|
|
1030
|
+
* same never-zero-a-count-on-a-partial-batch contract as the Proxmox
|
|
1031
|
+
* derive above.
|
|
1032
|
+
*/
|
|
1033
|
+
export function deriveCephClusterSnapshotExtras(
|
|
1034
|
+
entries: Array<CephResourceBufferEntry>,
|
|
1035
|
+
snap: CephClusterSnapshotBufferEntry | undefined,
|
|
1036
|
+
): CephClusterSnapshotExtras {
|
|
1037
|
+
const extras: CephClusterSnapshotExtras = {};
|
|
1038
|
+
|
|
1039
|
+
if (snap?.sawMonMetadata) {
|
|
1040
|
+
extras.monCount = entries.filter((e: CephResourceBufferEntry) => {
|
|
1041
|
+
return e.kind === "Mon";
|
|
1042
|
+
}).length;
|
|
1043
|
+
}
|
|
1044
|
+
if (snap?.sawOsdMetadata) {
|
|
1045
|
+
extras.osdCount = entries.filter((e: CephResourceBufferEntry) => {
|
|
1046
|
+
return e.kind === "Osd";
|
|
1047
|
+
}).length;
|
|
1048
|
+
}
|
|
1049
|
+
if (snap?.sawOsdUp) {
|
|
1050
|
+
extras.osdUpCount = entries.filter((e: CephResourceBufferEntry) => {
|
|
1051
|
+
return e.kind === "Osd" && e.isUp === true;
|
|
1052
|
+
}).length;
|
|
1053
|
+
}
|
|
1054
|
+
if (snap?.sawOsdIn) {
|
|
1055
|
+
extras.osdInCount = entries.filter((e: CephResourceBufferEntry) => {
|
|
1056
|
+
return e.kind === "Osd" && e.isIn === true;
|
|
1057
|
+
}).length;
|
|
1058
|
+
}
|
|
1059
|
+
if (snap?.sawPoolMetadata) {
|
|
1060
|
+
extras.poolCount = entries.filter((e: CephResourceBufferEntry) => {
|
|
1061
|
+
return e.kind === "Pool";
|
|
1062
|
+
}).length;
|
|
1063
|
+
}
|
|
1064
|
+
if (snap && snap.healthStatus !== null) {
|
|
1065
|
+
extras.healthStatus = snap.healthStatus;
|
|
1066
|
+
}
|
|
1067
|
+
if (
|
|
1068
|
+
snap &&
|
|
1069
|
+
snap.totalBytes !== null &&
|
|
1070
|
+
snap.totalBytes > 0 &&
|
|
1071
|
+
snap.totalUsedBytes !== null
|
|
1072
|
+
) {
|
|
1073
|
+
/*
|
|
1074
|
+
* Round to 2 decimals: full float precision would change the
|
|
1075
|
+
* extras fingerprint on every scrape and defeat the 60-s
|
|
1076
|
+
* write throttle for an invisible difference.
|
|
1077
|
+
*/
|
|
1078
|
+
extras.capacityUsedPercent =
|
|
1079
|
+
Math.round((snap.totalUsedBytes / snap.totalBytes) * 10000) / 100;
|
|
1080
|
+
}
|
|
1081
|
+
if (snap && snap.cephVersionCounts.size > 0) {
|
|
1082
|
+
let modalVersion: string | null = null;
|
|
1083
|
+
let modalCount: number = 0;
|
|
1084
|
+
for (const [version, count] of snap.cephVersionCounts.entries()) {
|
|
1085
|
+
if (count > modalCount) {
|
|
1086
|
+
modalVersion = version;
|
|
1087
|
+
modalCount = count;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
if (modalVersion) {
|
|
1091
|
+
extras.cephVersion = modalVersion;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
return extras;
|
|
1096
|
+
}
|