@oneuptime/common 14.0.10 → 14.0.11
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/AIConversation.ts +127 -1
- package/Models/DatabaseModels/CephCluster.ts +12 -0
- package/Models/DatabaseModels/CloudResource.ts +12 -0
- package/Models/DatabaseModels/DatabaseServer.ts +12 -0
- package/Models/DatabaseModels/DockerHost.ts +12 -0
- package/Models/DatabaseModels/DockerSwarmCluster.ts +12 -0
- package/Models/DatabaseModels/GlobalOidc.ts +0 -4
- package/Models/DatabaseModels/GlobalOidcProject.ts +0 -4
- package/Models/DatabaseModels/GlobalSso.ts +0 -4
- package/Models/DatabaseModels/GlobalSsoProject.ts +0 -4
- package/Models/DatabaseModels/Host.ts +12 -0
- package/Models/DatabaseModels/IncidentForm.ts +594 -0
- package/Models/DatabaseModels/IncidentFormSubmission.ts +283 -0
- package/Models/DatabaseModels/IncidentTemplate.ts +59 -0
- package/Models/DatabaseModels/Index.ts +14 -0
- package/Models/DatabaseModels/IoTFleet.ts +12 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +12 -0
- package/Models/DatabaseModels/MessageQueue.ts +1102 -0
- package/Models/DatabaseModels/MessageQueueLabelRule.ts +553 -0
- package/Models/DatabaseModels/MessageQueueOwnerRule.ts +633 -0
- package/Models/DatabaseModels/MessageQueueOwnerTeam.ts +491 -0
- package/Models/DatabaseModels/MessageQueueOwnerUser.ts +490 -0
- package/Models/DatabaseModels/PodmanHost.ts +12 -0
- package/Models/DatabaseModels/Project.ts +59 -0
- package/Models/DatabaseModels/ProjectOidc.ts +0 -4
- package/Models/DatabaseModels/ProjectSso.ts +0 -4
- package/Models/DatabaseModels/ProxmoxCluster.ts +12 -0
- package/Models/DatabaseModels/RumApplication.ts +12 -0
- package/Models/DatabaseModels/ServerlessFunction.ts +12 -0
- package/Models/DatabaseModels/Service.ts +12 -0
- package/Models/DatabaseModels/StatusPageOidc.ts +0 -4
- package/Models/DatabaseModels/StatusPageSso.ts +0 -4
- package/Models/DatabaseModels/TeamComplianceSetting.ts +47 -2
- package/Models/DatabaseModels/VMwareVCenter.ts +12 -0
- package/Server/API/AIChatAPI.ts +13 -114
- package/Server/API/AIInvestigationAPI.ts +3 -3
- package/Server/API/AIInvestigationConversationAPI.ts +266 -0
- package/Server/API/IncidentFormAPI.ts +376 -0
- package/Server/API/ProjectOIDC.ts +16 -25
- package/Server/API/ProjectSSO.ts +16 -24
- package/Server/API/StatusPageAPI.ts +42 -77
- package/Server/Enterprise/EnterpriseEdition.ts +93 -28
- package/Server/Enterprise/EnterpriseFeature.ts +25 -3
- package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +1 -1
- package/Server/Enterprise/EnterpriseServerModule.ts +4 -1
- package/Server/EnvironmentConfig.ts +1 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.ts +129 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.ts +52 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.ts +75 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.ts +477 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Middleware/IncidentFormRateLimit.ts +847 -0
- package/Server/Middleware/UserAuthorization.ts +16 -38
- package/Server/Services/AIService.ts +10 -13
- package/Server/Services/IncidentCustomFieldService.ts +112 -2
- package/Server/Services/IncidentEpisodeMemberService.ts +24 -4
- package/Server/Services/IncidentEpisodeService.ts +10 -1
- package/Server/Services/IncidentFormService.ts +1566 -0
- package/Server/Services/IncidentFormSubmissionService.ts +97 -0
- package/Server/Services/IncidentGroupingEngineService.ts +45 -8
- package/Server/Services/IncidentService.ts +123 -5
- package/Server/Services/IncidentTemplateService.ts +22 -0
- package/Server/Services/Index.ts +14 -0
- package/Server/Services/MessageQueueLabelRuleEngineService.ts +321 -0
- package/Server/Services/MessageQueueLabelRuleService.ts +14 -0
- package/Server/Services/MessageQueueOwnerRuleEngineService.ts +412 -0
- package/Server/Services/MessageQueueOwnerRuleService.ts +14 -0
- package/Server/Services/MessageQueueOwnerTeamService.ts +62 -0
- package/Server/Services/MessageQueueOwnerUserService.ts +62 -0
- package/Server/Services/MessageQueueService.ts +1463 -0
- package/Server/Services/OpenTelemetryIngestService.ts +37 -4
- package/Server/Services/ProjectService.ts +0 -59
- package/Server/Services/StatusPagePrivateUserService.ts +1 -4
- package/Server/Services/StatusPageService.ts +1 -51
- package/Server/Services/TeamComplianceSettingService.ts +163 -42
- package/Server/Services/UserNotificationRuleService.ts +8 -1
- package/Server/Services/WorkspaceNotificationSummaryService.ts +12 -3
- package/Server/Types/Database/Permissions/CreatePermission.ts +17 -3
- package/Server/Types/Database/Permissions/EditionPermission.ts +140 -38
- package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +16 -0
- package/Server/Types/Database/Permissions/TablePermission.ts +3 -3
- package/Server/Types/Database/Permissions/UpdatePermission.ts +20 -6
- package/Server/Utils/AI/Chat/AgentContextCompactor.ts +200 -0
- package/Server/Utils/AI/Chat/ChatAgentRunner.ts +307 -48
- package/Server/Utils/AI/Chat/ChatRunCancellation.ts +156 -0
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +78 -18
- package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +1 -1
- package/Server/Utils/AI/Chat/ToolOutputPager.ts +322 -0
- package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +88 -30
- package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +13 -1
- package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +0 -32
- package/Server/Utils/AI/CodeFix/FixRunBudget.ts +27 -25
- package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +7 -1
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +34 -14
- package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +69 -24
- package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +9 -0
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +53 -10
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +27 -10
- package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +27 -10
- package/Server/Utils/AI/SRE/InvestigationQueue.ts +16 -19
- package/Server/Utils/AI/SRE/InvestigationThread.ts +646 -0
- package/Server/Utils/AI/SRE/InvestigationThreadService.ts +715 -0
- package/Server/Utils/AI/Toolbox/Serializer.ts +10 -3
- package/Server/Utils/EditionEnforcement.ts +19 -202
- package/Server/Utils/LLM/LLMService.ts +259 -63
- package/Server/Utils/RelaxedScimTeamLocksReport.ts +245 -0
- package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
- package/Server/Utils/SameOriginRequest.ts +203 -0
- package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +4 -1
- package/Server/Utils/Telemetry/MessageQueueDiscovery.ts +1402 -0
- package/Server/Utils/Telemetry/ResourceEntityFilter.ts +4 -1
- package/Server/Utils/VendorAssets.ts +3 -3
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +3 -1
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +3 -1
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +10 -1
- package/Tests/App/Accounts/IncidentFormPage.test.tsx +2036 -0
- package/Tests/App/Accounts/IncidentFormServerMessages.test.ts +119 -0
- package/Tests/App/Accounts/IncidentFormUtils.test.ts +1157 -0
- package/Tests/App/Accounts/LoginPasskey.test.tsx +6 -4
- package/Tests/App/Accounts/LoginSso.test.tsx +724 -0
- package/Tests/App/AdminDashboard/AdminAuthenticationRequireSso.test.tsx +338 -0
- package/Tests/App/AdminDashboard/AdminAuthenticationSsoCardLocales.test.tsx +331 -0
- package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +47 -33
- package/Tests/App/AdminDashboard/GlobalSsoOidcPages.test.tsx +1021 -0
- package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +876 -78
- package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +20 -3
- package/Tests/App/Dashboard/CephProxmoxVMwareDocumentationCards.test.tsx +359 -0
- package/Tests/App/Dashboard/CephSetupGuide.test.ts +961 -0
- package/Tests/App/Dashboard/CloudDocumentationCard.test.tsx +381 -95
- package/Tests/App/Dashboard/CloudSetupGuide.test.ts +1463 -0
- package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +174 -12
- package/Tests/App/Dashboard/ContainerSetupGuideCards.test.tsx +345 -0
- package/Tests/App/Dashboard/CoreListEmptyStates.test.tsx +501 -0
- package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +0 -1
- package/Tests/App/Dashboard/DatabaseDocumentationCard.test.tsx +400 -0
- package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +5 -0
- package/Tests/App/Dashboard/DatabaseSetupGuide.test.ts +1869 -0
- package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +44 -0
- package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +50 -9
- package/Tests/App/Dashboard/DockerSetupGuide.test.ts +899 -0
- package/Tests/App/Dashboard/DockerSwarmSetupGuide.test.ts +964 -0
- package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +141 -7
- package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +52 -39
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +7 -1
- package/Tests/App/Dashboard/GettingStartedTaskRoutes.test.tsx +331 -0
- package/Tests/App/Dashboard/HomeOverviewStatsFirstRun.test.tsx +925 -0
- package/Tests/App/Dashboard/HostDocumentationCard.test.tsx +225 -0
- package/Tests/App/Dashboard/HostSetupGuide.test.ts +1183 -0
- package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +69 -45
- package/Tests/App/Dashboard/IncidentCreateTemplateCustomFieldSettings.test.tsx +745 -0
- package/Tests/App/Dashboard/IncidentCreateTemplateOwners.test.tsx +414 -0
- package/Tests/App/Dashboard/IncidentCustomFieldCreateSettingsForm.test.ts +1042 -0
- package/Tests/App/Dashboard/IncidentCustomFieldSettingsCard.test.tsx +1816 -0
- package/Tests/App/Dashboard/IncidentFormPlan.test.ts +89 -0
- package/Tests/App/Dashboard/IncidentFormShareLinkCard.test.tsx +846 -0
- package/Tests/App/Dashboard/IncidentFormsPages.test.tsx +1324 -0
- package/Tests/App/Dashboard/IncidentTemplateCustomFieldSettings.test.tsx +630 -0
- package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +13 -0
- package/Tests/App/Dashboard/IncomingEmailMonitorLinkVerifyHint.test.tsx +104 -0
- package/Tests/App/Dashboard/InvestigationConversation.test.tsx +1107 -0
- package/Tests/App/Dashboard/InvestigationConversationData.test.ts +546 -0
- package/Tests/App/Dashboard/InvestigationEvidenceList.test.tsx +170 -5
- package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +207 -2
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +729 -34
- package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +9 -21
- package/Tests/App/Dashboard/InvestigationReportView.test.tsx +274 -37
- package/Tests/App/Dashboard/InvestigationStatusBadge.test.tsx +130 -0
- package/Tests/App/Dashboard/IoTDocumentationCard.test.tsx +218 -0
- package/Tests/App/Dashboard/IoTSetupGuide.test.ts +1124 -0
- package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +20 -6
- package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +107 -83
- package/Tests/App/Dashboard/KubernetesSetupGuide.test.ts +787 -0
- package/Tests/App/Dashboard/MessageQueueBreadcrumbs.test.tsx +228 -0
- package/Tests/App/Dashboard/MessageQueueBrokerHealth.test.tsx +1538 -0
- package/Tests/App/Dashboard/MessageQueueBrokerHealthMetricChart.test.tsx +969 -0
- package/Tests/App/Dashboard/MessageQueueBrokerHealthReads.test.ts +889 -0
- package/Tests/App/Dashboard/MessageQueueBrokerMetricsReach.test.ts +236 -0
- package/Tests/App/Dashboard/MessageQueueDocumentationCard.test.tsx +479 -0
- package/Tests/App/Dashboard/MessageQueueListPages.test.tsx +1232 -0
- package/Tests/App/Dashboard/MessageQueueMetricDescriptions.test.ts +244 -0
- package/Tests/App/Dashboard/MessageQueueMonitorLink.test.ts +952 -0
- package/Tests/App/Dashboard/MessageQueueMonitorLinkMonitorCreate.test.tsx +859 -0
- package/Tests/App/Dashboard/MessageQueueOverview.test.tsx +1773 -0
- package/Tests/App/Dashboard/MessageQueueOverviewPresentation.test.ts +196 -0
- package/Tests/App/Dashboard/MessageQueuePresentation.test.ts +799 -0
- package/Tests/App/Dashboard/MessageQueueProducersConsumers.test.tsx +680 -0
- package/Tests/App/Dashboard/MessageQueueRoutes.test.tsx +383 -0
- package/Tests/App/Dashboard/MessageQueueRulePages.test.tsx +278 -0
- package/Tests/App/Dashboard/MessageQueueSetupGuide.test.ts +2778 -0
- package/Tests/App/Dashboard/MessageQueueSideMenus.test.tsx +273 -0
- package/Tests/App/Dashboard/MessageQueueSpanPopulations.test.ts +930 -0
- package/Tests/App/Dashboard/MessageQueueSpanStore.ts +351 -0
- package/Tests/App/Dashboard/MessageQueueTelemetryScope.test.ts +375 -0
- package/Tests/App/Dashboard/MessageQueueTelemetryScopeTabs.test.tsx +668 -0
- package/Tests/App/Dashboard/MessageQueueViewPages.test.tsx +685 -0
- package/Tests/App/Dashboard/MonitorAndHomeNotOperationalMenus.test.tsx +342 -0
- package/Tests/App/Dashboard/MonitorCreateFirstRunDefaults.test.tsx +623 -0
- package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +442 -0
- package/Tests/App/Dashboard/MonitorLogsPageViewSummary.test.tsx +814 -0
- package/Tests/App/Dashboard/MonitorSetupCardBodies.test.tsx +246 -0
- package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +8 -0
- package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +27 -12
- package/Tests/App/Dashboard/PlanGatedPage.test.tsx +292 -0
- package/Tests/App/Dashboard/PodmanSetupGuide.test.ts +948 -0
- package/Tests/App/Dashboard/ProductOrientation.test.tsx +5 -4
- package/Tests/App/Dashboard/ProductSettingsSectionCollapsed.test.tsx +266 -0
- package/Tests/App/Dashboard/ProxmoxSetupGuide.test.ts +1318 -0
- package/Tests/App/Dashboard/ResourceAiAgentAttentionServerParity.test.ts +555 -0
- package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +298 -9
- package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +453 -0
- package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +16 -0
- package/Tests/App/Dashboard/ResourceConnectionGuideCard.test.tsx +444 -0
- package/Tests/App/Dashboard/ResourceConnectionGuideOverviewsInfra.test.tsx +798 -0
- package/Tests/App/Dashboard/ResourceConnectionGuideOverviewsPlatforms.test.tsx +717 -0
- package/Tests/App/Dashboard/ResourceConnectionGuides.test.ts +981 -0
- package/Tests/App/Dashboard/ResourceConnectionState.test.ts +132 -0
- package/Tests/App/Dashboard/RumDocumentationCard.test.tsx +293 -0
- package/Tests/App/Dashboard/RumSessionReplayRetentionSettings.test.tsx +467 -0
- package/Tests/App/Dashboard/RumSetupGuide.test.ts +758 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +25 -0
- package/Tests/App/Dashboard/ServerlessDocumentationCard.test.tsx +254 -0
- package/Tests/App/Dashboard/ServerlessSetupGuide.test.ts +1074 -0
- package/Tests/App/Dashboard/SetupGuide.test.ts +246 -0
- package/Tests/App/Dashboard/SetupGuideCard.test.tsx +756 -0
- package/Tests/App/Dashboard/SloListSideMenu.test.tsx +120 -0
- package/Tests/App/Dashboard/SsoPages.test.tsx +833 -0
- package/Tests/App/Dashboard/TelemetryDocumentation.test.tsx +253 -0
- package/Tests/App/Dashboard/TelemetryRetentionShells.test.tsx +730 -0
- package/Tests/App/Dashboard/VMwareSetupGuide.test.ts +1033 -0
- package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +87 -7
- package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +28 -9
- package/Tests/Models/DatabaseModels/IncidentFormModel.test.ts +1250 -0
- package/Tests/Models/DatabaseModels/MessageQueueModels.test.ts +1983 -0
- package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
- package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +190 -14
- package/Tests/Models/IncidentTemplateCustomFieldSettingsColumn.test.ts +291 -0
- package/Tests/Server/API/AIInvestigationConversationAPI.test.ts +412 -0
- package/Tests/Server/API/GlobalConfigLicense.test.ts +38 -9
- package/Tests/Server/API/IncidentFormAPI.test.ts +942 -0
- package/Tests/Server/API/IncidentFormPublicRoutes.test.ts +1653 -0
- package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -1
- package/Tests/Server/API/{SsoProviderListingsEdition.test.ts → SsoProviderListings.test.ts} +164 -110
- package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +76 -54
- package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +125 -42
- package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +36 -10
- package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +51 -9
- package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +13 -8
- package/Tests/Server/Enterprise/SsoInCommunityEdition.test.ts +540 -0
- package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentFormsMigration.test.ts +438 -0
- package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleNotificationChannelsMigration.test.ts +255 -0
- package/Tests/Server/Infrastructure/Postgres/MessageQueueTablesMigration.test.ts +637 -0
- package/Tests/Server/Middleware/IncidentFormRateLimit.test.ts +1630 -0
- package/Tests/Server/Middleware/UserAuthorization.test.ts +16 -17
- package/Tests/Server/Middleware/UserAuthorizationSsoEnforcement.test.ts +544 -0
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +37 -8
- package/Tests/Server/Services/IncidentCreatedFeedTitle.test.ts +212 -0
- package/Tests/Server/Services/IncidentCustomFieldDeleteFormQuestions.test.ts +297 -0
- package/Tests/Server/Services/IncidentFormOwnersCreatedNotification.test.ts +497 -0
- package/Tests/Server/Services/IncidentFormPostgres.test.ts +958 -0
- package/Tests/Server/Services/IncidentFormPublicForm.test.ts +1260 -0
- package/Tests/Server/Services/IncidentFormPublicSubmission.test.ts +2520 -0
- package/Tests/Server/Services/IncidentFormRegistration.test.ts +159 -0
- package/Tests/Server/Services/IncidentFormService.test.ts +663 -0
- package/Tests/Server/Services/IncidentFormSubmissionService.test.ts +430 -0
- package/Tests/Server/Services/IncidentFormSubmissionTemplateBranch.test.ts +473 -0
- package/Tests/Server/Services/IncidentGroupingTemplateSubstitution.test.ts +81 -0
- package/Tests/Server/Services/IncidentTemplateCustomFieldSettings.test.ts +262 -0
- package/Tests/Server/Services/IncidentTemplateOwnersOnDeclare.test.ts +566 -0
- package/Tests/Server/Services/IncidentTitleMarkdownSinks.test.ts +640 -0
- package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +3 -3
- package/Tests/Server/Services/MessageQueueOwnerServices.test.ts +440 -0
- package/Tests/Server/Services/MessageQueueRegistries.test.ts +577 -0
- package/Tests/Server/Services/MessageQueueRuleEngines.test.ts +900 -0
- package/Tests/Server/Services/MessageQueueService.test.ts +2210 -0
- package/Tests/Server/Services/MessageQueueSqlPostgres.test.ts +1271 -0
- package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +28 -1
- package/Tests/Server/Services/OpenTelemetryRetentionOverrideEdition.test.ts +474 -0
- package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +21 -0
- package/Tests/Server/Services/OwnerRowUniqueness.test.ts +3 -0
- package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +0 -1
- package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +56 -6
- package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -1
- package/Tests/Server/Services/SsoRequirementReads.test.ts +454 -0
- package/Tests/Server/Services/SsoRequirementWrites.test.ts +529 -0
- package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
- package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +107 -89
- package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +1068 -176
- package/Tests/Server/Services/TeamComplianceSettingService.test.ts +1962 -200
- package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +654 -297
- package/Tests/Server/Types/Database/Permissions/RetentionOverrideColumns.test.ts +784 -0
- package/Tests/Server/Types/Database/Permissions/SsoScalePlanGate.test.ts +418 -0
- package/Tests/Server/Utils/AI/AIAlertGating.test.ts +0 -1
- package/Tests/Server/Utils/AI/AIInvestigationEngineRunLimits.test.ts +136 -0
- package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +15 -14
- package/Tests/Server/Utils/AI/AgentContextCompactor.test.ts +351 -0
- package/Tests/Server/Utils/AI/ChatAgentRunnerSharedThread.test.ts +842 -0
- package/Tests/Server/Utils/AI/ChatRunCancellation.test.ts +195 -0
- package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +10 -57
- package/Tests/Server/Utils/AI/FixRunBudget.test.ts +20 -24
- package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +7 -30
- package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +225 -29
- package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +266 -25
- package/Tests/Server/Utils/AI/InvestigationThread.test.ts +580 -0
- package/Tests/Server/Utils/AI/InvestigationThreadService.test.ts +801 -0
- package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +525 -43
- package/Tests/Server/Utils/AI/KubectlJobRunner.test.ts +4 -2
- package/Tests/Server/Utils/AI/LLMServiceUnreachableProvider.test.ts +435 -0
- package/Tests/Server/Utils/AI/ObservabilityAssistantLimits.test.ts +462 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +14 -25
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +2 -0
- package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +297 -15
- package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +10 -6
- package/Tests/Server/Utils/AI/ToolOutputPager.test.ts +382 -0
- package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +23 -4
- package/Tests/Server/Utils/AI/Toolbox/LogTools.test.ts +25 -6
- package/Tests/Server/Utils/AI/Toolbox/Serializer.test.ts +14 -3
- package/Tests/Server/Utils/EditionEnforcement.test.ts +107 -147
- package/Tests/Server/Utils/{CommunityEditionSsoReport.test.ts → RelaxedScimTeamLocksReport.test.ts} +212 -208
- package/Tests/Server/Utils/SameOriginRequest.test.ts +372 -0
- package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +20 -0
- package/Tests/Server/Utils/Telemetry/MessageQueueDiscovery.test.ts +2590 -0
- package/Tests/Server/Utils/Telemetry/MessageQueueSpanQueryTwin.ts +246 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +23 -0
- package/Tests/Types/AI/AIAgentRunLimits.test.ts +255 -0
- package/Tests/Types/Code/JsonSyntax.test.ts +1300 -0
- package/Tests/Types/CustomField/CustomFieldCreateSettings.test.ts +1113 -0
- package/Tests/Types/CustomField/CustomFieldMappingCatalog.test.ts +91 -0
- package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +30 -0
- package/Tests/Types/Incident/IncidentFormIpAllowlist.test.ts +227 -0
- package/Tests/Types/Incident/IncidentFormPublic.test.ts +1916 -0
- package/Tests/Types/MessageQueue/MessageQueueEntityKey.test.ts +916 -0
- package/Tests/Types/MessageQueue/MessageQueueIdentity.test.ts +567 -0
- package/Tests/Types/MessageQueue/MessageQueueManualIdentity.test.ts +686 -0
- package/Tests/Types/MessageQueue/MessageQueueMetricCatalog.test.ts +1726 -0
- package/Tests/Types/MessageQueue/MessageQueueMetricResolution.test.ts +1758 -0
- package/Tests/Types/MessageQueue/MessagingInstrumentationFixtures.test.ts +4686 -0
- package/Tests/Types/MessageQueue/MessagingSystem.test.ts +840 -0
- package/Tests/Types/MessageQueue/MessagingTelemetryFixtures.ts +3287 -0
- package/Tests/Types/MessageQueue/MessagingTelemetryResolver.test.ts +2966 -0
- package/Tests/Types/Monitor/MessageQueueAlertTemplates.test.ts +5401 -0
- package/Tests/Types/Monitor/MetricEngineFold.ts +154 -0
- package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +5 -3
- package/Tests/Types/Team/ComplianceRule.test.ts +588 -37
- package/Tests/Types/Team/TeamComplianceStatus.test.ts +64 -11
- package/Tests/Types/XML.test.ts +51 -0
- package/Tests/UI/Components/CodeEditor/CodeEditor.test.tsx +2362 -0
- package/Tests/UI/Components/CodeEditor/CodeEditorCommands.test.ts +810 -0
- package/Tests/UI/Components/CodeEditor/CodeEditorHighlight.test.ts +338 -0
- package/Tests/UI/Components/CodeEditor/CodeEditorLanguages.test.ts +222 -0
- package/Tests/UI/Components/CodeEditor/CodeEditorStyles.test.ts +807 -0
- package/Tests/UI/Components/CodeEditor/NoMonaco.test.ts +245 -0
- package/Tests/UI/Components/CodeEditor/YamlEditor.test.tsx +729 -0
- package/Tests/UI/Components/CopyTextButton.test.tsx +76 -0
- package/Tests/UI/Components/EditionLabelLapseCopy.ts +51 -14
- package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +131 -23
- package/Tests/UI/Components/ErrorMessageAction.test.tsx +142 -0
- package/Tests/UI/Components/FacetSidebarInScope.test.tsx +459 -0
- package/Tests/UI/Components/FacetValuesInScope.test.ts +270 -0
- package/Tests/UI/Components/Forms/FormFieldCodeEditors.test.tsx +294 -0
- package/Tests/UI/Components/Forms/FormFieldMarkdown.test.tsx +126 -0
- package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +22 -75
- package/Tests/UI/Components/Forms/ModelFormOnBeforeCreateMiscData.test.tsx +254 -0
- package/Tests/UI/Components/Icon.test.tsx +32 -0
- package/Tests/UI/Components/Input.test.tsx +31 -0
- package/Tests/UI/Components/LockedFilterChipScope.test.tsx +501 -0
- package/Tests/UI/Components/MarkdownConverters.test.ts +253 -1
- package/Tests/UI/Components/MarkdownEditor.test.tsx +2785 -2
- package/Tests/UI/Components/MarkdownEditorHistory.test.ts +243 -0
- package/Tests/UI/Components/MarkdownListEditing.test.ts +1118 -0
- package/Tests/UI/Components/MarkdownPaste.test.ts +1180 -0
- package/Tests/UI/Components/MarkdownViewerCopy.test.tsx +466 -0
- package/Tests/UI/Components/MarkdownVisualEditing.test.ts +1089 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableEmptyStateAction.test.tsx +623 -0
- package/Tests/UI/Components/ResetObjectID.test.tsx +282 -0
- package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
- package/Tests/UI/Components/Table/EmptyTableMessage.test.ts +439 -0
- package/Tests/UI/Components/Table/TableEmptyState.test.tsx +352 -0
- package/Tests/UI/Components/TableRowMobileTitles.test.tsx +78 -0
- package/Tests/UI/Components/TextArea.test.tsx +19 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +12 -29
- package/Tests/UI/Components/fixtures/MarkdownPasteFixtures.ts +460 -0
- package/Tests/UI/EsbuildConfig.test.ts +115 -39
- package/Tests/UI/EsbuildEnterpriseAlias.test.ts +4 -4
- package/Tests/UI/Monitor/MonitorLogSummaryRender.test.tsx +377 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +81 -1
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +17 -0
- package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
- package/Tests/Utils/Markdown/DashboardMarkdownRenderer.ts +81 -0
- package/Tests/Utils/Markdown/UntrustedMarkdown.test.ts +1339 -0
- package/Tests/Utils/Monitor/MonitorLogSummaryUtil.test.ts +862 -0
- package/Types/AI/AIAgentRunLimits.ts +146 -0
- package/Types/Code/CodeType.ts +2 -0
- package/Types/Code/JsonSyntax.ts +923 -0
- package/Types/CustomField/CustomFieldCreateSettings.ts +549 -0
- package/Types/CustomField/CustomFieldMappingCatalog.ts +77 -0
- package/Types/CustomField/CustomFieldValueValidator.ts +10 -7
- package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +1 -1
- package/Types/Icon/IconProp.ts +2 -0
- package/Types/Incident/IncidentFormIpAllowlist.ts +206 -0
- package/Types/Incident/IncidentFormPublic.ts +1145 -0
- package/Types/Kubernetes/KubernetesClusterAiAccess.ts +10 -3
- package/Types/MessageQueue/MessageQueueIdentity.ts +256 -0
- package/Types/MessageQueue/MessageQueueManualIdentity.ts +325 -0
- package/Types/MessageQueue/MessageQueueMetricCatalog.ts +1357 -0
- package/Types/MessageQueue/MessagingSystem.ts +518 -0
- package/Types/MessageQueue/MessagingTelemetryResolver.ts +2311 -0
- package/Types/Monitor/MessageQueueAlertTemplates.ts +2128 -0
- package/Types/Permission.ts +310 -0
- package/Types/ResourceAiAgent/ResourceAiAccess.ts +10 -4
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +12 -0
- package/Types/Rules/RuleRun.ts +4 -0
- package/Types/Team/ComplianceNotificationChannel.ts +4 -3
- package/Types/Team/ComplianceRule.ts +71 -18
- package/Types/Team/ComplianceRuleType.ts +2 -2
- package/Types/Team/TeamComplianceStatus.ts +6 -2
- package/Types/Telemetry/EntityType.ts +16 -0
- package/Types/Telemetry/LockedFilterDetail.ts +22 -0
- package/Types/XML.ts +26 -0
- package/UI/Components/CodeEditor/CodeEditor.css +379 -0
- package/UI/Components/CodeEditor/CodeEditor.tsx +1128 -294
- package/UI/Components/CodeEditor/CodeEditorCommands.ts +774 -0
- package/UI/Components/CodeEditor/CodeEditorHighlight.ts +106 -0
- package/UI/Components/CodeEditor/CodeEditorLanguages.ts +164 -0
- package/UI/Components/CodeEditor/YamlEditor.tsx +28 -326
- package/UI/Components/CopyTextButton/CopyTextButton.tsx +5 -1
- package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +6 -25
- package/UI/Components/EditionLabel/EditionLabel.tsx +14 -13
- package/UI/Components/ErrorMessage/ErrorMessage.tsx +11 -1
- package/UI/Components/Forms/Fields/FormField.tsx +8 -0
- package/UI/Components/Forms/Types/Field.ts +8 -0
- package/UI/Components/Icon/Icon.tsx +18 -0
- package/UI/Components/Input/Input.tsx +11 -4
- package/UI/Components/LogsViewer/LogsViewer.tsx +4 -0
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +21 -3
- package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
- package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +877 -79
- package/UI/Components/Markdown.tsx/MarkdownEditorHistory.ts +284 -0
- package/UI/Components/Markdown.tsx/MarkdownListEditing.ts +1362 -0
- package/UI/Components/Markdown.tsx/MarkdownPaste.ts +1326 -0
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +106 -10
- package/UI/Components/Markdown.tsx/MarkdownVisualEditing.ts +1032 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +85 -7
- package/UI/Components/ResetObjectID/ResetObjectID.tsx +35 -9
- package/UI/Components/Table/EmptyTableMessage.ts +81 -0
- package/UI/Components/Table/Table.tsx +50 -21
- package/UI/Components/Table/TableRow.tsx +9 -1
- package/UI/Components/TelemetryViewer/FacetVisibility.ts +97 -2
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +13 -0
- package/UI/Components/TelemetryViewer/components/LockedFilterChip.tsx +214 -7
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +21 -3
- package/UI/Components/TextArea/TextArea.tsx +9 -2
- package/UI/Components/Workflow/ModelFieldPicker.tsx +10 -12
- package/UI/Styles/Theme.css +8 -0
- package/UI/esbuild-config.js +2 -32
- package/Utils/LabelRuleImportExport.ts +2 -0
- package/Utils/Markdown/UntrustedMarkdown.ts +1513 -0
- package/Utils/Monitor/IncomingEmailMonitorRequestUtil.ts +41 -0
- package/Utils/Monitor/MonitorLogSummaryUtil.ts +212 -0
- package/Utils/Telemetry/EntityKey.ts +48 -0
- package/build/dist/Models/DatabaseModels/AIConversation.js +125 -1
- package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CephCluster.js +12 -0
- package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CloudResource.js +12 -0
- package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DatabaseServer.js +12 -0
- package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js +12 -0
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +12 -0
- package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalOidc.js +0 -4
- package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +0 -4
- package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSso.js +0 -4
- package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +0 -4
- package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Host.js +12 -0
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentForm.js +605 -0
- package/build/dist/Models/DatabaseModels/IncidentForm.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentFormSubmission.js +299 -0
- package/build/dist/Models/DatabaseModels/IncidentFormSubmission.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js +60 -0
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +14 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IoTFleet.js +12 -0
- package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +12 -0
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MessageQueue.js +1106 -0
- package/build/dist/Models/DatabaseModels/MessageQueue.js.map +1 -0
- package/build/dist/Models/DatabaseModels/MessageQueueLabelRule.js +562 -0
- package/build/dist/Models/DatabaseModels/MessageQueueLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerRule.js +643 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerTeam.js +506 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerUser.js +505 -0
- package/build/dist/Models/DatabaseModels/MessageQueueOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/PodmanHost.js +12 -0
- package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +61 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProjectOidc.js +0 -4
- package/build/dist/Models/DatabaseModels/ProjectOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProjectSso.js +0 -4
- package/build/dist/Models/DatabaseModels/ProjectSso.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +12 -0
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +12 -0
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServerlessFunction.js +12 -0
- package/build/dist/Models/DatabaseModels/ServerlessFunction.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Service.js +12 -0
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js +0 -4
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSso.js +0 -4
- package/build/dist/Models/DatabaseModels/StatusPageSso.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +49 -3
- package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
- package/build/dist/Models/DatabaseModels/VMwareVCenter.js +12 -0
- package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +9 -103
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +3 -3
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationConversationAPI.js +162 -0
- package/build/dist/Server/API/AIInvestigationConversationAPI.js.map +1 -0
- package/build/dist/Server/API/IncidentFormAPI.js +245 -0
- package/build/dist/Server/API/IncidentFormAPI.js.map +1 -0
- package/build/dist/Server/API/ProjectOIDC.js +16 -24
- package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
- package/build/dist/Server/API/ProjectSSO.js +16 -24
- package/build/dist/Server/API/ProjectSSO.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +42 -75
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/Enterprise/EnterpriseEdition.js +79 -25
- package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -1
- package/build/dist/Server/Enterprise/EnterpriseFeature.js +24 -3
- package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -1
- package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +1 -1
- package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.js +63 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.js +41 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.js +183 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.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/IncidentFormRateLimit.js +559 -0
- package/build/dist/Server/Middleware/IncidentFormRateLimit.js.map +1 -0
- package/build/dist/Server/Middleware/UserAuthorization.js +11 -30
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +10 -13
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/IncidentCustomFieldService.js +83 -0
- package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeMemberService.js +8 -4
- package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeService.js +10 -1
- package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
- package/build/dist/Server/Services/IncidentFormService.js +1202 -0
- package/build/dist/Server/Services/IncidentFormService.js.map +1 -0
- package/build/dist/Server/Services/IncidentFormSubmissionService.js +91 -0
- package/build/dist/Server/Services/IncidentFormSubmissionService.js.map +1 -0
- package/build/dist/Server/Services/IncidentGroupingEngineService.js +20 -8
- package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +106 -4
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentTemplateService.js +18 -0
- package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +14 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MessageQueueLabelRuleEngineService.js +262 -0
- package/build/dist/Server/Services/MessageQueueLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueLabelRuleService.js +13 -0
- package/build/dist/Server/Services/MessageQueueLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueOwnerRuleEngineService.js +334 -0
- package/build/dist/Server/Services/MessageQueueOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueOwnerRuleService.js +13 -0
- package/build/dist/Server/Services/MessageQueueOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueOwnerTeamService.js +62 -0
- package/build/dist/Server/Services/MessageQueueOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueOwnerUserService.js +62 -0
- package/build/dist/Server/Services/MessageQueueOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/MessageQueueService.js +1140 -0
- package/build/dist/Server/Services/MessageQueueService.js.map +1 -0
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +25 -4
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +0 -54
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserService.js +1 -4
- package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +0 -46
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TeamComplianceSettingService.js +120 -31
- package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +8 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +12 -3
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +6 -3
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +109 -37
- package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +16 -0
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TablePermission.js +3 -3
- package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +9 -6
- package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/AgentContextCompactor.js +128 -0
- package/build/dist/Server/Utils/AI/Chat/AgentContextCompactor.js.map +1 -0
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +192 -50
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ChatRunCancellation.js +133 -0
- package/build/dist/Server/Utils/AI/Chat/ChatRunCancellation.js.map +1 -0
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +53 -13
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +1 -1
- package/build/dist/Server/Utils/AI/Chat/ToolOutputPager.js +206 -0
- package/build/dist/Server/Utils/AI/Chat/ToolOutputPager.js.map +1 -0
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +53 -26
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +4 -2
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +1 -29
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +24 -24
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +7 -2
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +29 -14
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +38 -14
- package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -1
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +2 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +40 -10
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +19 -7
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +19 -7
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +15 -18
- package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationThread.js +412 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationThread.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationThreadService.js +495 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationThreadService.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +10 -3
- package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -1
- package/build/dist/Server/Utils/EditionEnforcement.js +20 -158
- package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +193 -54
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/RelaxedScimTeamLocksReport.js +160 -0
- package/build/dist/Server/Utils/RelaxedScimTeamLocksReport.js.map +1 -0
- 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/SameOriginRequest.js +151 -0
- package/build/dist/Server/Utils/SameOriginRequest.js.map +1 -0
- 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/EntityRegistry.js +4 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js +1015 -0
- package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +4 -1
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
- package/build/dist/Server/Utils/VendorAssets.js +3 -3
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +3 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +3 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +10 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/AI/AIAgentRunLimits.js +91 -0
- package/build/dist/Types/AI/AIAgentRunLimits.js.map +1 -0
- package/build/dist/Types/Code/CodeType.js +2 -0
- package/build/dist/Types/Code/CodeType.js.map +1 -1
- package/build/dist/Types/Code/JsonSyntax.js +604 -0
- package/build/dist/Types/Code/JsonSyntax.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldCreateSettings.js +310 -0
- package/build/dist/Types/CustomField/CustomFieldCreateSettings.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js +38 -0
- package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldValueValidator.js +10 -7
- package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -1
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +1 -1
- package/build/dist/Types/Icon/IconProp.js +2 -0
- package/build/dist/Types/Icon/IconProp.js.map +1 -1
- package/build/dist/Types/Incident/IncidentFormIpAllowlist.js +119 -0
- package/build/dist/Types/Incident/IncidentFormIpAllowlist.js.map +1 -0
- package/build/dist/Types/Incident/IncidentFormPublic.js +678 -0
- package/build/dist/Types/Incident/IncidentFormPublic.js.map +1 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +10 -3
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
- package/build/dist/Types/MessageQueue/MessageQueueIdentity.js +184 -0
- package/build/dist/Types/MessageQueue/MessageQueueIdentity.js.map +1 -0
- package/build/dist/Types/MessageQueue/MessageQueueManualIdentity.js +190 -0
- package/build/dist/Types/MessageQueue/MessageQueueManualIdentity.js.map +1 -0
- package/build/dist/Types/MessageQueue/MessageQueueMetricCatalog.js +1057 -0
- package/build/dist/Types/MessageQueue/MessageQueueMetricCatalog.js.map +1 -0
- package/build/dist/Types/MessageQueue/MessagingSystem.js +394 -0
- package/build/dist/Types/MessageQueue/MessagingSystem.js.map +1 -0
- package/build/dist/Types/MessageQueue/MessagingTelemetryResolver.js +1768 -0
- package/build/dist/Types/MessageQueue/MessagingTelemetryResolver.js.map +1 -0
- package/build/dist/Types/Monitor/MessageQueueAlertTemplates.js +1207 -0
- package/build/dist/Types/Monitor/MessageQueueAlertTemplates.js.map +1 -0
- package/build/dist/Types/Permission.js +274 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +10 -4
- package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -1
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +12 -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/Team/ComplianceNotificationChannel.js +4 -3
- package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -1
- package/build/dist/Types/Team/ComplianceRule.js +48 -14
- package/build/dist/Types/Team/ComplianceRule.js.map +1 -1
- package/build/dist/Types/Team/ComplianceRuleType.js +2 -2
- package/build/dist/Types/Telemetry/EntityType.js +16 -0
- package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
- package/build/dist/Types/XML.js +23 -0
- package/build/dist/Types/XML.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +641 -266
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditorCommands.js +464 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditorCommands.js.map +1 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditorHighlight.js +69 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditorHighlight.js.map +1 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditorLanguages.js +108 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditorLanguages.js.map +1 -0
- package/build/dist/UI/Components/CodeEditor/YamlEditor.js +14 -186
- package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -1
- package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js +5 -1
- package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +5 -14
- package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +14 -13
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js +1 -1
- package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +8 -3
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +8 -0
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +11 -3
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +2 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +14 -4
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +710 -65
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditorHistory.js +238 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditorHistory.js.map +1 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownListEditing.js +1028 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownListEditing.js.map +1 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownPaste.js +1088 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownPaste.js.map +1 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +70 -10
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownVisualEditing.js +887 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownVisualEditing.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +57 -6
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js +7 -5
- package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js.map +1 -1
- package/build/dist/UI/Components/Table/EmptyTableMessage.js +51 -0
- package/build/dist/UI/Components/Table/EmptyTableMessage.js.map +1 -0
- package/build/dist/UI/Components/Table/Table.js +30 -11
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +9 -2
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js +63 -1
- package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +4 -2
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js +91 -10
- package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +14 -4
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js +9 -3
- package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ModelFieldPicker.js +1 -2
- package/build/dist/UI/Components/Workflow/ModelFieldPicker.js.map +1 -1
- package/build/dist/Utils/LabelRuleImportExport.js +2 -0
- package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
- package/build/dist/Utils/Markdown/UntrustedMarkdown.js +988 -0
- package/build/dist/Utils/Markdown/UntrustedMarkdown.js.map +1 -0
- package/build/dist/Utils/Monitor/IncomingEmailMonitorRequestUtil.js +34 -0
- package/build/dist/Utils/Monitor/IncomingEmailMonitorRequestUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorLogSummaryUtil.js +148 -0
- package/build/dist/Utils/Monitor/MonitorLogSummaryUtil.js.map +1 -0
- package/build/dist/Utils/Telemetry/EntityKey.js +36 -0
- package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
- package/package.json +1 -3
- package/Server/Utils/CommunityEditionSsoReport.ts +0 -361
- package/Tests/App/Accounts/LoginSsoEdition.test.tsx +0 -616
- package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +0 -217
- package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +0 -271
- package/Tests/App/Dashboard/CloudDocumentationMarkdown.test.ts +0 -215
- package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +0 -784
- package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +0 -518
- package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +0 -419
- package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +0 -769
- package/Tests/UI/Components/CodeEditor.test.tsx +0 -638
- package/Tests/UI/Components/MonacoLoader.test.ts +0 -67
- package/Tests/UI/Components/MonacoRuntime.test.ts +0 -121
- package/Tests/UI/Components/Telemetry/TelemetryRetentionConfigForm.test.tsx +0 -329
- package/Tests/UI/Components/YamlEditor.test.tsx +0 -847
- package/UI/Components/CodeEditor/MonacoLoader.ts +0 -36
- package/UI/Components/Telemetry/TelemetryRetentionConfigForm.tsx +0 -319
- package/UI/Components/Telemetry/TelemetryRetentionConfigSummary.tsx +0 -223
- package/build/dist/Server/Utils/CommunityEditionSsoReport.js +0 -223
- package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +0 -1
- package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +0 -34
- package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +0 -1
- package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js +0 -171
- package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js.map +0 -1
- package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js +0 -119
- package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js.map +0 -1
|
@@ -0,0 +1,2590 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_MESSAGE_QUEUE_MIN_SPANS,
|
|
4
|
+
DiscoveredMessageQueue,
|
|
5
|
+
MESSAGE_QUEUE_DISCOVERY_EXCLUDED_SPAN_KIND,
|
|
6
|
+
MESSAGE_QUEUE_DISCOVERY_MAX_EXECUTION_SECONDS,
|
|
7
|
+
MESSAGE_QUEUE_DISCOVERY_METRIC_NAMES,
|
|
8
|
+
MESSAGE_QUEUE_DISCOVERY_QUERY_SETTINGS,
|
|
9
|
+
MESSAGE_QUEUE_DISCOVERY_SHADOWED_VALUE,
|
|
10
|
+
MESSAGE_QUEUE_LATE_METRIC_MINUTES,
|
|
11
|
+
MESSAGE_QUEUE_LATE_METRIC_NAMES,
|
|
12
|
+
MESSAGE_QUEUE_METRIC_PROJECTS_SQL_MARKER,
|
|
13
|
+
MESSAGE_QUEUE_METRIC_SQL_MARKER,
|
|
14
|
+
MESSAGE_QUEUE_MIN_SPANS_ENV,
|
|
15
|
+
MESSAGE_QUEUE_NATS_GENERATED_SUBJECTS,
|
|
16
|
+
MESSAGE_QUEUE_RABBITMQ_DEFAULT_EXCHANGES,
|
|
17
|
+
MESSAGE_QUEUE_RANKED_ROWS_PER_CAPPED_ROW,
|
|
18
|
+
MESSAGE_QUEUE_SPAN_SQL_MARKER,
|
|
19
|
+
MessageQueueDiscoveryWindow,
|
|
20
|
+
MessageQueueEvidence,
|
|
21
|
+
MessagingMetricDiscoveryRow,
|
|
22
|
+
MessagingSpanDiscoveryRow,
|
|
23
|
+
buildMessagingMetricDiscoverySql,
|
|
24
|
+
buildMessagingMetricProjectsSql,
|
|
25
|
+
buildMessagingSpanDiscoverySql,
|
|
26
|
+
getDiscoveredMessageQueueBrokerAddress,
|
|
27
|
+
getDiscoveredMessageQueueEvidenceCount,
|
|
28
|
+
getMessageQueueCreationSource,
|
|
29
|
+
getMessageQueueMinSpans,
|
|
30
|
+
getMessagingDiscoveryColumn,
|
|
31
|
+
isMessageQueueAutoCreateCandidate,
|
|
32
|
+
mergeDiscoveredMessageQueues,
|
|
33
|
+
resolveMessagingMetricDiscoveryRows,
|
|
34
|
+
resolveMessagingSpanDiscoveryRows,
|
|
35
|
+
} from "../../../../Server/Utils/Telemetry/MessageQueueDiscovery";
|
|
36
|
+
import { DATABASE_ENDPOINT_SQL_MARKER } from "../../../../Server/Utils/Telemetry/DatabaseEndpointDiscovery";
|
|
37
|
+
import { QUERY_SETTINGS } from "../../../../Server/Utils/Telemetry/ServiceDependencyDiscovery";
|
|
38
|
+
import { dataSourceOptions } from "../../../../Server/Infrastructure/ClickhouseConfig";
|
|
39
|
+
import { SpanKind, SpanStatus } from "../../../../Models/AnalyticsModels/Span";
|
|
40
|
+
import {
|
|
41
|
+
AZURE_MESSAGING_PROVIDER_NAMESPACES,
|
|
42
|
+
AZURE_RESOURCE_PROVIDER_ATTRIBUTES,
|
|
43
|
+
MESSAGE_QUEUE_METRIC_EXCLUDED_SERIES_ATTRIBUTES,
|
|
44
|
+
MESSAGING_DESTINATION_ATTRIBUTES,
|
|
45
|
+
MESSAGING_METRIC_RESOLVER_INPUT_ATTRIBUTES,
|
|
46
|
+
MESSAGING_RESOLVER_INPUT_ATTRIBUTES,
|
|
47
|
+
MESSAGING_SYSTEM_ATTRIBUTE,
|
|
48
|
+
MESSAGING_TEMPORARY_FLAG_ATTRIBUTES,
|
|
49
|
+
MESSAGING_TRIGGER_ATTRIBUTES,
|
|
50
|
+
RABBITMQ_ROUTING_KEY_ATTRIBUTES,
|
|
51
|
+
ResolvedMessagingDestination,
|
|
52
|
+
hasMessagingTrigger,
|
|
53
|
+
resolveMessagingMetricDatapoint,
|
|
54
|
+
resolveMessagingSpan,
|
|
55
|
+
} from "../../../../Types/MessageQueue/MessagingTelemetryResolver";
|
|
56
|
+
import {
|
|
57
|
+
StoredSpan,
|
|
58
|
+
foldStoredSpan,
|
|
59
|
+
foldedSpanRow,
|
|
60
|
+
natsSpellings,
|
|
61
|
+
} from "./MessageQueueSpanQueryTwin";
|
|
62
|
+
import {
|
|
63
|
+
MESSAGE_QUEUE_BROKER_METRIC_NAMES,
|
|
64
|
+
MESSAGE_QUEUE_METRICS,
|
|
65
|
+
MESSAGING_CLIENT_METRIC_NAMES,
|
|
66
|
+
MessageQueueMetricDescriptor,
|
|
67
|
+
} from "../../../../Types/MessageQueue/MessageQueueMetricCatalog";
|
|
68
|
+
import {
|
|
69
|
+
MessageQueueIdentity,
|
|
70
|
+
buildMessageQueueIdentifier,
|
|
71
|
+
toMessageQueueIdentity,
|
|
72
|
+
} from "../../../../Types/MessageQueue/MessageQueueIdentity";
|
|
73
|
+
import { getMessagingBrokerMetricsSource } from "../../../../Types/MessageQueue/MessagingSystem";
|
|
74
|
+
import {
|
|
75
|
+
FixtureAttributes,
|
|
76
|
+
METRIC_FIXTURES,
|
|
77
|
+
MetricFixture,
|
|
78
|
+
NUMERIC_SPAN_KIND_CASES,
|
|
79
|
+
SPAN_FIXTURES,
|
|
80
|
+
SpanFixture,
|
|
81
|
+
toStoredColumns,
|
|
82
|
+
toStoredKind,
|
|
83
|
+
} from "../../../Types/MessageQueue/MessagingTelemetryFixtures";
|
|
84
|
+
|
|
85
|
+
/*
|
|
86
|
+
* Message queue discovery turns the window's messaging spans and broker /
|
|
87
|
+
* client metric datapoints into MessageQueue rows. These tests pin what
|
|
88
|
+
* decides whether it is right:
|
|
89
|
+
*
|
|
90
|
+
* 1. the queries — the core's own attribute lists selected and grouped on,
|
|
91
|
+
* nothing per message or per instance, the ingest trigger (Azure
|
|
92
|
+
* provider values included) as a prefilter that can only admit MORE
|
|
93
|
+
* than ingest, late cloud metrics read further back, bounded (the cap
|
|
94
|
+
* shared out by broker or metric, part of it rotating every run), and
|
|
95
|
+
* recognisable by their markers;
|
|
96
|
+
* 2. that a stored row, read back through those columns, resolves to
|
|
97
|
+
* exactly the queue ingest keyed its telemetry on — for every fixture of
|
|
98
|
+
* the core's real-instrumentation corpus — and that the span query's
|
|
99
|
+
* folding of values that vary per message (held here through its
|
|
100
|
+
* TypeScript twin, MessageQueueSpanQueryTwin) never changes what the
|
|
101
|
+
* resolver answers;
|
|
102
|
+
* 3. merging (one entry per identity, the most specific system, the
|
|
103
|
+
* busiest spelling and address) and the create policy.
|
|
104
|
+
*
|
|
105
|
+
* The real-ClickHouse half lives in
|
|
106
|
+
* App/Tests/Workers/Jobs/TelemetryEntity/MessageQueueDiscoveryClickhouse.test.ts.
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
const PROJECT_ID: string = "8a4c5b1e-2b3f-4c1d-9e8f-1a2b3c4d5e6f";
|
|
110
|
+
|
|
111
|
+
const WINDOW: MessageQueueDiscoveryWindow = {
|
|
112
|
+
projectId: PROJECT_ID,
|
|
113
|
+
startSql: "toDateTime64('2026-09-24 09:45:00.000000000', 9)",
|
|
114
|
+
endSql: "toDateTime64('2026-09-24 10:00:00.000000000', 9)",
|
|
115
|
+
maxRows: 321,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Keys that vary per message, per partition or per client instance.
|
|
119
|
+
const PER_INSTANCE_KEYS: ReadonlyArray<string> = [
|
|
120
|
+
"resource.k8s.pod.name",
|
|
121
|
+
"resource.host.name",
|
|
122
|
+
"resource.service.instance.id",
|
|
123
|
+
"resource.container.id",
|
|
124
|
+
"messaging.message.id",
|
|
125
|
+
"messaging.message.conversation_id",
|
|
126
|
+
"messaging.kafka.message.offset",
|
|
127
|
+
"messaging.kafka.offset",
|
|
128
|
+
"messaging.kafka.message.key",
|
|
129
|
+
"messaging.destination.partition.id",
|
|
130
|
+
"messaging.kafka.destination.partition",
|
|
131
|
+
"messaging.kafka.partition",
|
|
132
|
+
"messaging.client.id",
|
|
133
|
+
"messaging.client_id",
|
|
134
|
+
"messaging.consumer_id",
|
|
135
|
+
"messaging.batch.message_count",
|
|
136
|
+
"messaging.message.body.size",
|
|
137
|
+
"messaging.rabbitmq.delivery_tag",
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
/*
|
|
141
|
+
* Characters String.prototype.trim removes and ClickHouse's trimBoth keeps,
|
|
142
|
+
* built from their code points so no formatter can rewrite them.
|
|
143
|
+
*/
|
|
144
|
+
const NO_BREAK_SPACE: string = String.fromCharCode(0xa0);
|
|
145
|
+
const LINE_SEPARATOR: string = String.fromCharCode(0x2028);
|
|
146
|
+
const BYTE_ORDER_MARK: string = String.fromCharCode(0xfeff);
|
|
147
|
+
const IDEOGRAPHIC_SPACE: string = String.fromCharCode(0x3000);
|
|
148
|
+
// Lowercases to two UTF-16 code units ("i" and a combining dot).
|
|
149
|
+
const DOTTED_CAPITAL_I: string = String.fromCharCode(0x130);
|
|
150
|
+
|
|
151
|
+
function collapse(sql: string): string {
|
|
152
|
+
return sql.replace(/\s+/g, " ");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/*
|
|
156
|
+
* Every column a query selects, in order, with the key it reads: a plain
|
|
157
|
+
* `attributes['<key>'] AS <column>`, or a folded
|
|
158
|
+
* `if(…, attributes['<key>']) AS <column>`, which holds the key as stored
|
|
159
|
+
* whenever it holds more than a placeholder.
|
|
160
|
+
*/
|
|
161
|
+
function selectedColumns(sql: string): Array<[string, string]> {
|
|
162
|
+
return Array.from(sql.matchAll(/attributes\['([^']+)'\]\)? AS (a\d+)/g)).map(
|
|
163
|
+
(match: RegExpMatchArray): [string, string] => {
|
|
164
|
+
return [match[1]!, match[2]!];
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Every attribute key a query reads anywhere.
|
|
170
|
+
function attributeReads(sql: string): Set<string> {
|
|
171
|
+
return new Set<string>(
|
|
172
|
+
Array.from(sql.matchAll(/attributes\['([^']+)'\]/g)).map(
|
|
173
|
+
(match: RegExpMatchArray): string => {
|
|
174
|
+
return match[1]!;
|
|
175
|
+
},
|
|
176
|
+
),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// The values of one SQL `IN (...)` tuple that follows `prefix`.
|
|
181
|
+
function inTuple(sql: string, prefix: string): Array<string> {
|
|
182
|
+
const start: number = sql.indexOf(prefix);
|
|
183
|
+
expect(start).toBeGreaterThanOrEqual(0);
|
|
184
|
+
const open: number = sql.indexOf("(", start + prefix.length - 1);
|
|
185
|
+
const close: number = sql.indexOf(")", open);
|
|
186
|
+
return Array.from(sql.substring(open, close).matchAll(/'([^']*)'/g)).map(
|
|
187
|
+
(match: RegExpMatchArray): string => {
|
|
188
|
+
return match[1]!;
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/*
|
|
194
|
+
* A span query row for stored attributes: every selected key's value as
|
|
195
|
+
* ClickHouse's Map(String, String) returns it (toStoredColumns), under its
|
|
196
|
+
* column.
|
|
197
|
+
*/
|
|
198
|
+
function spanRow(
|
|
199
|
+
kind: string | null,
|
|
200
|
+
attributes: FixtureAttributes,
|
|
201
|
+
overrides: Partial<MessagingSpanDiscoveryRow> = {},
|
|
202
|
+
): MessagingSpanDiscoveryRow {
|
|
203
|
+
const stored: Record<string, string> = toStoredColumns(
|
|
204
|
+
attributes,
|
|
205
|
+
MESSAGING_RESOLVER_INPUT_ATTRIBUTES,
|
|
206
|
+
);
|
|
207
|
+
const row: MessagingSpanDiscoveryRow = {
|
|
208
|
+
kind: kind,
|
|
209
|
+
spanCount: "5",
|
|
210
|
+
errorCount: "0",
|
|
211
|
+
lastSeenUnixMs: "1790243100123",
|
|
212
|
+
};
|
|
213
|
+
MESSAGING_RESOLVER_INPUT_ATTRIBUTES.forEach(
|
|
214
|
+
(key: string, index: number): void => {
|
|
215
|
+
row[getMessagingDiscoveryColumn(index)] = stored[key];
|
|
216
|
+
},
|
|
217
|
+
);
|
|
218
|
+
return { ...row, ...overrides };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function metricRow(
|
|
222
|
+
name: string,
|
|
223
|
+
attributes: FixtureAttributes,
|
|
224
|
+
overrides: Partial<MessagingMetricDiscoveryRow> = {},
|
|
225
|
+
): MessagingMetricDiscoveryRow {
|
|
226
|
+
const stored: Record<string, string> = toStoredColumns(
|
|
227
|
+
attributes,
|
|
228
|
+
MESSAGING_METRIC_RESOLVER_INPUT_ATTRIBUTES,
|
|
229
|
+
);
|
|
230
|
+
const row: MessagingMetricDiscoveryRow = {
|
|
231
|
+
name: name,
|
|
232
|
+
pointCount: "12",
|
|
233
|
+
lastSeenUnixMs: "1790243100123",
|
|
234
|
+
};
|
|
235
|
+
MESSAGING_METRIC_RESOLVER_INPUT_ATTRIBUTES.forEach(
|
|
236
|
+
(key: string, index: number): void => {
|
|
237
|
+
row[getMessagingDiscoveryColumn(index)] = stored[key];
|
|
238
|
+
},
|
|
239
|
+
);
|
|
240
|
+
return { ...row, ...overrides };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function identifierOf(
|
|
244
|
+
resolved: ResolvedMessagingDestination | null,
|
|
245
|
+
): string | null {
|
|
246
|
+
if (!resolved) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
const identity: MessageQueueIdentity | null =
|
|
250
|
+
toMessageQueueIdentity(resolved);
|
|
251
|
+
return identity ? buildMessageQueueIdentifier(identity) : null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function identifiers(queues: Array<DiscoveredMessageQueue>): Array<string> {
|
|
255
|
+
return queues.map((queue: DiscoveredMessageQueue): string => {
|
|
256
|
+
return queue.identifier;
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function getterOf(attributes: FixtureAttributes): (key: string) => unknown {
|
|
261
|
+
return (key: string): unknown => {
|
|
262
|
+
return Object.prototype.hasOwnProperty.call(attributes, key)
|
|
263
|
+
? attributes[key]
|
|
264
|
+
: undefined;
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const KAFKA_ORDERS: FixtureAttributes = {
|
|
269
|
+
"messaging.system": "kafka",
|
|
270
|
+
"messaging.destination.name": "orders",
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const SERVICE_BUS_HOST: string = "orders-prod.servicebus.windows.net";
|
|
274
|
+
|
|
275
|
+
function evidence(
|
|
276
|
+
overrides: Partial<MessageQueueEvidence>,
|
|
277
|
+
): MessageQueueEvidence {
|
|
278
|
+
return {
|
|
279
|
+
count: 1,
|
|
280
|
+
errorCount: 0,
|
|
281
|
+
directions: { publish: 0, consume: 0, settle: 0, unknown: 1 },
|
|
282
|
+
brokerAddress: null,
|
|
283
|
+
lastSeenAt: null,
|
|
284
|
+
...overrides,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function discovered(
|
|
289
|
+
overrides: Partial<DiscoveredMessageQueue>,
|
|
290
|
+
): DiscoveredMessageQueue {
|
|
291
|
+
const identity: MessageQueueIdentity = toMessageQueueIdentity({
|
|
292
|
+
system: "kafka",
|
|
293
|
+
destination: "orders",
|
|
294
|
+
})!;
|
|
295
|
+
return {
|
|
296
|
+
identity: identity,
|
|
297
|
+
identifier: buildMessageQueueIdentifier(identity)!,
|
|
298
|
+
system: "kafka",
|
|
299
|
+
destination: "orders",
|
|
300
|
+
spans: null,
|
|
301
|
+
brokerMetrics: null,
|
|
302
|
+
clientMetrics: null,
|
|
303
|
+
...overrides,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
describe("buildMessagingSpanDiscoverySql", () => {
|
|
308
|
+
const raw: string = buildMessagingSpanDiscoverySql(WINDOW);
|
|
309
|
+
const sql: string = collapse(raw);
|
|
310
|
+
|
|
311
|
+
test("carries a unique marker comment so the query is recognisable", () => {
|
|
312
|
+
expect(MESSAGE_QUEUE_SPAN_SQL_MARKER).toBe("message-queue-span-discovery");
|
|
313
|
+
expect(sql).toContain(`/* ${MESSAGE_QUEUE_SPAN_SQL_MARKER} */`);
|
|
314
|
+
expect(sql).not.toContain(MESSAGE_QUEUE_METRIC_SQL_MARKER);
|
|
315
|
+
expect(sql).not.toContain(MESSAGE_QUEUE_METRIC_PROJECTS_SQL_MARKER);
|
|
316
|
+
expect(sql).not.toContain(DATABASE_ENDPOINT_SQL_MARKER);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test("reads one project's spans inside the window, naming the table once", () => {
|
|
320
|
+
expect(sql).toContain(`WHERE projectId = '${PROJECT_ID}'`);
|
|
321
|
+
expect(sql).toContain(`AND startTime >= ${WINDOW.startSql}`);
|
|
322
|
+
expect(sql).toContain(`AND startTime < ${WINDOW.endSql}`);
|
|
323
|
+
// The real-ClickHouse suite redirects the table with one replace.
|
|
324
|
+
expect(sql.split("oneuptime.SpanItemV3").length - 1).toBe(1);
|
|
325
|
+
expect(sql).toContain("FROM oneuptime.SpanItemV3");
|
|
326
|
+
expect(sql).not.toContain("MetricItemV3");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test("reads every span ingest may key: any stored kind but SERVER, a missing one included", () => {
|
|
330
|
+
expect(MESSAGE_QUEUE_DISCOVERY_EXCLUDED_SPAN_KIND).toBe(SpanKind.Server);
|
|
331
|
+
expect(sql).toContain("AND ifNull(kind, '') != 'SPAN_KIND_SERVER'");
|
|
332
|
+
// No allow-list of kinds: a pipeline can store any kind, or none.
|
|
333
|
+
expect(sql).not.toMatch(/kind IN/i);
|
|
334
|
+
/*
|
|
335
|
+
* Outside string literals the column appears five times, and filters in
|
|
336
|
+
* one of them only: the returned and the grouped column, the rotating
|
|
337
|
+
* order's hash, the predicate and the GROUP BY column.
|
|
338
|
+
*/
|
|
339
|
+
expect(
|
|
340
|
+
sql
|
|
341
|
+
.replace(/'[^']*'/g, "''")
|
|
342
|
+
.match(/(SELECT |GROUP BY |ifNull\()?\bkind\b[^,]*/g),
|
|
343
|
+
).toEqual([
|
|
344
|
+
"SELECT kind",
|
|
345
|
+
"SELECT kind",
|
|
346
|
+
"ifNull(kind",
|
|
347
|
+
"ifNull(kind",
|
|
348
|
+
"GROUP BY kind",
|
|
349
|
+
]);
|
|
350
|
+
expect(sql).toContain(
|
|
351
|
+
"rotation FROM oneuptime.SpanItemV3 WHERE projectId =",
|
|
352
|
+
);
|
|
353
|
+
expect(sql).toContain("cityHash64(toUnixTimestamp64Milli(");
|
|
354
|
+
expect(sql).toContain("), ifNull(kind, ''), a0, a1,");
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("the kinds it reads resolve exactly as ingest resolved them: another SERVER spelling to nothing", () => {
|
|
358
|
+
// ClickHouse's `ifNull(kind, '') != 'SPAN_KIND_SERVER'`, as TypeScript.
|
|
359
|
+
const read: (kind: string | null) => boolean = (
|
|
360
|
+
kind: string | null,
|
|
361
|
+
): boolean => {
|
|
362
|
+
return (kind ?? "") !== MESSAGE_QUEUE_DISCOVERY_EXCLUDED_SPAN_KIND;
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// Stored kinds a trace pipeline's Span Kind Remapper can write.
|
|
366
|
+
const named: Array<string | null> = [
|
|
367
|
+
SpanKind.Producer,
|
|
368
|
+
SpanKind.Consumer,
|
|
369
|
+
SpanKind.Client,
|
|
370
|
+
SpanKind.Internal,
|
|
371
|
+
"SPAN_KIND_UNSPECIFIED",
|
|
372
|
+
"PRODUCER",
|
|
373
|
+
"consumer",
|
|
374
|
+
"",
|
|
375
|
+
null,
|
|
376
|
+
];
|
|
377
|
+
for (const kind of named) {
|
|
378
|
+
expect({ kind, read: read(kind) }).toEqual({ kind, read: true });
|
|
379
|
+
expect({
|
|
380
|
+
kind,
|
|
381
|
+
queues: identifiers(
|
|
382
|
+
resolveMessagingSpanDiscoveryRows([spanRow(kind, KAFKA_ORDERS)]),
|
|
383
|
+
),
|
|
384
|
+
}).toEqual({ kind, queues: ["kafka||orders"] });
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Other spellings of SERVER are read, and refused by the resolver.
|
|
388
|
+
for (const kind of ["SERVER", "server", " span_kind_server "]) {
|
|
389
|
+
expect(read(kind)).toBe(true);
|
|
390
|
+
expect(
|
|
391
|
+
resolveMessagingSpanDiscoveryRows([spanRow(kind, KAFKA_ORDERS)]),
|
|
392
|
+
).toEqual([]);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// The stored SERVER kind is not even read; the resolver refuses it too.
|
|
396
|
+
expect(read(SpanKind.Server)).toBe(false);
|
|
397
|
+
expect(
|
|
398
|
+
resolveMessagingSpan({
|
|
399
|
+
getAttribute: getterOf(KAFKA_ORDERS),
|
|
400
|
+
kind: SpanKind.Server,
|
|
401
|
+
}),
|
|
402
|
+
).toBeNull();
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test("a span without a kind resolves like one ingest keyed without a kind", () => {
|
|
406
|
+
const attributes: FixtureAttributes = {
|
|
407
|
+
...KAFKA_ORDERS,
|
|
408
|
+
"messaging.operation": "receive",
|
|
409
|
+
};
|
|
410
|
+
const atIngest: ResolvedMessagingDestination | null = resolveMessagingSpan({
|
|
411
|
+
getAttribute: getterOf(attributes),
|
|
412
|
+
kind: undefined,
|
|
413
|
+
});
|
|
414
|
+
const queues: Array<DiscoveredMessageQueue> =
|
|
415
|
+
resolveMessagingSpanDiscoveryRows([spanRow(null, attributes)]);
|
|
416
|
+
|
|
417
|
+
expect(atIngest?.direction).toBe("consume");
|
|
418
|
+
expect(identifiers(queues)).toEqual([identifierOf(atIngest)]);
|
|
419
|
+
expect(queues[0]!.spans?.directions.consume).toBe(5);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test('a numeric kind a Span Kind Remapper wrote: its stored text resolves as ingest resolved the value, so "2" and "02" are SERVER like 2', () => {
|
|
423
|
+
// ClickHouse's `ifNull(kind, '') != 'SPAN_KIND_SERVER'`, as TypeScript.
|
|
424
|
+
const read: (kind: string | null) => boolean = (
|
|
425
|
+
kind: string | null,
|
|
426
|
+
): boolean => {
|
|
427
|
+
return (kind ?? "") !== MESSAGE_QUEUE_DISCOVERY_EXCLUDED_SPAN_KIND;
|
|
428
|
+
};
|
|
429
|
+
const spans: ReadonlyArray<FixtureAttributes> = [
|
|
430
|
+
KAFKA_ORDERS,
|
|
431
|
+
// A joined name: a consumer names the queue, anything else the exchange.
|
|
432
|
+
{
|
|
433
|
+
"messaging.system": "rabbitmq",
|
|
434
|
+
"messaging.destination.name": "direct_logs:warning",
|
|
435
|
+
"messaging.rabbitmq.destination.routing_key": "warning",
|
|
436
|
+
},
|
|
437
|
+
];
|
|
438
|
+
|
|
439
|
+
for (const { kind, reads } of NUMERIC_SPAN_KIND_CASES) {
|
|
440
|
+
// What the kind column keeps: a number's JSON text, a string as is.
|
|
441
|
+
const stored: string | null = toStoredKind(kind);
|
|
442
|
+
expect(stored).toBe(typeof kind === "number" ? String(kind) : kind);
|
|
443
|
+
// The query reads every one of them: it leaves out SPAN_KIND_SERVER alone.
|
|
444
|
+
expect({ kind, read: read(stored) }).toEqual({ kind, read: true });
|
|
445
|
+
|
|
446
|
+
for (const attributes of spans) {
|
|
447
|
+
const atIngest: ResolvedMessagingDestination | null =
|
|
448
|
+
resolveMessagingSpan({
|
|
449
|
+
getAttribute: getterOf(attributes),
|
|
450
|
+
kind: kind,
|
|
451
|
+
});
|
|
452
|
+
const queues: Array<DiscoveredMessageQueue> =
|
|
453
|
+
resolveMessagingSpanDiscoveryRows([spanRow(stored, attributes)]);
|
|
454
|
+
const identifier: string | null = identifierOf(atIngest);
|
|
455
|
+
|
|
456
|
+
expect({ kind, keyed: atIngest !== null }).toEqual({
|
|
457
|
+
kind,
|
|
458
|
+
keyed: reads !== SpanKind.Server,
|
|
459
|
+
});
|
|
460
|
+
expect({ kind, queues: identifiers(queues) }).toEqual({
|
|
461
|
+
kind,
|
|
462
|
+
queues: identifier ? [identifier] : [],
|
|
463
|
+
});
|
|
464
|
+
if (atIngest) {
|
|
465
|
+
expect(queues[0]!.spans?.directions[atIngest.direction]).toBe(5);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test("selects exactly the attributes resolveMessagingSpan reads, one column each, in list order", () => {
|
|
472
|
+
const columns: Array<[string, string]> = selectedColumns(raw);
|
|
473
|
+
expect(
|
|
474
|
+
columns.map((entry: [string, string]): string => {
|
|
475
|
+
return entry[0];
|
|
476
|
+
}),
|
|
477
|
+
).toEqual([...MESSAGING_RESOLVER_INPUT_ATTRIBUTES]);
|
|
478
|
+
columns.forEach((entry: [string, string], index: number): void => {
|
|
479
|
+
expect(entry[1]).toBe(getMessagingDiscoveryColumn(index));
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
test("reads no attribute the resolver does not", () => {
|
|
484
|
+
for (const key of attributeReads(raw)) {
|
|
485
|
+
expect(MESSAGING_RESOLVER_INPUT_ATTRIBUTES).toContain(key);
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
test("groups on the kind and every attribute column, and on nothing else", () => {
|
|
490
|
+
const columns: string = MESSAGING_RESOLVER_INPUT_ATTRIBUTES.map(
|
|
491
|
+
(_key: string, index: number): string => {
|
|
492
|
+
return getMessagingDiscoveryColumn(index);
|
|
493
|
+
},
|
|
494
|
+
).join(", ");
|
|
495
|
+
expect(sql).toContain(`GROUP BY kind, ${columns} ORDER BY`);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
test("counts the spans, the failed ones and the newest start", () => {
|
|
499
|
+
expect(SpanStatus.Error).toBe(2);
|
|
500
|
+
expect(sql).toContain("count() AS spanCount");
|
|
501
|
+
expect(sql).toContain("countIf(statusCode = 2) AS errorCount");
|
|
502
|
+
expect(sql).toContain(
|
|
503
|
+
"toUnixTimestamp64Milli(max(startTime)) AS lastSeenUnixMs",
|
|
504
|
+
);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test("drops spans without a messaging trigger on attributeKeys before reading the map", () => {
|
|
508
|
+
const allTriggers: string = `AND hasAny(attributeKeys, [${MESSAGING_TRIGGER_ATTRIBUTES.map(
|
|
509
|
+
(key: string): string => {
|
|
510
|
+
return `'${key}'`;
|
|
511
|
+
},
|
|
512
|
+
).join(", ")}])`;
|
|
513
|
+
expect(sql).toContain(allTriggers);
|
|
514
|
+
// The bloom-indexed prefilter comes before the map-reading conditions.
|
|
515
|
+
const where: string = sql.substring(sql.indexOf("WHERE projectId ="));
|
|
516
|
+
expect(where.indexOf(allTriggers)).toBeGreaterThan(0);
|
|
517
|
+
expect(where.indexOf(allTriggers)).toBeLessThan(
|
|
518
|
+
where.indexOf("multiSearchAnyCaseInsensitive"),
|
|
519
|
+
);
|
|
520
|
+
expect(where.indexOf(allTriggers)).toBeLessThan(where.indexOf("AND NOT ("));
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
test("admits an Azure provider key only when it names Service Bus or Event Hubs, like the ingest trigger", () => {
|
|
524
|
+
const presence: Array<string> = MESSAGING_TRIGGER_ATTRIBUTES.filter(
|
|
525
|
+
(key: string): boolean => {
|
|
526
|
+
return !AZURE_RESOURCE_PROVIDER_ATTRIBUTES.includes(key);
|
|
527
|
+
},
|
|
528
|
+
);
|
|
529
|
+
const namespaces: string = `[${AZURE_MESSAGING_PROVIDER_NAMESPACES.map(
|
|
530
|
+
(value: string): string => {
|
|
531
|
+
return `'${value}'`;
|
|
532
|
+
},
|
|
533
|
+
).join(", ")}]`;
|
|
534
|
+
|
|
535
|
+
expect(sql).toContain(
|
|
536
|
+
`AND (hasAny(attributeKeys, [${presence
|
|
537
|
+
.map((key: string): string => {
|
|
538
|
+
return `'${key}'`;
|
|
539
|
+
})
|
|
540
|
+
.join(
|
|
541
|
+
", ",
|
|
542
|
+
)}]) OR multiSearchAnyCaseInsensitive(attributes['az.namespace'], ${namespaces}) OR multiSearchAnyCaseInsensitive(attributes['azure.resource_provider.namespace'], ${namespaces}))`,
|
|
543
|
+
);
|
|
544
|
+
expect(AZURE_RESOURCE_PROVIDER_ATTRIBUTES).toEqual([
|
|
545
|
+
"az.namespace",
|
|
546
|
+
"azure.resource_provider.namespace",
|
|
547
|
+
]);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
test("never groups on anything that varies per message or per instance", () => {
|
|
551
|
+
for (const key of PER_INSTANCE_KEYS) {
|
|
552
|
+
expect(sql).not.toContain(key);
|
|
553
|
+
}
|
|
554
|
+
for (const column of ["primaryEntityId", "traceId", "spanId"]) {
|
|
555
|
+
expect(sql).not.toContain(column);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
test("never reads an attribute the corpus shows varies per message or instance", () => {
|
|
560
|
+
const read: Set<string> = attributeReads(raw);
|
|
561
|
+
const corpusKeys: Set<string> = new Set<string>();
|
|
562
|
+
for (const fixture of SPAN_FIXTURES) {
|
|
563
|
+
for (const key of Object.keys(fixture.attributes)) {
|
|
564
|
+
corpusKeys.add(key);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
for (const key of corpusKeys) {
|
|
568
|
+
if (!MESSAGING_RESOLVER_INPUT_ATTRIBUTES.includes(key)) {
|
|
569
|
+
expect({ key, read: read.has(key) }).toEqual({ key, read: false });
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
// The corpus does carry such keys: the check above is not vacuous.
|
|
573
|
+
expect(corpusKeys.has("messaging.kafka.message.offset")).toBe(true);
|
|
574
|
+
expect(corpusKeys.has("messaging.client_id")).toBe(true);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
test("is bounded: the row cap shared out by messaging system, busiest and rotating rows alternating", () => {
|
|
578
|
+
const heap: number = 321 * MESSAGE_QUEUE_RANKED_ROWS_PER_CAPPED_ROW;
|
|
579
|
+
const system: string = getMessagingDiscoveryColumn(
|
|
580
|
+
MESSAGING_RESOLVER_INPUT_ATTRIBUTES.indexOf(MESSAGING_SYSTEM_ATTRIBUTE),
|
|
581
|
+
);
|
|
582
|
+
expect(system).toBe("a0");
|
|
583
|
+
|
|
584
|
+
// The grouped rows reach the ranking through a top-N heap…
|
|
585
|
+
expect(sql).toContain(
|
|
586
|
+
`GROUP BY kind, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44 ORDER BY spanCount DESC, rotation LIMIT ${heap} ) )`,
|
|
587
|
+
);
|
|
588
|
+
// …are ranked within their system twice: busiest, and rotating…
|
|
589
|
+
expect(sql).toContain(
|
|
590
|
+
`row_number() OVER (PARTITION BY ${system} ORDER BY spanCount DESC, rotation) AS busiestRank, row_number() OVER (PARTITION BY ${system} ORDER BY rotation) AS rotationRank`,
|
|
591
|
+
);
|
|
592
|
+
// …and taken alternately from both, every system in turn, under the cap.
|
|
593
|
+
expect(sql).toContain(
|
|
594
|
+
"ORDER BY least(2 * busiestRank - 1, 2 * rotationRank), spanCount DESC, rotation LIMIT 321 SETTINGS",
|
|
595
|
+
);
|
|
596
|
+
// The rows it returns are the grouped columns alone.
|
|
597
|
+
expect(sql).toMatch(
|
|
598
|
+
/^ \/\* message-queue-span-discovery \*\/ SELECT kind, a0, a1, [a0-9, ]+a44, spanCount, errorCount, lastSeenUnixMs FROM \( SELECT \*, row_number\(\)/,
|
|
599
|
+
);
|
|
600
|
+
expect(MESSAGE_QUEUE_RANKED_ROWS_PER_CAPPED_ROW).toBe(50);
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
test("scans as wide as the server lets it, and ends in time: the queue queries' settings, no thread or block pin", () => {
|
|
604
|
+
expect(
|
|
605
|
+
sql.trim().endsWith(` ${MESSAGE_QUEUE_DISCOVERY_QUERY_SETTINGS}`),
|
|
606
|
+
).toBe(true);
|
|
607
|
+
for (const pin of [
|
|
608
|
+
"max_threads",
|
|
609
|
+
"max_block_size",
|
|
610
|
+
"preferred_block_size_bytes",
|
|
611
|
+
]) {
|
|
612
|
+
expect(sql).not.toContain(pin);
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test("a nonsensical row cap still yields a bounded query", () => {
|
|
617
|
+
for (const maxRows of [0, 0.5, -5, Number.NaN, Number.POSITIVE_INFINITY]) {
|
|
618
|
+
expect(
|
|
619
|
+
collapse(buildMessagingSpanDiscoverySql({ ...WINDOW, maxRows })),
|
|
620
|
+
).toContain("LIMIT 1 ");
|
|
621
|
+
}
|
|
622
|
+
expect(
|
|
623
|
+
collapse(buildMessagingSpanDiscoverySql({ ...WINDOW, maxRows: 12.9 })),
|
|
624
|
+
).toContain("LIMIT 12 ");
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
test("escapes the project id", () => {
|
|
628
|
+
expect(
|
|
629
|
+
buildMessagingSpanDiscoverySql({
|
|
630
|
+
...WINDOW,
|
|
631
|
+
projectId: "x' OR '1'='1",
|
|
632
|
+
}),
|
|
633
|
+
).toContain("projectId = 'x\\' OR \\'1\\'=\\'1'");
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
test("never mentions the dependency queries' routing markers", () => {
|
|
637
|
+
// ComputeServiceDependencies tells its queries apart by these.
|
|
638
|
+
expect(sql).not.toContain("SELECT DISTINCT projectId");
|
|
639
|
+
expect(sql).not.toContain("NOT IN");
|
|
640
|
+
expect(sql).not.toContain("INNER JOIN");
|
|
641
|
+
});
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
/*
|
|
645
|
+
* ---- The span query's folding of values that vary per message -------------
|
|
646
|
+
*
|
|
647
|
+
* The query leaves out spans the resolver certainly names no queue for, and
|
|
648
|
+
* folds the values it reads no more than a placeholder of, so a
|
|
649
|
+
* per-message value is not a group of its own. These tests pin the SQL to
|
|
650
|
+
* the rules, then hold the rules — through their TypeScript twin
|
|
651
|
+
* (MessageQueueSpanQueryTwin, which the real-ClickHouse suite holds the SQL
|
|
652
|
+
* to) — to the resolver: whatever is left out names no queue, and whatever
|
|
653
|
+
* is folded resolves exactly as it was stored, over the core's corpus and
|
|
654
|
+
* every variant below.
|
|
655
|
+
*/
|
|
656
|
+
|
|
657
|
+
// A span stored with these attributes: its map as ClickHouse keeps it.
|
|
658
|
+
function storedSpanOf(
|
|
659
|
+
kind: string | null,
|
|
660
|
+
attributes: FixtureAttributes,
|
|
661
|
+
): StoredSpan {
|
|
662
|
+
return {
|
|
663
|
+
kind: kind,
|
|
664
|
+
attributeKeys: Object.keys(attributes),
|
|
665
|
+
attributes: toStoredColumns(
|
|
666
|
+
attributes,
|
|
667
|
+
MESSAGING_RESOLVER_INPUT_ATTRIBUTES,
|
|
668
|
+
),
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// The resolver over a span query row's values (by key), as discovery runs it.
|
|
673
|
+
function resolveValues(
|
|
674
|
+
kind: string | null,
|
|
675
|
+
values: Readonly<Record<string, string>>,
|
|
676
|
+
): ResolvedMessagingDestination | null {
|
|
677
|
+
return resolveMessagingSpan({
|
|
678
|
+
getAttribute: (key: string): unknown => {
|
|
679
|
+
return Object.prototype.hasOwnProperty.call(values, key)
|
|
680
|
+
? values[key]
|
|
681
|
+
: undefined;
|
|
682
|
+
},
|
|
683
|
+
kind: kind,
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// What the fold makes of a span, and whether the resolver still agrees.
|
|
688
|
+
function foldOutcome(
|
|
689
|
+
label: string,
|
|
690
|
+
kind: string | null,
|
|
691
|
+
attributes: FixtureAttributes,
|
|
692
|
+
): {
|
|
693
|
+
label: string;
|
|
694
|
+
dropped: boolean;
|
|
695
|
+
resolved: ResolvedMessagingDestination | null;
|
|
696
|
+
} {
|
|
697
|
+
const span: StoredSpan = storedSpanOf(kind, attributes);
|
|
698
|
+
const folded: Record<string, string> | null = foldStoredSpan(span);
|
|
699
|
+
return {
|
|
700
|
+
label: label,
|
|
701
|
+
dropped: folded === null,
|
|
702
|
+
resolved: folded ? resolveValues(kind, folded) : null,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function expectFoldKeepsTheAnswer(
|
|
707
|
+
label: string,
|
|
708
|
+
kind: string | null,
|
|
709
|
+
attributes: FixtureAttributes,
|
|
710
|
+
): void {
|
|
711
|
+
const asStored: ResolvedMessagingDestination | null = resolveValues(
|
|
712
|
+
kind,
|
|
713
|
+
storedSpanOf(kind, attributes).attributes,
|
|
714
|
+
);
|
|
715
|
+
const outcome: {
|
|
716
|
+
label: string;
|
|
717
|
+
dropped: boolean;
|
|
718
|
+
resolved: ResolvedMessagingDestination | null;
|
|
719
|
+
} = foldOutcome(label, kind, attributes);
|
|
720
|
+
if (outcome.dropped) {
|
|
721
|
+
// Left out: only a span the resolver names no queue for.
|
|
722
|
+
expect({ label, asStored }).toEqual({ label, asStored: null });
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
expect(outcome).toEqual({ label, dropped: false, resolved: asStored });
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// A publish to RabbitMQ's `orders` exchange, which the variants alone overlay.
|
|
729
|
+
const RABBITMQ_ORDERS_EXCHANGE: FixtureAttributes = {
|
|
730
|
+
"messaging.system": "rabbitmq",
|
|
731
|
+
"messaging.destination.name": "orders",
|
|
732
|
+
"messaging.operation": "publish",
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
/*
|
|
736
|
+
* Attributes laid over every corpus fixture. Each is a shape some
|
|
737
|
+
* instrumentation sends, or the edge of a rule: blank, non-ASCII and
|
|
738
|
+
* control-character text beside a destination, per-message names beside a
|
|
739
|
+
* template and routing keys beside an exchange, flags in every case, NATS
|
|
740
|
+
* spellings and subjects the resolver does and does not treat as
|
|
741
|
+
* generated, and RabbitMQ names the resolver splits or reads the routing key
|
|
742
|
+
* of.
|
|
743
|
+
*/
|
|
744
|
+
const FOLD_VARIANTS: ReadonlyArray<FixtureAttributes> = [
|
|
745
|
+
{ "messaging.destination.template": "orders.{id}" },
|
|
746
|
+
{ "messaging.destination.template": " " },
|
|
747
|
+
{ "messaging.destination.template": NO_BREAK_SPACE },
|
|
748
|
+
{ "messaging.destination.template": `${IDEOGRAPHIC_SPACE}orders` },
|
|
749
|
+
{ "messaging.destination.template": "заказы" },
|
|
750
|
+
{ "messaging.destination.template": "\u0001" },
|
|
751
|
+
{ "messaging.destination.template": "(temporary)" },
|
|
752
|
+
{ "messaging.destination.template": "***" },
|
|
753
|
+
{
|
|
754
|
+
"messaging.destination.template": "orders.{id}",
|
|
755
|
+
"message_bus.destination": "orders.42",
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
"messaging.destination.name": "orders.4711",
|
|
759
|
+
"messaging.source.name": "orders.4712",
|
|
760
|
+
},
|
|
761
|
+
{ "messaging.destination.name": " ", "messaging.source.name": "orders.4712" },
|
|
762
|
+
{
|
|
763
|
+
"messaging.source.template": "orders.{id}",
|
|
764
|
+
"messaging.source.name": "orders.1",
|
|
765
|
+
},
|
|
766
|
+
{ "messaging.destination": "orders.4711" },
|
|
767
|
+
{ "message_bus.destination": "orders/Subscriptions/billing" },
|
|
768
|
+
{
|
|
769
|
+
"messaging.destination.name": "orders",
|
|
770
|
+
"message_bus.destination": `${DOTTED_CAPITAL_I}`,
|
|
771
|
+
},
|
|
772
|
+
{ "messaging.rabbitmq.destination.routing_key": "order.4711.created" },
|
|
773
|
+
{ "messaging.rabbitmq.routing_key": "order.4711.created" },
|
|
774
|
+
{ "messaging.rabbitmq.destination.routing_key": " " },
|
|
775
|
+
{ "messaging.rabbitmq.destination.routing_key": "new-invoice" },
|
|
776
|
+
{ "messaging.rabbitmq.destination.routing_key": "" },
|
|
777
|
+
{ "messaging.destination.temporary": "TRUE" },
|
|
778
|
+
{ "messaging.destination.anonymous": "True" },
|
|
779
|
+
{ "messaging.source.temporary": " true" },
|
|
780
|
+
{ "messaging.source.anonymous": "false" },
|
|
781
|
+
{ "messaging.destination.temporary": true },
|
|
782
|
+
{
|
|
783
|
+
"messaging.system": "NATS",
|
|
784
|
+
"messaging.destination.name":
|
|
785
|
+
"$JS.ACK.ORDERS.billing.1.42.42.1727698123456789012.0",
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
"messaging.system": "JetStream",
|
|
789
|
+
"messaging.destination.name": "_INBOX.k3qTbUQ4AkVZe9L1u8A1rd",
|
|
790
|
+
},
|
|
791
|
+
{ "messaging.system": " nats ", "messaging.destination.name": "_INBOX.abc" },
|
|
792
|
+
{ "messaging.system": "nats", "messaging.destination.name": " _INBOX.abc" },
|
|
793
|
+
{ "messaging.system": "nats", "messaging.destination.name": "_INBOX" },
|
|
794
|
+
{ "messaging.system": "nats", "messaging.destination.name": "_INBOXES.abc" },
|
|
795
|
+
{ "messaging.system": "nats", "messaging.destination.name": "$JS.ACKED" },
|
|
796
|
+
{ "messaging.system": "nats", "messaging.destination.template": "$JS.ACK" },
|
|
797
|
+
{ "messaging.system": "nats", "messaging.destination.name": "orders.42" },
|
|
798
|
+
{ "messaging.system": "kafka", "messaging.destination.name": "_INBOX.abc" },
|
|
799
|
+
{
|
|
800
|
+
"messaging.system": "rabbitmq",
|
|
801
|
+
"messaging.destination.name": "shop:new-invoice:invoices",
|
|
802
|
+
"messaging.rabbitmq.destination.routing_key": "new-invoice",
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
"messaging.system": "rabbitmq",
|
|
806
|
+
"messaging.destination.name": "AMQ.Default",
|
|
807
|
+
"messaging.rabbitmq.destination.routing_key": "invoices",
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
"messaging.system": "rabbitmq",
|
|
811
|
+
"messaging.destination.name": "<DEFAULT>",
|
|
812
|
+
"messaging.rabbitmq.routing_key": "invoices",
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
"messaging.system": "rabbitmq",
|
|
816
|
+
"messaging.destination.name": " amq.default ",
|
|
817
|
+
"messaging.rabbitmq.routing_key": "invoices",
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
"messaging.system": "rabbitmq",
|
|
821
|
+
"messaging.destination.name": "amq.default:invoices",
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
"messaging.system": "rabbitmq",
|
|
825
|
+
"messaging.destination.name": "events,order.created",
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
"messaging.system": "rabbitmq",
|
|
829
|
+
"messaging.destination.name": "",
|
|
830
|
+
"messaging.rabbitmq.destination.routing_key": "invoices",
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
"messaging.system": "rabbitmq",
|
|
834
|
+
"messaging.destination.name": "events",
|
|
835
|
+
"messaging.destination.template": "amq.default",
|
|
836
|
+
},
|
|
837
|
+
];
|
|
838
|
+
|
|
839
|
+
describe("the span query's folding of values that vary per message", () => {
|
|
840
|
+
const sql: string = collapse(buildMessagingSpanDiscoverySql(WINDOW));
|
|
841
|
+
|
|
842
|
+
const quoted: (values: ReadonlyArray<string>) => string = (
|
|
843
|
+
values: ReadonlyArray<string>,
|
|
844
|
+
): string => {
|
|
845
|
+
return values
|
|
846
|
+
.map((value: string): string => {
|
|
847
|
+
return `'${value}'`;
|
|
848
|
+
})
|
|
849
|
+
.join(", ");
|
|
850
|
+
};
|
|
851
|
+
const destinationText: (count: number) => string = (
|
|
852
|
+
count: number,
|
|
853
|
+
): string => {
|
|
854
|
+
return `(${MESSAGING_DESTINATION_ATTRIBUTES.slice(0, count)
|
|
855
|
+
.map((_key: string, position: number): string => {
|
|
856
|
+
return `destinationText${position}`;
|
|
857
|
+
})
|
|
858
|
+
.join(" OR ")})`;
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
test("names each destination key's tests once, in the resolver's precedence", () => {
|
|
862
|
+
expect([...MESSAGING_DESTINATION_ATTRIBUTES]).toEqual([
|
|
863
|
+
"messaging.destination.template",
|
|
864
|
+
"messaging.destination.name",
|
|
865
|
+
"messaging.source.template",
|
|
866
|
+
"messaging.source.name",
|
|
867
|
+
"messaging.destination",
|
|
868
|
+
"message_bus.destination",
|
|
869
|
+
]);
|
|
870
|
+
const aliases: Array<string> = [
|
|
871
|
+
...MESSAGING_DESTINATION_ATTRIBUTES.map(
|
|
872
|
+
(key: string, position: number): string => {
|
|
873
|
+
return `match(attributes['${key}'], '[!-~]') AS destinationText${position}`;
|
|
874
|
+
},
|
|
875
|
+
),
|
|
876
|
+
`concat(${MESSAGING_DESTINATION_ATTRIBUTES.map((key: string): string => {
|
|
877
|
+
return `attributes['${key}']`;
|
|
878
|
+
}).join(", ")}) AS storedDestinations`,
|
|
879
|
+
`lower(ifNull(coalesce(${MESSAGING_DESTINATION_ATTRIBUTES.map(
|
|
880
|
+
(key: string): string => {
|
|
881
|
+
return `nullIf(attributes['${key}'], '')`;
|
|
882
|
+
},
|
|
883
|
+
).join(", ")}), '')) AS firstStoredDestination`,
|
|
884
|
+
];
|
|
885
|
+
expect(sql).toContain(`WITH ${aliases.join(", ")} SELECT kind,`);
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
test("leaves out flagged spans and NATS generated subjects (rule 1)", () => {
|
|
889
|
+
expect([...MESSAGE_QUEUE_NATS_GENERATED_SUBJECTS]).toEqual([
|
|
890
|
+
"_inbox",
|
|
891
|
+
"$js.ack",
|
|
892
|
+
]);
|
|
893
|
+
expect(natsSpellings()).toEqual(["jetstream", "nats"]);
|
|
894
|
+
expect(sql).toContain(
|
|
895
|
+
`AND NOT (${MESSAGING_TEMPORARY_FLAG_ATTRIBUTES.map(
|
|
896
|
+
(key: string): string => {
|
|
897
|
+
return `lower(attributes['${key}']) = 'true'`;
|
|
898
|
+
},
|
|
899
|
+
).join(" OR ")} OR (lower(attributes['messaging.system']) IN (${quoted(
|
|
900
|
+
natsSpellings(),
|
|
901
|
+
)}) AND (firstStoredDestination IN ('_inbox', '$js.ack') OR startsWith(firstStoredDestination, '_inbox.') OR startsWith(firstStoredDestination, '$js.ack.')))) GROUP BY kind,`,
|
|
902
|
+
);
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
test("selects each key as stored, a shadowed destination key as the placeholder (rule 2) and a routing key the resolver cannot read blank (rule 3)", () => {
|
|
906
|
+
expect(MESSAGE_QUEUE_DISCOVERY_SHADOWED_VALUE).toBe("*");
|
|
907
|
+
expect([...MESSAGE_QUEUE_RABBITMQ_DEFAULT_EXCHANGES]).toEqual([
|
|
908
|
+
"amq.default",
|
|
909
|
+
"<default>",
|
|
910
|
+
]);
|
|
911
|
+
MESSAGING_RESOLVER_INPUT_ATTRIBUTES.forEach(
|
|
912
|
+
(key: string, index: number): void => {
|
|
913
|
+
const stored: string = `attributes['${key}']`;
|
|
914
|
+
const position: number = MESSAGING_DESTINATION_ATTRIBUTES.indexOf(key);
|
|
915
|
+
let expression: string = stored;
|
|
916
|
+
if (position > 0) {
|
|
917
|
+
expression = `if(${destinationText(position)} AND destinationText${position}, '*', ${stored})`;
|
|
918
|
+
} else if (RABBITMQ_ROUTING_KEY_ATTRIBUTES.includes(key)) {
|
|
919
|
+
expression = `if(${destinationText(
|
|
920
|
+
MESSAGING_DESTINATION_ATTRIBUTES.length,
|
|
921
|
+
)} AND NOT multiSearchAny(storedDestinations, [':', ',']) AND NOT multiSearchAnyCaseInsensitive(storedDestinations, ['amq.default', '<default>']), '', ${stored})`;
|
|
922
|
+
}
|
|
923
|
+
expect({ key, sql }).toEqual({
|
|
924
|
+
key,
|
|
925
|
+
sql: expect.stringContaining(
|
|
926
|
+
`${index === 0 ? "SELECT kind, " : ", "}${expression} AS a${index},`,
|
|
927
|
+
),
|
|
928
|
+
});
|
|
929
|
+
},
|
|
930
|
+
);
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
test("its constants mean to the resolver what the rules say", () => {
|
|
934
|
+
// The placeholder is text, and a scrubbed value: never a queue itself.
|
|
935
|
+
expect(hasMessagingTrigger({ "messaging.destination.name": "*" })).toBe(
|
|
936
|
+
true,
|
|
937
|
+
);
|
|
938
|
+
expect(
|
|
939
|
+
resolveMessagingSpan({
|
|
940
|
+
getAttribute: getterOf({
|
|
941
|
+
"messaging.system": "kafka",
|
|
942
|
+
"messaging.destination.name": MESSAGE_QUEUE_DISCOVERY_SHADOWED_VALUE,
|
|
943
|
+
}),
|
|
944
|
+
kind: SpanKind.Producer,
|
|
945
|
+
}),
|
|
946
|
+
).toBeNull();
|
|
947
|
+
|
|
948
|
+
// A generated subject, alone or followed by "." and anything: no queue…
|
|
949
|
+
for (const subject of MESSAGE_QUEUE_NATS_GENERATED_SUBJECTS) {
|
|
950
|
+
for (const spelling of natsSpellings()) {
|
|
951
|
+
for (const destination of [
|
|
952
|
+
subject,
|
|
953
|
+
subject.toUpperCase(),
|
|
954
|
+
`${subject}.abc`,
|
|
955
|
+
`${subject.toUpperCase()}.ABC.1`,
|
|
956
|
+
]) {
|
|
957
|
+
expect({
|
|
958
|
+
destination,
|
|
959
|
+
resolved: resolveMessagingSpan({
|
|
960
|
+
getAttribute: getterOf({
|
|
961
|
+
"messaging.system": spelling,
|
|
962
|
+
"messaging.destination.name": destination,
|
|
963
|
+
}),
|
|
964
|
+
kind: SpanKind.Consumer,
|
|
965
|
+
}),
|
|
966
|
+
}).toEqual({ destination, resolved: null });
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
// …but a subject that merely starts like one is a queue.
|
|
970
|
+
expect(
|
|
971
|
+
identifierOf(
|
|
972
|
+
resolveMessagingSpan({
|
|
973
|
+
getAttribute: getterOf({
|
|
974
|
+
"messaging.system": "nats",
|
|
975
|
+
"messaging.destination.name": `${subject}x.orders`,
|
|
976
|
+
}),
|
|
977
|
+
kind: SpanKind.Producer,
|
|
978
|
+
}),
|
|
979
|
+
),
|
|
980
|
+
).toBe(`nats||${subject}x.orders`);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// A default exchange's routing key IS the queue.
|
|
984
|
+
for (const exchange of MESSAGE_QUEUE_RABBITMQ_DEFAULT_EXCHANGES) {
|
|
985
|
+
expect(
|
|
986
|
+
resolveMessagingSpan({
|
|
987
|
+
getAttribute: getterOf({
|
|
988
|
+
"messaging.system": "rabbitmq",
|
|
989
|
+
"messaging.destination.name": exchange.toUpperCase(),
|
|
990
|
+
"messaging.rabbitmq.destination.routing_key": "invoices",
|
|
991
|
+
}),
|
|
992
|
+
kind: SpanKind.Producer,
|
|
993
|
+
})?.destination,
|
|
994
|
+
).toBe("invoices");
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
test("whatever it leaves out names no queue, and whatever it folds resolves as stored: every corpus fixture", () => {
|
|
999
|
+
let dropped: number = 0;
|
|
1000
|
+
let shadowed: number = 0;
|
|
1001
|
+
let unrouted: number = 0;
|
|
1002
|
+
for (const fixture of SPAN_FIXTURES) {
|
|
1003
|
+
expectFoldKeepsTheAnswer(fixture.name, fixture.kind, fixture.attributes);
|
|
1004
|
+
|
|
1005
|
+
const span: StoredSpan = storedSpanOf(fixture.kind, fixture.attributes);
|
|
1006
|
+
const values: Record<string, string> | null = foldStoredSpan(span);
|
|
1007
|
+
if (!values) {
|
|
1008
|
+
dropped++;
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
if (
|
|
1012
|
+
MESSAGING_DESTINATION_ATTRIBUTES.some((key: string): boolean => {
|
|
1013
|
+
return values[key] !== span.attributes[key];
|
|
1014
|
+
})
|
|
1015
|
+
) {
|
|
1016
|
+
shadowed++;
|
|
1017
|
+
}
|
|
1018
|
+
if (
|
|
1019
|
+
RABBITMQ_ROUTING_KEY_ATTRIBUTES.some((key: string): boolean => {
|
|
1020
|
+
return values[key] !== span.attributes[key];
|
|
1021
|
+
})
|
|
1022
|
+
) {
|
|
1023
|
+
unrouted++;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// …and through the discovery path, to the queue ingest keyed it on.
|
|
1027
|
+
const atIngest: ResolvedMessagingDestination | null =
|
|
1028
|
+
resolveMessagingSpan({
|
|
1029
|
+
getAttribute: getterOf(fixture.attributes),
|
|
1030
|
+
kind: fixture.kind,
|
|
1031
|
+
});
|
|
1032
|
+
expect({
|
|
1033
|
+
fixture: fixture.name,
|
|
1034
|
+
queues: identifiers(
|
|
1035
|
+
resolveMessagingSpanDiscoveryRows([
|
|
1036
|
+
foldedSpanRow(fixture.kind, values),
|
|
1037
|
+
]),
|
|
1038
|
+
),
|
|
1039
|
+
}).toEqual({
|
|
1040
|
+
fixture: fixture.name,
|
|
1041
|
+
queues: identifierOf(atIngest) ? [identifierOf(atIngest)] : [],
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
// The corpus exercises every rule: the checks above are not vacuous.
|
|
1045
|
+
expect(dropped).toBeGreaterThanOrEqual(3);
|
|
1046
|
+
expect(shadowed).toBeGreaterThanOrEqual(2);
|
|
1047
|
+
expect(unrouted).toBeGreaterThanOrEqual(2);
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
test("…and every variant of the corpus that tests a rule's edges", () => {
|
|
1051
|
+
let dropped: number = 0;
|
|
1052
|
+
for (const fixture of SPAN_FIXTURES) {
|
|
1053
|
+
for (let index: number = 0; index < FOLD_VARIANTS.length; index++) {
|
|
1054
|
+
const attributes: FixtureAttributes = {
|
|
1055
|
+
...fixture.attributes,
|
|
1056
|
+
...FOLD_VARIANTS[index],
|
|
1057
|
+
};
|
|
1058
|
+
expectFoldKeepsTheAnswer(
|
|
1059
|
+
`${fixture.name} + variant ${index}`,
|
|
1060
|
+
fixture.kind,
|
|
1061
|
+
attributes,
|
|
1062
|
+
);
|
|
1063
|
+
if (foldOutcome("", fixture.kind, attributes).dropped) {
|
|
1064
|
+
dropped++;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
// Three flag variants alone leave out every fixture's span.
|
|
1069
|
+
expect(dropped).toBeGreaterThanOrEqual(SPAN_FIXTURES.length * 3);
|
|
1070
|
+
|
|
1071
|
+
// The variants alone, on RabbitMQ's exchange, in every kind.
|
|
1072
|
+
for (const kind of [
|
|
1073
|
+
SpanKind.Producer,
|
|
1074
|
+
SpanKind.Consumer,
|
|
1075
|
+
SpanKind.Client,
|
|
1076
|
+
SpanKind.Internal,
|
|
1077
|
+
null,
|
|
1078
|
+
]) {
|
|
1079
|
+
FOLD_VARIANTS.forEach((variant: FixtureAttributes, index: number) => {
|
|
1080
|
+
expectFoldKeepsTheAnswer(`variant ${index} (${kind})`, kind, {
|
|
1081
|
+
...RABBITMQ_ORDERS_EXCHANGE,
|
|
1082
|
+
...variant,
|
|
1083
|
+
});
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
test("the shapes that vary per message fold into one group each, or none", () => {
|
|
1089
|
+
// What distinct values of each shape become: the set of folded rows.
|
|
1090
|
+
const groupsOf: (
|
|
1091
|
+
kind: string,
|
|
1092
|
+
spans: Array<FixtureAttributes>,
|
|
1093
|
+
) => Set<string | null> = (
|
|
1094
|
+
kind: string,
|
|
1095
|
+
spans: Array<FixtureAttributes>,
|
|
1096
|
+
): Set<string | null> => {
|
|
1097
|
+
const groups: Set<string | null> = new Set<string | null>();
|
|
1098
|
+
for (const attributes of spans) {
|
|
1099
|
+
expectFoldKeepsTheAnswer(JSON.stringify(attributes), kind, attributes);
|
|
1100
|
+
const folded: Record<string, string> | null = foldStoredSpan(
|
|
1101
|
+
storedSpanOf(kind, attributes),
|
|
1102
|
+
);
|
|
1103
|
+
groups.add(folded ? JSON.stringify(folded) : null);
|
|
1104
|
+
}
|
|
1105
|
+
return groups;
|
|
1106
|
+
};
|
|
1107
|
+
const perMessage: (
|
|
1108
|
+
shape: (message: number) => FixtureAttributes,
|
|
1109
|
+
) => Array<FixtureAttributes> = (
|
|
1110
|
+
shape: (message: number) => FixtureAttributes,
|
|
1111
|
+
): Array<FixtureAttributes> => {
|
|
1112
|
+
const spans: Array<FixtureAttributes> = [];
|
|
1113
|
+
for (let message: number = 0; message < 300; message++) {
|
|
1114
|
+
spans.push(shape(message));
|
|
1115
|
+
}
|
|
1116
|
+
return spans;
|
|
1117
|
+
};
|
|
1118
|
+
|
|
1119
|
+
// JetStream acknowledgements, as the Java agent names them by default…
|
|
1120
|
+
expect(
|
|
1121
|
+
groupsOf(
|
|
1122
|
+
SpanKind.Client,
|
|
1123
|
+
perMessage((message: number): FixtureAttributes => {
|
|
1124
|
+
return {
|
|
1125
|
+
"messaging.system": "nats",
|
|
1126
|
+
"messaging.destination.name": `$JS.ACK.ORDERS.billing.1.${message}.${message}.1727698123456789012.0`,
|
|
1127
|
+
"messaging.operation": "settle",
|
|
1128
|
+
};
|
|
1129
|
+
}),
|
|
1130
|
+
),
|
|
1131
|
+
).toEqual(new Set<string | null>([null]));
|
|
1132
|
+
// …and with boundJetStreamAckDestination on.
|
|
1133
|
+
expect(
|
|
1134
|
+
groupsOf(
|
|
1135
|
+
SpanKind.Client,
|
|
1136
|
+
perMessage((message: number): FixtureAttributes => {
|
|
1137
|
+
return {
|
|
1138
|
+
"messaging.system": "nats",
|
|
1139
|
+
"messaging.destination.template": "$JS.ACK",
|
|
1140
|
+
"messaging.destination.name": `$JS.ACK.ORDERS.billing.1.${message}.${message}.1727698123456789012.0`,
|
|
1141
|
+
};
|
|
1142
|
+
}),
|
|
1143
|
+
),
|
|
1144
|
+
).toEqual(new Set<string | null>([null]));
|
|
1145
|
+
// A reply inbox in the stable-semconv mode.
|
|
1146
|
+
expect(
|
|
1147
|
+
groupsOf(
|
|
1148
|
+
SpanKind.Producer,
|
|
1149
|
+
perMessage((message: number): FixtureAttributes => {
|
|
1150
|
+
return {
|
|
1151
|
+
"messaging.system": "nats",
|
|
1152
|
+
"messaging.destination.template": "_INBOX.",
|
|
1153
|
+
"messaging.destination.name": `_INBOX.k3qTbUQ4AkVZe9L1u8A${message}`,
|
|
1154
|
+
"messaging.destination.temporary": true,
|
|
1155
|
+
"messaging.operation.type": "send",
|
|
1156
|
+
};
|
|
1157
|
+
}),
|
|
1158
|
+
),
|
|
1159
|
+
).toEqual(new Set<string | null>([null]));
|
|
1160
|
+
|
|
1161
|
+
// A topic exchange's publishes, the order id in the routing key.
|
|
1162
|
+
const routed: Set<string | null> = groupsOf(
|
|
1163
|
+
SpanKind.Producer,
|
|
1164
|
+
perMessage((message: number): FixtureAttributes => {
|
|
1165
|
+
return {
|
|
1166
|
+
...RABBITMQ_ORDERS_EXCHANGE,
|
|
1167
|
+
"messaging.destination.name": "events",
|
|
1168
|
+
"messaging.rabbitmq.destination.routing_key": `order.${message}.created`,
|
|
1169
|
+
};
|
|
1170
|
+
}),
|
|
1171
|
+
);
|
|
1172
|
+
expect(routed.size).toBe(1);
|
|
1173
|
+
expect(routed.has(null)).toBe(false);
|
|
1174
|
+
|
|
1175
|
+
// Names beside their template.
|
|
1176
|
+
const templated: Set<string | null> = groupsOf(
|
|
1177
|
+
SpanKind.Producer,
|
|
1178
|
+
perMessage((message: number): FixtureAttributes => {
|
|
1179
|
+
return {
|
|
1180
|
+
"messaging.system": "nats",
|
|
1181
|
+
"messaging.destination.template": "orders.{id}",
|
|
1182
|
+
"messaging.destination.name": `orders.${message}`,
|
|
1183
|
+
};
|
|
1184
|
+
}),
|
|
1185
|
+
);
|
|
1186
|
+
expect(templated.size).toBe(1);
|
|
1187
|
+
expect(templated.has(null)).toBe(false);
|
|
1188
|
+
|
|
1189
|
+
// A queue per request the resolver templates: no rule folds it.
|
|
1190
|
+
expect(
|
|
1191
|
+
groupsOf(
|
|
1192
|
+
SpanKind.Producer,
|
|
1193
|
+
perMessage((message: number): FixtureAttributes => {
|
|
1194
|
+
return kafkaReplyTopic(message);
|
|
1195
|
+
}),
|
|
1196
|
+
).size,
|
|
1197
|
+
).toBe(300);
|
|
1198
|
+
});
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
function kafkaReplyTopic(message: number): FixtureAttributes {
|
|
1202
|
+
const suffix: string = String(message).padStart(12, "0");
|
|
1203
|
+
return {
|
|
1204
|
+
"messaging.system": "kafka",
|
|
1205
|
+
"messaging.destination.name": `reply-7f1c2a9e-4b1d-4c3e-9f1a-${suffix}`,
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
describe("the Azure provider values the span query admits", () => {
|
|
1210
|
+
// ClickHouse's multiSearchAnyCaseInsensitive: ASCII case folding, substring.
|
|
1211
|
+
function sqlAdmits(value: string): boolean {
|
|
1212
|
+
const lower: string = value.replace(/[A-Z]/g, (letter: string): string => {
|
|
1213
|
+
return letter.toLowerCase();
|
|
1214
|
+
});
|
|
1215
|
+
return AZURE_MESSAGING_PROVIDER_NAMESPACES.some(
|
|
1216
|
+
(namespace: string): boolean => {
|
|
1217
|
+
return lower.includes(namespace);
|
|
1218
|
+
},
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
test("are exactly Service Bus's and Event Hubs' resource provider namespaces", () => {
|
|
1223
|
+
expect([...AZURE_MESSAGING_PROVIDER_NAMESPACES]).toEqual([
|
|
1224
|
+
"microsoft.eventhub",
|
|
1225
|
+
"microsoft.servicebus",
|
|
1226
|
+
]);
|
|
1227
|
+
|
|
1228
|
+
for (const [namespace, system] of [
|
|
1229
|
+
["Microsoft.ServiceBus", "servicebus"],
|
|
1230
|
+
["Microsoft.EventHub", "eventhubs"],
|
|
1231
|
+
] as Array<[string, string]>) {
|
|
1232
|
+
for (const key of AZURE_RESOURCE_PROVIDER_ATTRIBUTES) {
|
|
1233
|
+
const attributes: FixtureAttributes = {
|
|
1234
|
+
[key]: namespace,
|
|
1235
|
+
"messaging.destination.name": "orders",
|
|
1236
|
+
};
|
|
1237
|
+
expect(hasMessagingTrigger({ [key]: namespace })).toBe(true);
|
|
1238
|
+
expect(
|
|
1239
|
+
resolveMessagingSpan({
|
|
1240
|
+
getAttribute: getterOf(attributes),
|
|
1241
|
+
kind: SpanKind.Producer,
|
|
1242
|
+
})?.system,
|
|
1243
|
+
).toBe(system);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
test("admit every value ingest's trigger admits, whatever whitespace or case surrounds it", () => {
|
|
1249
|
+
const probes: Array<string> = [
|
|
1250
|
+
"Microsoft.ServiceBus",
|
|
1251
|
+
"Microsoft.EventHub",
|
|
1252
|
+
"MICROSOFT.SERVICEBUS",
|
|
1253
|
+
"microsoft.eventhub",
|
|
1254
|
+
" Microsoft.ServiceBus ",
|
|
1255
|
+
"\tMicrosoft.EventHub\n",
|
|
1256
|
+
`${NO_BREAK_SPACE}Microsoft.ServiceBus${LINE_SEPARATOR}`,
|
|
1257
|
+
`${BYTE_ORDER_MARK}microsoft.servicebus${IDEOGRAPHIC_SPACE}`,
|
|
1258
|
+
"\u000bMicrosoft.EventHub\u000c",
|
|
1259
|
+
"Microsoft.Storage",
|
|
1260
|
+
"Microsoft.KeyVault",
|
|
1261
|
+
"Microsoft.DocumentDB",
|
|
1262
|
+
"Microsoft.EventGrid",
|
|
1263
|
+
"Microsoft.ServiceBus/namespaces",
|
|
1264
|
+
"",
|
|
1265
|
+
" ",
|
|
1266
|
+
];
|
|
1267
|
+
|
|
1268
|
+
for (const value of probes) {
|
|
1269
|
+
for (const key of AZURE_RESOURCE_PROVIDER_ATTRIBUTES) {
|
|
1270
|
+
if (hasMessagingTrigger({ [key]: value })) {
|
|
1271
|
+
expect({ value, sql: sqlAdmits(value) }).toEqual({
|
|
1272
|
+
value,
|
|
1273
|
+
sql: true,
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
test("keep other Azure services' spans out of the query", () => {
|
|
1281
|
+
for (const value of [
|
|
1282
|
+
"Microsoft.Storage",
|
|
1283
|
+
"Microsoft.KeyVault",
|
|
1284
|
+
"Microsoft.DocumentDB",
|
|
1285
|
+
"Microsoft.EventGrid",
|
|
1286
|
+
"Microsoft.AppConfiguration",
|
|
1287
|
+
]) {
|
|
1288
|
+
expect(sqlAdmits(value)).toBe(false);
|
|
1289
|
+
expect(hasMessagingTrigger({ "az.namespace": value })).toBe(false);
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
describe("buildMessagingMetricDiscoverySql", () => {
|
|
1295
|
+
const raw: string = buildMessagingMetricDiscoverySql(WINDOW);
|
|
1296
|
+
const sql: string = collapse(raw);
|
|
1297
|
+
|
|
1298
|
+
test("carries a unique marker comment so the query is recognisable", () => {
|
|
1299
|
+
expect(MESSAGE_QUEUE_METRIC_SQL_MARKER).toBe(
|
|
1300
|
+
"message-queue-metric-discovery",
|
|
1301
|
+
);
|
|
1302
|
+
expect(sql).toContain(`/* ${MESSAGE_QUEUE_METRIC_SQL_MARKER} */`);
|
|
1303
|
+
expect(sql).not.toContain(`/* ${MESSAGE_QUEUE_SPAN_SQL_MARKER} */`);
|
|
1304
|
+
expect(sql).not.toContain(MESSAGE_QUEUE_METRIC_PROJECTS_SQL_MARKER);
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
test("reads one project's metric table, naming it once", () => {
|
|
1308
|
+
expect(sql).toContain(`WHERE projectId = '${PROJECT_ID}'`);
|
|
1309
|
+
expect(sql.split("oneuptime.MetricItemV3").length - 1).toBe(1);
|
|
1310
|
+
expect(sql).toContain("FROM oneuptime.MetricItemV3");
|
|
1311
|
+
expect(sql).not.toContain("SpanItemV3");
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
test("reads exactly the curated broker metrics and the messaging client metrics, as stored", () => {
|
|
1315
|
+
const expected: Array<string> = Array.from(
|
|
1316
|
+
new Set<string>([
|
|
1317
|
+
...MESSAGE_QUEUE_BROKER_METRIC_NAMES,
|
|
1318
|
+
...MESSAGING_CLIENT_METRIC_NAMES,
|
|
1319
|
+
]),
|
|
1320
|
+
).sort();
|
|
1321
|
+
|
|
1322
|
+
expect([...MESSAGE_QUEUE_DISCOVERY_METRIC_NAMES]).toEqual(expected);
|
|
1323
|
+
expect(inTuple(sql, "AND name IN (")).toEqual(expected);
|
|
1324
|
+
for (const name of MESSAGE_QUEUE_DISCOVERY_METRIC_NAMES) {
|
|
1325
|
+
expect(name).toBe(name.trim().toLowerCase());
|
|
1326
|
+
}
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1329
|
+
test("never reads an application's own gauge: it attaches to a queue, it never creates one", () => {
|
|
1330
|
+
expect(MESSAGE_QUEUE_DISCOVERY_METRIC_NAMES).not.toContain("queue.size");
|
|
1331
|
+
expect(sql).not.toContain("'queue.size'");
|
|
1332
|
+
});
|
|
1333
|
+
|
|
1334
|
+
test("selects exactly the attributes resolveMessagingMetricDatapoint reads, one column each, in list order", () => {
|
|
1335
|
+
const columns: Array<[string, string]> = selectedColumns(raw);
|
|
1336
|
+
expect(
|
|
1337
|
+
columns.map((entry: [string, string]): string => {
|
|
1338
|
+
return entry[0];
|
|
1339
|
+
}),
|
|
1340
|
+
).toEqual([...MESSAGING_METRIC_RESOLVER_INPUT_ATTRIBUTES]);
|
|
1341
|
+
columns.forEach((entry: [string, string], index: number): void => {
|
|
1342
|
+
expect(entry[1]).toBe(getMessagingDiscoveryColumn(index));
|
|
1343
|
+
});
|
|
1344
|
+
for (const key of attributeReads(raw)) {
|
|
1345
|
+
expect(MESSAGING_METRIC_RESOLVER_INPUT_ATTRIBUTES).toContain(key);
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
test("covers every catalog entry's destination, scope and required keys, and the span keys", () => {
|
|
1350
|
+
for (const descriptor of MESSAGE_QUEUE_METRICS) {
|
|
1351
|
+
for (const key of [
|
|
1352
|
+
...descriptor.destinationAttributes,
|
|
1353
|
+
...(descriptor.scopeAttributes || []),
|
|
1354
|
+
...Object.keys(descriptor.requiredAttributes || {}),
|
|
1355
|
+
]) {
|
|
1356
|
+
expect(MESSAGING_METRIC_RESOLVER_INPUT_ATTRIBUTES).toContain(key);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
for (const key of MESSAGING_RESOLVER_INPUT_ATTRIBUTES) {
|
|
1360
|
+
expect(MESSAGING_METRIC_RESOLVER_INPUT_ATTRIBUTES).toContain(key);
|
|
1361
|
+
}
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
test("never groups or filters on a per-consumer series key", () => {
|
|
1365
|
+
expect(
|
|
1366
|
+
MESSAGE_QUEUE_METRIC_EXCLUDED_SERIES_ATTRIBUTES.length,
|
|
1367
|
+
).toBeGreaterThan(0);
|
|
1368
|
+
for (const key of MESSAGE_QUEUE_METRIC_EXCLUDED_SERIES_ATTRIBUTES) {
|
|
1369
|
+
expect(sql).not.toContain(key);
|
|
1370
|
+
}
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
test("never groups on a series key or anything per instance", () => {
|
|
1374
|
+
const seriesKeys: Set<string> = new Set<string>();
|
|
1375
|
+
for (const descriptor of MESSAGE_QUEUE_METRICS) {
|
|
1376
|
+
for (const key of descriptor.seriesKeys || []) {
|
|
1377
|
+
seriesKeys.add(key);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
for (const key of seriesKeys) {
|
|
1381
|
+
if (!MESSAGING_METRIC_RESOLVER_INPUT_ATTRIBUTES.includes(key)) {
|
|
1382
|
+
expect(sql).not.toContain(`attributes['${key}']`);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
for (const key of PER_INSTANCE_KEYS) {
|
|
1386
|
+
expect(sql).not.toContain(key);
|
|
1387
|
+
}
|
|
1388
|
+
for (const column of ["primaryEntityId", "traceId", "spanId"]) {
|
|
1389
|
+
expect(sql).not.toContain(column);
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
test("groups on the name and every attribute column, counting datapoints and the newest time", () => {
|
|
1394
|
+
const columns: string = MESSAGING_METRIC_RESOLVER_INPUT_ATTRIBUTES.map(
|
|
1395
|
+
(_key: string, index: number): string => {
|
|
1396
|
+
return getMessagingDiscoveryColumn(index);
|
|
1397
|
+
},
|
|
1398
|
+
).join(", ");
|
|
1399
|
+
expect(sql).toContain(`GROUP BY name, ${columns} ORDER BY`);
|
|
1400
|
+
expect(sql).toContain("count() AS pointCount");
|
|
1401
|
+
expect(sql).toContain(
|
|
1402
|
+
"toUnixTimestamp64Milli(max(time)) AS lastSeenUnixMs",
|
|
1403
|
+
);
|
|
1404
|
+
});
|
|
1405
|
+
|
|
1406
|
+
test("reads cloud-monitoring metrics further back than the window, and the rest inside it", () => {
|
|
1407
|
+
expect(MESSAGE_QUEUE_LATE_METRIC_MINUTES).toBe(45);
|
|
1408
|
+
expect(sql).toContain(
|
|
1409
|
+
`AND time >= ${WINDOW.startSql} - INTERVAL 45 MINUTE AND time < ${WINDOW.endSql} AND (time >= ${WINDOW.startSql} OR name IN (`,
|
|
1410
|
+
);
|
|
1411
|
+
expect(inTuple(sql, `OR name IN (`)).toEqual([
|
|
1412
|
+
...MESSAGE_QUEUE_LATE_METRIC_NAMES,
|
|
1413
|
+
]);
|
|
1414
|
+
});
|
|
1415
|
+
|
|
1416
|
+
test("the late metrics are exactly the curated metrics of the cloud-monitoring systems", () => {
|
|
1417
|
+
const cloud: Array<string> = Array.from(
|
|
1418
|
+
new Set<string>(
|
|
1419
|
+
MESSAGE_QUEUE_METRICS.filter(
|
|
1420
|
+
(descriptor: MessageQueueMetricDescriptor): boolean => {
|
|
1421
|
+
return [
|
|
1422
|
+
"servicebus",
|
|
1423
|
+
"eventhubs",
|
|
1424
|
+
"eventgrid",
|
|
1425
|
+
"aws_sqs",
|
|
1426
|
+
"aws.sns",
|
|
1427
|
+
"gcp_pubsub",
|
|
1428
|
+
].includes(descriptor.system);
|
|
1429
|
+
},
|
|
1430
|
+
).map((descriptor: MessageQueueMetricDescriptor): string => {
|
|
1431
|
+
return descriptor.metricName;
|
|
1432
|
+
}),
|
|
1433
|
+
),
|
|
1434
|
+
).sort();
|
|
1435
|
+
|
|
1436
|
+
expect([...MESSAGE_QUEUE_LATE_METRIC_NAMES]).toEqual(cloud);
|
|
1437
|
+
expect(cloud.length).toBeGreaterThan(10);
|
|
1438
|
+
|
|
1439
|
+
for (const descriptor of MESSAGE_QUEUE_METRICS) {
|
|
1440
|
+
const late: boolean = MESSAGE_QUEUE_LATE_METRIC_NAMES.includes(
|
|
1441
|
+
descriptor.metricName,
|
|
1442
|
+
);
|
|
1443
|
+
if (
|
|
1444
|
+
getMessagingBrokerMetricsSource(descriptor.system).kind !==
|
|
1445
|
+
"cloud-monitoring"
|
|
1446
|
+
) {
|
|
1447
|
+
expect({ name: descriptor.metricName, late }).toEqual({
|
|
1448
|
+
name: descriptor.metricName,
|
|
1449
|
+
late: false,
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
for (const name of MESSAGING_CLIENT_METRIC_NAMES) {
|
|
1454
|
+
expect(MESSAGE_QUEUE_LATE_METRIC_NAMES).not.toContain(name);
|
|
1455
|
+
}
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1458
|
+
test("is bounded and escaped, and never mentions another query's routing markers", () => {
|
|
1459
|
+
// The row cap is shared out by metric: busiest and rotating rows alternate.
|
|
1460
|
+
expect(sql).toContain(
|
|
1461
|
+
`, a59, a60 ORDER BY pointCount DESC, rotation LIMIT ${321 * MESSAGE_QUEUE_RANKED_ROWS_PER_CAPPED_ROW} ) )`,
|
|
1462
|
+
);
|
|
1463
|
+
expect(sql).toContain(
|
|
1464
|
+
"row_number() OVER (PARTITION BY name ORDER BY pointCount DESC, rotation) AS busiestRank, row_number() OVER (PARTITION BY name ORDER BY rotation) AS rotationRank",
|
|
1465
|
+
);
|
|
1466
|
+
expect(sql).toContain(
|
|
1467
|
+
"ORDER BY least(2 * busiestRank - 1, 2 * rotationRank), pointCount DESC, rotation LIMIT 321 SETTINGS",
|
|
1468
|
+
);
|
|
1469
|
+
expect(sql).toContain(
|
|
1470
|
+
"AS lastSeenUnixMs, cityHash64(toUnixTimestamp64Milli(toDateTime64('2026-09-24 09:45:00.000000000', 9)), name, a0, a1,",
|
|
1471
|
+
);
|
|
1472
|
+
expect(sql).toMatch(
|
|
1473
|
+
/^ \/\* message-queue-metric-discovery \*\/ SELECT name, a0, a1, [a0-9, ]+a60, pointCount, lastSeenUnixMs FROM \(/,
|
|
1474
|
+
);
|
|
1475
|
+
expect(
|
|
1476
|
+
sql.trim().endsWith(` ${MESSAGE_QUEUE_DISCOVERY_QUERY_SETTINGS}`),
|
|
1477
|
+
).toBe(true);
|
|
1478
|
+
expect(
|
|
1479
|
+
collapse(buildMessagingMetricDiscoverySql({ ...WINDOW, maxRows: -1 })),
|
|
1480
|
+
).toContain("LIMIT 1 ");
|
|
1481
|
+
expect(
|
|
1482
|
+
buildMessagingMetricDiscoverySql({ ...WINDOW, projectId: "a'b" }),
|
|
1483
|
+
).toContain("projectId = 'a\\'b'");
|
|
1484
|
+
expect(sql).not.toContain("SELECT DISTINCT projectId");
|
|
1485
|
+
expect(sql).not.toContain("NOT IN");
|
|
1486
|
+
expect(sql).not.toContain("INNER JOIN");
|
|
1487
|
+
expect(sql).not.toContain(DATABASE_ENDPOINT_SQL_MARKER);
|
|
1488
|
+
});
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1491
|
+
describe("buildMessagingMetricProjectsSql", () => {
|
|
1492
|
+
const sql: string = collapse(
|
|
1493
|
+
buildMessagingMetricProjectsSql({
|
|
1494
|
+
startSql: WINDOW.startSql,
|
|
1495
|
+
endSql: WINDOW.endSql,
|
|
1496
|
+
maxProjects: 1000,
|
|
1497
|
+
}),
|
|
1498
|
+
);
|
|
1499
|
+
|
|
1500
|
+
test("lists the projects whose broker or client metrics the metric query would read", () => {
|
|
1501
|
+
expect(sql).toContain(`/* ${MESSAGE_QUEUE_METRIC_PROJECTS_SQL_MARKER} */`);
|
|
1502
|
+
expect(sql).toContain(
|
|
1503
|
+
"SELECT DISTINCT projectId FROM oneuptime.MetricItemV3",
|
|
1504
|
+
);
|
|
1505
|
+
expect(inTuple(sql, "WHERE name IN (")).toEqual([
|
|
1506
|
+
...MESSAGE_QUEUE_DISCOVERY_METRIC_NAMES,
|
|
1507
|
+
]);
|
|
1508
|
+
expect(sql).toContain(
|
|
1509
|
+
`AND time >= ${WINDOW.startSql} - INTERVAL 45 MINUTE AND time < ${WINDOW.endSql} AND (time >= ${WINDOW.startSql} OR name IN (`,
|
|
1510
|
+
);
|
|
1511
|
+
expect(sql).toContain("LIMIT 1000 ");
|
|
1512
|
+
expect(
|
|
1513
|
+
sql.trim().endsWith(` ${MESSAGE_QUEUE_DISCOVERY_QUERY_SETTINGS}`),
|
|
1514
|
+
).toBe(true);
|
|
1515
|
+
// A project scan reads no attribute at all.
|
|
1516
|
+
expect(sql).not.toContain("attributes[");
|
|
1517
|
+
expect(sql).not.toContain(MESSAGE_QUEUE_METRIC_SQL_MARKER + " ");
|
|
1518
|
+
});
|
|
1519
|
+
});
|
|
1520
|
+
|
|
1521
|
+
/*
|
|
1522
|
+
* ---- The queue queries' settings ---------------------------------------------
|
|
1523
|
+
*
|
|
1524
|
+
* The cron's ClickHouse client gives up on a query that has sent it nothing
|
|
1525
|
+
* for its request_timeout, and a grouped query sends nothing until it is
|
|
1526
|
+
* done. So every queue query ends on the server well before then, with
|
|
1527
|
+
* ClickHouse's own timeout error: QUERY_SETTINGS with a time limit of its
|
|
1528
|
+
* own and 'throw' (a 'break' returns no rows on ClickHouse 26.7, see the
|
|
1529
|
+
* real-ClickHouse suite), no setting named twice, and no thread or block
|
|
1530
|
+
* pin slowing the scan down.
|
|
1531
|
+
*/
|
|
1532
|
+
describe("the queue queries' settings", () => {
|
|
1533
|
+
const QUERIES: Array<[string, string]> = [
|
|
1534
|
+
["span", buildMessagingSpanDiscoverySql(WINDOW)],
|
|
1535
|
+
["metric", buildMessagingMetricDiscoverySql(WINDOW)],
|
|
1536
|
+
[
|
|
1537
|
+
"metric project",
|
|
1538
|
+
buildMessagingMetricProjectsSql({
|
|
1539
|
+
startSql: WINDOW.startSql,
|
|
1540
|
+
endSql: WINDOW.endSql,
|
|
1541
|
+
maxProjects: 1000,
|
|
1542
|
+
}),
|
|
1543
|
+
],
|
|
1544
|
+
];
|
|
1545
|
+
|
|
1546
|
+
/*
|
|
1547
|
+
* How long after its time limit a query's error may take to reach the
|
|
1548
|
+
* client: within 50 ms in the measurements MessageQueueDiscovery cites,
|
|
1549
|
+
* mid-merge of a grouping spilled to disk too; ten seconds leaves room for
|
|
1550
|
+
* a loaded server.
|
|
1551
|
+
*/
|
|
1552
|
+
const TAIL_AFTER_LIMIT_MS: number = 10000;
|
|
1553
|
+
|
|
1554
|
+
// The SETTINGS clause a query ends with.
|
|
1555
|
+
function settingsClauseOf(sql: string): string {
|
|
1556
|
+
const collapsed: string = collapse(sql).trim();
|
|
1557
|
+
const start: number = collapsed.lastIndexOf(" SETTINGS ");
|
|
1558
|
+
expect(start).toBeGreaterThan(0);
|
|
1559
|
+
return collapsed.substring(start + 1);
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
// A SETTINGS clause's settings, in order.
|
|
1563
|
+
function settingsOf(clause: string): Array<[string, string]> {
|
|
1564
|
+
const body: string = collapse(clause).trim();
|
|
1565
|
+
expect(body.startsWith("SETTINGS ")).toBe(true);
|
|
1566
|
+
return body
|
|
1567
|
+
.substring("SETTINGS ".length)
|
|
1568
|
+
.split(", ")
|
|
1569
|
+
.map((setting: string): [string, string] => {
|
|
1570
|
+
const match: RegExpMatchArray | null =
|
|
1571
|
+
setting.match(/^([a-z_]+) = (\S+)$/);
|
|
1572
|
+
expect({ setting, match: Boolean(match) }).toEqual({
|
|
1573
|
+
setting,
|
|
1574
|
+
match: true,
|
|
1575
|
+
});
|
|
1576
|
+
return [match![1]!, match![2]!];
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
test("are QUERY_SETTINGS — its memory limit and its spills to disk — with a time limit of their own, at which a query fails with ClickHouse's timeout error", () => {
|
|
1581
|
+
expect(settingsOf(MESSAGE_QUEUE_DISCOVERY_QUERY_SETTINGS)).toEqual(
|
|
1582
|
+
settingsOf(QUERY_SETTINGS).map(
|
|
1583
|
+
(setting: [string, string]): [string, string] => {
|
|
1584
|
+
if (setting[0] === "max_execution_time") {
|
|
1585
|
+
return [
|
|
1586
|
+
setting[0],
|
|
1587
|
+
String(MESSAGE_QUEUE_DISCOVERY_MAX_EXECUTION_SECONDS),
|
|
1588
|
+
];
|
|
1589
|
+
}
|
|
1590
|
+
if (setting[0] === "timeout_overflow_mode") {
|
|
1591
|
+
return [setting[0], "'throw'"];
|
|
1592
|
+
}
|
|
1593
|
+
return setting;
|
|
1594
|
+
},
|
|
1595
|
+
),
|
|
1596
|
+
);
|
|
1597
|
+
// Spelled out, so that a change to either shows here.
|
|
1598
|
+
expect(MESSAGE_QUEUE_DISCOVERY_QUERY_SETTINGS).toBe(
|
|
1599
|
+
"SETTINGS max_execution_time = 45, timeout_overflow_mode = 'throw', max_memory_usage = 2000000000, max_bytes_before_external_group_by = 1000000000, max_bytes_before_external_sort = 1000000000",
|
|
1600
|
+
);
|
|
1601
|
+
});
|
|
1602
|
+
|
|
1603
|
+
test.each(QUERIES)(
|
|
1604
|
+
"the %s query ends with them, names each setting once and pins no thread or block size",
|
|
1605
|
+
(_name: string, sql: string) => {
|
|
1606
|
+
const clause: string = settingsClauseOf(sql);
|
|
1607
|
+
expect(clause).toBe(MESSAGE_QUEUE_DISCOVERY_QUERY_SETTINGS);
|
|
1608
|
+
const names: Array<string> = settingsOf(clause).map(
|
|
1609
|
+
(setting: [string, string]): string => {
|
|
1610
|
+
return setting[0];
|
|
1611
|
+
},
|
|
1612
|
+
);
|
|
1613
|
+
expect(new Set<string>(names).size).toBe(names.length);
|
|
1614
|
+
for (const pin of [
|
|
1615
|
+
"max_threads",
|
|
1616
|
+
"max_block_size",
|
|
1617
|
+
"preferred_block_size_bytes",
|
|
1618
|
+
]) {
|
|
1619
|
+
expect(sql).not.toContain(pin);
|
|
1620
|
+
}
|
|
1621
|
+
},
|
|
1622
|
+
);
|
|
1623
|
+
|
|
1624
|
+
test.each(QUERIES)(
|
|
1625
|
+
"the %s query ends while the client still waits for it: its time limit well under the client's request_timeout",
|
|
1626
|
+
(_name: string, sql: string) => {
|
|
1627
|
+
const requestTimeoutMs: number = Number(
|
|
1628
|
+
dataSourceOptions.request_timeout,
|
|
1629
|
+
);
|
|
1630
|
+
expect(requestTimeoutMs).toBeGreaterThan(0);
|
|
1631
|
+
|
|
1632
|
+
const settings: Map<string, string> = new Map<string, string>(
|
|
1633
|
+
settingsOf(settingsClauseOf(sql)),
|
|
1634
|
+
);
|
|
1635
|
+
const limitMs: number = Number(settings.get("max_execution_time")) * 1000;
|
|
1636
|
+
expect(limitMs).toBeGreaterThan(0);
|
|
1637
|
+
expect(limitMs + TAIL_AFTER_LIMIT_MS).toBeLessThanOrEqual(
|
|
1638
|
+
requestTimeoutMs,
|
|
1639
|
+
);
|
|
1640
|
+
// …and there it fails, with ClickHouse's own timeout error.
|
|
1641
|
+
expect(settings.get("timeout_overflow_mode")).toBe("'throw'");
|
|
1642
|
+
},
|
|
1643
|
+
);
|
|
1644
|
+
});
|
|
1645
|
+
|
|
1646
|
+
describe("a stored row resolves to exactly the queue ingest keyed its telemetry on", () => {
|
|
1647
|
+
test.each(
|
|
1648
|
+
SPAN_FIXTURES.map((fixture: SpanFixture): [string, SpanFixture] => {
|
|
1649
|
+
return [fixture.name, fixture];
|
|
1650
|
+
}),
|
|
1651
|
+
)("span: %s", (_name: string, fixture: SpanFixture) => {
|
|
1652
|
+
const atIngest: ResolvedMessagingDestination | null = resolveMessagingSpan({
|
|
1653
|
+
getAttribute: getterOf(fixture.attributes),
|
|
1654
|
+
kind: fixture.kind,
|
|
1655
|
+
});
|
|
1656
|
+
const expected: string | null = identifierOf(atIngest);
|
|
1657
|
+
const queues: Array<DiscoveredMessageQueue> =
|
|
1658
|
+
resolveMessagingSpanDiscoveryRows([
|
|
1659
|
+
spanRow(fixture.kind, fixture.attributes),
|
|
1660
|
+
]);
|
|
1661
|
+
|
|
1662
|
+
expect(identifiers(queues)).toEqual(expected ? [expected] : []);
|
|
1663
|
+
if (expected) {
|
|
1664
|
+
expect(queues[0]!.system).toBe(atIngest!.system);
|
|
1665
|
+
expect(queues[0]!.destination).toBe(atIngest!.destination);
|
|
1666
|
+
expect(queues[0]!.spans?.brokerAddress).toBe(atIngest!.brokerAddress);
|
|
1667
|
+
expect(queues[0]!.spans?.directions[atIngest!.direction]).toBe(5);
|
|
1668
|
+
}
|
|
1669
|
+
});
|
|
1670
|
+
|
|
1671
|
+
test.each(
|
|
1672
|
+
METRIC_FIXTURES.map((fixture: MetricFixture): [string, MetricFixture] => {
|
|
1673
|
+
return [fixture.name, fixture];
|
|
1674
|
+
}),
|
|
1675
|
+
)("datapoint: %s", (_name: string, fixture: MetricFixture) => {
|
|
1676
|
+
const atIngest: ResolvedMessagingDestination | null =
|
|
1677
|
+
resolveMessagingMetricDatapoint({
|
|
1678
|
+
metricName: fixture.metricName,
|
|
1679
|
+
getAttribute: getterOf(fixture.attributes),
|
|
1680
|
+
});
|
|
1681
|
+
const expected: string | null = identifierOf(atIngest);
|
|
1682
|
+
const queues: Array<DiscoveredMessageQueue> =
|
|
1683
|
+
resolveMessagingMetricDiscoveryRows([
|
|
1684
|
+
metricRow(fixture.metricName, fixture.attributes),
|
|
1685
|
+
]);
|
|
1686
|
+
|
|
1687
|
+
expect(identifiers(queues)).toEqual(expected ? [expected] : []);
|
|
1688
|
+
if (expected) {
|
|
1689
|
+
const broker: boolean = MESSAGE_QUEUE_BROKER_METRIC_NAMES.has(
|
|
1690
|
+
fixture.metricName,
|
|
1691
|
+
);
|
|
1692
|
+
expect(queues[0]!.system).toBe(atIngest!.system);
|
|
1693
|
+
expect(queues[0]!.brokerMetrics !== null).toBe(broker);
|
|
1694
|
+
expect(queues[0]!.clientMetrics !== null).toBe(!broker);
|
|
1695
|
+
expect(queues[0]!.spans).toBeNull();
|
|
1696
|
+
}
|
|
1697
|
+
});
|
|
1698
|
+
|
|
1699
|
+
test("the corpus covers every kind of outcome", () => {
|
|
1700
|
+
const spanQueues: number = SPAN_FIXTURES.filter(
|
|
1701
|
+
(fixture: SpanFixture): boolean => {
|
|
1702
|
+
return fixture.expected !== null;
|
|
1703
|
+
},
|
|
1704
|
+
).length;
|
|
1705
|
+
expect(spanQueues).toBeGreaterThan(60);
|
|
1706
|
+
expect(SPAN_FIXTURES.length - spanQueues).toBeGreaterThan(15);
|
|
1707
|
+
expect(METRIC_FIXTURES.length).toBeGreaterThan(80);
|
|
1708
|
+
});
|
|
1709
|
+
|
|
1710
|
+
test("every key the resolvers read for the corpus is a column the queries select", () => {
|
|
1711
|
+
const spanKeys: Set<string> = new Set<string>(
|
|
1712
|
+
selectedColumns(buildMessagingSpanDiscoverySql(WINDOW)).map(
|
|
1713
|
+
(entry: [string, string]): string => {
|
|
1714
|
+
return entry[0];
|
|
1715
|
+
},
|
|
1716
|
+
),
|
|
1717
|
+
);
|
|
1718
|
+
const metricKeys: Set<string> = new Set<string>(
|
|
1719
|
+
selectedColumns(buildMessagingMetricDiscoverySql(WINDOW)).map(
|
|
1720
|
+
(entry: [string, string]): string => {
|
|
1721
|
+
return entry[0];
|
|
1722
|
+
},
|
|
1723
|
+
),
|
|
1724
|
+
);
|
|
1725
|
+
|
|
1726
|
+
for (const fixture of SPAN_FIXTURES) {
|
|
1727
|
+
resolveMessagingSpan({
|
|
1728
|
+
getAttribute: (key: string): unknown => {
|
|
1729
|
+
expect({
|
|
1730
|
+
fixture: fixture.name,
|
|
1731
|
+
key,
|
|
1732
|
+
selected: spanKeys.has(key),
|
|
1733
|
+
}).toEqual({ fixture: fixture.name, key, selected: true });
|
|
1734
|
+
return fixture.attributes[key];
|
|
1735
|
+
},
|
|
1736
|
+
kind: fixture.kind,
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
for (const fixture of METRIC_FIXTURES) {
|
|
1741
|
+
resolveMessagingMetricDatapoint({
|
|
1742
|
+
metricName: fixture.metricName,
|
|
1743
|
+
getAttribute: (key: string): unknown => {
|
|
1744
|
+
/*
|
|
1745
|
+
* A per-consumer marker is read only to DROP a datapoint, and is
|
|
1746
|
+
* deliberately not selected (see the metric query).
|
|
1747
|
+
*/
|
|
1748
|
+
const selected: boolean =
|
|
1749
|
+
metricKeys.has(key) ||
|
|
1750
|
+
MESSAGE_QUEUE_METRIC_EXCLUDED_SERIES_ATTRIBUTES.includes(key);
|
|
1751
|
+
expect({ fixture: fixture.name, key, selected }).toEqual({
|
|
1752
|
+
fixture: fixture.name,
|
|
1753
|
+
key,
|
|
1754
|
+
selected: true,
|
|
1755
|
+
});
|
|
1756
|
+
return fixture.attributes[key];
|
|
1757
|
+
},
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
});
|
|
1761
|
+
});
|
|
1762
|
+
|
|
1763
|
+
describe("resolveMessagingSpanDiscoveryRows", () => {
|
|
1764
|
+
test("rows that name one queue merge: spans, failures and directions summed", () => {
|
|
1765
|
+
const queues: Array<DiscoveredMessageQueue> =
|
|
1766
|
+
resolveMessagingSpanDiscoveryRows([
|
|
1767
|
+
spanRow(
|
|
1768
|
+
SpanKind.Producer,
|
|
1769
|
+
{ ...KAFKA_ORDERS, "messaging.operation": "publish" },
|
|
1770
|
+
{ spanCount: "40", errorCount: "2" },
|
|
1771
|
+
),
|
|
1772
|
+
spanRow(
|
|
1773
|
+
SpanKind.Consumer,
|
|
1774
|
+
{
|
|
1775
|
+
...KAFKA_ORDERS,
|
|
1776
|
+
"messaging.operation.type": "process",
|
|
1777
|
+
"messaging.consumer.group.name": "billing",
|
|
1778
|
+
},
|
|
1779
|
+
{ spanCount: "35", errorCount: "1" },
|
|
1780
|
+
),
|
|
1781
|
+
spanRow(
|
|
1782
|
+
SpanKind.Client,
|
|
1783
|
+
{
|
|
1784
|
+
...KAFKA_ORDERS,
|
|
1785
|
+
"messaging.operation.type": "receive",
|
|
1786
|
+
"messaging.operation.name": "poll",
|
|
1787
|
+
},
|
|
1788
|
+
{ spanCount: 7, errorCount: 0 },
|
|
1789
|
+
),
|
|
1790
|
+
]);
|
|
1791
|
+
|
|
1792
|
+
expect(queues).toHaveLength(1);
|
|
1793
|
+
expect(queues[0]!.identifier).toBe("kafka||orders");
|
|
1794
|
+
expect(queues[0]!.spans).toEqual({
|
|
1795
|
+
count: 82,
|
|
1796
|
+
errorCount: 3,
|
|
1797
|
+
directions: { publish: 40, consume: 42, settle: 0, unknown: 0 },
|
|
1798
|
+
brokerAddress: null,
|
|
1799
|
+
lastSeenAt: new Date(1790243100123),
|
|
1800
|
+
});
|
|
1801
|
+
expect(queues[0]!.brokerMetrics).toBeNull();
|
|
1802
|
+
expect(queues[0]!.clientMetrics).toBeNull();
|
|
1803
|
+
});
|
|
1804
|
+
|
|
1805
|
+
test("a SERVER span, a span without a trigger and another Azure service's span resolve to nothing", () => {
|
|
1806
|
+
expect(
|
|
1807
|
+
resolveMessagingSpanDiscoveryRows([
|
|
1808
|
+
spanRow(SpanKind.Server, KAFKA_ORDERS),
|
|
1809
|
+
spanRow(SpanKind.Client, {
|
|
1810
|
+
"server.address": "api.example.com",
|
|
1811
|
+
"rpc.system": "grpc",
|
|
1812
|
+
}),
|
|
1813
|
+
spanRow(SpanKind.Client, {
|
|
1814
|
+
"az.namespace": "Microsoft.Storage",
|
|
1815
|
+
"server.address": "acct.blob.core.windows.net",
|
|
1816
|
+
}),
|
|
1817
|
+
]),
|
|
1818
|
+
).toEqual([]);
|
|
1819
|
+
});
|
|
1820
|
+
|
|
1821
|
+
test("temporary, generated and placeholder destinations resolve to nothing", () => {
|
|
1822
|
+
expect(
|
|
1823
|
+
resolveMessagingSpanDiscoveryRows([
|
|
1824
|
+
spanRow(SpanKind.Consumer, {
|
|
1825
|
+
"messaging.system": "rabbitmq",
|
|
1826
|
+
"messaging.destination.name": "amq.gen-JzTY20BRgKO-HjmUJj0wLg",
|
|
1827
|
+
}),
|
|
1828
|
+
spanRow(SpanKind.Consumer, {
|
|
1829
|
+
"messaging.system": "jms",
|
|
1830
|
+
"messaging.destination.name": "(temporary)",
|
|
1831
|
+
}),
|
|
1832
|
+
spanRow(SpanKind.Producer, {
|
|
1833
|
+
...KAFKA_ORDERS,
|
|
1834
|
+
"messaging.destination.temporary": "true",
|
|
1835
|
+
}),
|
|
1836
|
+
spanRow(SpanKind.Producer, {
|
|
1837
|
+
"messaging.system": "nats",
|
|
1838
|
+
"messaging.destination.name": "_INBOX.abc123",
|
|
1839
|
+
}),
|
|
1840
|
+
spanRow(SpanKind.Producer, {
|
|
1841
|
+
"messaging.system": "kafka",
|
|
1842
|
+
"messaging.destination.name": "7f1c2a9e-4b1d-4c3e-9f1a-2b3c4d5e6f70",
|
|
1843
|
+
}),
|
|
1844
|
+
]),
|
|
1845
|
+
).toEqual([]);
|
|
1846
|
+
});
|
|
1847
|
+
|
|
1848
|
+
test("per-request names template into one queue", () => {
|
|
1849
|
+
const queues: Array<DiscoveredMessageQueue> =
|
|
1850
|
+
resolveMessagingSpanDiscoveryRows([
|
|
1851
|
+
spanRow(SpanKind.Producer, {
|
|
1852
|
+
"messaging.system": "rabbitmq",
|
|
1853
|
+
"messaging.destination.name":
|
|
1854
|
+
"reply-7f1c2a9e-4b1d-4c3e-9f1a-2b3c4d5e6f70",
|
|
1855
|
+
}),
|
|
1856
|
+
spanRow(SpanKind.Producer, {
|
|
1857
|
+
"messaging.system": "rabbitmq",
|
|
1858
|
+
"messaging.destination.name":
|
|
1859
|
+
"reply-0b1c2d3e-4f50-4617-8829-3a4b5c6d7e8f",
|
|
1860
|
+
}),
|
|
1861
|
+
]);
|
|
1862
|
+
|
|
1863
|
+
expect(identifiers(queues)).toEqual(["rabbitmq||reply-{uuid}"]);
|
|
1864
|
+
expect(queues[0]!.spans?.count).toBe(10);
|
|
1865
|
+
});
|
|
1866
|
+
|
|
1867
|
+
test("Pulsar partitions fold into their topic, and Service Bus entity paths into their entity", () => {
|
|
1868
|
+
const queues: Array<DiscoveredMessageQueue> =
|
|
1869
|
+
resolveMessagingSpanDiscoveryRows([
|
|
1870
|
+
spanRow(SpanKind.Producer, {
|
|
1871
|
+
"messaging.system": "pulsar",
|
|
1872
|
+
"messaging.destination.name": "orders-partition-0",
|
|
1873
|
+
}),
|
|
1874
|
+
spanRow(SpanKind.Producer, {
|
|
1875
|
+
"messaging.system": "pulsar",
|
|
1876
|
+
"messaging.destination.name": "orders-partition-3",
|
|
1877
|
+
}),
|
|
1878
|
+
spanRow(SpanKind.Consumer, {
|
|
1879
|
+
"messaging.system": "servicebus",
|
|
1880
|
+
"messaging.destination.name": "orders/Subscriptions/billing",
|
|
1881
|
+
"server.address": SERVICE_BUS_HOST,
|
|
1882
|
+
}),
|
|
1883
|
+
spanRow(SpanKind.Consumer, {
|
|
1884
|
+
"messaging.system": "servicebus",
|
|
1885
|
+
"messaging.destination.name": "orders/$DeadLetterQueue",
|
|
1886
|
+
"server.address": SERVICE_BUS_HOST,
|
|
1887
|
+
}),
|
|
1888
|
+
]);
|
|
1889
|
+
|
|
1890
|
+
expect(identifiers(queues).sort()).toEqual([
|
|
1891
|
+
"pulsar||persistent://public/default/orders",
|
|
1892
|
+
"servicebus|orders-prod|orders",
|
|
1893
|
+
]);
|
|
1894
|
+
});
|
|
1895
|
+
|
|
1896
|
+
test("a destination whose identifier cannot be built is dropped, as ingest stamps no key on it", () => {
|
|
1897
|
+
// Lowercasing U+0130 doubles it: 255 characters, 510 once canonical.
|
|
1898
|
+
const overlong: string = DOTTED_CAPITAL_I.repeat(255);
|
|
1899
|
+
expect(
|
|
1900
|
+
resolveMessagingSpan({
|
|
1901
|
+
getAttribute: getterOf({
|
|
1902
|
+
"messaging.system": "kafka",
|
|
1903
|
+
"messaging.destination.name": overlong,
|
|
1904
|
+
}),
|
|
1905
|
+
kind: SpanKind.Producer,
|
|
1906
|
+
})?.destination,
|
|
1907
|
+
).toBe(overlong);
|
|
1908
|
+
|
|
1909
|
+
expect(
|
|
1910
|
+
resolveMessagingSpanDiscoveryRows([
|
|
1911
|
+
spanRow(SpanKind.Producer, {
|
|
1912
|
+
"messaging.system": "kafka",
|
|
1913
|
+
"messaging.destination.name": overlong,
|
|
1914
|
+
}),
|
|
1915
|
+
]),
|
|
1916
|
+
).toEqual([]);
|
|
1917
|
+
});
|
|
1918
|
+
|
|
1919
|
+
test("keeps the busiest spelling of a destination and the busiest broker address", () => {
|
|
1920
|
+
const queues: Array<DiscoveredMessageQueue> =
|
|
1921
|
+
resolveMessagingSpanDiscoveryRows([
|
|
1922
|
+
spanRow(
|
|
1923
|
+
SpanKind.Producer,
|
|
1924
|
+
{
|
|
1925
|
+
"messaging.system": "kafka",
|
|
1926
|
+
"messaging.destination.name": "orders",
|
|
1927
|
+
"server.address": "kafka-1",
|
|
1928
|
+
"server.port": "9092",
|
|
1929
|
+
},
|
|
1930
|
+
{ spanCount: "2" },
|
|
1931
|
+
),
|
|
1932
|
+
spanRow(
|
|
1933
|
+
SpanKind.Consumer,
|
|
1934
|
+
{
|
|
1935
|
+
"messaging.system": "kafka",
|
|
1936
|
+
"messaging.destination.name": "Orders",
|
|
1937
|
+
"server.address": "kafka-2",
|
|
1938
|
+
"server.port": "9092",
|
|
1939
|
+
},
|
|
1940
|
+
{ spanCount: "9" },
|
|
1941
|
+
),
|
|
1942
|
+
]);
|
|
1943
|
+
|
|
1944
|
+
expect(queues).toHaveLength(1);
|
|
1945
|
+
expect(queues[0]!.destination).toBe("Orders");
|
|
1946
|
+
expect(queues[0]!.spans?.brokerAddress).toBe("kafka-2:9092");
|
|
1947
|
+
});
|
|
1948
|
+
|
|
1949
|
+
test("ties are broken by value, so the answer never depends on row order", () => {
|
|
1950
|
+
const a: MessagingSpanDiscoveryRow = spanRow(SpanKind.Producer, {
|
|
1951
|
+
"messaging.system": "kafka",
|
|
1952
|
+
"messaging.destination.name": "orders",
|
|
1953
|
+
"server.address": "kafka-b",
|
|
1954
|
+
});
|
|
1955
|
+
const b: MessagingSpanDiscoveryRow = spanRow(SpanKind.Producer, {
|
|
1956
|
+
"messaging.system": "kafka",
|
|
1957
|
+
"messaging.destination.name": "Orders",
|
|
1958
|
+
"server.address": "kafka-a",
|
|
1959
|
+
});
|
|
1960
|
+
|
|
1961
|
+
for (const rows of [
|
|
1962
|
+
[a, b],
|
|
1963
|
+
[b, a],
|
|
1964
|
+
]) {
|
|
1965
|
+
const [queue]: Array<DiscoveredMessageQueue> =
|
|
1966
|
+
resolveMessagingSpanDiscoveryRows(rows);
|
|
1967
|
+
expect(queue!.destination).toBe("Orders");
|
|
1968
|
+
expect(queue!.spans?.brokerAddress).toBe("kafka-a");
|
|
1969
|
+
}
|
|
1970
|
+
});
|
|
1971
|
+
|
|
1972
|
+
test("keeps the newest time; a missing or unreadable one is unknown", () => {
|
|
1973
|
+
const [queue]: Array<DiscoveredMessageQueue> =
|
|
1974
|
+
resolveMessagingSpanDiscoveryRows([
|
|
1975
|
+
spanRow(SpanKind.Producer, KAFKA_ORDERS, {
|
|
1976
|
+
lastSeenUnixMs: "1790243000000",
|
|
1977
|
+
}),
|
|
1978
|
+
spanRow(
|
|
1979
|
+
SpanKind.Consumer,
|
|
1980
|
+
{ ...KAFKA_ORDERS, "messaging.operation.type": "process" },
|
|
1981
|
+
{ lastSeenUnixMs: 1790243100999 },
|
|
1982
|
+
),
|
|
1983
|
+
]);
|
|
1984
|
+
expect(queue!.spans?.lastSeenAt?.getTime()).toBe(1790243100999);
|
|
1985
|
+
|
|
1986
|
+
for (const lastSeenUnixMs of [undefined, "", "yesterday", "-5", 0]) {
|
|
1987
|
+
const [unknown]: Array<DiscoveredMessageQueue> =
|
|
1988
|
+
resolveMessagingSpanDiscoveryRows([
|
|
1989
|
+
spanRow(SpanKind.Producer, KAFKA_ORDERS, { lastSeenUnixMs }),
|
|
1990
|
+
]);
|
|
1991
|
+
expect(unknown!.spans?.lastSeenAt).toBeNull();
|
|
1992
|
+
}
|
|
1993
|
+
});
|
|
1994
|
+
|
|
1995
|
+
test("malformed counts count as zero, never NaN, and the queue is still seen", () => {
|
|
1996
|
+
const [queue]: Array<DiscoveredMessageQueue> =
|
|
1997
|
+
resolveMessagingSpanDiscoveryRows([
|
|
1998
|
+
spanRow(SpanKind.Producer, KAFKA_ORDERS, {
|
|
1999
|
+
spanCount: "lots",
|
|
2000
|
+
errorCount: undefined,
|
|
2001
|
+
}),
|
|
2002
|
+
]);
|
|
2003
|
+
expect(queue!.spans?.count).toBe(0);
|
|
2004
|
+
expect(queue!.spans?.errorCount).toBe(0);
|
|
2005
|
+
});
|
|
2006
|
+
|
|
2007
|
+
test("returns the busiest queues first, then by identifier", () => {
|
|
2008
|
+
const queues: Array<DiscoveredMessageQueue> =
|
|
2009
|
+
resolveMessagingSpanDiscoveryRows([
|
|
2010
|
+
spanRow(
|
|
2011
|
+
SpanKind.Producer,
|
|
2012
|
+
{ "messaging.system": "kafka", "messaging.destination.name": "b" },
|
|
2013
|
+
{ spanCount: "3" },
|
|
2014
|
+
),
|
|
2015
|
+
spanRow(
|
|
2016
|
+
SpanKind.Producer,
|
|
2017
|
+
{ "messaging.system": "kafka", "messaging.destination.name": "c" },
|
|
2018
|
+
{ spanCount: "30" },
|
|
2019
|
+
),
|
|
2020
|
+
spanRow(
|
|
2021
|
+
SpanKind.Producer,
|
|
2022
|
+
{ "messaging.system": "kafka", "messaging.destination.name": "a" },
|
|
2023
|
+
{ spanCount: "3" },
|
|
2024
|
+
),
|
|
2025
|
+
]);
|
|
2026
|
+
expect(identifiers(queues)).toEqual(["kafka||c", "kafka||a", "kafka||b"]);
|
|
2027
|
+
});
|
|
2028
|
+
|
|
2029
|
+
test("tolerates junk input", () => {
|
|
2030
|
+
expect(resolveMessagingSpanDiscoveryRows([])).toEqual([]);
|
|
2031
|
+
expect(
|
|
2032
|
+
resolveMessagingSpanDiscoveryRows(
|
|
2033
|
+
null as unknown as Array<MessagingSpanDiscoveryRow>,
|
|
2034
|
+
),
|
|
2035
|
+
).toEqual([]);
|
|
2036
|
+
expect(
|
|
2037
|
+
resolveMessagingSpanDiscoveryRows([
|
|
2038
|
+
null as unknown as MessagingSpanDiscoveryRow,
|
|
2039
|
+
"row" as unknown as MessagingSpanDiscoveryRow,
|
|
2040
|
+
{ kind: 5 as unknown as string, a0: "kafka" },
|
|
2041
|
+
]),
|
|
2042
|
+
).toEqual([]);
|
|
2043
|
+
});
|
|
2044
|
+
|
|
2045
|
+
test("1000 pods of one queue are one group: nothing selected varies per instance", () => {
|
|
2046
|
+
const projections: Set<string> = new Set<string>();
|
|
2047
|
+
for (let pod: number = 0; pod < 1000; pod++) {
|
|
2048
|
+
const row: MessagingSpanDiscoveryRow = spanRow(SpanKind.Consumer, {
|
|
2049
|
+
...KAFKA_ORDERS,
|
|
2050
|
+
"messaging.operation.type": "process",
|
|
2051
|
+
"messaging.consumer.group.name": "billing",
|
|
2052
|
+
"resource.k8s.pod.name": `billing-${pod}`,
|
|
2053
|
+
"resource.service.instance.id": `instance-${pod}`,
|
|
2054
|
+
"resource.host.name": `node-${pod % 7}`,
|
|
2055
|
+
"messaging.client.id": `consumer-billing-${pod}`,
|
|
2056
|
+
"messaging.kafka.offset": pod * 17,
|
|
2057
|
+
"messaging.destination.partition.id": String(pod % 12),
|
|
2058
|
+
"messaging.message.id": `message-${pod}`,
|
|
2059
|
+
});
|
|
2060
|
+
delete row["spanCount"];
|
|
2061
|
+
delete row["errorCount"];
|
|
2062
|
+
delete row["lastSeenUnixMs"];
|
|
2063
|
+
projections.add(JSON.stringify(row));
|
|
2064
|
+
}
|
|
2065
|
+
expect(projections.size).toBe(1);
|
|
2066
|
+
});
|
|
2067
|
+
});
|
|
2068
|
+
|
|
2069
|
+
describe("resolveMessagingMetricDiscoveryRows", () => {
|
|
2070
|
+
test("a curated broker metric is broker evidence; a messaging client metric is client evidence", () => {
|
|
2071
|
+
const queues: Array<DiscoveredMessageQueue> =
|
|
2072
|
+
resolveMessagingMetricDiscoveryRows([
|
|
2073
|
+
metricRow(
|
|
2074
|
+
"kafka.consumer_group.lag_sum",
|
|
2075
|
+
{ topic: "orders", group: "billing" },
|
|
2076
|
+
{ pointCount: "60", lastSeenUnixMs: "1790243000000" },
|
|
2077
|
+
),
|
|
2078
|
+
metricRow(
|
|
2079
|
+
"messaging.client.sent.messages",
|
|
2080
|
+
{ ...KAFKA_ORDERS, "server.address": "kafka-1" },
|
|
2081
|
+
{ pointCount: "4", lastSeenUnixMs: "1790243100000" },
|
|
2082
|
+
),
|
|
2083
|
+
]);
|
|
2084
|
+
|
|
2085
|
+
expect(queues).toHaveLength(1);
|
|
2086
|
+
expect(queues[0]!.brokerMetrics).toEqual({
|
|
2087
|
+
count: 60,
|
|
2088
|
+
errorCount: 0,
|
|
2089
|
+
directions: { publish: 0, consume: 0, settle: 0, unknown: 60 },
|
|
2090
|
+
brokerAddress: null,
|
|
2091
|
+
lastSeenAt: new Date(1790243000000),
|
|
2092
|
+
});
|
|
2093
|
+
expect(queues[0]!.clientMetrics).toEqual({
|
|
2094
|
+
count: 4,
|
|
2095
|
+
errorCount: 0,
|
|
2096
|
+
directions: { publish: 4, consume: 0, settle: 0, unknown: 0 },
|
|
2097
|
+
brokerAddress: "kafka-1",
|
|
2098
|
+
lastSeenAt: new Date(1790243100000),
|
|
2099
|
+
});
|
|
2100
|
+
expect(queues[0]!.spans).toBeNull();
|
|
2101
|
+
});
|
|
2102
|
+
|
|
2103
|
+
test("an ActiveMQ datapoint keys the JMS family and records ActiveMQ", () => {
|
|
2104
|
+
const [queue]: Array<DiscoveredMessageQueue> =
|
|
2105
|
+
resolveMessagingMetricDiscoveryRows([
|
|
2106
|
+
metricRow("activemq.message.queue.size", {
|
|
2107
|
+
"messaging.destination.name": "orders",
|
|
2108
|
+
"activemq.destination.type": "queue",
|
|
2109
|
+
}),
|
|
2110
|
+
]);
|
|
2111
|
+
expect(queue!.identifier).toBe("jms||orders");
|
|
2112
|
+
expect(queue!.identity.system).toBe("jms");
|
|
2113
|
+
expect(queue!.system).toBe("activemq");
|
|
2114
|
+
});
|
|
2115
|
+
|
|
2116
|
+
test("Azure Monitor's shared names tell Service Bus from Event Hubs, scoped to the namespace", () => {
|
|
2117
|
+
const queues: Array<DiscoveredMessageQueue> =
|
|
2118
|
+
resolveMessagingMetricDiscoveryRows([
|
|
2119
|
+
metricRow("azure_incomingmessages_total", {
|
|
2120
|
+
type: "Microsoft.ServiceBus/Namespaces",
|
|
2121
|
+
name: "orders-prod",
|
|
2122
|
+
metadata_entityname: "orders",
|
|
2123
|
+
}),
|
|
2124
|
+
metricRow("azure_incomingmessages_total", {
|
|
2125
|
+
type: "Microsoft.EventHub/namespaces",
|
|
2126
|
+
name: "ingest-prod",
|
|
2127
|
+
metadata_EntityName: "telemetry",
|
|
2128
|
+
}),
|
|
2129
|
+
metricRow("azure_incomingmessages_total", {
|
|
2130
|
+
type: "Microsoft.Storage/storageAccounts",
|
|
2131
|
+
name: "acct",
|
|
2132
|
+
metadata_entityname: "orders",
|
|
2133
|
+
}),
|
|
2134
|
+
]);
|
|
2135
|
+
expect(identifiers(queues).sort()).toEqual([
|
|
2136
|
+
"eventhubs|ingest-prod|telemetry",
|
|
2137
|
+
"servicebus|orders-prod|orders",
|
|
2138
|
+
]);
|
|
2139
|
+
});
|
|
2140
|
+
|
|
2141
|
+
test("both CloudWatch shapes of an SQS metric name one queue", () => {
|
|
2142
|
+
const queues: Array<DiscoveredMessageQueue> =
|
|
2143
|
+
resolveMessagingMetricDiscoveryRows([
|
|
2144
|
+
metricRow(
|
|
2145
|
+
"amazonaws.com/aws/sqs/approximatenumberofmessagesvisible",
|
|
2146
|
+
{ "Dimensions.QueueName": "orders" },
|
|
2147
|
+
{ pointCount: "3" },
|
|
2148
|
+
),
|
|
2149
|
+
metricRow(
|
|
2150
|
+
"approximatenumberofmessagesvisible",
|
|
2151
|
+
{ QueueName: "orders", "resource.service.name": "SQS" },
|
|
2152
|
+
{ pointCount: "2" },
|
|
2153
|
+
),
|
|
2154
|
+
// The JSON shape of another service's metric is not SQS's.
|
|
2155
|
+
metricRow("approximatenumberofmessagesvisible", {
|
|
2156
|
+
QueueName: "orders",
|
|
2157
|
+
"resource.service.name": "Kinesis",
|
|
2158
|
+
}),
|
|
2159
|
+
]);
|
|
2160
|
+
expect(identifiers(queues)).toEqual(["aws_sqs||orders"]);
|
|
2161
|
+
expect(queues[0]!.brokerMetrics?.count).toBe(5);
|
|
2162
|
+
});
|
|
2163
|
+
|
|
2164
|
+
test("a Pulsar per-consumer repeat folds into its topic's group: the key that marks it is not selected", () => {
|
|
2165
|
+
const repeat: FixtureAttributes = {
|
|
2166
|
+
topic: "persistent://public/default/orders-partition-0",
|
|
2167
|
+
subscription: "billing",
|
|
2168
|
+
consumer_name: "billing-1",
|
|
2169
|
+
consumer_id: "0",
|
|
2170
|
+
};
|
|
2171
|
+
|
|
2172
|
+
// Ingest drops the repeat itself…
|
|
2173
|
+
expect(
|
|
2174
|
+
resolveMessagingMetricDatapoint({
|
|
2175
|
+
metricName: "pulsar_out_messages_total",
|
|
2176
|
+
getAttribute: getterOf(repeat),
|
|
2177
|
+
}),
|
|
2178
|
+
).toBeNull();
|
|
2179
|
+
|
|
2180
|
+
// …while its grouped row, which cannot carry the marker, names the topic.
|
|
2181
|
+
const [queue]: Array<DiscoveredMessageQueue> =
|
|
2182
|
+
resolveMessagingMetricDiscoveryRows([
|
|
2183
|
+
metricRow("pulsar_out_messages_total", repeat),
|
|
2184
|
+
]);
|
|
2185
|
+
expect(queue!.identifier).toBe(
|
|
2186
|
+
"pulsar||persistent://public/default/orders",
|
|
2187
|
+
);
|
|
2188
|
+
});
|
|
2189
|
+
|
|
2190
|
+
test("anything but a curated broker metric is client evidence, which never creates a queue", () => {
|
|
2191
|
+
const [queue]: Array<DiscoveredMessageQueue> =
|
|
2192
|
+
resolveMessagingMetricDiscoveryRows([
|
|
2193
|
+
metricRow("queue.size", {
|
|
2194
|
+
"messaging.system": "bullmq",
|
|
2195
|
+
"messaging.destination.name": "orders",
|
|
2196
|
+
state: "waiting",
|
|
2197
|
+
}),
|
|
2198
|
+
]);
|
|
2199
|
+
expect(queue!.clientMetrics?.count).toBe(12);
|
|
2200
|
+
expect(queue!.brokerMetrics).toBeNull();
|
|
2201
|
+
expect(
|
|
2202
|
+
isMessageQueueAutoCreateCandidate({
|
|
2203
|
+
discovered: queue!,
|
|
2204
|
+
minSpans: DEFAULT_MESSAGE_QUEUE_MIN_SPANS,
|
|
2205
|
+
}),
|
|
2206
|
+
).toBe(false);
|
|
2207
|
+
});
|
|
2208
|
+
|
|
2209
|
+
test("placeholders and other resources' metrics resolve to nothing", () => {
|
|
2210
|
+
expect(
|
|
2211
|
+
resolveMessagingMetricDiscoveryRows([
|
|
2212
|
+
metricRow("azure_activemessages_average", {
|
|
2213
|
+
type: "Microsoft.ServiceBus/Namespaces",
|
|
2214
|
+
name: "orders-prod",
|
|
2215
|
+
metadata_entityname: "-NamespaceOnlyMetric-",
|
|
2216
|
+
}),
|
|
2217
|
+
metricRow("pulsar_msg_backlog", {
|
|
2218
|
+
topic: "persistent://public/default/__change_events",
|
|
2219
|
+
}),
|
|
2220
|
+
metricRow("system.cpu.utilization", { state: "user" }),
|
|
2221
|
+
]),
|
|
2222
|
+
).toEqual([]);
|
|
2223
|
+
});
|
|
2224
|
+
|
|
2225
|
+
test("tolerates junk input", () => {
|
|
2226
|
+
expect(
|
|
2227
|
+
resolveMessagingMetricDiscoveryRows(
|
|
2228
|
+
undefined as unknown as Array<MessagingMetricDiscoveryRow>,
|
|
2229
|
+
),
|
|
2230
|
+
).toEqual([]);
|
|
2231
|
+
expect(
|
|
2232
|
+
resolveMessagingMetricDiscoveryRows([
|
|
2233
|
+
null as unknown as MessagingMetricDiscoveryRow,
|
|
2234
|
+
{ name: 42 as unknown as string, a0: "kafka" },
|
|
2235
|
+
]),
|
|
2236
|
+
).toEqual([]);
|
|
2237
|
+
});
|
|
2238
|
+
});
|
|
2239
|
+
|
|
2240
|
+
describe("mergeDiscoveredMessageQueues", () => {
|
|
2241
|
+
test("JMS spans and ActiveMQ broker metrics are one queue, recorded as ActiveMQ, in either order", () => {
|
|
2242
|
+
const spans: Array<DiscoveredMessageQueue> =
|
|
2243
|
+
resolveMessagingSpanDiscoveryRows([
|
|
2244
|
+
spanRow(
|
|
2245
|
+
SpanKind.Producer,
|
|
2246
|
+
{
|
|
2247
|
+
"messaging.system": "jms",
|
|
2248
|
+
"messaging.destination.name": "orders",
|
|
2249
|
+
},
|
|
2250
|
+
{ spanCount: "50" },
|
|
2251
|
+
),
|
|
2252
|
+
]);
|
|
2253
|
+
const metrics: Array<DiscoveredMessageQueue> =
|
|
2254
|
+
resolveMessagingMetricDiscoveryRows([
|
|
2255
|
+
metricRow(
|
|
2256
|
+
"activemq.message.current",
|
|
2257
|
+
{ destination: "orders" },
|
|
2258
|
+
{ pointCount: "2" },
|
|
2259
|
+
),
|
|
2260
|
+
]);
|
|
2261
|
+
|
|
2262
|
+
for (const merged of [
|
|
2263
|
+
mergeDiscoveredMessageQueues(spans, metrics),
|
|
2264
|
+
mergeDiscoveredMessageQueues(metrics, spans),
|
|
2265
|
+
]) {
|
|
2266
|
+
expect(merged).toHaveLength(1);
|
|
2267
|
+
expect(merged[0]!.identifier).toBe("jms||orders");
|
|
2268
|
+
expect(merged[0]!.system).toBe("activemq");
|
|
2269
|
+
expect(merged[0]!.spans?.count).toBe(50);
|
|
2270
|
+
expect(merged[0]!.brokerMetrics?.count).toBe(2);
|
|
2271
|
+
expect(merged[0]!.clientMetrics).toBeNull();
|
|
2272
|
+
}
|
|
2273
|
+
});
|
|
2274
|
+
|
|
2275
|
+
test("Service Bus spans join Azure Monitor's metrics through the namespace; spans without one stay apart", () => {
|
|
2276
|
+
const spans: Array<DiscoveredMessageQueue> =
|
|
2277
|
+
resolveMessagingSpanDiscoveryRows([
|
|
2278
|
+
spanRow(SpanKind.Producer, {
|
|
2279
|
+
"messaging.system": "servicebus",
|
|
2280
|
+
"messaging.destination.name": "orders",
|
|
2281
|
+
"server.address": SERVICE_BUS_HOST,
|
|
2282
|
+
}),
|
|
2283
|
+
spanRow(SpanKind.Producer, {
|
|
2284
|
+
"messaging.system": "servicebus",
|
|
2285
|
+
"messaging.destination.name": "orders",
|
|
2286
|
+
"server.address": "localhost",
|
|
2287
|
+
}),
|
|
2288
|
+
]);
|
|
2289
|
+
const metrics: Array<DiscoveredMessageQueue> =
|
|
2290
|
+
resolveMessagingMetricDiscoveryRows([
|
|
2291
|
+
metricRow("azure_activemessages_average", {
|
|
2292
|
+
type: "Microsoft.ServiceBus/Namespaces",
|
|
2293
|
+
name: "orders-prod",
|
|
2294
|
+
metadata_entityname: "orders",
|
|
2295
|
+
}),
|
|
2296
|
+
]);
|
|
2297
|
+
|
|
2298
|
+
const merged: Array<DiscoveredMessageQueue> = mergeDiscoveredMessageQueues(
|
|
2299
|
+
spans,
|
|
2300
|
+
metrics,
|
|
2301
|
+
);
|
|
2302
|
+
expect(identifiers(merged).sort()).toEqual(
|
|
2303
|
+
["servicebus||orders", "servicebus|orders-prod|orders"].sort(),
|
|
2304
|
+
);
|
|
2305
|
+
const scoped: DiscoveredMessageQueue = merged.find(
|
|
2306
|
+
(queue: DiscoveredMessageQueue): boolean => {
|
|
2307
|
+
return queue.identity.brokerScope === "orders-prod";
|
|
2308
|
+
},
|
|
2309
|
+
)!;
|
|
2310
|
+
expect(scoped.spans?.count).toBe(5);
|
|
2311
|
+
expect(scoped.brokerMetrics?.count).toBe(12);
|
|
2312
|
+
});
|
|
2313
|
+
|
|
2314
|
+
test("keeps each kind of evidence apart, with the newest time and the busiest address of each", () => {
|
|
2315
|
+
const first: DiscoveredMessageQueue = discovered({
|
|
2316
|
+
destination: "orders",
|
|
2317
|
+
spans: evidence({
|
|
2318
|
+
count: 10,
|
|
2319
|
+
brokerAddress: "kafka-1:9092",
|
|
2320
|
+
lastSeenAt: new Date(1000),
|
|
2321
|
+
}),
|
|
2322
|
+
});
|
|
2323
|
+
const second: DiscoveredMessageQueue = discovered({
|
|
2324
|
+
destination: "Orders",
|
|
2325
|
+
spans: evidence({
|
|
2326
|
+
count: 30,
|
|
2327
|
+
errorCount: 2,
|
|
2328
|
+
brokerAddress: "kafka-2:9092",
|
|
2329
|
+
lastSeenAt: new Date(500),
|
|
2330
|
+
}),
|
|
2331
|
+
brokerMetrics: evidence({
|
|
2332
|
+
count: 4,
|
|
2333
|
+
brokerAddress: "exporter:9308",
|
|
2334
|
+
lastSeenAt: new Date(2000),
|
|
2335
|
+
}),
|
|
2336
|
+
});
|
|
2337
|
+
|
|
2338
|
+
const [merged]: Array<DiscoveredMessageQueue> =
|
|
2339
|
+
mergeDiscoveredMessageQueues([first], [second]);
|
|
2340
|
+
|
|
2341
|
+
expect(merged!.destination).toBe("Orders");
|
|
2342
|
+
expect(merged!.spans).toEqual({
|
|
2343
|
+
count: 40,
|
|
2344
|
+
errorCount: 2,
|
|
2345
|
+
directions: { publish: 0, consume: 0, settle: 0, unknown: 2 },
|
|
2346
|
+
brokerAddress: "kafka-2:9092",
|
|
2347
|
+
lastSeenAt: new Date(1000),
|
|
2348
|
+
});
|
|
2349
|
+
expect(merged!.brokerMetrics?.brokerAddress).toBe("exporter:9308");
|
|
2350
|
+
expect(merged!.brokerMetrics?.lastSeenAt).toEqual(new Date(2000));
|
|
2351
|
+
expect(merged!.clientMetrics).toBeNull();
|
|
2352
|
+
expect(getDiscoveredMessageQueueEvidenceCount(merged!)).toBe(44);
|
|
2353
|
+
});
|
|
2354
|
+
|
|
2355
|
+
test("tolerates junk input", () => {
|
|
2356
|
+
expect(mergeDiscoveredMessageQueues()).toEqual([]);
|
|
2357
|
+
expect(
|
|
2358
|
+
mergeDiscoveredMessageQueues(
|
|
2359
|
+
null as unknown as Array<DiscoveredMessageQueue>,
|
|
2360
|
+
[null as unknown as DiscoveredMessageQueue],
|
|
2361
|
+
[{} as DiscoveredMessageQueue],
|
|
2362
|
+
),
|
|
2363
|
+
).toEqual([]);
|
|
2364
|
+
});
|
|
2365
|
+
});
|
|
2366
|
+
|
|
2367
|
+
describe("isMessageQueueAutoCreateCandidate", () => {
|
|
2368
|
+
test("spans at or above the minimum create; fewer do not", () => {
|
|
2369
|
+
for (const [count, candidate] of [
|
|
2370
|
+
[2, false],
|
|
2371
|
+
[3, true],
|
|
2372
|
+
[300, true],
|
|
2373
|
+
] as Array<[number, boolean]>) {
|
|
2374
|
+
expect(
|
|
2375
|
+
isMessageQueueAutoCreateCandidate({
|
|
2376
|
+
discovered: discovered({ spans: evidence({ count }) }),
|
|
2377
|
+
minSpans: 3,
|
|
2378
|
+
}),
|
|
2379
|
+
).toBe(candidate);
|
|
2380
|
+
}
|
|
2381
|
+
});
|
|
2382
|
+
|
|
2383
|
+
test("one broker metric sighting creates, whatever the spans say", () => {
|
|
2384
|
+
expect(
|
|
2385
|
+
isMessageQueueAutoCreateCandidate({
|
|
2386
|
+
discovered: discovered({
|
|
2387
|
+
spans: evidence({ count: 1 }),
|
|
2388
|
+
brokerMetrics: evidence({ count: 1 }),
|
|
2389
|
+
}),
|
|
2390
|
+
minSpans: 3,
|
|
2391
|
+
}),
|
|
2392
|
+
).toBe(true);
|
|
2393
|
+
expect(
|
|
2394
|
+
isMessageQueueAutoCreateCandidate({
|
|
2395
|
+
discovered: discovered({ brokerMetrics: evidence({ count: 0 }) }),
|
|
2396
|
+
minSpans: 3,
|
|
2397
|
+
}),
|
|
2398
|
+
).toBe(true);
|
|
2399
|
+
});
|
|
2400
|
+
|
|
2401
|
+
test("messaging client metrics alone never create", () => {
|
|
2402
|
+
expect(
|
|
2403
|
+
isMessageQueueAutoCreateCandidate({
|
|
2404
|
+
discovered: discovered({ clientMetrics: evidence({ count: 5000 }) }),
|
|
2405
|
+
minSpans: 1,
|
|
2406
|
+
}),
|
|
2407
|
+
).toBe(false);
|
|
2408
|
+
});
|
|
2409
|
+
|
|
2410
|
+
test("a minimum below one is one: no spans never create", () => {
|
|
2411
|
+
for (const minSpans of [0, -3, Number.NaN]) {
|
|
2412
|
+
expect(
|
|
2413
|
+
isMessageQueueAutoCreateCandidate({
|
|
2414
|
+
discovered: discovered({}),
|
|
2415
|
+
minSpans,
|
|
2416
|
+
}),
|
|
2417
|
+
).toBe(false);
|
|
2418
|
+
expect(
|
|
2419
|
+
isMessageQueueAutoCreateCandidate({
|
|
2420
|
+
discovered: discovered({ spans: evidence({ count: 0 }) }),
|
|
2421
|
+
minSpans,
|
|
2422
|
+
}),
|
|
2423
|
+
).toBe(false);
|
|
2424
|
+
}
|
|
2425
|
+
});
|
|
2426
|
+
});
|
|
2427
|
+
|
|
2428
|
+
describe("getMessageQueueCreationSource", () => {
|
|
2429
|
+
test("traces when the spans meet the minimum, else broker metrics when the broker reported it", () => {
|
|
2430
|
+
expect(
|
|
2431
|
+
getMessageQueueCreationSource({
|
|
2432
|
+
discovered: discovered({
|
|
2433
|
+
spans: evidence({ count: 3 }),
|
|
2434
|
+
brokerMetrics: evidence({}),
|
|
2435
|
+
}),
|
|
2436
|
+
minSpans: 3,
|
|
2437
|
+
}),
|
|
2438
|
+
).toBe("traces");
|
|
2439
|
+
expect(
|
|
2440
|
+
getMessageQueueCreationSource({
|
|
2441
|
+
discovered: discovered({
|
|
2442
|
+
spans: evidence({ count: 2 }),
|
|
2443
|
+
brokerMetrics: evidence({}),
|
|
2444
|
+
}),
|
|
2445
|
+
minSpans: 3,
|
|
2446
|
+
}),
|
|
2447
|
+
).toBe("broker-metrics");
|
|
2448
|
+
expect(
|
|
2449
|
+
getMessageQueueCreationSource({
|
|
2450
|
+
discovered: discovered({ brokerMetrics: evidence({}) }),
|
|
2451
|
+
minSpans: 3,
|
|
2452
|
+
}),
|
|
2453
|
+
).toBe("broker-metrics");
|
|
2454
|
+
expect(
|
|
2455
|
+
getMessageQueueCreationSource({
|
|
2456
|
+
discovered: discovered({ clientMetrics: evidence({}) }),
|
|
2457
|
+
minSpans: 3,
|
|
2458
|
+
}),
|
|
2459
|
+
).toBe("traces");
|
|
2460
|
+
});
|
|
2461
|
+
});
|
|
2462
|
+
|
|
2463
|
+
describe("getDiscoveredMessageQueueBrokerAddress", () => {
|
|
2464
|
+
test("the application's address — its spans', else its client metrics' — before a broker scrape's target", () => {
|
|
2465
|
+
expect(
|
|
2466
|
+
getDiscoveredMessageQueueBrokerAddress(
|
|
2467
|
+
discovered({
|
|
2468
|
+
spans: evidence({ brokerAddress: "pulsar-broker:6650" }),
|
|
2469
|
+
brokerMetrics: evidence({ brokerAddress: "pulsar-broker:8080" }),
|
|
2470
|
+
clientMetrics: evidence({ brokerAddress: "pulsar-proxy:6650" }),
|
|
2471
|
+
}),
|
|
2472
|
+
),
|
|
2473
|
+
).toBe("pulsar-broker:6650");
|
|
2474
|
+
// Client metrics name the broker the client connected to, as spans do.
|
|
2475
|
+
expect(
|
|
2476
|
+
getDiscoveredMessageQueueBrokerAddress(
|
|
2477
|
+
discovered({
|
|
2478
|
+
spans: evidence({}),
|
|
2479
|
+
brokerMetrics: evidence({ brokerAddress: "pulsar-broker:8080" }),
|
|
2480
|
+
clientMetrics: evidence({ brokerAddress: "pulsar-proxy:6650" }),
|
|
2481
|
+
}),
|
|
2482
|
+
),
|
|
2483
|
+
).toBe("pulsar-proxy:6650");
|
|
2484
|
+
expect(
|
|
2485
|
+
getDiscoveredMessageQueueBrokerAddress(
|
|
2486
|
+
discovered({
|
|
2487
|
+
clientMetrics: evidence({ brokerAddress: "pulsar-proxy:6650" }),
|
|
2488
|
+
}),
|
|
2489
|
+
),
|
|
2490
|
+
).toBe("pulsar-proxy:6650");
|
|
2491
|
+
});
|
|
2492
|
+
|
|
2493
|
+
test("a broker scrape's target only when the application named no address", () => {
|
|
2494
|
+
expect(
|
|
2495
|
+
getDiscoveredMessageQueueBrokerAddress(
|
|
2496
|
+
discovered({
|
|
2497
|
+
spans: evidence({}),
|
|
2498
|
+
clientMetrics: evidence({}),
|
|
2499
|
+
brokerMetrics: evidence({ brokerAddress: "nats-exporter:7777" }),
|
|
2500
|
+
}),
|
|
2501
|
+
),
|
|
2502
|
+
).toBe("nats-exporter:7777");
|
|
2503
|
+
expect(
|
|
2504
|
+
getDiscoveredMessageQueueBrokerAddress(
|
|
2505
|
+
discovered({
|
|
2506
|
+
brokerMetrics: evidence({ brokerAddress: "nats-exporter:7777" }),
|
|
2507
|
+
}),
|
|
2508
|
+
),
|
|
2509
|
+
).toBe("nats-exporter:7777");
|
|
2510
|
+
expect(
|
|
2511
|
+
getDiscoveredMessageQueueBrokerAddress(
|
|
2512
|
+
discovered({ spans: evidence({}), brokerMetrics: evidence({}) }),
|
|
2513
|
+
),
|
|
2514
|
+
).toBeNull();
|
|
2515
|
+
expect(getDiscoveredMessageQueueBrokerAddress(discovered({}))).toBeNull();
|
|
2516
|
+
});
|
|
2517
|
+
|
|
2518
|
+
test("the broker metrics' address is the scrape target the resolver reads from the resource", () => {
|
|
2519
|
+
const [pulsar]: Array<DiscoveredMessageQueue> =
|
|
2520
|
+
resolveMessagingMetricDiscoveryRows([
|
|
2521
|
+
metricRow("pulsar_msg_backlog", {
|
|
2522
|
+
topic: "persistent://public/default/orders",
|
|
2523
|
+
"resource.server.address": "pulsar-broker",
|
|
2524
|
+
"resource.server.port": "8080",
|
|
2525
|
+
}),
|
|
2526
|
+
]);
|
|
2527
|
+
const [client]: Array<DiscoveredMessageQueue> =
|
|
2528
|
+
resolveMessagingMetricDiscoveryRows([
|
|
2529
|
+
metricRow("messaging.client.sent.messages", {
|
|
2530
|
+
"messaging.system": "pulsar",
|
|
2531
|
+
"messaging.destination.name": "orders",
|
|
2532
|
+
"server.address": "pulsar-broker",
|
|
2533
|
+
"server.port": 6650,
|
|
2534
|
+
}),
|
|
2535
|
+
]);
|
|
2536
|
+
|
|
2537
|
+
expect(pulsar!.brokerMetrics?.brokerAddress).toBe("pulsar-broker:8080");
|
|
2538
|
+
expect(client!.clientMetrics?.brokerAddress).toBe("pulsar-broker:6650");
|
|
2539
|
+
expect(client!.identifier).toBe(pulsar!.identifier);
|
|
2540
|
+
expect(
|
|
2541
|
+
getDiscoveredMessageQueueBrokerAddress(
|
|
2542
|
+
mergeDiscoveredMessageQueues([pulsar!], [client!])[0]!,
|
|
2543
|
+
),
|
|
2544
|
+
).toBe("pulsar-broker:6650");
|
|
2545
|
+
});
|
|
2546
|
+
});
|
|
2547
|
+
|
|
2548
|
+
describe("getMessageQueueMinSpans", () => {
|
|
2549
|
+
let saved: string | undefined;
|
|
2550
|
+
|
|
2551
|
+
beforeEach(() => {
|
|
2552
|
+
saved = process.env[MESSAGE_QUEUE_MIN_SPANS_ENV];
|
|
2553
|
+
delete process.env[MESSAGE_QUEUE_MIN_SPANS_ENV];
|
|
2554
|
+
});
|
|
2555
|
+
|
|
2556
|
+
afterEach(() => {
|
|
2557
|
+
if (saved === undefined) {
|
|
2558
|
+
delete process.env[MESSAGE_QUEUE_MIN_SPANS_ENV];
|
|
2559
|
+
} else {
|
|
2560
|
+
process.env[MESSAGE_QUEUE_MIN_SPANS_ENV] = saved;
|
|
2561
|
+
}
|
|
2562
|
+
});
|
|
2563
|
+
|
|
2564
|
+
test("defaults to 3, read from MESSAGE_QUEUE_MIN_SPANS", () => {
|
|
2565
|
+
expect(MESSAGE_QUEUE_MIN_SPANS_ENV).toBe("MESSAGE_QUEUE_MIN_SPANS");
|
|
2566
|
+
expect(DEFAULT_MESSAGE_QUEUE_MIN_SPANS).toBe(3);
|
|
2567
|
+
expect(getMessageQueueMinSpans()).toBe(3);
|
|
2568
|
+
});
|
|
2569
|
+
|
|
2570
|
+
test("reads the environment on every call", () => {
|
|
2571
|
+
process.env[MESSAGE_QUEUE_MIN_SPANS_ENV] = "25";
|
|
2572
|
+
expect(getMessageQueueMinSpans()).toBe(25);
|
|
2573
|
+
process.env[MESSAGE_QUEUE_MIN_SPANS_ENV] = " 7 ";
|
|
2574
|
+
expect(getMessageQueueMinSpans()).toBe(7);
|
|
2575
|
+
});
|
|
2576
|
+
|
|
2577
|
+
test("is at least 1", () => {
|
|
2578
|
+
process.env[MESSAGE_QUEUE_MIN_SPANS_ENV] = "0";
|
|
2579
|
+
expect(getMessageQueueMinSpans()).toBe(1);
|
|
2580
|
+
process.env[MESSAGE_QUEUE_MIN_SPANS_ENV] = "-4";
|
|
2581
|
+
expect(getMessageQueueMinSpans()).toBe(1);
|
|
2582
|
+
});
|
|
2583
|
+
|
|
2584
|
+
test("falls back to the default for anything unparseable", () => {
|
|
2585
|
+
for (const value of ["", " ", "many", "2.5", "NaN", "Infinity"]) {
|
|
2586
|
+
process.env[MESSAGE_QUEUE_MIN_SPANS_ENV] = value;
|
|
2587
|
+
expect(getMessageQueueMinSpans()).toBe(DEFAULT_MESSAGE_QUEUE_MIN_SPANS);
|
|
2588
|
+
}
|
|
2589
|
+
});
|
|
2590
|
+
});
|