@oneuptime/common 14.0.2 → 14.0.5
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 +55 -0
- package/Models/DatabaseModels/AlertFeed.ts +2 -0
- package/Models/DatabaseModels/DatabaseServer.ts +1650 -0
- package/Models/DatabaseModels/DatabaseServerEndpoint.ts +540 -0
- package/Models/DatabaseModels/DatabaseServerFeed.ts +625 -0
- package/Models/DatabaseModels/DatabaseServerLabelRule.ts +514 -0
- package/Models/DatabaseModels/DatabaseServerOwnerRule.ts +596 -0
- package/Models/DatabaseModels/DatabaseServerOwnerTeam.ts +491 -0
- package/Models/DatabaseModels/DatabaseServerOwnerUser.ts +490 -0
- package/Models/DatabaseModels/Incident.ts +55 -0
- package/Models/DatabaseModels/IncidentAlert.ts +540 -0
- package/Models/DatabaseModels/IncidentFeed.ts +2 -0
- package/Models/DatabaseModels/Index.ts +18 -0
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +26 -0
- package/Models/DatabaseModels/Project.ts +60 -0
- package/Models/DatabaseModels/RumApplication.ts +52 -1
- package/Models/DatabaseModels/ScheduledMaintenance.ts +55 -0
- package/Models/DatabaseModels/TelemetryException.ts +2 -0
- package/Models/DatabaseModels/UserProjectSsoConsent.ts +146 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.ts +28 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.ts +89 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.ts +673 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Middleware/MultipartFormData.ts +1 -1
- package/Server/Middleware/TelemetryIngest.ts +15 -8
- package/Server/Services/AlertReminderRuleService.ts +100 -24
- package/Server/Services/DatabaseServerEndpointService.ts +895 -0
- package/Server/Services/DatabaseServerFeedService.ts +128 -0
- package/Server/Services/DatabaseServerLabelRuleEngineService.ts +340 -0
- package/Server/Services/DatabaseServerLabelRuleService.ts +14 -0
- package/Server/Services/DatabaseServerOwnerRuleEngineService.ts +425 -0
- package/Server/Services/DatabaseServerOwnerRuleService.ts +14 -0
- package/Server/Services/DatabaseServerOwnerTeamService.ts +180 -0
- package/Server/Services/DatabaseServerOwnerUserService.ts +173 -0
- package/Server/Services/DatabaseServerService.ts +4546 -0
- package/Server/Services/DatabaseService.ts +14 -8
- package/Server/Services/DockerResourceService.ts +43 -2
- package/Server/Services/IncidentAlertService.ts +1811 -0
- package/Server/Services/IncidentReminderRuleService.ts +103 -23
- package/Server/Services/IncidentService.ts +210 -10
- package/Server/Services/IncidentStateTimelineService.ts +24 -0
- package/Server/Services/Index.ts +18 -0
- package/Server/Services/InventoryItemService.ts +113 -5
- package/Server/Services/IoTDeviceService.ts +145 -37
- package/Server/Services/OpenTelemetryIngestService.ts +19 -0
- package/Server/Services/PodmanResourceService.ts +43 -2
- package/Server/Services/ScheduledMaintenanceReminderRuleService.ts +107 -23
- package/Server/Services/ServiceService.ts +1 -1
- package/Server/Services/TelemetryUsageBillingService.ts +29 -2
- package/Server/Services/UserProjectSsoConsentService.ts +74 -0
- package/Server/Services/UserService.ts +14 -1
- package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +18 -0
- package/Server/Types/Database/QueryHelper.ts +30 -1
- package/Server/Utils/AI/Toolbox/ResourceTools.ts +69 -1
- package/Server/Utils/Database/AffectedResourceRelations.ts +6 -0
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +188 -6
- package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +2262 -755
- package/Server/Utils/Monitor/MonitorIncident.ts +28 -1
- package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +268 -6
- package/Server/Utils/Monitor/MonitorResource.ts +47 -11
- package/Server/Utils/Monitor/MonitorResourceContext.ts +1 -0
- package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +239 -21
- package/Server/Utils/Monitor/PerSeriesResolutionRootCause.ts +43 -0
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +291 -0
- package/Server/Utils/Monitor/SeriesResourceLinker.ts +111 -12
- package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
- package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +24 -2
- package/Server/Utils/StartServer.ts +26 -6
- package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
- package/Server/Utils/Telemetry/DatabaseEndpointDiscovery.ts +688 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +5 -1
- package/Server/Utils/Telemetry/LogExceptionExtractor.ts +18 -1
- package/Server/Utils/Telemetry/ResourceEntityFilter.ts +175 -12
- package/Server/Utils/Telemetry/ResourceFacetResolver.ts +12 -0
- package/Server/Utils/Telemetry/ServiceDependencyDiscovery.ts +606 -36
- package/Server/Utils/Telemetry/Telemetry.ts +16 -0
- package/Server/Utils/Telemetry/TelemetryEntity.ts +22 -22
- package/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.ts +16 -10
- package/Tests/App/Accounts/SignedInRedirect.test.tsx +241 -0
- package/Tests/App/AdminDashboard/AdminHeaderSmallScreens.test.tsx +221 -4
- package/Tests/App/Dashboard/AIInsightsResourcePageContext.test.ts +42 -0
- package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +100 -0
- package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +86 -0
- package/Tests/App/Dashboard/AffectedResourcesPickerHiddenTypes.test.tsx +2 -0
- package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +19 -0
- package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +14 -0
- package/Tests/App/Dashboard/BulkIncidentLinkActions.test.tsx +869 -0
- package/Tests/App/Dashboard/CephMetricTooltips.test.tsx +1777 -0
- package/Tests/App/Dashboard/CloudFleetSummary.test.ts +8 -0
- package/Tests/App/Dashboard/CloudMetricTooltips.test.tsx +887 -0
- package/Tests/App/Dashboard/ContainerHostMetricTooltips.test.tsx +1309 -0
- package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +26 -1
- package/Tests/App/Dashboard/DashboardAppSsoError.test.tsx +501 -0
- package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +64 -1
- package/Tests/App/Dashboard/DashboardHeaderSmallScreens.test.tsx +178 -18
- package/Tests/App/Dashboard/DashboardMasterPageSsoRedirect.test.tsx +444 -0
- package/Tests/App/Dashboard/DashboardNavBarForeignHiddenRule.test.tsx +573 -0
- package/Tests/App/Dashboard/DatabaseChartAxes.test.tsx +431 -0
- package/Tests/App/Dashboard/DatabaseContainerDetailBadge.test.tsx +249 -0
- package/Tests/App/Dashboard/DatabaseDashboardE2EFixes.test.ts +681 -0
- package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +1801 -0
- package/Tests/App/Dashboard/DatabaseFleetSummaryRefresh.test.tsx +280 -0
- package/Tests/App/Dashboard/DatabaseFormHelpers.test.tsx +321 -0
- package/Tests/App/Dashboard/DatabaseMetricMonitorLink.test.tsx +795 -0
- package/Tests/App/Dashboard/DatabaseOverviewHeader.test.tsx +580 -0
- package/Tests/App/Dashboard/DatabaseRecommendationsEngineMetrics.test.ts +270 -0
- package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +1486 -0
- package/Tests/App/Dashboard/DatabaseServerEngineMetricsFilter.test.ts +223 -0
- package/Tests/App/Dashboard/DatabaseServerPresentation.test.ts +435 -0
- package/Tests/App/Dashboard/DatabaseServerSummary.test.ts +186 -0
- package/Tests/App/Dashboard/DatabaseServerTelemetryQueries.test.ts +1553 -0
- package/Tests/App/Dashboard/DatabaseTableTruncation.test.tsx +326 -0
- package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +512 -0
- package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +774 -0
- package/Tests/App/Dashboard/DatabaseViewLayout.test.tsx +233 -0
- package/Tests/App/Dashboard/DatabaseWorkloadBadge.test.tsx +972 -0
- package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +5 -0
- package/Tests/App/Dashboard/DiscoveryReviewUnnamedHostHint.test.tsx +1061 -0
- package/Tests/App/Dashboard/DiscoveryScanLivePage.test.tsx +39 -0
- package/Tests/App/Dashboard/DockerSwarmClustersListTooltips.test.tsx +414 -0
- package/Tests/App/Dashboard/DockerSwarmMetricTooltips.test.ts +796 -0
- package/Tests/App/Dashboard/DockerSwarmOverviewTooltips.test.tsx +747 -0
- package/Tests/App/Dashboard/EventOverlayScope.test.ts +147 -0
- package/Tests/App/Dashboard/EventOverlayScopeHook.test.tsx +1 -0
- package/Tests/App/Dashboard/ExceptionMonitorEnvironmentFilter.test.tsx +38 -18
- package/Tests/App/Dashboard/HostDetailViewTooltips.test.tsx +725 -0
- package/Tests/App/Dashboard/HostListColumnTooltips.test.tsx +472 -0
- package/Tests/App/Dashboard/HostMetricTooltips.test.ts +709 -0
- package/Tests/App/Dashboard/HostOverviewTooltips.test.tsx +698 -0
- package/Tests/App/Dashboard/HostTooltipHarness.ts +124 -0
- package/Tests/App/Dashboard/HostsListTooltips.test.tsx +395 -0
- package/Tests/App/Dashboard/IncidentAlertLinkDialog.test.tsx +691 -0
- package/Tests/App/Dashboard/IncidentAlertLinkHelpers.test.ts +458 -0
- package/Tests/App/Dashboard/IncidentAlertLinkPages.test.tsx +828 -0
- package/Tests/App/Dashboard/IncidentAlertLinkSideMenus.test.tsx +231 -0
- package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +689 -0
- package/Tests/App/Dashboard/IoTMetricTooltips.test.tsx +1123 -0
- package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +837 -0
- package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +699 -0
- package/Tests/App/Dashboard/KubernetesContainersTabTooltips.test.tsx +320 -0
- package/Tests/App/Dashboard/KubernetesCostTooltips.test.tsx +502 -0
- package/Tests/App/Dashboard/KubernetesInsightsTooltips.test.tsx +222 -0
- package/Tests/App/Dashboard/KubernetesResourceMetricTooltips.test.ts +561 -0
- package/Tests/App/Dashboard/KubernetesResourcePagesTooltips.test.tsx +1168 -0
- package/Tests/App/Dashboard/LinkIncidentAlertModal.test.tsx +154 -0
- package/Tests/App/Dashboard/LogsResourceFacetChips.test.tsx +1 -0
- package/Tests/App/Dashboard/MetricDescriptionRules.ts +170 -0
- package/Tests/App/Dashboard/MetricDescriptionsCatalog.test.ts +206 -0
- package/Tests/App/Dashboard/MetricDescriptionsConsistency.test.ts +275 -0
- package/Tests/App/Dashboard/MetricsViewerDrillDown.test.tsx +310 -0
- package/Tests/App/Dashboard/MetricsViewerEntityScopedRows.test.tsx +495 -0
- package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +132 -1
- package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +5 -0
- package/Tests/App/Dashboard/NetworkDeviceTooltips.test.tsx +852 -0
- package/Tests/App/Dashboard/NetworkDiagnosticsTooltips.test.tsx +556 -0
- package/Tests/App/Dashboard/NetworkMetricTooltips.test.ts +1348 -0
- package/Tests/App/Dashboard/NetworkSiteTooltips.test.tsx +679 -0
- package/Tests/App/Dashboard/ProductOrientation.test.tsx +4 -3
- package/Tests/App/Dashboard/ProxmoxMetricTooltips.test.ts +592 -0
- package/Tests/App/Dashboard/ProxmoxOverviewTooltips.test.tsx +1002 -0
- package/Tests/App/Dashboard/ProxmoxResourcePageTooltips.test.tsx +923 -0
- package/Tests/App/Dashboard/RecommendationsLoadedContext.test.tsx +349 -0
- package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +62 -0
- package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +8 -1
- package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +14 -0
- package/Tests/App/Dashboard/ResourceOverviewTileTooltips.test.tsx +1066 -0
- package/Tests/App/Dashboard/RumOverviewPage.test.tsx +1195 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +2 -0
- package/Tests/App/Dashboard/ServerlessMetricTooltips.test.tsx +474 -0
- package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +1085 -0
- package/Tests/App/Dashboard/ServiceMetricTooltips.test.tsx +772 -0
- package/Tests/App/Dashboard/SessionReplayAuditTable.test.tsx +56 -1
- package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +1207 -0
- package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +10 -0
- package/Tests/App/Dashboard/TelemetryResourceTabTooltips.test.tsx +437 -0
- package/Tests/App/Dashboard/TracesResourceFacets.test.tsx +2 -1
- package/Tests/App/Dashboard/VMwareMetricTooltips.test.ts +801 -0
- package/Tests/App/Dashboard/VMwareOverviewTooltips.test.tsx +601 -0
- package/Tests/App/Dashboard/VMwareResourcePagesTooltips.test.tsx +646 -0
- package/Tests/App/Dashboard/VMwareTooltipHarness.ts +145 -0
- package/Tests/App/StatusPage/StatusPageHeaderForeignHiddenRule.test.tsx +284 -0
- package/Tests/ClassStrings.ts +178 -0
- package/Tests/Fixtures/MobilePasskeyUser.json +1 -2
- package/Tests/ForeignHiddenRuleGuard.ts +2152 -0
- package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +2250 -0
- package/Tests/Models/DatabaseModels/IncidentAlertModel.test.ts +554 -0
- package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +24 -5
- package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +117 -0
- package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
- package/Tests/RenderedMarkup.ts +518 -0
- package/Tests/ResponsiveVisibility.test.ts +490 -4
- package/Tests/ResponsiveVisibility.ts +505 -52
- package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
- package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +541 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentAlertMigration.test.ts +341 -0
- package/Tests/Server/Infrastructure/Postgres/AddSessionReplaySameOriginTracePropagationMigration.test.ts +247 -0
- package/Tests/Server/Infrastructure/Postgres/AddUserProjectSsoConsentMigration.test.ts +234 -0
- package/Tests/Server/Services/ContainerSnapshotUpsertPostgres.test.ts +319 -0
- package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +13 -0
- package/Tests/Server/Services/DatabaseServerEndpointService.test.ts +2033 -0
- package/Tests/Server/Services/DatabaseServerFeedAndOwnerServices.test.ts +529 -0
- package/Tests/Server/Services/DatabaseServerLifecycle.test.ts +3302 -0
- package/Tests/Server/Services/DatabaseServerRegistries.test.ts +603 -0
- package/Tests/Server/Services/DatabaseServerRuleEngines.test.ts +754 -0
- package/Tests/Server/Services/DatabaseServerService.test.ts +3558 -0
- package/Tests/Server/Services/DatabaseServerSqlPostgres.test.ts +2045 -0
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedPostgres.test.ts +362 -0
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedReply.test.ts +210 -0
- package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +18 -0
- package/Tests/Server/Services/IncidentAlertPostgres.test.ts +903 -0
- package/Tests/Server/Services/IncidentAlertRegistration.test.ts +96 -0
- package/Tests/Server/Services/IncidentAlertService.test.ts +2225 -0
- package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +2 -0
- package/Tests/Server/Services/IncidentAlertStateCascade.test.ts +970 -0
- package/Tests/Server/Services/IncidentCreateFromAlerts.test.ts +1284 -0
- package/Tests/Server/Services/IncomingCallPolicyEscalationRuleTarget.test.ts +225 -0
- package/Tests/Server/Services/InventoryItemDisplayName.test.ts +280 -2
- package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +8 -2
- package/Tests/Server/Services/IoTDeviceLatestMetricsPostgres.test.ts +689 -0
- package/Tests/Server/Services/IoTDeviceService.test.ts +379 -0
- package/Tests/Server/Services/LatestMetricsValuesShape.test.ts +360 -0
- package/Tests/Server/Services/LlmModelPriceService.test.ts +402 -0
- package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +7 -1
- package/Tests/Server/Services/OwnerRowUniqueness.test.ts +61 -9
- package/Tests/Server/Services/ReminderRuleLookupPostgres.test.ts +2250 -0
- package/Tests/Server/Services/ReminderRuleRefreshOpenSubjectsPaging.test.ts +653 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +68 -20
- package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
- package/Tests/Server/Services/TelemetryResourceFacetCounting.test.ts +9 -0
- package/Tests/Server/Services/UserProjectSsoConsentPostgres.test.ts +153 -0
- package/Tests/Server/Services/UserProjectSsoConsentService.test.ts +192 -0
- package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +71 -0
- package/Tests/Server/Services/UserSignupIdTakeoverPostgres.test.ts +238 -0
- package/Tests/Server/Services/WorkspaceNotificationSummaryHelpers.test.ts +592 -0
- package/Tests/Server/Types/Billing/PayAsYouGoMeteredAuthorization.test.ts +3 -0
- package/Tests/Server/Types/Database/EventOverlayScopePostgres.test.ts +1 -0
- package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +11 -0
- package/Tests/Server/Types/Database/Permissions/InfrastructureOwnershipScope.test.ts +8 -0
- package/Tests/Server/Types/Database/QueryHelperBooleanForCriteriaBackedRule.test.ts +1094 -0
- package/Tests/Server/Utils/AI/ResourceTools.test.ts +134 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +41 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +279 -9
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaDisplayContext.test.ts +933 -0
- package/Tests/Server/Utils/Monitor/DatabaseMaintenanceSuppression.test.ts +582 -0
- package/Tests/Server/Utils/Monitor/DatabaseServerMonitorLinking.test.ts +753 -0
- package/Tests/Server/Utils/Monitor/MonitorActionTimestamps.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertCriteriaReferences.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +119 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +60 -25
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorAffectedResourceEdgeCases.test.ts +1719 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesAffectedResources.test.ts +1447 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesRatioEmail.test.ts +352 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformMetricUnits.test.ts +717 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformRawScan.test.ts +2125 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformSeriesAffectedResources.test.ts +1288 -0
- package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentCriteriaReferences.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentMemberRoleMembership.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +88 -0
- package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +205 -0
- package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +62 -0
- package/Tests/Server/Utils/Monitor/MonitorResourceDatabaseMaintenance.test.ts +803 -0
- package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +133 -3
- package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/PerSeriesRecoveryResolution.test.ts +472 -1
- package/Tests/Server/Utils/Monitor/PerSeriesSecondEntityAlerting.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +236 -2
- package/Tests/Server/Utils/SeriesResourceLabels.test.ts +70 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +103 -0
- package/Tests/Server/Utils/StartServerProtobufContentType.test.ts +54 -0
- package/Tests/Server/Utils/Telemetry/DatabaseEndpointDiscovery.test.ts +1895 -0
- package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +7 -0
- package/Tests/Server/Utils/Telemetry/LogExceptionExtractorSeverityGate.test.ts +90 -0
- package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +570 -0
- package/Tests/Server/Utils/Telemetry/ResourceFacetResolver.test.ts +97 -2
- package/Tests/Server/Utils/Telemetry/ServiceDependencyDiscovery.test.ts +1337 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +295 -0
- package/Tests/Types/AI/AIResourceContext.test.ts +30 -0
- package/Tests/Types/DatabaseServer/DatabaseContainerClassifier.test.ts +3644 -0
- package/Tests/Types/DatabaseServer/DatabaseContainerCommand.test.ts +340 -0
- package/Tests/Types/DatabaseServer/DatabaseContainerCommandCorpus.ts +826 -0
- package/Tests/Types/DatabaseServer/DatabaseContainerCommandPostgres.test.ts +150 -0
- package/Tests/Types/DatabaseServer/DatabaseEndpoint.test.ts +2657 -0
- package/Tests/Types/DatabaseServer/DatabaseServerDiscoverySource.test.ts +64 -0
- package/Tests/Types/DatabaseServer/DatabaseServerEntityKeys.test.ts +952 -0
- package/Tests/Types/DatabaseServer/DatabaseServerMetricCatalog.test.ts +1161 -0
- package/Tests/Types/DatabaseServer/DatabaseSystem.test.ts +1514 -0
- package/Tests/Types/DatabaseServer/DatabaseTelemetryResolver.test.ts +1558 -0
- package/Tests/Types/Monitor/CephAlertTemplates.test.ts +255 -6
- package/Tests/Types/Monitor/DatabaseAlertTemplates.test.ts +2254 -0
- package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +298 -0
- package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +271 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +253 -9
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +13 -5
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +4 -2
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +111 -2
- package/Tests/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.test.ts +61 -0
- package/Tests/Types/Monitor/SeriesContext/SeriesLabelDisplay.test.ts +114 -0
- package/Tests/Types/Monitor/Utils/KubernetesAgentEmittedMetrics.ts +181 -0
- package/Tests/Types/NetworkDevice/DiscoveredHostNamingStatus.test.ts +401 -0
- package/Tests/Types/Rum/WebVitals.test.ts +173 -0
- package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +4 -1
- package/Tests/Types/Telemetry/ResourceFacetCatalog.test.ts +24 -2
- package/Tests/UI/Components/Card.test.tsx +93 -2
- package/Tests/UI/Components/FeedOptions.test.ts +6 -0
- package/Tests/UI/Components/FeedOptionsButton.test.tsx +8 -1
- package/Tests/UI/Components/FieldLabelInfoCardTooltip.test.tsx +413 -0
- package/Tests/UI/Components/ForeignHiddenRuleResilience.test.tsx +1147 -0
- package/Tests/UI/Components/HeaderRightRail.test.tsx +41 -1
- package/Tests/UI/Components/InfoTooltip.test.tsx +242 -0
- package/Tests/UI/Components/LogsEntityNames.test.ts +16 -1
- package/Tests/UI/Components/LogsFacetSidebarHiddenFacets.test.tsx +7 -3
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +207 -3
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDeleteVerb.test.tsx +367 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableCreateButtonTitle.test.tsx +275 -0
- package/Tests/UI/Components/ResourceFacetConfigs.test.ts +2 -1
- package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
- package/Tests/UI/Components/SrOnlyForeignRule.test.tsx +994 -0
- package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +34 -1
- package/Tests/UI/Components/TableHeaderTooltip.test.tsx +662 -0
- package/Tests/UI/Components/TelemetryFacetSidebarHiddenFacets.test.tsx +4 -1
- package/Tests/UI/ForeignHiddenRuleGuard.test.ts +1951 -0
- package/Tests/UI/Rum/PrivacySummaryCard.test.tsx +166 -9
- package/Tests/UI/Rum/RecordingHealthTooltips.test.tsx +447 -0
- package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +164 -0
- package/Tests/UI/Rum/ReplayFrameCapture.test.ts +6750 -0
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +19 -1
- package/Tests/UI/Rum/ReplayRail.test.tsx +723 -12
- package/Tests/UI/Rum/ReplayRailDetail.test.tsx +86 -0
- package/Tests/UI/Rum/ReplayScreenshot.test.ts +2124 -0
- package/Tests/UI/Rum/ReplayScreenshotActions.test.tsx +3288 -0
- package/Tests/UI/Rum/ReplayStage.test.tsx +62 -0
- package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +983 -2
- package/Tests/UI/Rum/ReplayTabSwitcher.test.tsx +19 -1
- package/Tests/UI/Rum/SessionReplaySettingsPolicyLoading.test.tsx +149 -0
- package/Tests/UI/Rum/SessionReplaySetupGuide.test.tsx +107 -6
- package/Tests/UI/Rum/SessionReplayUsersTableTooltips.test.tsx +330 -0
- package/Tests/UI/Rum/UserFlowTooltips.test.tsx +886 -0
- package/Tests/UI/TailwindPlayCdnForeignHiddenRule.test.ts +1005 -0
- package/Tests/UI/ThemeGroupHoverReveal.test.ts +847 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +80 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +23 -0
- package/Tests/UI/Utils/ModelAPISsoRedirect.test.ts +127 -0
- package/Tests/UI/Utils/Navigation.test.ts +102 -0
- package/Tests/UI/Utils/PermissionGate.test.ts +76 -0
- package/Tests/UI/Utils/Telemetry/TelemetryEntityNames.test.ts +23 -0
- package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +696 -0
- package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
- package/Tests/Utils/MetricUnitUtil.test.ts +227 -2
- package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +288 -2
- package/Tests/Utils/Monitor/PlatformResourceIdentityAndUnits.test.ts +1559 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.test.ts +2546 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +315 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveryReverseDnsChain.test.ts +336 -1
- package/Tests/Utils/NetworkDiscovery/NetbiosNameUtil.test.ts +244 -0
- package/Tests/Utils/Rum/SessionTraceState.test.ts +239 -0
- package/Tests/Utils/ValueFormatter.test.ts +50 -0
- package/Tests/tsconfig.json +16 -0
- package/Types/AI/AIResourceContext.ts +7 -0
- package/Types/Dashboard/DashboardTemplates.ts +2 -1
- package/Types/DatabaseServer/DatabaseConnectionSpan.ts +77 -0
- package/Types/DatabaseServer/DatabaseContainerClassifier.ts +2068 -0
- package/Types/DatabaseServer/DatabaseContainerCommand.ts +801 -0
- package/Types/DatabaseServer/DatabaseEndpoint.ts +1943 -0
- package/Types/DatabaseServer/DatabaseServerDiscoverySource.ts +68 -0
- package/Types/DatabaseServer/DatabaseServerMetricCatalog.ts +966 -0
- package/Types/DatabaseServer/DatabaseSystem.ts +2124 -0
- package/Types/DatabaseServer/DatabaseTelemetryResolver.ts +555 -0
- package/Types/Email/EmailTemplateType.ts +1 -0
- package/Types/Incident/IncidentAlertLink.ts +38 -0
- package/Types/Monitor/CephAlertTemplates.ts +18 -0
- package/Types/Monitor/DatabaseAlertTemplates.ts +2020 -0
- package/Types/Monitor/KubernetesAlertTemplates.ts +64 -24
- package/Types/Monitor/KubernetesMetricCatalog.ts +118 -19
- package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +14 -0
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +93 -10
- package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +109 -8
- package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +36 -7
- package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +43 -2
- package/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.ts +41 -6
- package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +26 -0
- package/Types/NetworkDevice/DiscoveredHostNamingStatus.ts +147 -0
- package/Types/Permission.ts +315 -0
- package/Types/Probe/ProbeApiIngestResponse.ts +8 -0
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +10 -0
- package/Types/Rules/RuleRun.ts +12 -0
- package/Types/Rum/SessionReplay.ts +25 -6
- package/Types/Rum/WebVitals.ts +52 -0
- package/Types/Telemetry/EntityType.ts +23 -0
- package/Types/Telemetry/ResourceFacetCatalog.ts +14 -0
- package/Types/Telemetry/ServiceType.ts +8 -0
- package/UI/Components/Banner/Banner.tsx +1 -1
- package/UI/Components/Card/Card.tsx +1 -1
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +2 -2
- package/UI/Components/Charts/Line/LineChart.tsx +7 -0
- package/UI/Components/CommandPalette/CommandPalette.tsx +1 -1
- package/UI/Components/Date/StartAndEndDate.tsx +5 -5
- package/UI/Components/Detail/FieldLabel.tsx +8 -0
- package/UI/Components/Diagram/HorizontalStepChain.tsx +1 -1
- package/UI/Components/Feed/FeedOptionsButton.tsx +1 -1
- package/UI/Components/Forms/BasicForm.tsx +1 -1
- package/UI/Components/Header/Header.tsx +1 -1
- package/UI/Components/Header/HeaderIconDropdownButton.tsx +1 -1
- package/UI/Components/HeaderAlert/HeaderAlert.tsx +1 -1
- package/UI/Components/InfoCard/InfoCard.tsx +47 -1
- package/UI/Components/LogsViewer/components/FacetValueRow.tsx +1 -1
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +1 -1
- package/UI/Components/Modal/ModalFooter.tsx +1 -1
- package/UI/Components/ModelDetail/CardModelDetail.tsx +2 -2
- package/UI/Components/ModelTable/BaseModelTable.tsx +99 -21
- package/UI/Components/ModelTable/Column.ts +5 -0
- package/UI/Components/Navbar/NavBar.tsx +9 -2
- package/UI/Components/Navbar/NavBarMenuModal.tsx +2 -2
- package/UI/Components/Page/Page.tsx +1 -1
- package/UI/Components/Pagination/Pagination.tsx +3 -3
- package/UI/Components/ProgressButtons/ProgressButtonItem.tsx +1 -1
- package/UI/Components/SideMenu/SideMenu.tsx +4 -1
- package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +4 -2
- package/UI/Components/StatusPage/ResourceList.tsx +6 -6
- package/UI/Components/Table/TableHeader.tsx +47 -2
- package/UI/Components/Table/Types/Column.ts +7 -0
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +1 -1
- package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +1 -1
- package/UI/Components/Tooltip/InfoTooltip.tsx +78 -0
- package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +1 -1
- package/UI/Components/Workflow/ComponentsModal.tsx +2 -2
- package/UI/Components/Workflow/StepTraceViewer.tsx +1 -1
- package/UI/Styles/Theme.css +8 -1
- package/UI/Utils/Navigation.ts +89 -42
- package/UI/Utils/PermissionGate.ts +11 -2
- package/UI/Utils/Telemetry/TelemetryEntityNames.ts +7 -0
- package/UI/Utils/TelemetryService.ts +4 -3
- package/Utils/Incident/IncidentFromAlerts.ts +516 -0
- package/Utils/LabelRuleImportExport.ts +2 -0
- package/Utils/MetricUnitUtil.ts +103 -11
- package/Utils/Monitor/MetricValueFormatter.ts +66 -13
- package/Utils/Monitor/PlatformMetricUnitUtil.ts +215 -0
- package/Utils/Monitor/PlatformResourceIdentity.ts +514 -0
- package/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.ts +425 -0
- package/Utils/NetworkDiscovery/DiscoveredHostUtil.ts +32 -0
- package/Utils/NetworkDiscovery/NetbiosNameUtil.ts +73 -0
- package/Utils/Rum/SessionTraceState.ts +212 -0
- package/Utils/Telemetry/DatabaseServerEntityKeys.ts +275 -0
- package/Utils/Telemetry/EntityKey.ts +176 -0
- package/Utils/ValueFormatter.ts +16 -0
- package/build/dist/Models/DatabaseModels/Alert.js +54 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DatabaseServer.js +1683 -0
- package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js +559 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js +648 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js +522 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js +603 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js +506 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js +505 -0
- package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Incident.js +54 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentAlert.js +553 -0
- package/build/dist/Models/DatabaseModels/IncidentAlert.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +18 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +62 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +53 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +54 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js +2 -0
- package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js +162 -0
- package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js +42 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js +240 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.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/Middleware/MultipartFormData.js +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +15 -8
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AlertReminderRuleService.js +72 -23
- package/build/dist/Server/Services/AlertReminderRuleService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseServerEndpointService.js +694 -0
- package/build/dist/Server/Services/DatabaseServerEndpointService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerFeedService.js +110 -0
- package/build/dist/Server/Services/DatabaseServerFeedService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js +272 -0
- package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerLabelRuleService.js +13 -0
- package/build/dist/Server/Services/DatabaseServerLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js +340 -0
- package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js +13 -0
- package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js +160 -0
- package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerOwnerUserService.js +153 -0
- package/build/dist/Server/Services/DatabaseServerOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseServerService.js +3272 -0
- package/build/dist/Server/Services/DatabaseServerService.js.map +1 -0
- package/build/dist/Server/Services/DatabaseService.js +8 -4
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerResourceService.js +30 -3
- package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
- package/build/dist/Server/Services/IncidentAlertService.js +1370 -0
- package/build/dist/Server/Services/IncidentAlertService.js.map +1 -0
- package/build/dist/Server/Services/IncidentReminderRuleService.js +73 -23
- package/build/dist/Server/Services/IncidentReminderRuleService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +164 -12
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +21 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +18 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +88 -5
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceService.js +111 -24
- package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +24 -9
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/PodmanResourceService.js +30 -3
- package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js +75 -23
- package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +1 -1
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +23 -2
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Services/UserProjectSsoConsentService.js +79 -0
- package/build/dist/Server/Services/UserProjectSsoConsentService.js.map +1 -0
- package/build/dist/Server/Services/UserService.js +13 -1
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +18 -0
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +26 -1
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +46 -0
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
- package/build/dist/Server/Utils/Database/AffectedResourceRelations.js +6 -0
- package/build/dist/Server/Utils/Database/AffectedResourceRelations.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +134 -13
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -6
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1489 -524
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +22 -6
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +196 -6
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +36 -9
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +175 -17
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js +29 -0
- package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +211 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +82 -10
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +25 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +8 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +20 -4
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +2 -0
- package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js +442 -0
- package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +5 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js +15 -1
- package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +121 -5
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js +12 -0
- package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js +438 -36
- package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +12 -0
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +17 -18
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js +16 -10
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js.map +1 -1
- package/build/dist/Types/AI/AIResourceContext.js +7 -0
- package/build/dist/Types/AI/AIResourceContext.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js +2 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js +71 -0
- package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js +1462 -0
- package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js +691 -0
- package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js +1407 -0
- package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js +60 -0
- package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js +804 -0
- package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseSystem.js +1860 -0
- package/build/dist/Types/DatabaseServer/DatabaseSystem.js.map +1 -0
- package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js +406 -0
- package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js.map +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Incident/IncidentAlertLink.js +34 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -0
- package/build/dist/Types/Monitor/CephAlertTemplates.js +18 -0
- package/build/dist/Types/Monitor/CephAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/DatabaseAlertTemplates.js +1518 -0
- package/build/dist/Types/Monitor/DatabaseAlertTemplates.js.map +1 -0
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +57 -24
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/KubernetesMetricCatalog.js +104 -18
- package/build/dist/Types/Monitor/KubernetesMetricCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +80 -3
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +10 -3
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +29 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js +21 -6
- package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js.map +1 -1
- package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +25 -0
- package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -1
- package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js +136 -0
- package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js.map +1 -0
- package/build/dist/Types/Permission.js +276 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +10 -0
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
- package/build/dist/Types/Rules/RuleRun.js +4 -0
- package/build/dist/Types/Rules/RuleRun.js.map +1 -1
- package/build/dist/Types/Rum/WebVitals.js +25 -0
- package/build/dist/Types/Rum/WebVitals.js.map +1 -1
- package/build/dist/Types/Telemetry/EntityType.js +23 -0
- package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
- package/build/dist/Types/Telemetry/ResourceFacetCatalog.js +14 -0
- package/build/dist/Types/Telemetry/ResourceFacetCatalog.js.map +1 -1
- package/build/dist/Types/Telemetry/ServiceType.js +8 -0
- package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
- package/build/dist/UI/Components/Banner/Banner.js +1 -1
- package/build/dist/UI/Components/Banner/Banner.js.map +1 -1
- package/build/dist/UI/Components/Card/Card.js +1 -1
- package/build/dist/UI/Components/Card/Card.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js +1 -1
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -1
- package/build/dist/UI/Components/Date/StartAndEndDate.js +5 -5
- package/build/dist/UI/Components/Date/StartAndEndDate.js.map +1 -1
- package/build/dist/UI/Components/Detail/FieldLabel.js +2 -0
- package/build/dist/UI/Components/Detail/FieldLabel.js.map +1 -1
- package/build/dist/UI/Components/Diagram/HorizontalStepChain.js +1 -1
- package/build/dist/UI/Components/Diagram/HorizontalStepChain.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js +1 -1
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
- package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js +1 -1
- package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js.map +1 -1
- package/build/dist/UI/Components/InfoCard/InfoCard.js +22 -1
- package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js +1 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +2 -2
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +70 -18
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +9 -2
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +2 -2
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Components/Page/Page.js +1 -1
- package/build/dist/UI/Components/Page/Page.js.map +1 -1
- package/build/dist/UI/Components/Pagination/Pagination.js +3 -3
- package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
- package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js +1 -1
- package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenu.js +4 -1
- package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +3 -1
- package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceList.js +6 -6
- package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
- package/build/dist/UI/Components/Table/TableHeader.js +18 -2
- package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/InfoTooltip.js +28 -0
- package/build/dist/UI/Components/Tooltip/InfoTooltip.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +1 -1
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentsModal.js +2 -2
- package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js +1 -1
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -1
- package/build/dist/UI/Utils/Navigation.js +66 -33
- package/build/dist/UI/Utils/Navigation.js.map +1 -1
- package/build/dist/UI/Utils/PermissionGate.js +4 -2
- package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
- package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js +7 -0
- package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js.map +1 -1
- package/build/dist/UI/Utils/TelemetryService.js +3 -2
- package/build/dist/UI/Utils/TelemetryService.js.map +1 -1
- package/build/dist/Utils/Incident/IncidentFromAlerts.js +306 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -0
- package/build/dist/Utils/LabelRuleImportExport.js +2 -0
- package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
- package/build/dist/Utils/MetricUnitUtil.js +77 -13
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +58 -13
- package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -1
- package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js +161 -0
- package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/PlatformResourceIdentity.js +323 -0
- package/build/dist/Utils/Monitor/PlatformResourceIdentity.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js +270 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js +23 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js +61 -0
- package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js.map +1 -1
- package/build/dist/Utils/Rum/SessionTraceState.js +154 -0
- package/build/dist/Utils/Rum/SessionTraceState.js.map +1 -0
- package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js +178 -0
- package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js.map +1 -0
- package/build/dist/Utils/Telemetry/EntityKey.js +133 -0
- package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
- package/build/dist/Utils/ValueFormatter.js +15 -0
- package/build/dist/Utils/ValueFormatter.js.map +1 -1
- package/package.json +1 -1
- package/tsconfig.tests.json +7 -5
|
@@ -0,0 +1,1719 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MonitorCriteriaEvaluator reaches the template renderer, which loads the
|
|
3
|
+
* native isolated-vm addon. Nothing here uses the sandbox and the
|
|
4
|
+
* prebuilt binary cannot always dlopen in the test environment.
|
|
5
|
+
*/
|
|
6
|
+
jest.mock("isolated-vm", () => {
|
|
7
|
+
return {};
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
import Monitor from "../../../../Models/DatabaseModels/Monitor";
|
|
11
|
+
import MonitorCriteriaEvaluator from "../../../../Server/Utils/Monitor/MonitorCriteriaEvaluator";
|
|
12
|
+
import AggregateModel from "../../../../Types/BaseDatabase/AggregatedModel";
|
|
13
|
+
import AggregatedResult from "../../../../Types/BaseDatabase/AggregatedResult";
|
|
14
|
+
import Dictionary from "../../../../Types/Dictionary";
|
|
15
|
+
import FilterCondition from "../../../../Types/Filter/FilterCondition";
|
|
16
|
+
import { JSONObject } from "../../../../Types/JSON";
|
|
17
|
+
import MetricFormulaConfigData from "../../../../Types/Metrics/MetricFormulaConfigData";
|
|
18
|
+
import MetricQueryConfigData from "../../../../Types/Metrics/MetricQueryConfigData";
|
|
19
|
+
import MetricsAggregationType from "../../../../Types/Metrics/MetricsAggregationType";
|
|
20
|
+
import MetricsViewConfig from "../../../../Types/Metrics/MetricsViewConfig";
|
|
21
|
+
import {
|
|
22
|
+
CephAlertTemplate,
|
|
23
|
+
getCephAlertTemplateById,
|
|
24
|
+
} from "../../../../Types/Monitor/CephAlertTemplates";
|
|
25
|
+
import { getCephMetricByMetricName } from "../../../../Types/Monitor/CephMetricCatalog";
|
|
26
|
+
import {
|
|
27
|
+
CheckOn,
|
|
28
|
+
CriteriaFilter,
|
|
29
|
+
FilterType,
|
|
30
|
+
} from "../../../../Types/Monitor/CriteriaFilter";
|
|
31
|
+
import {
|
|
32
|
+
DockerSwarmAlertTemplate,
|
|
33
|
+
getDockerSwarmAlertTemplateById,
|
|
34
|
+
} from "../../../../Types/Monitor/DockerSwarmAlertTemplates";
|
|
35
|
+
import { getDockerSwarmMetricByMetricName } from "../../../../Types/Monitor/DockerSwarmMetricCatalog";
|
|
36
|
+
import {
|
|
37
|
+
buildKubernetesMonitorConfig,
|
|
38
|
+
buildKubernetesMonitorStep,
|
|
39
|
+
buildOfflineCriteriaInstance,
|
|
40
|
+
buildOnlineCriteriaInstance,
|
|
41
|
+
getKubernetesAlertTemplateById,
|
|
42
|
+
KubernetesAlertTemplate,
|
|
43
|
+
} from "../../../../Types/Monitor/KubernetesAlertTemplates";
|
|
44
|
+
import { getKubernetesMetricByMetricName } from "../../../../Types/Monitor/KubernetesMetricCatalog";
|
|
45
|
+
import MetricMonitorResponse from "../../../../Types/Monitor/MetricMonitor/MetricMonitorResponse";
|
|
46
|
+
import MetricSeriesResult from "../../../../Types/Monitor/MetricMonitor/MetricSeriesResult";
|
|
47
|
+
import MonitorCriteriaInstance from "../../../../Types/Monitor/MonitorCriteriaInstance";
|
|
48
|
+
import MonitorEvaluationSummary from "../../../../Types/Monitor/MonitorEvaluationSummary";
|
|
49
|
+
import MonitorStep from "../../../../Types/Monitor/MonitorStep";
|
|
50
|
+
import { KubernetesResourceScope } from "../../../../Types/Monitor/MonitorStepKubernetesMonitor";
|
|
51
|
+
import MonitorType from "../../../../Types/Monitor/MonitorType";
|
|
52
|
+
import {
|
|
53
|
+
getProxmoxAlertTemplateById,
|
|
54
|
+
ProxmoxAlertTemplate,
|
|
55
|
+
} from "../../../../Types/Monitor/ProxmoxAlertTemplates";
|
|
56
|
+
import { getProxmoxMetricByMetricName } from "../../../../Types/Monitor/ProxmoxMetricCatalog";
|
|
57
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
58
|
+
import ProbeApiIngestResponse, {
|
|
59
|
+
MatchedCriteriaResult,
|
|
60
|
+
} from "../../../../Types/Probe/ProbeApiIngestResponse";
|
|
61
|
+
import RollingTime from "../../../../Types/RollingTime/RollingTime";
|
|
62
|
+
import MetricFormulaEvaluator from "../../../../Utils/Metrics/MetricFormulaEvaluator";
|
|
63
|
+
import MetricSeriesFingerprint from "../../../../Utils/Metrics/MetricSeriesFingerprint";
|
|
64
|
+
import PlatformMetricUnitUtil, {
|
|
65
|
+
MetricCatalogPlatform,
|
|
66
|
+
} from "../../../../Utils/Monitor/PlatformMetricUnitUtil";
|
|
67
|
+
import PlatformResourceIdentity, {
|
|
68
|
+
CephResourceIdentity,
|
|
69
|
+
DockerSwarmResourceIdentity,
|
|
70
|
+
KubernetesResourceIdentity,
|
|
71
|
+
ProxmoxResourceIdentity,
|
|
72
|
+
} from "../../../../Utils/Monitor/PlatformResourceIdentity";
|
|
73
|
+
import { describe, expect, test } from "@jest/globals";
|
|
74
|
+
|
|
75
|
+
/*
|
|
76
|
+
* Why this suite exists.
|
|
77
|
+
*
|
|
78
|
+
* An adversarial review of the "Affected Resources" list found five inputs
|
|
79
|
+
* the per-platform suites did not cover, each of which produced a wrong or
|
|
80
|
+
* missing list in the incident email:
|
|
81
|
+
*
|
|
82
|
+
* 1. An "Any" criteria (ceph-pg-damaged: PG_DAMAGED > 0 OR
|
|
83
|
+
* OSD_SCRUB_ERRORS > 0) was always described by its FIRST filter. With
|
|
84
|
+
* only the scrub-error check active there is no PG_DAMAGED scan, so the
|
|
85
|
+
* list the scrub errors had was dropped.
|
|
86
|
+
* 2. A grouped "Any" criteria over two aliases ranked a node that matched
|
|
87
|
+
* on "used bytes > 200 GB" against nodes valued in "% utilization" —
|
|
88
|
+
* one list, two units — and the analysis could say "memory usage is at
|
|
89
|
+
* **210 GB**" under a percentage threshold.
|
|
90
|
+
* 3. The dashboard's criteria form saves a threshold as a STRING ("60").
|
|
91
|
+
* The list's breach predicate only read numeric thresholds, so every
|
|
92
|
+
* edited criteria fell back to "> 0, highest first": pod-pending named
|
|
93
|
+
* the Unknown pod, task-down ranked the healthiest task first, and
|
|
94
|
+
* node-offline listed the nodes that were up.
|
|
95
|
+
* 4. The raw scan is in the metric's native unit, the threshold in the
|
|
96
|
+
* filter's thresholdUnit: "uptime < 1 min" tested 30 seconds against 1
|
|
97
|
+
* and listed nothing.
|
|
98
|
+
* 5. A monitor grouped by a key that names no platform object (a PVC
|
|
99
|
+
* name) rendered every series as the same anonymous "**Cluster**" row,
|
|
100
|
+
* and Proxmox dropped the list outright.
|
|
101
|
+
*
|
|
102
|
+
* Each case below drives the REAL template (or the form-built monitor the
|
|
103
|
+
* review reproduced) through MonitorCriteriaEvaluator.processMonitorStep
|
|
104
|
+
* with a worker-shaped response, so the evaluator sets each filter's
|
|
105
|
+
* context itself, and pins the rendered markdown.
|
|
106
|
+
*/
|
|
107
|
+
|
|
108
|
+
const MINUTES: Array<Date> = [0, 1, 2, 3, 4].map((minute: number): Date => {
|
|
109
|
+
return new Date(Date.UTC(2026, 8, 25, 10, minute, 0));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const K8S_CLUSTER: string = "prod-east";
|
|
113
|
+
const PVE_CLUSTER: string = "pve-lab";
|
|
114
|
+
const CEPH_CLUSTER: string = "ceph-prod";
|
|
115
|
+
const SWARM_CLUSTER: string = "swarm-prod";
|
|
116
|
+
|
|
117
|
+
const K8S_CLUSTER_KEY: string = "resource.k8s.cluster.name";
|
|
118
|
+
const NODE_KEY: string = "resource.k8s.node.name";
|
|
119
|
+
const NAMESPACE_KEY: string = "resource.k8s.namespace.name";
|
|
120
|
+
const POD_KEY: string = "resource.k8s.pod.name";
|
|
121
|
+
const PVC_KEY: string = "resource.k8s.persistentvolumeclaim.name";
|
|
122
|
+
|
|
123
|
+
const GB: number = 1_000_000_000;
|
|
124
|
+
|
|
125
|
+
// No rendered value may be a raw, unscaled 10+ digit number (bytes, epochs).
|
|
126
|
+
const RAW_NUMBER: RegExp = /\b\d{10,}\b/;
|
|
127
|
+
|
|
128
|
+
/*
|
|
129
|
+
* ---------------------------------------------------------------------------
|
|
130
|
+
* Platform fixtures
|
|
131
|
+
* ---------------------------------------------------------------------------
|
|
132
|
+
*/
|
|
133
|
+
|
|
134
|
+
type ScannedResource<I> = I & {
|
|
135
|
+
metricValue: number;
|
|
136
|
+
lowestMetricValue?: number | undefined;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// A platform raw-scan breakdown without its cluster name.
|
|
140
|
+
interface ScannedBreakdown<I> {
|
|
141
|
+
metricName: string;
|
|
142
|
+
metricFriendlyName: string;
|
|
143
|
+
metricAlias: string | undefined;
|
|
144
|
+
metricUnit: string | undefined;
|
|
145
|
+
attributes: Dictionary<string>;
|
|
146
|
+
affectedResources: Array<ScannedResource<I>>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface PlatformFixture<I> {
|
|
150
|
+
monitorType: MonitorType;
|
|
151
|
+
platform: MetricCatalogPlatform;
|
|
152
|
+
friendlyName: (metricName: string) => string | undefined;
|
|
153
|
+
toIdentity: (attributes: JSONObject) => I;
|
|
154
|
+
identityKey: (identity: I) => string;
|
|
155
|
+
attach: (
|
|
156
|
+
response: MetricMonitorResponse,
|
|
157
|
+
breakdowns: Array<ScannedBreakdown<I>>,
|
|
158
|
+
) => void;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const KUBERNETES: PlatformFixture<KubernetesResourceIdentity> = {
|
|
162
|
+
monitorType: MonitorType.Kubernetes,
|
|
163
|
+
platform: "kubernetes",
|
|
164
|
+
friendlyName: (metricName: string): string | undefined => {
|
|
165
|
+
return getKubernetesMetricByMetricName(metricName)?.friendlyName;
|
|
166
|
+
},
|
|
167
|
+
toIdentity: PlatformResourceIdentity.kubernetes,
|
|
168
|
+
identityKey: PlatformResourceIdentity.kubernetesKey,
|
|
169
|
+
attach: (
|
|
170
|
+
response: MetricMonitorResponse,
|
|
171
|
+
breakdowns: Array<ScannedBreakdown<KubernetesResourceIdentity>>,
|
|
172
|
+
): void => {
|
|
173
|
+
response.kubernetesResourceBreakdowns = breakdowns.map(
|
|
174
|
+
(breakdown: ScannedBreakdown<KubernetesResourceIdentity>) => {
|
|
175
|
+
return { ...breakdown, clusterName: K8S_CLUSTER };
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const PROXMOX: PlatformFixture<ProxmoxResourceIdentity> = {
|
|
182
|
+
monitorType: MonitorType.Proxmox,
|
|
183
|
+
platform: "proxmox",
|
|
184
|
+
friendlyName: (metricName: string): string | undefined => {
|
|
185
|
+
return getProxmoxMetricByMetricName(metricName)?.friendlyName;
|
|
186
|
+
},
|
|
187
|
+
toIdentity: PlatformResourceIdentity.proxmox,
|
|
188
|
+
identityKey: PlatformResourceIdentity.proxmoxKey,
|
|
189
|
+
attach: (
|
|
190
|
+
response: MetricMonitorResponse,
|
|
191
|
+
breakdowns: Array<ScannedBreakdown<ProxmoxResourceIdentity>>,
|
|
192
|
+
): void => {
|
|
193
|
+
response.proxmoxResourceBreakdowns = breakdowns.map(
|
|
194
|
+
(breakdown: ScannedBreakdown<ProxmoxResourceIdentity>) => {
|
|
195
|
+
return { ...breakdown, clusterName: PVE_CLUSTER };
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const CEPH: PlatformFixture<CephResourceIdentity> = {
|
|
202
|
+
monitorType: MonitorType.Ceph,
|
|
203
|
+
platform: "ceph",
|
|
204
|
+
friendlyName: (metricName: string): string | undefined => {
|
|
205
|
+
return getCephMetricByMetricName(metricName)?.friendlyName;
|
|
206
|
+
},
|
|
207
|
+
toIdentity: PlatformResourceIdentity.ceph,
|
|
208
|
+
identityKey: PlatformResourceIdentity.cephKey,
|
|
209
|
+
attach: (
|
|
210
|
+
response: MetricMonitorResponse,
|
|
211
|
+
breakdowns: Array<ScannedBreakdown<CephResourceIdentity>>,
|
|
212
|
+
): void => {
|
|
213
|
+
response.cephResourceBreakdowns = breakdowns.map(
|
|
214
|
+
(breakdown: ScannedBreakdown<CephResourceIdentity>) => {
|
|
215
|
+
return { ...breakdown, clusterName: CEPH_CLUSTER };
|
|
216
|
+
},
|
|
217
|
+
);
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const DOCKER_SWARM: PlatformFixture<DockerSwarmResourceIdentity> = {
|
|
222
|
+
monitorType: MonitorType.DockerSwarm,
|
|
223
|
+
platform: "dockerSwarm",
|
|
224
|
+
friendlyName: (metricName: string): string | undefined => {
|
|
225
|
+
return getDockerSwarmMetricByMetricName(metricName)?.friendlyName;
|
|
226
|
+
},
|
|
227
|
+
toIdentity: PlatformResourceIdentity.dockerSwarm,
|
|
228
|
+
identityKey: PlatformResourceIdentity.dockerSwarmKey,
|
|
229
|
+
attach: (
|
|
230
|
+
response: MetricMonitorResponse,
|
|
231
|
+
breakdowns: Array<ScannedBreakdown<DockerSwarmResourceIdentity>>,
|
|
232
|
+
): void => {
|
|
233
|
+
response.dockerSwarmResourceBreakdowns = breakdowns.map(
|
|
234
|
+
(breakdown: ScannedBreakdown<DockerSwarmResourceIdentity>) => {
|
|
235
|
+
return { ...breakdown, clusterName: SWARM_CLUSTER };
|
|
236
|
+
},
|
|
237
|
+
);
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/*
|
|
242
|
+
* ---------------------------------------------------------------------------
|
|
243
|
+
* Monitor step helpers
|
|
244
|
+
* ---------------------------------------------------------------------------
|
|
245
|
+
*/
|
|
246
|
+
|
|
247
|
+
interface CommonTemplateArgs {
|
|
248
|
+
onlineMonitorStatusId: ObjectID;
|
|
249
|
+
offlineMonitorStatusId: ObjectID;
|
|
250
|
+
defaultIncidentSeverityId: ObjectID;
|
|
251
|
+
defaultAlertSeverityId: ObjectID;
|
|
252
|
+
monitorName: string;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function commonArgs(monitorName: string): CommonTemplateArgs {
|
|
256
|
+
return {
|
|
257
|
+
onlineMonitorStatusId: ObjectID.generate(),
|
|
258
|
+
offlineMonitorStatusId: ObjectID.generate(),
|
|
259
|
+
defaultIncidentSeverityId: ObjectID.generate(),
|
|
260
|
+
defaultAlertSeverityId: ObjectID.generate(),
|
|
261
|
+
monitorName: monitorName,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function requireTemplate<T>(template: T | undefined, id: string): T {
|
|
266
|
+
if (!template) {
|
|
267
|
+
throw new Error(`${id} template missing`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return template;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function kubernetesStep(templateId: string): MonitorStep {
|
|
274
|
+
const template: KubernetesAlertTemplate = requireTemplate(
|
|
275
|
+
getKubernetesAlertTemplateById(templateId),
|
|
276
|
+
templateId,
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
return template.getMonitorStep({
|
|
280
|
+
clusterIdentifier: K8S_CLUSTER,
|
|
281
|
+
...commonArgs(`${K8S_CLUSTER} - ${template.name}`),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function proxmoxStep(templateId: string): MonitorStep {
|
|
286
|
+
const template: ProxmoxAlertTemplate = requireTemplate(
|
|
287
|
+
getProxmoxAlertTemplateById(templateId),
|
|
288
|
+
templateId,
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
return template.getMonitorStep({
|
|
292
|
+
clusterIdentifier: PVE_CLUSTER,
|
|
293
|
+
...commonArgs(`${PVE_CLUSTER} - ${template.name}`),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function cephStep(templateId: string): MonitorStep {
|
|
298
|
+
const template: CephAlertTemplate = requireTemplate(
|
|
299
|
+
getCephAlertTemplateById(templateId),
|
|
300
|
+
templateId,
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
return template.getMonitorStep({
|
|
304
|
+
clusterIdentifier: CEPH_CLUSTER,
|
|
305
|
+
...commonArgs(`${CEPH_CLUSTER} - ${template.name}`),
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function dockerSwarmStep(templateId: string): MonitorStep {
|
|
310
|
+
const template: DockerSwarmAlertTemplate = requireTemplate(
|
|
311
|
+
getDockerSwarmAlertTemplateById(templateId),
|
|
312
|
+
templateId,
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
return template.getMonitorStep({
|
|
316
|
+
clusterIdentifier: SWARM_CLUSTER,
|
|
317
|
+
...commonArgs(`${SWARM_CLUSTER} - ${template.name}`),
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function viewConfigOf(monitorStep: MonitorStep): MetricsViewConfig {
|
|
322
|
+
const config: MetricsViewConfig | undefined =
|
|
323
|
+
MonitorStep.getMetricsViewConfig(monitorStep);
|
|
324
|
+
|
|
325
|
+
if (!config) {
|
|
326
|
+
throw new Error("monitor step has no metric view config");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return config;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function aliasOf(config: {
|
|
333
|
+
metricAliasData?: { metricVariable?: string | undefined } | undefined;
|
|
334
|
+
}): string {
|
|
335
|
+
return config.metricAliasData?.metricVariable || "";
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function metricNameOf(queryConfig: MetricQueryConfigData): string {
|
|
339
|
+
return (queryConfig.metricQueryData.filterData.metricName as string) || "";
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function queryOf(
|
|
343
|
+
monitorStep: MonitorStep,
|
|
344
|
+
alias: string,
|
|
345
|
+
): MetricQueryConfigData {
|
|
346
|
+
const query: MetricQueryConfigData | undefined = viewConfigOf(
|
|
347
|
+
monitorStep,
|
|
348
|
+
).queryConfigs.find((queryConfig: MetricQueryConfigData): boolean => {
|
|
349
|
+
return aliasOf(queryConfig) === alias;
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
if (!query) {
|
|
353
|
+
throw new Error(`no query aliased ${alias}`);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return query;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// The template's FIRING criteria — every template builds it first.
|
|
360
|
+
function firingOf(monitorStep: MonitorStep): MonitorCriteriaInstance {
|
|
361
|
+
const instance: MonitorCriteriaInstance | undefined =
|
|
362
|
+
monitorStep.data?.monitorCriteria?.data?.monitorCriteriaInstanceArray[0];
|
|
363
|
+
|
|
364
|
+
if (!instance) {
|
|
365
|
+
throw new Error("firing criteria instance missing");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return instance;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function metricFiltersOf(
|
|
372
|
+
criteria: MonitorCriteriaInstance,
|
|
373
|
+
): Array<CriteriaFilter> {
|
|
374
|
+
return (criteria.data?.filters || []).filter(
|
|
375
|
+
(filter: CriteriaFilter): boolean => {
|
|
376
|
+
return filter.checkOn === CheckOn.MetricValue;
|
|
377
|
+
},
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Drop every query's group-by: the monitor as a user saves it ungrouped.
|
|
382
|
+
function ungroup(monitorStep: MonitorStep): MonitorStep {
|
|
383
|
+
for (const queryConfig of viewConfigOf(monitorStep).queryConfigs) {
|
|
384
|
+
delete queryConfig.metricQueryData.groupByAttributeKeys;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (MonitorStep.getGroupByAttributeKeys(monitorStep).length > 0) {
|
|
388
|
+
throw new Error("monitor step is still grouped");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return monitorStep;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/*
|
|
395
|
+
* Every metric threshold of every criteria as the dashboard's criteria
|
|
396
|
+
* form saves it: a string ("60", not 60).
|
|
397
|
+
*/
|
|
398
|
+
function stringifyThresholds(monitorStep: MonitorStep): MonitorStep {
|
|
399
|
+
for (const instance of monitorStep.data?.monitorCriteria?.data
|
|
400
|
+
?.monitorCriteriaInstanceArray || []) {
|
|
401
|
+
for (const filter of instance.data?.filters || []) {
|
|
402
|
+
if (typeof filter.value === "number") {
|
|
403
|
+
filter.value = String(filter.value);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return monitorStep;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/*
|
|
412
|
+
* ---------------------------------------------------------------------------
|
|
413
|
+
* Worker responses
|
|
414
|
+
* ---------------------------------------------------------------------------
|
|
415
|
+
*/
|
|
416
|
+
|
|
417
|
+
function unitsByMetricNameOf<I>(input: {
|
|
418
|
+
monitorStep: MonitorStep;
|
|
419
|
+
fixture: PlatformFixture<I>;
|
|
420
|
+
declaredUnits?: Dictionary<string> | undefined;
|
|
421
|
+
}): Dictionary<string> {
|
|
422
|
+
return PlatformMetricUnitUtil.buildUnitsByMetricName({
|
|
423
|
+
platform: input.fixture.platform,
|
|
424
|
+
metricNames: viewConfigOf(input.monitorStep).queryConfigs.map(metricNameOf),
|
|
425
|
+
declaredUnitsByMetricName: new Map<string, string>(
|
|
426
|
+
Object.entries(input.declaredUnits || {}).map(
|
|
427
|
+
([metricName, unit]: [string, string]): [string, string] => {
|
|
428
|
+
return [metricName.toLowerCase(), unit];
|
|
429
|
+
},
|
|
430
|
+
),
|
|
431
|
+
),
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function rowsOf(input: {
|
|
436
|
+
values: Array<number>;
|
|
437
|
+
attributes: JSONObject;
|
|
438
|
+
}): Array<AggregateModel> {
|
|
439
|
+
return input.values.map((value: number, minute: number): AggregateModel => {
|
|
440
|
+
return {
|
|
441
|
+
timestamp: MINUTES[minute]!,
|
|
442
|
+
value: value,
|
|
443
|
+
attributes: input.attributes,
|
|
444
|
+
} as unknown as AggregateModel;
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/*
|
|
449
|
+
* One resource as the worker's raw rows see it: the datapoint attributes
|
|
450
|
+
* every row of it carries, and one value per minute for each query alias.
|
|
451
|
+
*/
|
|
452
|
+
interface RawSeries {
|
|
453
|
+
attributes: JSONObject;
|
|
454
|
+
valuesByAlias: Dictionary<Array<number>>;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/*
|
|
458
|
+
* What the telemetry worker hands the evaluator for a GROUPED monitor:
|
|
459
|
+
* per-series rows aligned [queries..., formulas...] (query rows carry the
|
|
460
|
+
* raw datapoint's attributes, formula slots are evaluated by the worker's
|
|
461
|
+
* own MetricFormulaEvaluator), labels / fingerprints from the step's
|
|
462
|
+
* group-by keys, one alias-tagged raw scan per query, and the
|
|
463
|
+
* catalog-overlaid unit map.
|
|
464
|
+
*/
|
|
465
|
+
function groupedResponse<I>(input: {
|
|
466
|
+
monitorStep: MonitorStep;
|
|
467
|
+
fixture: PlatformFixture<I>;
|
|
468
|
+
series: Array<RawSeries>;
|
|
469
|
+
declaredUnits?: Dictionary<string> | undefined;
|
|
470
|
+
}): MetricMonitorResponse {
|
|
471
|
+
const viewConfig: MetricsViewConfig = viewConfigOf(input.monitorStep);
|
|
472
|
+
const queryConfigs: Array<MetricQueryConfigData> = viewConfig.queryConfigs;
|
|
473
|
+
const formulaConfigs: Array<MetricFormulaConfigData> =
|
|
474
|
+
viewConfig.formulaConfigs || [];
|
|
475
|
+
const groupByKeys: Array<string> = MonitorStep.getGroupByAttributeKeys(
|
|
476
|
+
input.monitorStep,
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
if (groupByKeys.length === 0) {
|
|
480
|
+
throw new Error("groupedResponse is for GROUPED monitors");
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const unitsByMetricName: Dictionary<string> = unitsByMetricNameOf(input);
|
|
484
|
+
|
|
485
|
+
const seriesBreakdown: Array<MetricSeriesResult> = input.series.map(
|
|
486
|
+
(series: RawSeries): MetricSeriesResult => {
|
|
487
|
+
const results: Array<AggregatedResult> = queryConfigs.map(
|
|
488
|
+
(queryConfig: MetricQueryConfigData): AggregatedResult => {
|
|
489
|
+
return {
|
|
490
|
+
data: rowsOf({
|
|
491
|
+
values: series.valuesByAlias[aliasOf(queryConfig)] || [],
|
|
492
|
+
attributes: series.attributes,
|
|
493
|
+
}),
|
|
494
|
+
};
|
|
495
|
+
},
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
formulaConfigs.forEach(
|
|
499
|
+
(formulaConfig: MetricFormulaConfigData, index: number): void => {
|
|
500
|
+
results.push(
|
|
501
|
+
MetricFormulaEvaluator.evaluateFormula({
|
|
502
|
+
formula: formulaConfig.metricFormulaData.metricFormula,
|
|
503
|
+
queryConfigs: queryConfigs,
|
|
504
|
+
formulaConfigs: formulaConfigs.slice(0, index),
|
|
505
|
+
results: [...results],
|
|
506
|
+
}),
|
|
507
|
+
);
|
|
508
|
+
},
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
const labels: JSONObject = MetricSeriesFingerprint.extractSeriesLabels({
|
|
512
|
+
sample: { attributes: series.attributes } as unknown as AggregateModel,
|
|
513
|
+
attributeKeys: groupByKeys,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
return {
|
|
517
|
+
fingerprint: MetricSeriesFingerprint.computeFingerprint(labels),
|
|
518
|
+
labels: labels,
|
|
519
|
+
aggregatedResults: results,
|
|
520
|
+
};
|
|
521
|
+
},
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
const breakdowns: Array<ScannedBreakdown<I>> = queryConfigs.map(
|
|
525
|
+
(queryConfig: MetricQueryConfigData): ScannedBreakdown<I> => {
|
|
526
|
+
const alias: string = aliasOf(queryConfig);
|
|
527
|
+
const metricName: string = metricNameOf(queryConfig);
|
|
528
|
+
const byKey: Map<string, ScannedResource<I>> = new Map<
|
|
529
|
+
string,
|
|
530
|
+
ScannedResource<I>
|
|
531
|
+
>();
|
|
532
|
+
|
|
533
|
+
for (const series of input.series) {
|
|
534
|
+
for (const value of series.valuesByAlias[alias] || []) {
|
|
535
|
+
const identity: I = input.fixture.toIdentity(series.attributes);
|
|
536
|
+
const key: string = input.fixture.identityKey(identity);
|
|
537
|
+
const existing: ScannedResource<I> | undefined = byKey.get(key);
|
|
538
|
+
|
|
539
|
+
if (!existing) {
|
|
540
|
+
byKey.set(key, {
|
|
541
|
+
...identity,
|
|
542
|
+
metricValue: value,
|
|
543
|
+
lowestMetricValue: value,
|
|
544
|
+
});
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
existing.metricValue = Math.max(existing.metricValue, value);
|
|
549
|
+
existing.lowestMetricValue = Math.min(
|
|
550
|
+
existing.lowestMetricValue ?? value,
|
|
551
|
+
value,
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return {
|
|
557
|
+
metricName: metricName,
|
|
558
|
+
metricFriendlyName:
|
|
559
|
+
input.fixture.friendlyName(metricName) || metricName,
|
|
560
|
+
metricAlias: alias,
|
|
561
|
+
metricUnit: unitsByMetricName[metricName.toLowerCase()],
|
|
562
|
+
attributes: {},
|
|
563
|
+
affectedResources: Array.from(byKey.values()),
|
|
564
|
+
};
|
|
565
|
+
},
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
const response: MetricMonitorResponse = {
|
|
569
|
+
projectId: ObjectID.generate(),
|
|
570
|
+
monitorId: ObjectID.generate(),
|
|
571
|
+
metricResult: [],
|
|
572
|
+
metricViewConfig: viewConfig,
|
|
573
|
+
seriesBreakdown: seriesBreakdown,
|
|
574
|
+
nativeUnitsByMetricName: unitsByMetricName,
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
input.fixture.attach(
|
|
578
|
+
response,
|
|
579
|
+
breakdowns.filter((breakdown: ScannedBreakdown<I>): boolean => {
|
|
580
|
+
return breakdown.affectedResources.length > 0;
|
|
581
|
+
}),
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
return response;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// One query's raw scan, as the worker tags it.
|
|
588
|
+
interface ScanSpec<I> {
|
|
589
|
+
alias: string;
|
|
590
|
+
metricUnit?: string | undefined;
|
|
591
|
+
resources: Array<ScannedResource<I>>;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/*
|
|
595
|
+
* What the worker hands the evaluator for an UNGROUPED monitor: one
|
|
596
|
+
* AggregatedResult per query (aligned with the step's queries — the
|
|
597
|
+
* criteria read their own alias's slot), and a raw scan for each query
|
|
598
|
+
* that returned datapoints, tagged with that query's alias.
|
|
599
|
+
*/
|
|
600
|
+
function ungroupedResponse<I>(input: {
|
|
601
|
+
monitorStep: MonitorStep;
|
|
602
|
+
fixture: PlatformFixture<I>;
|
|
603
|
+
valuesByAlias: Dictionary<Array<number>>;
|
|
604
|
+
scans: Array<ScanSpec<I>>;
|
|
605
|
+
declaredUnits?: Dictionary<string> | undefined;
|
|
606
|
+
}): MetricMonitorResponse {
|
|
607
|
+
const viewConfig: MetricsViewConfig = viewConfigOf(input.monitorStep);
|
|
608
|
+
|
|
609
|
+
if (MonitorStep.getGroupByAttributeKeys(input.monitorStep).length > 0) {
|
|
610
|
+
throw new Error("ungroupedResponse is for UNGROUPED monitors");
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const unitsByMetricName: Dictionary<string> = unitsByMetricNameOf(input);
|
|
614
|
+
|
|
615
|
+
const response: MetricMonitorResponse = {
|
|
616
|
+
projectId: ObjectID.generate(),
|
|
617
|
+
monitorId: ObjectID.generate(),
|
|
618
|
+
metricResult: viewConfig.queryConfigs.map(
|
|
619
|
+
(queryConfig: MetricQueryConfigData): AggregatedResult => {
|
|
620
|
+
return {
|
|
621
|
+
data: rowsOf({
|
|
622
|
+
values: input.valuesByAlias[aliasOf(queryConfig)] || [],
|
|
623
|
+
attributes: {},
|
|
624
|
+
}),
|
|
625
|
+
};
|
|
626
|
+
},
|
|
627
|
+
),
|
|
628
|
+
metricViewConfig: viewConfig,
|
|
629
|
+
nativeUnitsByMetricName: unitsByMetricName,
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
input.fixture.attach(
|
|
633
|
+
response,
|
|
634
|
+
input.scans.map((scan: ScanSpec<I>): ScannedBreakdown<I> => {
|
|
635
|
+
const metricName: string = metricNameOf(
|
|
636
|
+
queryOf(input.monitorStep, scan.alias),
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
return {
|
|
640
|
+
metricName: metricName,
|
|
641
|
+
metricFriendlyName:
|
|
642
|
+
input.fixture.friendlyName(metricName) || metricName,
|
|
643
|
+
metricAlias: scan.alias,
|
|
644
|
+
metricUnit: scan.metricUnit,
|
|
645
|
+
attributes: {},
|
|
646
|
+
affectedResources: scan.resources,
|
|
647
|
+
};
|
|
648
|
+
}),
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
return response;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/*
|
|
655
|
+
* ---------------------------------------------------------------------------
|
|
656
|
+
* Evaluation + rendered-markdown helpers
|
|
657
|
+
* ---------------------------------------------------------------------------
|
|
658
|
+
*/
|
|
659
|
+
|
|
660
|
+
async function evaluate<I>(input: {
|
|
661
|
+
monitorStep: MonitorStep;
|
|
662
|
+
fixture: PlatformFixture<I>;
|
|
663
|
+
response: MetricMonitorResponse;
|
|
664
|
+
}): Promise<ProbeApiIngestResponse> {
|
|
665
|
+
const monitor: Monitor = new Monitor();
|
|
666
|
+
monitor._id = ObjectID.generate().toString();
|
|
667
|
+
monitor.monitorType = input.fixture.monitorType;
|
|
668
|
+
monitor.name = "edge-case monitor";
|
|
669
|
+
|
|
670
|
+
return MonitorCriteriaEvaluator.processMonitorStep({
|
|
671
|
+
dataToProcess: input.response,
|
|
672
|
+
monitorStep: input.monitorStep,
|
|
673
|
+
monitor: monitor,
|
|
674
|
+
probeApiIngestResponse: {
|
|
675
|
+
monitorId: monitor.id!,
|
|
676
|
+
rootCause: null,
|
|
677
|
+
},
|
|
678
|
+
evaluationSummary: {
|
|
679
|
+
criteriaResults: [],
|
|
680
|
+
events: [],
|
|
681
|
+
} as unknown as MonitorEvaluationSummary,
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/*
|
|
686
|
+
* The rendered root cause of one criteria, or "" when it did not match: a
|
|
687
|
+
* grouped monitor reports every matched criteria, an ungrouped one only
|
|
688
|
+
* its first.
|
|
689
|
+
*/
|
|
690
|
+
function rootCauseFor(
|
|
691
|
+
response: ProbeApiIngestResponse,
|
|
692
|
+
criteria: MonitorCriteriaInstance,
|
|
693
|
+
): string {
|
|
694
|
+
const criteriaId: string = criteria.data?.id?.toString() || "";
|
|
695
|
+
|
|
696
|
+
const matched: MatchedCriteriaResult | undefined = (
|
|
697
|
+
response.matchedCriteria || []
|
|
698
|
+
).find((result: MatchedCriteriaResult): boolean => {
|
|
699
|
+
return result.criteriaId === criteriaId;
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
if (matched) {
|
|
703
|
+
return matched.rootCause;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
return response.criteriaMetId?.toString() === criteriaId
|
|
707
|
+
? response.rootCause || ""
|
|
708
|
+
: "";
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
async function firingRootCause<I>(input: {
|
|
712
|
+
monitorStep: MonitorStep;
|
|
713
|
+
fixture: PlatformFixture<I>;
|
|
714
|
+
response: MetricMonitorResponse;
|
|
715
|
+
}): Promise<string> {
|
|
716
|
+
return rootCauseFor(await evaluate(input), firingOf(input.monitorStep));
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// From a list heading up to the analysis that follows it, trimmed.
|
|
720
|
+
function listSection(rootCause: string, heading: string): string {
|
|
721
|
+
const start: number = rootCause.indexOf(`**${heading}**`);
|
|
722
|
+
|
|
723
|
+
if (start < 0) {
|
|
724
|
+
return "";
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const end: number = rootCause.indexOf("**Root Cause Analysis**", start);
|
|
728
|
+
|
|
729
|
+
return (
|
|
730
|
+
end < 0 ? rootCause.substring(start) : rootCause.substring(start, end)
|
|
731
|
+
).trim();
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const ENTRY_HEAD: RegExp = /^\d+\. /;
|
|
735
|
+
|
|
736
|
+
// The first line of every numbered entry in a list.
|
|
737
|
+
function entryHeads(section: string): Array<string> {
|
|
738
|
+
return section.split("\n").filter((line: string): boolean => {
|
|
739
|
+
return ENTRY_HEAD.test(line);
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function linesOf(rootCause: string): Array<string> {
|
|
744
|
+
return rootCause.split("\n");
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/*
|
|
748
|
+
* The "- Metric:" line for one query, with the catalog's name looked up
|
|
749
|
+
* rather than hard-coded (catalogs change).
|
|
750
|
+
*/
|
|
751
|
+
function metricLine<I>(
|
|
752
|
+
fixture: PlatformFixture<I>,
|
|
753
|
+
metricName: string,
|
|
754
|
+
): string {
|
|
755
|
+
const friendlyName: string | undefined = fixture.friendlyName(metricName);
|
|
756
|
+
|
|
757
|
+
return friendlyName && friendlyName !== metricName
|
|
758
|
+
? `- Metric: ${friendlyName} (\`${metricName}\`)`
|
|
759
|
+
: `- Metric: \`${metricName}\``;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function metricLinesOf(rootCause: string): Array<string> {
|
|
763
|
+
return linesOf(rootCause).filter((line: string): boolean => {
|
|
764
|
+
return line.startsWith("- Metric:");
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/*
|
|
769
|
+
* ---------------------------------------------------------------------------
|
|
770
|
+
* 1. An "Any" criteria is described by the filter that fired
|
|
771
|
+
* ---------------------------------------------------------------------------
|
|
772
|
+
*/
|
|
773
|
+
|
|
774
|
+
interface CephAnyCase {
|
|
775
|
+
templateId: string;
|
|
776
|
+
// The one health check that is active, and the query that watches it.
|
|
777
|
+
activeAlias: string;
|
|
778
|
+
activeCheck: string;
|
|
779
|
+
// The check that is NOT active.
|
|
780
|
+
inactiveCheck: string;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const CEPH_ANY_CASES: Array<CephAnyCase> = [
|
|
784
|
+
{
|
|
785
|
+
templateId: "ceph-pg-damaged",
|
|
786
|
+
activeAlias: "scrub_errors",
|
|
787
|
+
activeCheck: "OSD_SCRUB_ERRORS",
|
|
788
|
+
inactiveCheck: "PG_DAMAGED",
|
|
789
|
+
},
|
|
790
|
+
{
|
|
791
|
+
templateId: "ceph-pg-damaged",
|
|
792
|
+
activeAlias: "pg_damaged",
|
|
793
|
+
activeCheck: "PG_DAMAGED",
|
|
794
|
+
inactiveCheck: "OSD_SCRUB_ERRORS",
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
templateId: "ceph-osd-slow-heartbeats",
|
|
798
|
+
activeAlias: "slow_ping_back",
|
|
799
|
+
activeCheck: "OSD_SLOW_PING_TIME_BACK",
|
|
800
|
+
inactiveCheck: "OSD_SLOW_PING_TIME_FRONT",
|
|
801
|
+
},
|
|
802
|
+
];
|
|
803
|
+
|
|
804
|
+
describe("An Any criteria follows the filter that fired", () => {
|
|
805
|
+
/*
|
|
806
|
+
* ceph_health_detail reports a check only while it is active (an absent
|
|
807
|
+
* series is healthy), so the inactive query returns no datapoints and
|
|
808
|
+
* the worker has no scan for it: metricResult is [[], [rows]] and the
|
|
809
|
+
* one breakdown is the active query's.
|
|
810
|
+
*/
|
|
811
|
+
function cephAnyResponse(input: {
|
|
812
|
+
monitorStep: MonitorStep;
|
|
813
|
+
testCase: CephAnyCase;
|
|
814
|
+
}): MetricMonitorResponse {
|
|
815
|
+
return ungroupedResponse({
|
|
816
|
+
monitorStep: input.monitorStep,
|
|
817
|
+
fixture: CEPH,
|
|
818
|
+
valuesByAlias: { [input.testCase.activeAlias]: [1, 1, 1, 1, 1] },
|
|
819
|
+
scans: [
|
|
820
|
+
{
|
|
821
|
+
alias: input.testCase.activeAlias,
|
|
822
|
+
resources: [{ poolName: input.testCase.activeCheck, metricValue: 1 }],
|
|
823
|
+
},
|
|
824
|
+
],
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
for (const testCase of CEPH_ANY_CASES) {
|
|
829
|
+
test(`${testCase.templateId}: only ${testCase.activeCheck} active — it is listed and its query named`, async () => {
|
|
830
|
+
const monitorStep: MonitorStep = cephStep(testCase.templateId);
|
|
831
|
+
const firing: MonitorCriteriaInstance = firingOf(monitorStep);
|
|
832
|
+
|
|
833
|
+
// Fixture guard: an ungrouped "Any" over two ceph_health_detail queries.
|
|
834
|
+
expect(firing.data?.filterCondition).toBe(FilterCondition.Any);
|
|
835
|
+
expect(
|
|
836
|
+
metricFiltersOf(firing).map((filter: CriteriaFilter): string => {
|
|
837
|
+
return filter.metricMonitorOptions?.metricAlias || "";
|
|
838
|
+
}),
|
|
839
|
+
).toContain(testCase.activeAlias);
|
|
840
|
+
expect(metricFiltersOf(firing)).toHaveLength(2);
|
|
841
|
+
expect(MonitorStep.getGroupByAttributeKeys(monitorStep)).toEqual([]);
|
|
842
|
+
|
|
843
|
+
const rootCause: string = await firingRootCause({
|
|
844
|
+
monitorStep: monitorStep,
|
|
845
|
+
fixture: CEPH,
|
|
846
|
+
response: cephAnyResponse({
|
|
847
|
+
monitorStep: monitorStep,
|
|
848
|
+
testCase: testCase,
|
|
849
|
+
}),
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
// The evaluator marked exactly the active query's filter as breaching.
|
|
853
|
+
const breached: Array<string> = metricFiltersOf(firing)
|
|
854
|
+
.filter((filter: CriteriaFilter): boolean => {
|
|
855
|
+
return (
|
|
856
|
+
(filter.metricCriteriaContext?.breachingSamples || []).length > 0
|
|
857
|
+
);
|
|
858
|
+
})
|
|
859
|
+
.map((filter: CriteriaFilter): string => {
|
|
860
|
+
return filter.metricMonitorOptions?.metricAlias || "";
|
|
861
|
+
});
|
|
862
|
+
expect(breached).toEqual([testCase.activeAlias]);
|
|
863
|
+
|
|
864
|
+
expect(listSection(rootCause, "Affected Resources")).toBe(
|
|
865
|
+
[
|
|
866
|
+
"**Affected Resources** (1 total)",
|
|
867
|
+
"",
|
|
868
|
+
`1. **Health Check** \`${testCase.activeCheck}\` — **1**`,
|
|
869
|
+
].join("\n"),
|
|
870
|
+
);
|
|
871
|
+
|
|
872
|
+
/*
|
|
873
|
+
* Both queries read ceph_health_detail, so the fired query's metric
|
|
874
|
+
* line is the only one — never a second, and never the metric-count
|
|
875
|
+
* fallback that stood in for the dropped list.
|
|
876
|
+
*/
|
|
877
|
+
expect(metricLinesOf(rootCause)).toEqual([
|
|
878
|
+
metricLine(
|
|
879
|
+
CEPH,
|
|
880
|
+
metricNameOf(queryOf(monitorStep, testCase.activeAlias)),
|
|
881
|
+
),
|
|
882
|
+
]);
|
|
883
|
+
expect(rootCause).toContain(`- Cluster: ${CEPH_CLUSTER}`);
|
|
884
|
+
expect(rootCause).not.toContain("**Metric Summary**");
|
|
885
|
+
expect(rootCause).not.toContain(testCase.inactiveCheck);
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
/*
|
|
891
|
+
* ---------------------------------------------------------------------------
|
|
892
|
+
* 2. A grouped Any criteria over two aliases
|
|
893
|
+
* ---------------------------------------------------------------------------
|
|
894
|
+
*/
|
|
895
|
+
|
|
896
|
+
/*
|
|
897
|
+
* k8s-high-memory with its firing criteria widened, as a user would in the
|
|
898
|
+
* form, to "utilization > 85% OR used memory > 200 GB": node_memory_
|
|
899
|
+
* utilization is the formula (%), used_mem its numerator (bytes).
|
|
900
|
+
*/
|
|
901
|
+
function highMemoryAnyStep(): MonitorStep {
|
|
902
|
+
const monitorStep: MonitorStep = kubernetesStep("k8s-high-memory");
|
|
903
|
+
const firing: MonitorCriteriaInstance = firingOf(monitorStep);
|
|
904
|
+
const utilizationFilter: CriteriaFilter = metricFiltersOf(firing)[0]!;
|
|
905
|
+
|
|
906
|
+
expect(utilizationFilter.metricMonitorOptions?.metricAlias).toBe(
|
|
907
|
+
"node_memory_utilization",
|
|
908
|
+
);
|
|
909
|
+
|
|
910
|
+
firing.data!.filterCondition = FilterCondition.Any;
|
|
911
|
+
firing.data!.filters.push({
|
|
912
|
+
...utilizationFilter,
|
|
913
|
+
metricMonitorOptions: {
|
|
914
|
+
...utilizationFilter.metricMonitorOptions!,
|
|
915
|
+
metricAlias: "used_mem",
|
|
916
|
+
},
|
|
917
|
+
value: 200 * GB,
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
return monitorStep;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function k8sNode(input: {
|
|
924
|
+
name: string;
|
|
925
|
+
used: number | Array<number>;
|
|
926
|
+
allocatable: number;
|
|
927
|
+
}): RawSeries {
|
|
928
|
+
return {
|
|
929
|
+
attributes: { [K8S_CLUSTER_KEY]: K8S_CLUSTER, [NODE_KEY]: input.name },
|
|
930
|
+
valuesByAlias: {
|
|
931
|
+
used_mem: Array.isArray(input.used)
|
|
932
|
+
? input.used
|
|
933
|
+
: MINUTES.map((): number => {
|
|
934
|
+
return input.used as number;
|
|
935
|
+
}),
|
|
936
|
+
alloc_mem: MINUTES.map((): number => {
|
|
937
|
+
return input.allocatable;
|
|
938
|
+
}),
|
|
939
|
+
},
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
const K8S_DECLARED_UNITS: Dictionary<string> = {
|
|
944
|
+
"k8s.node.memory.usage": "By",
|
|
945
|
+
"k8s.node.allocatable_memory": "By",
|
|
946
|
+
"k8s.volume.available": "By",
|
|
947
|
+
"k8s.pod.phase": "1",
|
|
948
|
+
};
|
|
949
|
+
|
|
950
|
+
// 86.26% .. 88.82% of 257.76 GB, and over 200 GB: matches BOTH filters.
|
|
951
|
+
const HOT_NODE: RawSeries = k8sNode({
|
|
952
|
+
name: "node-hot",
|
|
953
|
+
used: [222344608010, 224 * GB, 226 * GB, 228 * GB, 228943289000],
|
|
954
|
+
allocatable: 257760964608,
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
// 210 GB of 400 GB = 52.5%: matches only "used memory > 200 GB".
|
|
958
|
+
const BIG_NODE: RawSeries = k8sNode({
|
|
959
|
+
name: "node-big",
|
|
960
|
+
used: 210 * GB,
|
|
961
|
+
allocatable: 400 * GB,
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
// 100 GB of 400 GB: matches neither.
|
|
965
|
+
const CALM_NODE: RawSeries = k8sNode({
|
|
966
|
+
name: "node-calm",
|
|
967
|
+
used: 100 * GB,
|
|
968
|
+
allocatable: 400 * GB,
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
describe("A grouped Any criteria over two aliases keeps one unit per rank", () => {
|
|
972
|
+
async function run(series: Array<RawSeries>): Promise<string> {
|
|
973
|
+
const monitorStep: MonitorStep = highMemoryAnyStep();
|
|
974
|
+
|
|
975
|
+
return firingRootCause({
|
|
976
|
+
monitorStep: monitorStep,
|
|
977
|
+
fixture: KUBERNETES,
|
|
978
|
+
response: groupedResponse({
|
|
979
|
+
monitorStep: monitorStep,
|
|
980
|
+
fixture: KUBERNETES,
|
|
981
|
+
series: series,
|
|
982
|
+
declaredUnits: K8S_DECLARED_UNITS,
|
|
983
|
+
}),
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
test("the node over 85% leads in %; the node that matched only on bytes follows, its metric named", async () => {
|
|
988
|
+
// The off-filter node arrives FIRST: its position is the sort's doing.
|
|
989
|
+
const rootCause: string = await run([BIG_NODE, HOT_NODE, CALM_NODE]);
|
|
990
|
+
|
|
991
|
+
expect(listSection(rootCause, "Affected Resources")).toBe(
|
|
992
|
+
[
|
|
993
|
+
"**Affected Resources** (2 total)",
|
|
994
|
+
"",
|
|
995
|
+
"1. **Node** `node-hot` — **88.82%**",
|
|
996
|
+
"2. **Node** `node-big` — **210 GB** (`used_mem`)",
|
|
997
|
+
].join("\n"),
|
|
998
|
+
);
|
|
999
|
+
|
|
1000
|
+
expect(rootCause).not.toContain("node-calm");
|
|
1001
|
+
expect(rootCause).not.toMatch(RAW_NUMBER);
|
|
1002
|
+
// Never the bytes node ranked as if 210 were a percentage.
|
|
1003
|
+
expect(rootCause).not.toContain("**210.00%**");
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
test("the list and the analysis are about the filter that fired — the % one", async () => {
|
|
1007
|
+
const rootCause: string = await run([BIG_NODE, HOT_NODE, CALM_NODE]);
|
|
1008
|
+
|
|
1009
|
+
expect(rootCause).toContain("- Metric: Node Memory Utilization (%)");
|
|
1010
|
+
expect(rootCause).toContain("**Root Cause Analysis**");
|
|
1011
|
+
expect(rootCause).toContain(
|
|
1012
|
+
"Node `node-hot` memory usage is at **88.82%**.",
|
|
1013
|
+
);
|
|
1014
|
+
expect(rootCause).not.toContain("memory usage is at **210 GB**");
|
|
1015
|
+
expect(rootCause).not.toMatch(/memory usage is at \*\*[\d.]+ GB/);
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
test("off-filter rows never interleave with on-filter rows, and are never ranked", async () => {
|
|
1019
|
+
/*
|
|
1020
|
+
* Input interleaves the two kinds. The % rows are ranked worst first;
|
|
1021
|
+
* the bytes rows follow in the order they came — 230 GB after 210 GB,
|
|
1022
|
+
* because their numbers are not comparable with the % rows and are
|
|
1023
|
+
* not re-ranked among themselves either.
|
|
1024
|
+
*/
|
|
1025
|
+
const rootCause: string = await run([
|
|
1026
|
+
BIG_NODE,
|
|
1027
|
+
HOT_NODE,
|
|
1028
|
+
k8sNode({ name: "node-big-2", used: 230 * GB, allocatable: 512 * GB }),
|
|
1029
|
+
CALM_NODE,
|
|
1030
|
+
k8sNode({ name: "node-hot-2", used: 58.88 * GB, allocatable: 64 * GB }),
|
|
1031
|
+
]);
|
|
1032
|
+
|
|
1033
|
+
expect(entryHeads(listSection(rootCause, "Affected Resources"))).toEqual([
|
|
1034
|
+
"1. **Node** `node-hot-2` — **92.00%**",
|
|
1035
|
+
"2. **Node** `node-hot` — **88.82%**",
|
|
1036
|
+
"3. **Node** `node-big` — **210 GB** (`used_mem`)",
|
|
1037
|
+
"4. **Node** `node-big-2` — **230 GB** (`used_mem`)",
|
|
1038
|
+
]);
|
|
1039
|
+
expect(listSection(rootCause, "Affected Resources")).toContain(
|
|
1040
|
+
"**Affected Resources** (4 total)",
|
|
1041
|
+
);
|
|
1042
|
+
expect(rootCause).toContain(
|
|
1043
|
+
"Node `node-hot-2` memory usage is at **92.00%**.",
|
|
1044
|
+
);
|
|
1045
|
+
});
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
/*
|
|
1049
|
+
* The ordering and value-cell rules on their own, through the typed shim:
|
|
1050
|
+
* a fall criteria ranks lowest first, rows valued by another filter follow
|
|
1051
|
+
* in arrival order, a no-data row comes last, and only an off-filter
|
|
1052
|
+
* value carries a note.
|
|
1053
|
+
*/
|
|
1054
|
+
interface ShimRow {
|
|
1055
|
+
identity: { name: string };
|
|
1056
|
+
value: number | null;
|
|
1057
|
+
formattedValue: string;
|
|
1058
|
+
valueNote?: string | undefined;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
type EvaluatorPrivate = {
|
|
1062
|
+
sortAffectedRows: (input: {
|
|
1063
|
+
rows: Array<ShimRow>;
|
|
1064
|
+
worstIsLowest: boolean;
|
|
1065
|
+
}) => Array<ShimRow>;
|
|
1066
|
+
renderAffectedRowValue: (row: ShimRow) => string;
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
const Evaluator: EvaluatorPrivate =
|
|
1070
|
+
MonitorCriteriaEvaluator as unknown as EvaluatorPrivate;
|
|
1071
|
+
|
|
1072
|
+
describe("Affected-row ordering and value cells", () => {
|
|
1073
|
+
function shimRow(
|
|
1074
|
+
name: string,
|
|
1075
|
+
value: number | null,
|
|
1076
|
+
valueNote?: string | undefined,
|
|
1077
|
+
): ShimRow {
|
|
1078
|
+
return {
|
|
1079
|
+
identity: { name: name },
|
|
1080
|
+
value: value,
|
|
1081
|
+
formattedValue: value === null ? "no data" : `${value}`,
|
|
1082
|
+
...(valueNote ? { valueNote: valueNote } : {}),
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function rendered(rows: Array<ShimRow>): Array<string> {
|
|
1087
|
+
return rows.map((row: ShimRow): string => {
|
|
1088
|
+
return `${row.identity.name} ${Evaluator.renderAffectedRowValue(row)}`;
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
const ROWS: Array<ShimRow> = [
|
|
1093
|
+
shimRow("off-a", 5, "`other_alias`"),
|
|
1094
|
+
shimRow("silent", null),
|
|
1095
|
+
shimRow("on-40", 40),
|
|
1096
|
+
shimRow("off-b", 1, "Other Metric"),
|
|
1097
|
+
shimRow("on-10", 10),
|
|
1098
|
+
shimRow("on-25", 25),
|
|
1099
|
+
];
|
|
1100
|
+
|
|
1101
|
+
test("a rise criteria: on-filter highest first, then off-filter as they came, then no data", () => {
|
|
1102
|
+
expect(
|
|
1103
|
+
rendered(
|
|
1104
|
+
Evaluator.sortAffectedRows({ rows: ROWS, worstIsLowest: false }),
|
|
1105
|
+
),
|
|
1106
|
+
).toEqual([
|
|
1107
|
+
"on-40 **40**",
|
|
1108
|
+
"on-25 **25**",
|
|
1109
|
+
"on-10 **10**",
|
|
1110
|
+
"off-a **5** (`other_alias`)",
|
|
1111
|
+
"off-b **1** (Other Metric)",
|
|
1112
|
+
"silent **no data**",
|
|
1113
|
+
]);
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
test("a fall criteria: on-filter lowest first, then off-filter as they came, then no data", () => {
|
|
1117
|
+
expect(
|
|
1118
|
+
rendered(Evaluator.sortAffectedRows({ rows: ROWS, worstIsLowest: true })),
|
|
1119
|
+
).toEqual([
|
|
1120
|
+
"on-10 **10**",
|
|
1121
|
+
"on-25 **25**",
|
|
1122
|
+
"on-40 **40**",
|
|
1123
|
+
"off-a **5** (`other_alias`)",
|
|
1124
|
+
"off-b **1** (Other Metric)",
|
|
1125
|
+
"silent **no data**",
|
|
1126
|
+
]);
|
|
1127
|
+
});
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
/*
|
|
1131
|
+
* ---------------------------------------------------------------------------
|
|
1132
|
+
* 3. Thresholds saved as strings
|
|
1133
|
+
* ---------------------------------------------------------------------------
|
|
1134
|
+
*/
|
|
1135
|
+
|
|
1136
|
+
describe("A threshold saved as a string reads like the number", () => {
|
|
1137
|
+
test('k8s-pod-pending with "1": only the Pending pod is listed, and the analysis sends the reader to it', async () => {
|
|
1138
|
+
const monitorStep: MonitorStep = stringifyThresholds(
|
|
1139
|
+
kubernetesStep("k8s-pod-pending"),
|
|
1140
|
+
);
|
|
1141
|
+
const firing: MonitorCriteriaInstance = firingOf(monitorStep);
|
|
1142
|
+
|
|
1143
|
+
// Fixture guard: an ungrouped, incident-opening `= "1"`.
|
|
1144
|
+
expect(metricFiltersOf(firing)[0]?.filterType).toBe(FilterType.EqualTo);
|
|
1145
|
+
expect(metricFiltersOf(firing)[0]?.value).toBe("1");
|
|
1146
|
+
expect(MonitorStep.getGroupByAttributeKeys(monitorStep)).toEqual([]);
|
|
1147
|
+
|
|
1148
|
+
const alias: string = aliasOf(viewConfigOf(monitorStep).queryConfigs[0]!);
|
|
1149
|
+
|
|
1150
|
+
const rootCause: string = await firingRootCause({
|
|
1151
|
+
monitorStep: monitorStep,
|
|
1152
|
+
fixture: KUBERNETES,
|
|
1153
|
+
response: ungroupedResponse({
|
|
1154
|
+
monitorStep: monitorStep,
|
|
1155
|
+
fixture: KUBERNETES,
|
|
1156
|
+
declaredUnits: K8S_DECLARED_UNITS,
|
|
1157
|
+
// The cluster-wide Min phase: a pod has been Pending all window.
|
|
1158
|
+
valuesByAlias: { [alias]: [1, 1, 1, 1, 1] },
|
|
1159
|
+
scans: [
|
|
1160
|
+
{
|
|
1161
|
+
alias: alias,
|
|
1162
|
+
metricUnit: "1",
|
|
1163
|
+
resources: [
|
|
1164
|
+
{
|
|
1165
|
+
podName: "checkout-7d9f-running",
|
|
1166
|
+
namespace: "shop",
|
|
1167
|
+
metricValue: 2,
|
|
1168
|
+
lowestMetricValue: 2,
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
podName: "checkout-7d9f-pending",
|
|
1172
|
+
namespace: "shop",
|
|
1173
|
+
metricValue: 1,
|
|
1174
|
+
lowestMetricValue: 1,
|
|
1175
|
+
},
|
|
1176
|
+
{
|
|
1177
|
+
podName: "report-job-failed",
|
|
1178
|
+
namespace: "batch",
|
|
1179
|
+
metricValue: 4,
|
|
1180
|
+
lowestMetricValue: 4,
|
|
1181
|
+
},
|
|
1182
|
+
{
|
|
1183
|
+
podName: "lost-pod",
|
|
1184
|
+
namespace: "shop",
|
|
1185
|
+
metricValue: 5,
|
|
1186
|
+
lowestMetricValue: 5,
|
|
1187
|
+
},
|
|
1188
|
+
],
|
|
1189
|
+
},
|
|
1190
|
+
],
|
|
1191
|
+
}),
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
expect(listSection(rootCause, "Affected Resources")).toBe(
|
|
1195
|
+
[
|
|
1196
|
+
"**Affected Resources** (1 total)",
|
|
1197
|
+
"",
|
|
1198
|
+
"1. **Pod** `checkout-7d9f-pending` — **1**",
|
|
1199
|
+
" - Namespace: `shop`",
|
|
1200
|
+
].join("\n"),
|
|
1201
|
+
);
|
|
1202
|
+
expect(rootCause).toContain("kubectl describe pod checkout-7d9f-pending");
|
|
1203
|
+
expect(rootCause).not.toContain("checkout-7d9f-running");
|
|
1204
|
+
expect(rootCause).not.toContain("report-job-failed");
|
|
1205
|
+
expect(rootCause).not.toContain("lost-pod");
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
describe('grouped docker-swarm-task-down with "60"', () => {
|
|
1209
|
+
function swarmTask(input: {
|
|
1210
|
+
container: string;
|
|
1211
|
+
service: string;
|
|
1212
|
+
node: string;
|
|
1213
|
+
uptimes: Array<number>;
|
|
1214
|
+
}): RawSeries {
|
|
1215
|
+
return {
|
|
1216
|
+
attributes: {
|
|
1217
|
+
"resource.container.name": input.container,
|
|
1218
|
+
"resource.docker.swarm.cluster.name": SWARM_CLUSTER,
|
|
1219
|
+
"docker.swarm.service.name": input.service,
|
|
1220
|
+
"docker.swarm.node.name": input.node,
|
|
1221
|
+
},
|
|
1222
|
+
valuesByAlias: { container_uptime: input.uptimes },
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
const TASKS: Array<RawSeries> = [
|
|
1227
|
+
swarmTask({
|
|
1228
|
+
container: "web.2.bbb",
|
|
1229
|
+
service: "web",
|
|
1230
|
+
node: "n2",
|
|
1231
|
+
uptimes: [40, 55, 59],
|
|
1232
|
+
}),
|
|
1233
|
+
swarmTask({
|
|
1234
|
+
container: "db.1.ccc",
|
|
1235
|
+
service: "db",
|
|
1236
|
+
node: "n3",
|
|
1237
|
+
uptimes: [3000, 3060, 3120],
|
|
1238
|
+
}),
|
|
1239
|
+
swarmTask({
|
|
1240
|
+
container: "web.1.aaa",
|
|
1241
|
+
service: "web",
|
|
1242
|
+
node: "n1",
|
|
1243
|
+
uptimes: [5, 35, 58],
|
|
1244
|
+
}),
|
|
1245
|
+
];
|
|
1246
|
+
|
|
1247
|
+
function run(monitorStep: MonitorStep): Promise<string> {
|
|
1248
|
+
return firingRootCause({
|
|
1249
|
+
monitorStep: monitorStep,
|
|
1250
|
+
fixture: DOCKER_SWARM,
|
|
1251
|
+
response: groupedResponse({
|
|
1252
|
+
monitorStep: monitorStep,
|
|
1253
|
+
fixture: DOCKER_SWARM,
|
|
1254
|
+
series: TASKS,
|
|
1255
|
+
declaredUnits: { "container.uptime": "s" },
|
|
1256
|
+
}),
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
test("ranks the lowest-uptime task first, each at its LOWEST breaching sample", async () => {
|
|
1261
|
+
const monitorStep: MonitorStep = stringifyThresholds(
|
|
1262
|
+
dockerSwarmStep("docker-swarm-task-down"),
|
|
1263
|
+
);
|
|
1264
|
+
|
|
1265
|
+
expect(metricFiltersOf(firingOf(monitorStep))[0]?.value).toBe("60");
|
|
1266
|
+
expect(metricFiltersOf(firingOf(monitorStep))[0]?.filterType).toBe(
|
|
1267
|
+
FilterType.LessThan,
|
|
1268
|
+
);
|
|
1269
|
+
|
|
1270
|
+
const rootCause: string = await run(monitorStep);
|
|
1271
|
+
|
|
1272
|
+
expect(listSection(rootCause, "Affected Tasks")).toBe(
|
|
1273
|
+
[
|
|
1274
|
+
"**Affected Tasks** (2 total)",
|
|
1275
|
+
"",
|
|
1276
|
+
"1. **Task** `web.1.aaa` — **5 sec**",
|
|
1277
|
+
" - Service: `web`",
|
|
1278
|
+
" - Node: `n1`",
|
|
1279
|
+
"2. **Task** `web.2.bbb` — **40 sec**",
|
|
1280
|
+
" - Service: `web`",
|
|
1281
|
+
" - Node: `n2`",
|
|
1282
|
+
].join("\n"),
|
|
1283
|
+
);
|
|
1284
|
+
expect(rootCause).not.toContain("db.1.ccc");
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
test("renders exactly what the numeric threshold 60 renders", async () => {
|
|
1288
|
+
const numeric: string = await run(
|
|
1289
|
+
dockerSwarmStep("docker-swarm-task-down"),
|
|
1290
|
+
);
|
|
1291
|
+
const text: string = await run(
|
|
1292
|
+
stringifyThresholds(dockerSwarmStep("docker-swarm-task-down")),
|
|
1293
|
+
);
|
|
1294
|
+
|
|
1295
|
+
expect(listSection(numeric, "Affected Tasks")).not.toBe("");
|
|
1296
|
+
expect(text).toBe(numeric);
|
|
1297
|
+
});
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
test('pve-node-offline ungrouped with pve_up < "1": the raw scan lists the down node, not the up ones', async () => {
|
|
1301
|
+
const monitorStep: MonitorStep = stringifyThresholds(
|
|
1302
|
+
ungroup(proxmoxStep("pve-node-offline")),
|
|
1303
|
+
);
|
|
1304
|
+
|
|
1305
|
+
expect(metricFiltersOf(firingOf(monitorStep))[0]?.filterType).toBe(
|
|
1306
|
+
FilterType.LessThan,
|
|
1307
|
+
);
|
|
1308
|
+
expect(metricFiltersOf(firingOf(monitorStep))[0]?.value).toBe("1");
|
|
1309
|
+
|
|
1310
|
+
const rootCause: string = await firingRootCause({
|
|
1311
|
+
monitorStep: monitorStep,
|
|
1312
|
+
fixture: PROXMOX,
|
|
1313
|
+
response: ungroupedResponse({
|
|
1314
|
+
monitorStep: monitorStep,
|
|
1315
|
+
fixture: PROXMOX,
|
|
1316
|
+
valuesByAlias: { node_up: [0, 0, 0, 0, 0] },
|
|
1317
|
+
scans: [
|
|
1318
|
+
{
|
|
1319
|
+
alias: "node_up",
|
|
1320
|
+
resources: [
|
|
1321
|
+
{
|
|
1322
|
+
resourceId: "node/pve1",
|
|
1323
|
+
resourceType: "node",
|
|
1324
|
+
scope: "node",
|
|
1325
|
+
metricValue: 1,
|
|
1326
|
+
lowestMetricValue: 1,
|
|
1327
|
+
},
|
|
1328
|
+
{
|
|
1329
|
+
resourceId: "node/pve2",
|
|
1330
|
+
resourceType: "node",
|
|
1331
|
+
scope: "node",
|
|
1332
|
+
metricValue: 0,
|
|
1333
|
+
lowestMetricValue: 0,
|
|
1334
|
+
},
|
|
1335
|
+
{
|
|
1336
|
+
resourceId: "node/pve3",
|
|
1337
|
+
resourceType: "node",
|
|
1338
|
+
scope: "node",
|
|
1339
|
+
metricValue: 1,
|
|
1340
|
+
lowestMetricValue: 1,
|
|
1341
|
+
},
|
|
1342
|
+
],
|
|
1343
|
+
},
|
|
1344
|
+
],
|
|
1345
|
+
}),
|
|
1346
|
+
});
|
|
1347
|
+
|
|
1348
|
+
expect(listSection(rootCause, "Affected Resources")).toBe(
|
|
1349
|
+
[
|
|
1350
|
+
"**Affected Resources** (1 total)",
|
|
1351
|
+
"",
|
|
1352
|
+
"1. **Node** `node/pve2` — **0**",
|
|
1353
|
+
].join("\n"),
|
|
1354
|
+
);
|
|
1355
|
+
expect(rootCause).not.toContain("node/pve1");
|
|
1356
|
+
expect(rootCause).not.toContain("node/pve3");
|
|
1357
|
+
});
|
|
1358
|
+
});
|
|
1359
|
+
|
|
1360
|
+
/*
|
|
1361
|
+
* ---------------------------------------------------------------------------
|
|
1362
|
+
* 4. The raw scan is judged in the threshold's unit
|
|
1363
|
+
* ---------------------------------------------------------------------------
|
|
1364
|
+
*/
|
|
1365
|
+
|
|
1366
|
+
describe("The raw scan is judged in the unit the threshold was written in", () => {
|
|
1367
|
+
test('ungrouped docker-swarm-task-down, "uptime < 1 min": the 30 s task is listed in seconds, the 50000 s one is not', async () => {
|
|
1368
|
+
const monitorStep: MonitorStep = ungroup(
|
|
1369
|
+
dockerSwarmStep("docker-swarm-task-down"),
|
|
1370
|
+
);
|
|
1371
|
+
const filter: CriteriaFilter = metricFiltersOf(firingOf(monitorStep))[0]!;
|
|
1372
|
+
|
|
1373
|
+
expect(filter.filterType).toBe(FilterType.LessThan);
|
|
1374
|
+
filter.value = 1;
|
|
1375
|
+
filter.metricMonitorOptions = {
|
|
1376
|
+
...filter.metricMonitorOptions!,
|
|
1377
|
+
thresholdUnit: "min",
|
|
1378
|
+
};
|
|
1379
|
+
|
|
1380
|
+
const rootCause: string = await firingRootCause({
|
|
1381
|
+
monitorStep: monitorStep,
|
|
1382
|
+
fixture: DOCKER_SWARM,
|
|
1383
|
+
response: ungroupedResponse({
|
|
1384
|
+
monitorStep: monitorStep,
|
|
1385
|
+
fixture: DOCKER_SWARM,
|
|
1386
|
+
declaredUnits: { "container.uptime": "s" },
|
|
1387
|
+
// The youngest scrape in the window: 30 s, under a minute.
|
|
1388
|
+
valuesByAlias: { container_uptime: [30] },
|
|
1389
|
+
scans: [
|
|
1390
|
+
{
|
|
1391
|
+
alias: "container_uptime",
|
|
1392
|
+
metricUnit: "s",
|
|
1393
|
+
resources: [
|
|
1394
|
+
{
|
|
1395
|
+
containerName: "web.2.old",
|
|
1396
|
+
serviceName: "web",
|
|
1397
|
+
nodeName: "n1",
|
|
1398
|
+
metricValue: 50000,
|
|
1399
|
+
lowestMetricValue: 50000,
|
|
1400
|
+
},
|
|
1401
|
+
{
|
|
1402
|
+
containerName: "web.1.restarted",
|
|
1403
|
+
serviceName: "web",
|
|
1404
|
+
nodeName: "n1",
|
|
1405
|
+
metricValue: 30,
|
|
1406
|
+
lowestMetricValue: 30,
|
|
1407
|
+
},
|
|
1408
|
+
],
|
|
1409
|
+
},
|
|
1410
|
+
],
|
|
1411
|
+
}),
|
|
1412
|
+
});
|
|
1413
|
+
|
|
1414
|
+
expect(listSection(rootCause, "Affected Tasks")).toBe(
|
|
1415
|
+
[
|
|
1416
|
+
"**Affected Tasks** (1 total)",
|
|
1417
|
+
"",
|
|
1418
|
+
"1. **Task** `web.1.restarted` — **30 sec**",
|
|
1419
|
+
" - Service: `web`",
|
|
1420
|
+
" - Node: `n1`",
|
|
1421
|
+
].join("\n"),
|
|
1422
|
+
);
|
|
1423
|
+
expect(rootCause).not.toContain("web.2.old");
|
|
1424
|
+
expect(rootCause).toContain(metricLine(DOCKER_SWARM, "container.uptime"));
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
test('ungrouped pve-node-high-cpu, "> 90 %" over a 0–1 ratio: only the node at 0.95 is listed, as 95.00%', async () => {
|
|
1428
|
+
const monitorStep: MonitorStep = ungroup(proxmoxStep("pve-node-high-cpu"));
|
|
1429
|
+
const filter: CriteriaFilter = metricFiltersOf(firingOf(monitorStep))[0]!;
|
|
1430
|
+
|
|
1431
|
+
expect(filter.filterType).toBe(FilterType.GreaterThan);
|
|
1432
|
+
filter.value = 90;
|
|
1433
|
+
filter.metricMonitorOptions = {
|
|
1434
|
+
...filter.metricMonitorOptions!,
|
|
1435
|
+
thresholdUnit: "%",
|
|
1436
|
+
};
|
|
1437
|
+
|
|
1438
|
+
const rootCause: string = await firingRootCause({
|
|
1439
|
+
monitorStep: monitorStep,
|
|
1440
|
+
fixture: PROXMOX,
|
|
1441
|
+
response: ungroupedResponse({
|
|
1442
|
+
monitorStep: monitorStep,
|
|
1443
|
+
fixture: PROXMOX,
|
|
1444
|
+
declaredUnits: { pve_cpu_usage_ratio: "1" },
|
|
1445
|
+
valuesByAlias: { node_cpu: [0.95, 0.95, 0.95, 0.95, 0.95] },
|
|
1446
|
+
scans: [
|
|
1447
|
+
{
|
|
1448
|
+
alias: "node_cpu",
|
|
1449
|
+
metricUnit: "1",
|
|
1450
|
+
resources: [
|
|
1451
|
+
{
|
|
1452
|
+
resourceId: "node/pve2",
|
|
1453
|
+
resourceType: "node",
|
|
1454
|
+
scope: "node",
|
|
1455
|
+
metricValue: 0.5,
|
|
1456
|
+
lowestMetricValue: 0.5,
|
|
1457
|
+
},
|
|
1458
|
+
{
|
|
1459
|
+
resourceId: "node/pve1",
|
|
1460
|
+
resourceType: "node",
|
|
1461
|
+
scope: "node",
|
|
1462
|
+
metricValue: 0.95,
|
|
1463
|
+
lowestMetricValue: 0.95,
|
|
1464
|
+
},
|
|
1465
|
+
],
|
|
1466
|
+
},
|
|
1467
|
+
],
|
|
1468
|
+
}),
|
|
1469
|
+
});
|
|
1470
|
+
|
|
1471
|
+
expect(listSection(rootCause, "Affected Resources")).toBe(
|
|
1472
|
+
[
|
|
1473
|
+
"**Affected Resources** (1 total)",
|
|
1474
|
+
"",
|
|
1475
|
+
"1. **Node** `node/pve1` — **95.00%**",
|
|
1476
|
+
].join("\n"),
|
|
1477
|
+
);
|
|
1478
|
+
// The node at 50% is healthy under a "> 90%" criteria.
|
|
1479
|
+
expect(rootCause).not.toContain("node/pve2");
|
|
1480
|
+
expect(rootCause).not.toContain("**0.95**");
|
|
1481
|
+
});
|
|
1482
|
+
});
|
|
1483
|
+
|
|
1484
|
+
/*
|
|
1485
|
+
* ---------------------------------------------------------------------------
|
|
1486
|
+
* 5. Grouped by a key that names no platform object
|
|
1487
|
+
* ---------------------------------------------------------------------------
|
|
1488
|
+
*/
|
|
1489
|
+
|
|
1490
|
+
describe("A monitor grouped by a key that names no platform object titles each series by its labels", () => {
|
|
1491
|
+
test("Kubernetes k8s.volume.available grouped by PVC name: one distinct **Series** row per claim", async () => {
|
|
1492
|
+
const args: CommonTemplateArgs = commonArgs(`${K8S_CLUSTER} - PVC space`);
|
|
1493
|
+
|
|
1494
|
+
// What the monitor form builds: one query, grouped by the claim name.
|
|
1495
|
+
const monitorStep: MonitorStep = buildKubernetesMonitorStep({
|
|
1496
|
+
kubernetesMonitor: buildKubernetesMonitorConfig({
|
|
1497
|
+
clusterIdentifier: K8S_CLUSTER,
|
|
1498
|
+
metricName: "k8s.volume.available",
|
|
1499
|
+
metricAlias: "vol_avail",
|
|
1500
|
+
resourceScope: KubernetesResourceScope.Cluster,
|
|
1501
|
+
rollingTime: RollingTime.Past5Minutes,
|
|
1502
|
+
aggregationType: MetricsAggregationType.Min,
|
|
1503
|
+
groupByAttributeKeys: [PVC_KEY],
|
|
1504
|
+
}),
|
|
1505
|
+
offlineCriteriaInstance: buildOfflineCriteriaInstance({
|
|
1506
|
+
offlineMonitorStatusId: args.offlineMonitorStatusId,
|
|
1507
|
+
incidentSeverityId: args.defaultIncidentSeverityId,
|
|
1508
|
+
alertSeverityId: args.defaultAlertSeverityId,
|
|
1509
|
+
monitorName: args.monitorName,
|
|
1510
|
+
metricAlias: "vol_avail",
|
|
1511
|
+
filterType: FilterType.LessThan,
|
|
1512
|
+
value: GB,
|
|
1513
|
+
}),
|
|
1514
|
+
onlineCriteriaInstance: buildOnlineCriteriaInstance({
|
|
1515
|
+
onlineMonitorStatusId: args.onlineMonitorStatusId,
|
|
1516
|
+
metricAlias: "vol_avail",
|
|
1517
|
+
filterType: FilterType.GreaterThanOrEqualTo,
|
|
1518
|
+
value: GB,
|
|
1519
|
+
}),
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1522
|
+
/*
|
|
1523
|
+
* Each claim's datapoints also carry the pod that mounts it — which a
|
|
1524
|
+
* claim-level series must not be titled by.
|
|
1525
|
+
*/
|
|
1526
|
+
const claim: (input: {
|
|
1527
|
+
name: string;
|
|
1528
|
+
namespace: string;
|
|
1529
|
+
pod: string;
|
|
1530
|
+
node: string;
|
|
1531
|
+
available: number;
|
|
1532
|
+
}) => RawSeries = (input: {
|
|
1533
|
+
name: string;
|
|
1534
|
+
namespace: string;
|
|
1535
|
+
pod: string;
|
|
1536
|
+
node: string;
|
|
1537
|
+
available: number;
|
|
1538
|
+
}): RawSeries => {
|
|
1539
|
+
return {
|
|
1540
|
+
attributes: {
|
|
1541
|
+
[K8S_CLUSTER_KEY]: K8S_CLUSTER,
|
|
1542
|
+
[PVC_KEY]: input.name,
|
|
1543
|
+
[NAMESPACE_KEY]: input.namespace,
|
|
1544
|
+
[POD_KEY]: input.pod,
|
|
1545
|
+
[NODE_KEY]: input.node,
|
|
1546
|
+
},
|
|
1547
|
+
valuesByAlias: {
|
|
1548
|
+
vol_avail: MINUTES.map((): number => {
|
|
1549
|
+
return input.available;
|
|
1550
|
+
}),
|
|
1551
|
+
},
|
|
1552
|
+
};
|
|
1553
|
+
};
|
|
1554
|
+
|
|
1555
|
+
const rootCause: string = await firingRootCause({
|
|
1556
|
+
monitorStep: monitorStep,
|
|
1557
|
+
fixture: KUBERNETES,
|
|
1558
|
+
response: groupedResponse({
|
|
1559
|
+
monitorStep: monitorStep,
|
|
1560
|
+
fixture: KUBERNETES,
|
|
1561
|
+
declaredUnits: K8S_DECLARED_UNITS,
|
|
1562
|
+
series: [
|
|
1563
|
+
claim({
|
|
1564
|
+
name: "data-postgres-0",
|
|
1565
|
+
namespace: "db",
|
|
1566
|
+
pod: "postgres-0",
|
|
1567
|
+
node: "node-a",
|
|
1568
|
+
available: 0.5 * GB,
|
|
1569
|
+
}),
|
|
1570
|
+
claim({
|
|
1571
|
+
name: "data-redis-0",
|
|
1572
|
+
namespace: "cache",
|
|
1573
|
+
pod: "redis-0",
|
|
1574
|
+
node: "node-b",
|
|
1575
|
+
available: 0.3 * GB,
|
|
1576
|
+
}),
|
|
1577
|
+
claim({
|
|
1578
|
+
name: "data-kafka-0",
|
|
1579
|
+
namespace: "stream",
|
|
1580
|
+
pod: "kafka-0",
|
|
1581
|
+
node: "node-c",
|
|
1582
|
+
available: 40 * GB,
|
|
1583
|
+
}),
|
|
1584
|
+
],
|
|
1585
|
+
}),
|
|
1586
|
+
});
|
|
1587
|
+
|
|
1588
|
+
expect(listSection(rootCause, "Affected Resources")).toBe(
|
|
1589
|
+
[
|
|
1590
|
+
"**Affected Resources** (2 total)",
|
|
1591
|
+
"",
|
|
1592
|
+
"1. **Series** `k8s.persistentvolumeclaim.name=data-redis-0` — **300 MB**",
|
|
1593
|
+
"2. **Series** `k8s.persistentvolumeclaim.name=data-postgres-0` — **500 MB**",
|
|
1594
|
+
].join("\n"),
|
|
1595
|
+
);
|
|
1596
|
+
|
|
1597
|
+
expect(rootCause).not.toContain("**Cluster**");
|
|
1598
|
+
expect(rootCause).not.toContain("resource.k8s.persistentvolumeclaim.name=");
|
|
1599
|
+
expect(rootCause).not.toContain("data-kafka-0");
|
|
1600
|
+
// Never titled by the pod that happens to mount the claim.
|
|
1601
|
+
expect(rootCause).not.toContain("**Pod**");
|
|
1602
|
+
expect(rootCause).not.toMatch(RAW_NUMBER);
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
test("Proxmox pve-node-high-cpu grouped by the exporter instance: the list renders, one **Series** row per instance", async () => {
|
|
1606
|
+
const monitorStep: MonitorStep = proxmoxStep("pve-node-high-cpu");
|
|
1607
|
+
|
|
1608
|
+
for (const queryConfig of viewConfigOf(monitorStep).queryConfigs) {
|
|
1609
|
+
queryConfig.metricQueryData.groupByAttributeKeys = ["instance"];
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
const exporter: (instance: string, ratio: number) => RawSeries = (
|
|
1613
|
+
instance: string,
|
|
1614
|
+
ratio: number,
|
|
1615
|
+
): RawSeries => {
|
|
1616
|
+
return {
|
|
1617
|
+
/*
|
|
1618
|
+
* pve-exporter's datapoint names the node by `id`, but the series
|
|
1619
|
+
* is keyed by the scrape target only — so its labels name no
|
|
1620
|
+
* Proxmox object.
|
|
1621
|
+
*/
|
|
1622
|
+
attributes: {
|
|
1623
|
+
instance: instance,
|
|
1624
|
+
"resource.proxmox.cluster.name": PVE_CLUSTER,
|
|
1625
|
+
},
|
|
1626
|
+
valuesByAlias: {
|
|
1627
|
+
node_cpu: MINUTES.map((): number => {
|
|
1628
|
+
return ratio;
|
|
1629
|
+
}),
|
|
1630
|
+
},
|
|
1631
|
+
};
|
|
1632
|
+
};
|
|
1633
|
+
|
|
1634
|
+
const rootCause: string = await firingRootCause({
|
|
1635
|
+
monitorStep: monitorStep,
|
|
1636
|
+
fixture: PROXMOX,
|
|
1637
|
+
response: groupedResponse({
|
|
1638
|
+
monitorStep: monitorStep,
|
|
1639
|
+
fixture: PROXMOX,
|
|
1640
|
+
declaredUnits: { pve_cpu_usage_ratio: "1" },
|
|
1641
|
+
series: [
|
|
1642
|
+
exporter("10.0.0.11:9221", 0.93),
|
|
1643
|
+
exporter("10.0.0.12:9221", 0.2),
|
|
1644
|
+
exporter("10.0.0.13:9221", 0.97),
|
|
1645
|
+
],
|
|
1646
|
+
}),
|
|
1647
|
+
});
|
|
1648
|
+
|
|
1649
|
+
expect(listSection(rootCause, "Affected Resources")).toBe(
|
|
1650
|
+
[
|
|
1651
|
+
"**Affected Resources** (2 total)",
|
|
1652
|
+
"",
|
|
1653
|
+
"1. **Series** `instance=10.0.0.13:9221` — **97.00%**",
|
|
1654
|
+
"2. **Series** `instance=10.0.0.11:9221` — **93.00%**",
|
|
1655
|
+
].join("\n"),
|
|
1656
|
+
);
|
|
1657
|
+
expect(rootCause).not.toContain("10.0.0.12");
|
|
1658
|
+
expect(rootCause).not.toContain("**Cluster**");
|
|
1659
|
+
expect(rootCause).not.toContain("**Metric Summary**");
|
|
1660
|
+
});
|
|
1661
|
+
|
|
1662
|
+
test("Proxmox pve-guest-high-cpu grouped by guest type (pve.type): the list renders, one **Series** row per type", async () => {
|
|
1663
|
+
/*
|
|
1664
|
+
* "One alert per guest type": each series aggregates every VM (or
|
|
1665
|
+
* every container), so its only label is the type — a kind, not an
|
|
1666
|
+
* object. No row can be titled by a guest id, so each is titled by
|
|
1667
|
+
* its labels rather than the list being dropped.
|
|
1668
|
+
*/
|
|
1669
|
+
const monitorStep: MonitorStep = proxmoxStep("pve-guest-high-cpu");
|
|
1670
|
+
|
|
1671
|
+
for (const queryConfig of viewConfigOf(monitorStep).queryConfigs) {
|
|
1672
|
+
queryConfig.metricQueryData.groupByAttributeKeys = ["pve.type"];
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
const guest: (id: string, ratio: number) => RawSeries = (
|
|
1676
|
+
id: string,
|
|
1677
|
+
ratio: number,
|
|
1678
|
+
): RawSeries => {
|
|
1679
|
+
return {
|
|
1680
|
+
attributes: {
|
|
1681
|
+
id: id,
|
|
1682
|
+
"pve.type": id.split("/")[0]!,
|
|
1683
|
+
"pve.scope": "guest",
|
|
1684
|
+
"pve.id": id.split("/")[1]!,
|
|
1685
|
+
"resource.proxmox.cluster.name": PVE_CLUSTER,
|
|
1686
|
+
},
|
|
1687
|
+
valuesByAlias: {
|
|
1688
|
+
guest_cpu: MINUTES.map((): number => {
|
|
1689
|
+
return ratio;
|
|
1690
|
+
}),
|
|
1691
|
+
},
|
|
1692
|
+
};
|
|
1693
|
+
};
|
|
1694
|
+
|
|
1695
|
+
const rootCause: string = await firingRootCause({
|
|
1696
|
+
monitorStep: monitorStep,
|
|
1697
|
+
fixture: PROXMOX,
|
|
1698
|
+
response: groupedResponse({
|
|
1699
|
+
monitorStep: monitorStep,
|
|
1700
|
+
fixture: PROXMOX,
|
|
1701
|
+
declaredUnits: { pve_cpu_usage_ratio: "1" },
|
|
1702
|
+
series: [guest("qemu/100", 0.97), guest("lxc/101", 0.98)],
|
|
1703
|
+
}),
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
// Fixture guard: the criteria fired on both types.
|
|
1707
|
+
expect(rootCause).toContain("**Proxmox Cluster Details**");
|
|
1708
|
+
|
|
1709
|
+
expect(listSection(rootCause, "Affected Resources")).toBe(
|
|
1710
|
+
[
|
|
1711
|
+
"**Affected Resources** (2 total)",
|
|
1712
|
+
"",
|
|
1713
|
+
"1. **Series** `pve.type=lxc` — **98.00%**",
|
|
1714
|
+
"2. **Series** `pve.type=qemu` — **97.00%**",
|
|
1715
|
+
].join("\n"),
|
|
1716
|
+
);
|
|
1717
|
+
expect(rootCause).not.toContain("**Cluster**");
|
|
1718
|
+
});
|
|
1719
|
+
});
|