@oneuptime/common 12.0.13 → 12.0.14
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/OnCallDutyPolicySchedule.ts +14 -0
- package/Models/DatabaseModels/UserCall.ts +94 -3
- package/Models/DatabaseModels/UserEmail.ts +94 -3
- package/Models/DatabaseModels/UserIncomingCallNumber.ts +94 -3
- package/Models/DatabaseModels/UserPush.ts +37 -0
- package/Models/DatabaseModels/UserSMS.ts +94 -3
- package/Models/DatabaseModels/UserWhatsApp.ts +93 -2
- package/Server/API/BaseAPI.ts +79 -14
- package/Server/API/UserCallAPI.ts +53 -45
- package/Server/API/UserEmailAPI.ts +53 -44
- package/Server/API/UserIncomingCallNumberAPI.ts +48 -38
- package/Server/API/UserNotificationMethodAdminAPI.ts +481 -0
- package/Server/API/UserPushAPI.ts +126 -2
- package/Server/API/UserSmsAPI.ts +53 -44
- package/Server/API/UserWhatsAppAPI.ts +53 -50
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.ts +154 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.ts +45 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Middleware/VerificationCodeRateLimit.ts +586 -0
- package/Server/Services/AnalyticsDatabaseService.ts +169 -0
- package/Server/Services/DatabaseService.ts +101 -0
- package/Server/Services/LogAggregationService.ts +5 -0
- package/Server/Services/PushNotificationService.ts +127 -12
- package/Server/Services/TeamMemberService.ts +53 -13
- package/Server/Services/UserCallService.ts +55 -24
- package/Server/Services/UserEmailService.ts +83 -17
- package/Server/Services/UserIncomingCallNumberService.ts +49 -85
- package/Server/Services/UserNotificationMethodAdminService.ts +1527 -0
- package/Server/Services/UserNotificationRuleService.ts +26 -0
- package/Server/Services/UserPushService.ts +89 -0
- package/Server/Services/UserSmsService.ts +50 -17
- package/Server/Services/UserWhatsAppService.ts +48 -15
- package/Server/Types/Database/QueryHelper.ts +30 -0
- package/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.ts +59 -0
- package/Server/Utils/ChannelVerification.ts +510 -0
- package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +13 -16
- package/Server/Utils/Monitor/MonitorAlert.ts +24 -21
- package/Server/Utils/Monitor/MonitorIncident.ts +21 -18
- package/Server/Utils/Monitor/MonitorResourceContext.ts +107 -0
- package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +442 -0
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +3 -3
- package/Server/Utils/Monitor/SeriesResourceLinker.ts +96 -28
- package/Server/Utils/TotpAuth.ts +137 -9
- package/Server/Utils/UserRegistrationToken.ts +182 -0
- package/Server/Utils/VerificationCode.ts +134 -0
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +173 -94
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +1151 -0
- package/Tests/App/Dashboard/AdminUserOnCallPages.test.tsx +905 -0
- package/Tests/App/Dashboard/DashboardCommandPalette.test.tsx +338 -0
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1 -1
- package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +28 -0
- package/Tests/Models/UserPushCriticalAlertColumn.test.ts +172 -0
- package/Tests/Server/API/BaseAPIGetListParallel.test.ts +519 -0
- package/Tests/Server/API/NotificationChannelVerificationAPI.test.ts +543 -0
- package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +108 -18
- package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +963 -0
- package/Tests/Server/API/UserPushCriticalAlertsApi.test.ts +114 -0
- package/Tests/Server/API/UserSmsApi.test.ts +107 -14
- package/Tests/Server/API/UserTotpAuthAPI.test.ts +427 -0
- package/Tests/Server/Infrastructure/Postgres/FixTotpOtpUrlAlgorithmMigration.test.ts +315 -0
- package/Tests/Server/Middleware/VerificationCodeRateLimit.test.ts +775 -0
- package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +10 -62
- package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +33 -13
- package/Tests/Server/Services/AnalyticsDatabaseSortKeyBoundary.test.ts +658 -0
- package/Tests/Server/Services/CriticalOnCallAlertDelivery.test.ts +458 -0
- package/Tests/Server/Services/CriticalPushAlertPayload.test.ts +301 -0
- package/Tests/Server/Services/DatabaseServiceAtomicIncrement.test.ts +143 -0
- package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +1 -0
- package/Tests/Server/Services/LogAggregationScanMemory.test.ts +421 -0
- package/Tests/Server/Services/LogAggregationService.test.ts +2 -2
- package/Tests/Server/Services/NotificationChannelCodeIssuance.test.ts +401 -0
- package/Tests/Server/Services/PushNotificationDeliveryOptions.test.ts +204 -0
- package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +21 -1
- package/Tests/Server/Services/TeamMemberInviteRegistrationToken.test.ts +290 -0
- package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +948 -0
- package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +1 -0
- package/Tests/Server/Services/UserPushCriticalAlertToggle.test.ts +255 -0
- package/Tests/Server/Services/UserTotpAuthEnrolment.test.ts +218 -0
- package/Tests/Server/TestingUtils/AuthenticatorApp.ts +248 -0
- package/Tests/Server/Types/Database/Permissions/NotificationChannelVerificationColumns.test.ts +135 -0
- package/Tests/Server/Types/Database/Permissions/OnCallScheduleRelationSelect.test.ts +504 -0
- package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAnyOf.test.ts +91 -0
- package/Tests/Server/Types/Workflow/Components/JsonToText.test.ts +384 -0
- package/Tests/Server/Types/Workflow/Components/MergeJson.test.ts +403 -0
- package/Tests/Server/Utils/Browser.test.ts +162 -0
- package/Tests/Server/Utils/ChannelVerification.test.ts +689 -0
- package/Tests/Server/Utils/Express.test.ts +371 -0
- package/Tests/Server/Utils/Monitor/Criteria/CustomCodeMonitorCriteria.test.ts +707 -0
- package/Tests/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.test.ts +698 -0
- package/Tests/Server/Utils/Monitor/Criteria/IncomingEmailBodyCriteria.test.ts +243 -0
- package/Tests/Server/Utils/Monitor/Criteria/IncomingRequestBodyCriteria.test.ts +278 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +160 -20
- package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +13 -8
- package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +162 -18
- package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +349 -0
- package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +690 -0
- package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +7 -2
- package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +92 -18
- package/Tests/Server/Utils/PushNotificationUtilCreators.test.ts +267 -0
- package/Tests/Server/Utils/Telemetry/OneuptimeLabel.test.ts +439 -0
- package/Tests/Server/Utils/TotpAuth.test.ts +719 -0
- package/Tests/Server/Utils/UserRegistrationToken.test.ts +322 -0
- package/Tests/Server/Utils/VerificationCode.test.ts +296 -0
- package/Tests/Types/AutoRemediation/AutoRemediationSuggestionStatus.test.ts +192 -0
- package/Tests/Types/Call/CallRequest.test.ts +189 -0
- package/Tests/Types/Dashboard/DashboardSize.test.ts +226 -0
- package/Tests/Types/Incident/IncidentSlaStatus.test.ts +191 -0
- package/Tests/Types/Metrics/MetricDashboardMetricType.test.ts +278 -0
- package/Tests/Types/Metrics/MetricPipelineRuleType.test.ts +517 -0
- package/Tests/Types/Monitor/IncomingMonitorDefaultCriteria.test.ts +470 -0
- package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +6 -4
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +262 -5
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +1 -1
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +1 -1
- package/Tests/Types/Monitor/ServiceAlertTemplates.test.ts +905 -0
- package/Tests/Types/Service/ServiceLanguage.test.ts +255 -0
- package/Tests/Types/TextRandomGeneration.test.ts +211 -0
- package/Tests/UI/Components/Button.test.tsx +175 -1
- package/Tests/UI/Components/CodeBlockLanguages.test.tsx +236 -0
- package/Tests/UI/Components/CommandPalette/CommandPalette.test.tsx +568 -0
- package/Tests/UI/Components/CommandPalette/CommandPaletteKeyboard.test.tsx +239 -0
- package/Tests/UI/Components/CommandPalette/CommandPaletteProviders.test.tsx +400 -0
- package/Tests/UI/Components/CommandPalette/PaletteFilter.test.ts +234 -0
- package/Tests/UI/Components/CommandPalette/RecentCommands.test.ts +97 -0
- package/Tests/UI/Components/CountModelSideMenuItemGuard.test.tsx +198 -0
- package/Tests/UI/Components/FeedItemSafeMode.test.tsx +1 -1
- package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +267 -0
- package/Tests/UI/Components/List.test.tsx +9 -2
- package/Tests/UI/Components/ListLoadingStates.test.tsx +173 -0
- package/Tests/UI/Components/MarkdownLazy.test.tsx +113 -0
- package/Tests/UI/Components/MarkdownMermaidRetry.test.tsx +110 -0
- package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +8 -0
- package/Tests/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.test.ts +692 -0
- package/Tests/UI/Components/MoreMenuMotion.test.tsx +118 -0
- package/Tests/UI/Components/NavBarCommandKGating.test.tsx +178 -0
- package/Tests/UI/Components/OrderedStatesList.test.tsx +19 -2
- package/Tests/UI/Components/SideOverMotion.test.tsx +109 -0
- package/Tests/UI/Components/Skeleton.test.tsx +71 -0
- package/Tests/UI/Components/TableLoadingStates.test.tsx +279 -0
- package/Tests/UI/Components/Tabs.test.tsx +24 -0
- package/Tests/UI/Components/Toast.test.tsx +130 -7
- package/Tests/UI/Components/ToastStacking.test.tsx +198 -0
- package/Tests/UI/Utils/Dropdown.test.ts +423 -0
- package/Tests/UI/Utils/ModelAPITenantHeader.test.ts +122 -0
- package/Tests/UI/Utils/ModelListCache.test.ts +338 -0
- package/Tests/UI/Utils/ProjectListCacheInvalidation.test.ts +225 -0
- package/Tests/Utils/API.test.ts +16 -2
- package/Tests/Utils/Dashboard/Components/DashboardKubernetesResourceListShared.test.ts +347 -0
- package/Tests/Utils/Dashboard/Components/DashboardListSharedArgs.test.ts +259 -0
- package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +466 -0
- package/Tests/Utils/Rum/UrlScrubberSegments.test.ts +362 -0
- package/Types/Email/EmailTemplateType.ts +1 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +41 -20
- package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +131 -8
- package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +48 -8
- package/Types/Monitor/ServiceAlertTemplates.ts +1352 -0
- package/Types/Permission.ts +35 -0
- package/Types/PushNotification/AndroidNotificationChannel.ts +26 -0
- package/Types/PushNotification/PushNotificationMessage.ts +15 -0
- package/Types/PushNotification/PushNotificationRequest.ts +8 -16
- package/Types/Service/ServiceLanguage.ts +144 -0
- package/Types/Text.ts +111 -19
- package/UI/Components/Button/Button.tsx +27 -14
- package/UI/Components/CodeBlock/CodeBlock.tsx +54 -7
- package/UI/Components/CodeBlock/LanguageRegistry.ts +103 -0
- package/UI/Components/CommandPalette/CommandPalette.tsx +790 -0
- package/UI/Components/CommandPalette/PaletteFilter.ts +196 -0
- package/UI/Components/CommandPalette/PaletteRow.tsx +173 -0
- package/UI/Components/CommandPalette/RecentCommands.ts +57 -0
- package/UI/Components/CommandPalette/Types.ts +49 -0
- package/UI/Components/CommandPalette/UseProviderSearch.ts +129 -0
- package/UI/Components/Dropdown/Dropdown.tsx +6 -1
- package/UI/Components/Feed/FeedItem.tsx +1 -1
- package/UI/Components/List/List.tsx +37 -5
- package/UI/Components/List/ListSkeleton.tsx +54 -0
- package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +6 -1
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +71 -26
- package/UI/Components/Modal/Modal.tsx +2 -2
- package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
- package/UI/Components/ModelTable/useCustomFieldColumns.ts +11 -5
- package/UI/Components/MoreMenu/MoreMenu.tsx +39 -1
- package/UI/Components/Navbar/NavBar.tsx +14 -1
- package/UI/Components/OrderedStatesList/OrderedStatesList.tsx +50 -5
- package/UI/Components/SideMenu/CountModelSideMenuItem.tsx +40 -1
- package/UI/Components/SideOver/SideOver.tsx +37 -2
- package/UI/Components/Skeleton/Skeleton.tsx +53 -0
- package/UI/Components/Table/Table.tsx +74 -44
- package/UI/Components/Table/TableSkeletonRows.tsx +141 -0
- package/UI/Components/Tabs/Tab.tsx +1 -1
- package/UI/Components/Toast/Toast.tsx +118 -53
- package/UI/Components/Toast/ToastInit.tsx +82 -32
- package/UI/Styles/Theme.css +40 -0
- package/UI/Utils/ModelAPI/ModelAPI.ts +20 -10
- package/UI/Utils/ModelListCache.ts +188 -0
- package/UI/Utils/Project.ts +29 -0
- package/Utils/API.ts +6 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +14 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserCall.js +101 -3
- package/build/dist/Models/DatabaseModels/UserCall.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserEmail.js +101 -3
- package/build/dist/Models/DatabaseModels/UserEmail.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserIncomingCallNumber.js +101 -3
- package/build/dist/Models/DatabaseModels/UserIncomingCallNumber.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserPush.js +38 -0
- package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserSMS.js +101 -3
- package/build/dist/Models/DatabaseModels/UserSMS.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWhatsApp.js +100 -2
- package/build/dist/Models/DatabaseModels/UserWhatsApp.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +69 -13
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/UserCallAPI.js +42 -38
- package/build/dist/Server/API/UserCallAPI.js.map +1 -1
- package/build/dist/Server/API/UserEmailAPI.js +42 -38
- package/build/dist/Server/API/UserEmailAPI.js.map +1 -1
- package/build/dist/Server/API/UserIncomingCallNumberAPI.js +37 -32
- package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
- package/build/dist/Server/API/UserNotificationMethodAdminAPI.js +341 -0
- package/build/dist/Server/API/UserNotificationMethodAdminAPI.js.map +1 -0
- package/build/dist/Server/API/UserPushAPI.js +95 -2
- package/build/dist/Server/API/UserPushAPI.js.map +1 -1
- package/build/dist/Server/API/UserSmsAPI.js +42 -38
- package/build/dist/Server/API/UserSmsAPI.js.map +1 -1
- package/build/dist/Server/API/UserWhatsAppAPI.js +42 -40
- package/build/dist/Server/API/UserWhatsAppAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.js +89 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/VerificationCodeRateLimit.js +393 -0
- package/build/dist/Server/Middleware/VerificationCodeRateLimit.js.map +1 -0
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +136 -0
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +74 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +5 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/PushNotificationService.js +69 -30
- package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +43 -7
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/UserCallService.js +54 -22
- package/build/dist/Server/Services/UserCallService.js.map +1 -1
- package/build/dist/Server/Services/UserEmailService.js +78 -17
- package/build/dist/Server/Services/UserEmailService.js.map +1 -1
- package/build/dist/Server/Services/UserIncomingCallNumberService.js +45 -74
- package/build/dist/Server/Services/UserIncomingCallNumberService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js +1117 -0
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationRuleService.js +20 -0
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserPushService.js +77 -0
- package/build/dist/Server/Services/UserPushService.js.map +1 -1
- package/build/dist/Server/Services/UserSmsService.js +50 -17
- package/build/dist/Server/Services/UserSmsService.js.map +1 -1
- package/build/dist/Server/Services/UserWhatsAppService.js +48 -15
- package/build/dist/Server/Services/UserWhatsAppService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +27 -0
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.js +46 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.js.map +1 -1
- package/build/dist/Server/Utils/ChannelVerification.js +348 -0
- package/build/dist/Server/Utils/ChannelVerification.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +13 -12
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +20 -17
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +17 -14
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +95 -0
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +313 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +3 -3
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +67 -26
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
- package/build/dist/Server/Utils/TotpAuth.js +127 -8
- package/build/dist/Server/Utils/TotpAuth.js.map +1 -1
- package/build/dist/Server/Utils/UserRegistrationToken.js +170 -0
- package/build/dist/Server/Utils/UserRegistrationToken.js.map +1 -0
- package/build/dist/Server/Utils/VerificationCode.js +120 -0
- package/build/dist/Server/Utils/VerificationCode.js.map +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +42 -21
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +77 -5
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +18 -6
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
- package/build/dist/Types/Monitor/ServiceAlertTemplates.js +1028 -0
- package/build/dist/Types/Monitor/ServiceAlertTemplates.js.map +1 -0
- package/build/dist/Types/Permission.js +33 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/PushNotification/AndroidNotificationChannel.js +27 -0
- package/build/dist/Types/PushNotification/AndroidNotificationChannel.js.map +1 -0
- package/build/dist/Types/Service/ServiceLanguage.js +96 -0
- package/build/dist/Types/Service/ServiceLanguage.js.map +1 -0
- package/build/dist/Types/Text.js +82 -17
- package/build/dist/Types/Text.js.map +1 -1
- package/build/dist/UI/Components/Button/Button.js +22 -14
- package/build/dist/UI/Components/Button/Button.js.map +1 -1
- package/build/dist/UI/Components/CodeBlock/CodeBlock.js +35 -3
- package/build/dist/UI/Components/CodeBlock/CodeBlock.js.map +1 -1
- package/build/dist/UI/Components/CodeBlock/LanguageRegistry.js +57 -0
- package/build/dist/UI/Components/CodeBlock/LanguageRegistry.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js +439 -0
- package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/PaletteFilter.js +135 -0
- package/build/dist/UI/Components/CommandPalette/PaletteFilter.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/PaletteRow.js +83 -0
- package/build/dist/UI/Components/CommandPalette/PaletteRow.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/RecentCommands.js +47 -0
- package/build/dist/UI/Components/CommandPalette/RecentCommands.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/Types.js +2 -0
- package/build/dist/UI/Components/CommandPalette/Types.js.map +1 -0
- package/build/dist/UI/Components/CommandPalette/UseProviderSearch.js +88 -0
- package/build/dist/UI/Components/CommandPalette/UseProviderSearch.js.map +1 -0
- package/build/dist/UI/Components/Dropdown/Dropdown.js +6 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedItem.js +1 -1
- package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
- package/build/dist/UI/Components/List/List.js +19 -5
- package/build/dist/UI/Components/List/List.js.map +1 -1
- package/build/dist/UI/Components/List/ListSkeleton.js +21 -0
- package/build/dist/UI/Components/List/ListSkeleton.js.map +1 -0
- package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +52 -25
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +2 -2
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +9 -2
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js +32 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +5 -1
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js +29 -5
- package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js +23 -1
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js.map +1 -1
- package/build/dist/UI/Components/SideOver/SideOver.js +23 -2
- package/build/dist/UI/Components/SideOver/SideOver.js.map +1 -1
- package/build/dist/UI/Components/Skeleton/Skeleton.js +23 -0
- package/build/dist/UI/Components/Skeleton/Skeleton.js.map +1 -0
- package/build/dist/UI/Components/Table/Table.js +35 -24
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/Table/TableSkeletonRows.js +52 -0
- package/build/dist/UI/Components/Table/TableSkeletonRows.js.map +1 -0
- package/build/dist/UI/Components/Tabs/Tab.js +1 -1
- package/build/dist/UI/Components/Tabs/Tab.js.map +1 -1
- package/build/dist/UI/Components/Toast/Toast.js +77 -23
- package/build/dist/UI/Components/Toast/Toast.js.map +1 -1
- package/build/dist/UI/Components/Toast/ToastInit.js +37 -15
- package/build/dist/UI/Components/Toast/ToastInit.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +18 -9
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/ModelListCache.js +126 -0
- package/build/dist/UI/Utils/ModelListCache.js.map +1 -0
- package/build/dist/UI/Utils/Project.js +24 -1
- package/build/dist/UI/Utils/Project.js.map +1 -1
- package/build/dist/Utils/API.js +6 -1
- package/build/dist/Utils/API.js.map +1 -1
- package/jest.config.json +1 -1
- package/package.json +2 -3
- package/Server/Utils/Monitor/MonitorClusterContext.ts +0 -182
- package/Tests/__mocks__/otpauth.js +0 -30
- package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js +0 -141
- package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js.map +0 -1
|
@@ -0,0 +1,1352 @@
|
|
|
1
|
+
import { SpanStatus } from "../../Models/AnalyticsModels/Span";
|
|
2
|
+
import ObjectID from "../ObjectID";
|
|
3
|
+
import MonitorStep from "./MonitorStep";
|
|
4
|
+
import MonitorCriteria from "./MonitorCriteria";
|
|
5
|
+
import MonitorCriteriaInstance from "./MonitorCriteriaInstance";
|
|
6
|
+
import FilterCondition from "../Filter/FilterCondition";
|
|
7
|
+
import { CheckOn, EvaluateOverTimeType, FilterType } from "./CriteriaFilter";
|
|
8
|
+
import MonitorType from "./MonitorType";
|
|
9
|
+
import RollingTime from "../RollingTime/RollingTime";
|
|
10
|
+
import MetricsAggregationType from "../Metrics/MetricsAggregationType";
|
|
11
|
+
import MetricQueryConfigData from "../Metrics/MetricQueryConfigData";
|
|
12
|
+
import MetricFormulaConfigData from "../Metrics/MetricFormulaConfigData";
|
|
13
|
+
import { ServiceLanguage } from "../Service/ServiceLanguage";
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* The curated alert library for an APM telemetry service — a backend process
|
|
17
|
+
* reporting OpenTelemetry traces, metrics and exceptions under one
|
|
18
|
+
* `service.name`.
|
|
19
|
+
*
|
|
20
|
+
* WHAT MAKES THIS MODULE DIFFERENT FROM THE OTHER NINE
|
|
21
|
+
*
|
|
22
|
+
* Every other `<X>AlertTemplates.ts` returns a constant: every Kubernetes
|
|
23
|
+
* cluster is offered the same eighteen templates. A service's useful alerts
|
|
24
|
+
* are not a constant, because the signals a runtime emits are a property of
|
|
25
|
+
* the runtime. "Heap above 90% of -Xmx" is the single best leading indicator
|
|
26
|
+
* of a Java outage and is meaningless on a Go service, which has no heap
|
|
27
|
+
* limit metric and instead leaks goroutines. Offering JVM templates to a Go
|
|
28
|
+
* service does not produce a wrong alert — it produces a monitor that queries
|
|
29
|
+
* a metric nobody emits and therefore never fires, which is the worst outcome
|
|
30
|
+
* available: the team believes they are covered.
|
|
31
|
+
*
|
|
32
|
+
* So templates carry a `language` discriminator and callers ask for the set
|
|
33
|
+
* that matches the service's detected runtime — see `getServiceAlertTemplates`.
|
|
34
|
+
* Templates with no `language` apply to every service whatever it runs.
|
|
35
|
+
*
|
|
36
|
+
* THREE CONSTRAINTS THE PLATFORM IMPOSES, WHICH SHAPE EVERY TEMPLATE HERE
|
|
37
|
+
*
|
|
38
|
+
* 1. Only one CheckOn exists per telemetry monitor type: `MetricValue` for
|
|
39
|
+
* Metrics, `SpanCount` for Traces, `ExceptionCount` for Exceptions. There
|
|
40
|
+
* is no span-duration CheckOn (`TraceMonitorResponse` carries only a
|
|
41
|
+
* count), so latency alerts MUST be metric monitors over a duration
|
|
42
|
+
* histogram, never trace monitors.
|
|
43
|
+
*
|
|
44
|
+
* 2. Cumulative monotonic counters are unusable. Ingest stores OTel counters
|
|
45
|
+
* raw — the per-second-rate transform on `MetricQueryConfigData` is a
|
|
46
|
+
* chart-side transform the monitor worker never reads — so thresholding
|
|
47
|
+
* one compares against a since-process-start total that only grows: it
|
|
48
|
+
* fires once and never clears. That disqualifies the most tempting
|
|
49
|
+
* metrics in three languages (`dotnet.exceptions`,
|
|
50
|
+
* `cpython.gc.collections`, `process.runtime.go.gc.count`) and is why
|
|
51
|
+
* counting signals below go through trace and exception monitors, which
|
|
52
|
+
* count rows in a rolling window. Everything thresholded here is a gauge,
|
|
53
|
+
* an UpDownCounter, or a histogram read through a percentile.
|
|
54
|
+
*
|
|
55
|
+
* 3. A monitor holds exactly ONE metric name — there is no server-side
|
|
56
|
+
* equivalent of the dashboard's candidate probing (`probeRuntimeCharts`,
|
|
57
|
+
* which tries a stable name then a legacy one and keeps whichever has
|
|
58
|
+
* data). Every template below therefore targets the STABLE semantic
|
|
59
|
+
* convention name, and names the pre-stabilization metric in its
|
|
60
|
+
* description so a team on an older SDK can retarget the created monitor
|
|
61
|
+
* in one edit rather than wonder why it is silent.
|
|
62
|
+
*
|
|
63
|
+
* The metric names, and the attribute filters that go with them, are taken
|
|
64
|
+
* verbatim from the service overview's runtime charts
|
|
65
|
+
* (`serviceGoldenMetrics.ts`) wherever the two overlap, so a chart the user
|
|
66
|
+
* can see and an alert they can create describe the same number.
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
export type ServiceAlertTemplateCategory =
|
|
70
|
+
| "Errors"
|
|
71
|
+
| "Latency"
|
|
72
|
+
| "Throughput"
|
|
73
|
+
| "Saturation"
|
|
74
|
+
| "JVM Runtime"
|
|
75
|
+
| ".NET Runtime"
|
|
76
|
+
| "Node.js Runtime"
|
|
77
|
+
| "Python Runtime"
|
|
78
|
+
| "Go Runtime";
|
|
79
|
+
|
|
80
|
+
export type ServiceAlertTemplateSeverity = "Critical" | "Warning";
|
|
81
|
+
|
|
82
|
+
export interface ServiceAlertTemplateArgs {
|
|
83
|
+
serviceId: string;
|
|
84
|
+
onlineMonitorStatusId: ObjectID;
|
|
85
|
+
offlineMonitorStatusId: ObjectID;
|
|
86
|
+
defaultIncidentSeverityId: ObjectID;
|
|
87
|
+
defaultAlertSeverityId: ObjectID;
|
|
88
|
+
monitorName: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ServiceAlertTemplate {
|
|
92
|
+
id: string;
|
|
93
|
+
name: string;
|
|
94
|
+
description: string;
|
|
95
|
+
category: ServiceAlertTemplateCategory;
|
|
96
|
+
severity: ServiceAlertTemplateSeverity;
|
|
97
|
+
monitorType: MonitorType;
|
|
98
|
+
/*
|
|
99
|
+
* The runtime this template is for. Undefined means "every service" — the
|
|
100
|
+
* RED signals and the process-level ones, which come from spans, exceptions
|
|
101
|
+
* and the host-metrics instrumentation rather than from any runtime.
|
|
102
|
+
*/
|
|
103
|
+
language?: ServiceLanguage | undefined;
|
|
104
|
+
getMonitorStep: (args: ServiceAlertTemplateArgs) => MonitorStep;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/*
|
|
108
|
+
* `EvaluateOverTimeType` names the window collapse, and its two members used
|
|
109
|
+
* here do NOT mean what their names suggest — worth stating once, here, rather
|
|
110
|
+
* than being rediscovered per template:
|
|
111
|
+
*
|
|
112
|
+
* AnyValue -> ANY bucket in the rolling window breaching is a breach.
|
|
113
|
+
* Right for spikes.
|
|
114
|
+
* AllValues -> EVERY bucket must breach. Right for sustained pressure, and
|
|
115
|
+
* what the comparators actually do for every member that is not
|
|
116
|
+
* `AnyValue` (they fall through to `.every()`; there is no
|
|
117
|
+
* averaging on this code path despite `Average` existing).
|
|
118
|
+
*
|
|
119
|
+
* A window with no samples at all never reaches the comparator — the
|
|
120
|
+
* evaluator's no-data guard returns "not breaching" under the default
|
|
121
|
+
* `NoDataPolicy.Ignore` — so `AllValues` cannot fire on an empty window.
|
|
122
|
+
*
|
|
123
|
+
* Applying `AllValues` to the healthy criteria too, as these templates do,
|
|
124
|
+
* buys hysteresis for free: a window where some buckets breach and others do
|
|
125
|
+
* not matches neither criteria, so the monitor holds its current status
|
|
126
|
+
* instead of flipping on every evaluation while a metric sits on the
|
|
127
|
+
* threshold. Alerting on a metric that hovers is the fastest way to teach a
|
|
128
|
+
* team to mute it.
|
|
129
|
+
*/
|
|
130
|
+
const SUSTAINED: EvaluateOverTimeType = EvaluateOverTimeType.AllValues;
|
|
131
|
+
|
|
132
|
+
interface CountCriteriaArgs {
|
|
133
|
+
args: ServiceAlertTemplateArgs;
|
|
134
|
+
checkOn: CheckOn.SpanCount | CheckOn.ExceptionCount;
|
|
135
|
+
unhealthyFilterType: FilterType;
|
|
136
|
+
healthyFilterType: FilterType;
|
|
137
|
+
threshold: number;
|
|
138
|
+
unhealthyName: string;
|
|
139
|
+
unhealthyDescription: string;
|
|
140
|
+
incidentTitle: string;
|
|
141
|
+
incidentDescription: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
interface MetricCriteriaArgs {
|
|
145
|
+
args: ServiceAlertTemplateArgs;
|
|
146
|
+
unhealthyFilterType: FilterType;
|
|
147
|
+
healthyFilterType: FilterType;
|
|
148
|
+
threshold: number;
|
|
149
|
+
metricAlias: string;
|
|
150
|
+
thresholdUnit?: string | undefined;
|
|
151
|
+
unhealthyName: string;
|
|
152
|
+
unhealthyDescription: string;
|
|
153
|
+
incidentTitle: string;
|
|
154
|
+
incidentDescription: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/*
|
|
158
|
+
* The unhealthy/healthy criteria pair every template ships.
|
|
159
|
+
*
|
|
160
|
+
* Both instances are always produced, with mirrored filter types, because a
|
|
161
|
+
* monitor with no healthy criteria never comes back online: it opens an
|
|
162
|
+
* incident, auto-resolves it (the incidents carry `autoResolveIncident`), and
|
|
163
|
+
* then sits in its offline status forever with nothing to move it back.
|
|
164
|
+
*/
|
|
165
|
+
function buildCriteriaPair(data: {
|
|
166
|
+
args: ServiceAlertTemplateArgs;
|
|
167
|
+
checkOn: CheckOn;
|
|
168
|
+
unhealthyFilterType: FilterType;
|
|
169
|
+
healthyFilterType: FilterType;
|
|
170
|
+
threshold: number;
|
|
171
|
+
metricAlias?: string | undefined;
|
|
172
|
+
thresholdUnit?: string | undefined;
|
|
173
|
+
unhealthyName: string;
|
|
174
|
+
unhealthyDescription: string;
|
|
175
|
+
incidentTitle: string;
|
|
176
|
+
incidentDescription: string;
|
|
177
|
+
healthyDescription: string;
|
|
178
|
+
}): MonitorCriteria {
|
|
179
|
+
const metricMonitorOptions:
|
|
180
|
+
| {
|
|
181
|
+
metricAggregationType: EvaluateOverTimeType;
|
|
182
|
+
metricAlias: string;
|
|
183
|
+
thresholdUnit?: string | undefined;
|
|
184
|
+
}
|
|
185
|
+
| undefined = data.metricAlias
|
|
186
|
+
? {
|
|
187
|
+
metricAggregationType: SUSTAINED,
|
|
188
|
+
metricAlias: data.metricAlias,
|
|
189
|
+
thresholdUnit: data.thresholdUnit,
|
|
190
|
+
}
|
|
191
|
+
: undefined;
|
|
192
|
+
|
|
193
|
+
const unhealthy: MonitorCriteriaInstance = new MonitorCriteriaInstance();
|
|
194
|
+
|
|
195
|
+
unhealthy.data = {
|
|
196
|
+
id: ObjectID.generate().toString(),
|
|
197
|
+
monitorStatusId: data.args.offlineMonitorStatusId,
|
|
198
|
+
filterCondition: FilterCondition.Any,
|
|
199
|
+
filters: [
|
|
200
|
+
{
|
|
201
|
+
checkOn: data.checkOn,
|
|
202
|
+
filterType: data.unhealthyFilterType,
|
|
203
|
+
value: data.threshold,
|
|
204
|
+
metricMonitorOptions: metricMonitorOptions,
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
incidents: [
|
|
208
|
+
{
|
|
209
|
+
title: data.incidentTitle,
|
|
210
|
+
description: data.incidentDescription,
|
|
211
|
+
incidentSeverityId: data.args.defaultIncidentSeverityId,
|
|
212
|
+
autoResolveIncident: true,
|
|
213
|
+
id: ObjectID.generate().toString(),
|
|
214
|
+
onCallPolicyIds: [],
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
alerts: [
|
|
218
|
+
{
|
|
219
|
+
title: data.incidentTitle,
|
|
220
|
+
description: data.incidentDescription,
|
|
221
|
+
alertSeverityId: data.args.defaultAlertSeverityId,
|
|
222
|
+
autoResolveAlert: true,
|
|
223
|
+
id: ObjectID.generate().toString(),
|
|
224
|
+
onCallPolicyIds: [],
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
changeMonitorStatus: true,
|
|
228
|
+
createIncidents: true,
|
|
229
|
+
createAlerts: true,
|
|
230
|
+
name: data.unhealthyName,
|
|
231
|
+
description: data.unhealthyDescription,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const healthy: MonitorCriteriaInstance = new MonitorCriteriaInstance();
|
|
235
|
+
|
|
236
|
+
healthy.data = {
|
|
237
|
+
id: ObjectID.generate().toString(),
|
|
238
|
+
monitorStatusId: data.args.onlineMonitorStatusId,
|
|
239
|
+
filterCondition: FilterCondition.Any,
|
|
240
|
+
filters: [
|
|
241
|
+
{
|
|
242
|
+
checkOn: data.checkOn,
|
|
243
|
+
filterType: data.healthyFilterType,
|
|
244
|
+
value: data.threshold,
|
|
245
|
+
metricMonitorOptions: metricMonitorOptions,
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
incidents: [],
|
|
249
|
+
alerts: [],
|
|
250
|
+
changeMonitorStatus: true,
|
|
251
|
+
createIncidents: false,
|
|
252
|
+
createAlerts: false,
|
|
253
|
+
name: "Healthy",
|
|
254
|
+
description: data.healthyDescription,
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const criteria: MonitorCriteria = new MonitorCriteria();
|
|
258
|
+
criteria.data = {
|
|
259
|
+
monitorCriteriaInstanceArray: [unhealthy, healthy],
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
return criteria;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function buildCountCriteria(data: CountCriteriaArgs): MonitorCriteria {
|
|
266
|
+
return buildCriteriaPair({
|
|
267
|
+
args: data.args,
|
|
268
|
+
checkOn: data.checkOn,
|
|
269
|
+
unhealthyFilterType: data.unhealthyFilterType,
|
|
270
|
+
healthyFilterType: data.healthyFilterType,
|
|
271
|
+
threshold: data.threshold,
|
|
272
|
+
unhealthyName: data.unhealthyName,
|
|
273
|
+
unhealthyDescription: data.unhealthyDescription,
|
|
274
|
+
incidentTitle: data.incidentTitle,
|
|
275
|
+
incidentDescription: data.incidentDescription,
|
|
276
|
+
healthyDescription: `${data.args.monitorName} is within its recommended threshold.`,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function buildMetricCriteria(data: MetricCriteriaArgs): MonitorCriteria {
|
|
281
|
+
return buildCriteriaPair({
|
|
282
|
+
args: data.args,
|
|
283
|
+
checkOn: CheckOn.MetricValue,
|
|
284
|
+
unhealthyFilterType: data.unhealthyFilterType,
|
|
285
|
+
healthyFilterType: data.healthyFilterType,
|
|
286
|
+
threshold: data.threshold,
|
|
287
|
+
metricAlias: data.metricAlias,
|
|
288
|
+
thresholdUnit: data.thresholdUnit,
|
|
289
|
+
unhealthyName: data.unhealthyName,
|
|
290
|
+
unhealthyDescription: data.unhealthyDescription,
|
|
291
|
+
incidentTitle: data.incidentTitle,
|
|
292
|
+
incidentDescription: data.incidentDescription,
|
|
293
|
+
healthyDescription: `${data.args.monitorName} is within its recommended threshold.`,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/*
|
|
298
|
+
* One metric query.
|
|
299
|
+
*
|
|
300
|
+
* `metricVariable` is the alias the criteria filter names, and getting it
|
|
301
|
+
* wrong is silent: an alias that matches no query falls back to query result
|
|
302
|
+
* slot 0, so a two-query template with a typo'd alias would threshold the
|
|
303
|
+
* wrong series. Every alias below is passed to both the query config and its
|
|
304
|
+
* criteria from the same local constant for exactly that reason.
|
|
305
|
+
*
|
|
306
|
+
* `legendUnit` is what makes a threshold portable across SDKs that report the
|
|
307
|
+
* same metric in different units: the worker converts each sample from the
|
|
308
|
+
* metric's own reported unit into this one before comparing. Setting it to
|
|
309
|
+
* `"ms"` on a semconv duration histogram (reported in seconds) is what lets
|
|
310
|
+
* the threshold below be written as a readable millisecond count.
|
|
311
|
+
*/
|
|
312
|
+
function buildQueryConfig(data: {
|
|
313
|
+
metricName: string;
|
|
314
|
+
metricAlias: string;
|
|
315
|
+
title: string;
|
|
316
|
+
aggregationType: MetricsAggregationType;
|
|
317
|
+
attributes?: Record<string, string> | undefined;
|
|
318
|
+
legendUnit?: string | undefined;
|
|
319
|
+
}): MetricQueryConfigData {
|
|
320
|
+
return {
|
|
321
|
+
metricAliasData: {
|
|
322
|
+
metricVariable: data.metricAlias,
|
|
323
|
+
title: data.title,
|
|
324
|
+
description: data.title,
|
|
325
|
+
legend: data.title,
|
|
326
|
+
legendUnit: data.legendUnit,
|
|
327
|
+
},
|
|
328
|
+
metricQueryData: {
|
|
329
|
+
filterData: {
|
|
330
|
+
metricName: data.metricName,
|
|
331
|
+
attributes: data.attributes || {},
|
|
332
|
+
aggegationType: data.aggregationType,
|
|
333
|
+
aggregateBy: {},
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/*
|
|
340
|
+
* A single-metric threshold template: one query, one criteria pair.
|
|
341
|
+
*/
|
|
342
|
+
function buildMetricTemplate(data: {
|
|
343
|
+
id: string;
|
|
344
|
+
name: string;
|
|
345
|
+
description: string;
|
|
346
|
+
category: ServiceAlertTemplateCategory;
|
|
347
|
+
severity: ServiceAlertTemplateSeverity;
|
|
348
|
+
language?: ServiceLanguage | undefined;
|
|
349
|
+
metricName: string;
|
|
350
|
+
metricAlias: string;
|
|
351
|
+
aggregationType: MetricsAggregationType;
|
|
352
|
+
attributes?: Record<string, string> | undefined;
|
|
353
|
+
legendUnit?: string | undefined;
|
|
354
|
+
threshold: number;
|
|
355
|
+
thresholdLabel: string;
|
|
356
|
+
rollingTime?: RollingTime | undefined;
|
|
357
|
+
unhealthyFilterType?: FilterType | undefined;
|
|
358
|
+
healthyFilterType?: FilterType | undefined;
|
|
359
|
+
incidentDescription: string;
|
|
360
|
+
}): ServiceAlertTemplate {
|
|
361
|
+
return {
|
|
362
|
+
id: data.id,
|
|
363
|
+
name: data.name,
|
|
364
|
+
description: data.description,
|
|
365
|
+
category: data.category,
|
|
366
|
+
severity: data.severity,
|
|
367
|
+
monitorType: MonitorType.Metrics,
|
|
368
|
+
language: data.language,
|
|
369
|
+
getMonitorStep: (args: ServiceAlertTemplateArgs): MonitorStep => {
|
|
370
|
+
const step: MonitorStep = MonitorStep.getDefaultMonitorStep({
|
|
371
|
+
monitorName: args.monitorName,
|
|
372
|
+
monitorType: MonitorType.Metrics,
|
|
373
|
+
onlineMonitorStatusId: args.onlineMonitorStatusId,
|
|
374
|
+
offlineMonitorStatusId: args.offlineMonitorStatusId,
|
|
375
|
+
defaultIncidentSeverityId: args.defaultIncidentSeverityId,
|
|
376
|
+
defaultAlertSeverityId: args.defaultAlertSeverityId,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
step.setMetricMonitor({
|
|
380
|
+
telemetryServiceIds: [new ObjectID(args.serviceId)],
|
|
381
|
+
rollingTime: data.rollingTime || RollingTime.Past5Minutes,
|
|
382
|
+
metricViewConfig: {
|
|
383
|
+
queryConfigs: [
|
|
384
|
+
buildQueryConfig({
|
|
385
|
+
metricName: data.metricName,
|
|
386
|
+
metricAlias: data.metricAlias,
|
|
387
|
+
title: data.name,
|
|
388
|
+
aggregationType: data.aggregationType,
|
|
389
|
+
attributes: data.attributes,
|
|
390
|
+
legendUnit: data.legendUnit,
|
|
391
|
+
}),
|
|
392
|
+
],
|
|
393
|
+
formulaConfigs: [],
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
step.setMonitorCriteria(
|
|
398
|
+
buildMetricCriteria({
|
|
399
|
+
args: args,
|
|
400
|
+
unhealthyFilterType:
|
|
401
|
+
data.unhealthyFilterType || FilterType.GreaterThanOrEqualTo,
|
|
402
|
+
healthyFilterType: data.healthyFilterType || FilterType.LessThan,
|
|
403
|
+
threshold: data.threshold,
|
|
404
|
+
metricAlias: data.metricAlias,
|
|
405
|
+
thresholdUnit: data.legendUnit,
|
|
406
|
+
unhealthyName: `${data.name} - ${data.thresholdLabel} or worse`,
|
|
407
|
+
unhealthyDescription: `Triggers when ${data.metricName} stays at ${data.thresholdLabel} or worse for the whole evaluation window.`,
|
|
408
|
+
incidentTitle: `[Service] ${data.name} - ${args.monitorName}`,
|
|
409
|
+
incidentDescription: data.incidentDescription,
|
|
410
|
+
}),
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
return step;
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/*
|
|
419
|
+
* A ratio template: two queries and a formula, thresholded as a percentage.
|
|
420
|
+
*
|
|
421
|
+
* This is the shape worth reaching for whenever the runtime reports both a
|
|
422
|
+
* usage and its limit, because it is the only kind of memory threshold that is
|
|
423
|
+
* portable. "Heap above 2 GB" is a guess about someone else's `-Xmx`; "heap
|
|
424
|
+
* above 90% of the limit the JVM was actually given" is true on a 512 MB
|
|
425
|
+
* sidecar and a 64 GB batch node alike, with no tuning.
|
|
426
|
+
*
|
|
427
|
+
* The criteria filter names the FORMULA's alias, not either query's — the
|
|
428
|
+
* evaluator resolves an alias against the query configs first and the formula
|
|
429
|
+
* configs second, so the three aliases must all differ.
|
|
430
|
+
*/
|
|
431
|
+
function buildRatioTemplate(data: {
|
|
432
|
+
id: string;
|
|
433
|
+
name: string;
|
|
434
|
+
description: string;
|
|
435
|
+
category: ServiceAlertTemplateCategory;
|
|
436
|
+
severity: ServiceAlertTemplateSeverity;
|
|
437
|
+
language?: ServiceLanguage | undefined;
|
|
438
|
+
numeratorMetricName: string;
|
|
439
|
+
numeratorAttributes?: Record<string, string> | undefined;
|
|
440
|
+
denominatorMetricName: string;
|
|
441
|
+
denominatorAttributes?: Record<string, string> | undefined;
|
|
442
|
+
numeratorAlias: string;
|
|
443
|
+
denominatorAlias: string;
|
|
444
|
+
resultAlias: string;
|
|
445
|
+
thresholdPercent: number;
|
|
446
|
+
incidentDescription: string;
|
|
447
|
+
}): ServiceAlertTemplate {
|
|
448
|
+
return {
|
|
449
|
+
id: data.id,
|
|
450
|
+
name: data.name,
|
|
451
|
+
description: data.description,
|
|
452
|
+
category: data.category,
|
|
453
|
+
severity: data.severity,
|
|
454
|
+
monitorType: MonitorType.Metrics,
|
|
455
|
+
language: data.language,
|
|
456
|
+
getMonitorStep: (args: ServiceAlertTemplateArgs): MonitorStep => {
|
|
457
|
+
const step: MonitorStep = MonitorStep.getDefaultMonitorStep({
|
|
458
|
+
monitorName: args.monitorName,
|
|
459
|
+
monitorType: MonitorType.Metrics,
|
|
460
|
+
onlineMonitorStatusId: args.onlineMonitorStatusId,
|
|
461
|
+
offlineMonitorStatusId: args.offlineMonitorStatusId,
|
|
462
|
+
defaultIncidentSeverityId: args.defaultIncidentSeverityId,
|
|
463
|
+
defaultAlertSeverityId: args.defaultAlertSeverityId,
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
const formulaConfig: MetricFormulaConfigData = {
|
|
467
|
+
metricAliasData: {
|
|
468
|
+
metricVariable: data.resultAlias,
|
|
469
|
+
title: data.name,
|
|
470
|
+
description: data.name,
|
|
471
|
+
legend: data.name,
|
|
472
|
+
legendUnit: "%",
|
|
473
|
+
},
|
|
474
|
+
metricFormulaData: {
|
|
475
|
+
metricFormula: `(${data.numeratorAlias} / ${data.denominatorAlias}) * 100`,
|
|
476
|
+
},
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
step.setMetricMonitor({
|
|
480
|
+
telemetryServiceIds: [new ObjectID(args.serviceId)],
|
|
481
|
+
rollingTime: RollingTime.Past5Minutes,
|
|
482
|
+
metricViewConfig: {
|
|
483
|
+
/*
|
|
484
|
+
* Both sides are summed rather than averaged. A runtime reports
|
|
485
|
+
* usage and limit per memory pool, so a service with three heap
|
|
486
|
+
* pools produces three series on each side; summing gives
|
|
487
|
+
* "total used / total limit", which is the number a human means by
|
|
488
|
+
* "heap is 90% full".
|
|
489
|
+
*/
|
|
490
|
+
queryConfigs: [
|
|
491
|
+
buildQueryConfig({
|
|
492
|
+
metricName: data.numeratorMetricName,
|
|
493
|
+
metricAlias: data.numeratorAlias,
|
|
494
|
+
title: `${data.name} (used)`,
|
|
495
|
+
aggregationType: MetricsAggregationType.Sum,
|
|
496
|
+
attributes: data.numeratorAttributes,
|
|
497
|
+
}),
|
|
498
|
+
buildQueryConfig({
|
|
499
|
+
metricName: data.denominatorMetricName,
|
|
500
|
+
metricAlias: data.denominatorAlias,
|
|
501
|
+
title: `${data.name} (limit)`,
|
|
502
|
+
aggregationType: MetricsAggregationType.Sum,
|
|
503
|
+
attributes: data.denominatorAttributes,
|
|
504
|
+
}),
|
|
505
|
+
],
|
|
506
|
+
formulaConfigs: [formulaConfig],
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
step.setMonitorCriteria(
|
|
511
|
+
buildMetricCriteria({
|
|
512
|
+
args: args,
|
|
513
|
+
unhealthyFilterType: FilterType.GreaterThanOrEqualTo,
|
|
514
|
+
healthyFilterType: FilterType.LessThan,
|
|
515
|
+
threshold: data.thresholdPercent,
|
|
516
|
+
metricAlias: data.resultAlias,
|
|
517
|
+
thresholdUnit: "%",
|
|
518
|
+
unhealthyName: `${data.name} - ${data.thresholdPercent}% or higher`,
|
|
519
|
+
unhealthyDescription: `Triggers when ${data.numeratorMetricName} stays at ${data.thresholdPercent}% or more of ${data.denominatorMetricName} for the whole evaluation window.`,
|
|
520
|
+
incidentTitle: `[Service] ${data.name} - ${args.monitorName}`,
|
|
521
|
+
incidentDescription: data.incidentDescription,
|
|
522
|
+
}),
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
return step;
|
|
526
|
+
},
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/*
|
|
531
|
+
* A span-count template.
|
|
532
|
+
*
|
|
533
|
+
* `spanStatuses: [SpanStatus.Error]` is what turns "how busy is this service"
|
|
534
|
+
* into "how much of it is failing" — the count query filters to error-status
|
|
535
|
+
* spans server-side, so the threshold is a failure count and not a traffic
|
|
536
|
+
* count.
|
|
537
|
+
*/
|
|
538
|
+
function buildSpanCountTemplate(data: {
|
|
539
|
+
id: string;
|
|
540
|
+
name: string;
|
|
541
|
+
description: string;
|
|
542
|
+
category: ServiceAlertTemplateCategory;
|
|
543
|
+
severity: ServiceAlertTemplateSeverity;
|
|
544
|
+
spanStatuses: Array<SpanStatus>;
|
|
545
|
+
lastXSecondsOfSpans: number;
|
|
546
|
+
threshold: number;
|
|
547
|
+
unhealthyFilterType: FilterType;
|
|
548
|
+
healthyFilterType: FilterType;
|
|
549
|
+
unhealthyName: string;
|
|
550
|
+
unhealthyDescription: string;
|
|
551
|
+
incidentDescription: string;
|
|
552
|
+
}): ServiceAlertTemplate {
|
|
553
|
+
return {
|
|
554
|
+
id: data.id,
|
|
555
|
+
name: data.name,
|
|
556
|
+
description: data.description,
|
|
557
|
+
category: data.category,
|
|
558
|
+
severity: data.severity,
|
|
559
|
+
monitorType: MonitorType.Traces,
|
|
560
|
+
getMonitorStep: (args: ServiceAlertTemplateArgs): MonitorStep => {
|
|
561
|
+
const step: MonitorStep = MonitorStep.getDefaultMonitorStep({
|
|
562
|
+
monitorName: args.monitorName,
|
|
563
|
+
monitorType: MonitorType.Traces,
|
|
564
|
+
onlineMonitorStatusId: args.onlineMonitorStatusId,
|
|
565
|
+
offlineMonitorStatusId: args.offlineMonitorStatusId,
|
|
566
|
+
defaultIncidentSeverityId: args.defaultIncidentSeverityId,
|
|
567
|
+
defaultAlertSeverityId: args.defaultAlertSeverityId,
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
step.setTraceMonitor({
|
|
571
|
+
attributes: {},
|
|
572
|
+
spanName: "",
|
|
573
|
+
spanStatuses: data.spanStatuses,
|
|
574
|
+
telemetryServiceIds: [new ObjectID(args.serviceId)],
|
|
575
|
+
entityKeys: [],
|
|
576
|
+
lastXSecondsOfSpans: data.lastXSecondsOfSpans,
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
step.setMonitorCriteria(
|
|
580
|
+
buildCountCriteria({
|
|
581
|
+
args: args,
|
|
582
|
+
checkOn: CheckOn.SpanCount,
|
|
583
|
+
unhealthyFilterType: data.unhealthyFilterType,
|
|
584
|
+
healthyFilterType: data.healthyFilterType,
|
|
585
|
+
threshold: data.threshold,
|
|
586
|
+
unhealthyName: data.unhealthyName,
|
|
587
|
+
unhealthyDescription: data.unhealthyDescription,
|
|
588
|
+
incidentTitle: `[Service] ${data.name} - ${args.monitorName}`,
|
|
589
|
+
incidentDescription: data.incidentDescription,
|
|
590
|
+
}),
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
return step;
|
|
594
|
+
},
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// --- Language-agnostic templates ---
|
|
599
|
+
|
|
600
|
+
/*
|
|
601
|
+
* Ten minutes rather than the five the RUM catalog uses for its equivalent.
|
|
602
|
+
* A browser session is a few seconds long, so RUM wants the shortest window
|
|
603
|
+
* that can hold a signal; a backend service is judged over the window its SLO
|
|
604
|
+
* is written against, and ten minutes rides out the single transient blip that
|
|
605
|
+
* would otherwise page someone for a request that had already been retried
|
|
606
|
+
* successfully.
|
|
607
|
+
*
|
|
608
|
+
* It also keeps this template's coverage fingerprint distinct from
|
|
609
|
+
* `rum-failed-user-operations`, which is otherwise structurally identical to
|
|
610
|
+
* it — same config kind, same error-status filter, same threshold. The
|
|
611
|
+
* fingerprint separates them today by resource identifier (a Service row id
|
|
612
|
+
* can never equal a RumApplication row id), but the evaluation window is what
|
|
613
|
+
* separates them on their own terms. Shortening this back to 300 makes the two
|
|
614
|
+
* indistinguishable to anything that compares templates rather than monitors.
|
|
615
|
+
*/
|
|
616
|
+
const failedOperationsTemplate: ServiceAlertTemplate = buildSpanCountTemplate({
|
|
617
|
+
id: "service-failed-operations",
|
|
618
|
+
name: "Failed Operations",
|
|
619
|
+
description:
|
|
620
|
+
"Alert when the service reports any error-status span in ten minutes. The earliest signal that requests are failing, whatever the cause.",
|
|
621
|
+
category: "Errors",
|
|
622
|
+
severity: "Warning",
|
|
623
|
+
spanStatuses: [SpanStatus.Error],
|
|
624
|
+
lastXSecondsOfSpans: 600,
|
|
625
|
+
threshold: 0,
|
|
626
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
627
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
628
|
+
unhealthyName: "Error spans detected",
|
|
629
|
+
unhealthyDescription:
|
|
630
|
+
"Triggers when at least one error-status span is reported in ten minutes.",
|
|
631
|
+
incidentDescription:
|
|
632
|
+
"The service reported an error-status span. Open the trace to see which operation failed, what it was called by, and which downstream dependency it was waiting on.",
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
/*
|
|
636
|
+
* Warning above, Critical here, on the same signal at a much higher bar.
|
|
637
|
+
* A backend service that has never once returned an error is rare; one
|
|
638
|
+
* returning twenty-five in five minutes is having an incident. Shipping both
|
|
639
|
+
* lets a team pick the sensitivity that matches their traffic instead of
|
|
640
|
+
* picking between "pages constantly" and "nothing".
|
|
641
|
+
*/
|
|
642
|
+
const errorBurstTemplate: ServiceAlertTemplate = buildSpanCountTemplate({
|
|
643
|
+
id: "service-error-burst",
|
|
644
|
+
name: "Error Burst",
|
|
645
|
+
description:
|
|
646
|
+
"Alert when error-status spans exceed 25 in five minutes — a failure rate well past routine noise.",
|
|
647
|
+
category: "Errors",
|
|
648
|
+
severity: "Critical",
|
|
649
|
+
spanStatuses: [SpanStatus.Error],
|
|
650
|
+
lastXSecondsOfSpans: 300,
|
|
651
|
+
threshold: 25,
|
|
652
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
653
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
654
|
+
unhealthyName: "Error spans above 25 in five minutes",
|
|
655
|
+
unhealthyDescription:
|
|
656
|
+
"Triggers when more than 25 error-status spans are reported in five minutes.",
|
|
657
|
+
incidentDescription:
|
|
658
|
+
"The service is failing at a rate well above routine noise. Group the failing spans by operation and by downstream dependency to find the common factor, and check what deployed recently.",
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
/*
|
|
662
|
+
* Fires when a service goes quiet, so the healthy/unhealthy filter types are
|
|
663
|
+
* inverted relative to every other template here: below the threshold is the
|
|
664
|
+
* bad state.
|
|
665
|
+
*
|
|
666
|
+
* Fifteen minutes rather than five, and Warning rather than Critical, because
|
|
667
|
+
* plenty of legitimate services are idle at 3am. It is offered rather than
|
|
668
|
+
* assumed — for an always-on request path it is the cheapest possible
|
|
669
|
+
* liveness check, needing no health endpoint and no probe.
|
|
670
|
+
*/
|
|
671
|
+
const trafficStoppedTemplate: ServiceAlertTemplate = buildSpanCountTemplate({
|
|
672
|
+
id: "service-traffic-stopped",
|
|
673
|
+
name: "Traffic Stopped",
|
|
674
|
+
description:
|
|
675
|
+
"Alert when the service produces no spans at all for fifteen minutes. Best on an always-on request path; a service with idle periods will trigger it legitimately.",
|
|
676
|
+
category: "Throughput",
|
|
677
|
+
severity: "Warning",
|
|
678
|
+
spanStatuses: [],
|
|
679
|
+
lastXSecondsOfSpans: 900,
|
|
680
|
+
threshold: 1,
|
|
681
|
+
unhealthyFilterType: FilterType.LessThan,
|
|
682
|
+
healthyFilterType: FilterType.GreaterThanOrEqualTo,
|
|
683
|
+
unhealthyName: "No spans in fifteen minutes",
|
|
684
|
+
unhealthyDescription:
|
|
685
|
+
"Triggers when the service reports no spans at all over fifteen minutes.",
|
|
686
|
+
incidentDescription:
|
|
687
|
+
"The service has stopped producing telemetry. Either it is down, it lost its route to the collector, or its callers stopped calling — check the process, then the exporter, then upstream traffic.",
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
const unhandledExceptionsTemplate: ServiceAlertTemplate = {
|
|
691
|
+
id: "service-unhandled-exceptions",
|
|
692
|
+
name: "Unhandled Exceptions",
|
|
693
|
+
description:
|
|
694
|
+
"Alert when the service reports any unresolved exception in ten minutes. Exceptions already marked resolved or archived are excluded, so acknowledging one closes the alert.",
|
|
695
|
+
category: "Errors",
|
|
696
|
+
severity: "Critical",
|
|
697
|
+
monitorType: MonitorType.Exceptions,
|
|
698
|
+
getMonitorStep: (args: ServiceAlertTemplateArgs): MonitorStep => {
|
|
699
|
+
const step: MonitorStep = MonitorStep.getDefaultMonitorStep({
|
|
700
|
+
monitorName: args.monitorName,
|
|
701
|
+
monitorType: MonitorType.Exceptions,
|
|
702
|
+
onlineMonitorStatusId: args.onlineMonitorStatusId,
|
|
703
|
+
offlineMonitorStatusId: args.offlineMonitorStatusId,
|
|
704
|
+
defaultIncidentSeverityId: args.defaultIncidentSeverityId,
|
|
705
|
+
defaultAlertSeverityId: args.defaultAlertSeverityId,
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
step.setExceptionMonitor({
|
|
709
|
+
telemetryServiceIds: [new ObjectID(args.serviceId)],
|
|
710
|
+
entityKeys: [],
|
|
711
|
+
exceptionTypes: [],
|
|
712
|
+
message: "",
|
|
713
|
+
includeResolved: false,
|
|
714
|
+
includeArchived: false,
|
|
715
|
+
// Ten minutes, for the reasons given on failedOperationsTemplate.
|
|
716
|
+
lastXSecondsOfExceptions: 600,
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
step.setMonitorCriteria(
|
|
720
|
+
buildCountCriteria({
|
|
721
|
+
args: args,
|
|
722
|
+
checkOn: CheckOn.ExceptionCount,
|
|
723
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
724
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
725
|
+
threshold: 0,
|
|
726
|
+
unhealthyName: "Unhandled exception detected",
|
|
727
|
+
unhealthyDescription:
|
|
728
|
+
"Triggers when at least one unresolved exception is reported in ten minutes.",
|
|
729
|
+
incidentTitle: `[Service] Unhandled exception - ${args.monitorName}`,
|
|
730
|
+
incidentDescription:
|
|
731
|
+
"The service reported an unresolved exception. Inspect the exception group, its stack trace, the release it appeared in, and how many distinct requests it affected.",
|
|
732
|
+
}),
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
return step;
|
|
736
|
+
},
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
/*
|
|
740
|
+
* Latency has to be a metric monitor: trace monitors can only count spans,
|
|
741
|
+
* and no span-duration criterion exists anywhere in the evaluator.
|
|
742
|
+
*
|
|
743
|
+
* `http.server.request.duration` is the histogram the percentile path was
|
|
744
|
+
* built for — the metric service fans its buckets into weighted samples and
|
|
745
|
+
* runs a real quantile rather than taking a percentile of per-row sums.
|
|
746
|
+
*
|
|
747
|
+
* `legendUnit: "ms"` normalizes the two units this metric arrives in: the
|
|
748
|
+
* semantic convention says seconds, while OneUptime's own instrumentation
|
|
749
|
+
* reports milliseconds. Converting both to milliseconds before comparing is
|
|
750
|
+
* what makes one threshold correct for both.
|
|
751
|
+
*/
|
|
752
|
+
const latencyP95Template: ServiceAlertTemplate = buildMetricTemplate({
|
|
753
|
+
id: "service-latency-p95",
|
|
754
|
+
name: "High Request Latency (p95)",
|
|
755
|
+
description:
|
|
756
|
+
"Alert when the 95th-percentile server request duration stays at or above one second. Reads http.server.request.duration; older SDKs report this as http.server.duration.",
|
|
757
|
+
category: "Latency",
|
|
758
|
+
severity: "Warning",
|
|
759
|
+
metricName: "http.server.request.duration",
|
|
760
|
+
metricAlias: "service_latency_p95",
|
|
761
|
+
aggregationType: MetricsAggregationType.P95,
|
|
762
|
+
legendUnit: "ms",
|
|
763
|
+
threshold: 1000,
|
|
764
|
+
thresholdLabel: "1,000 ms",
|
|
765
|
+
incidentDescription:
|
|
766
|
+
"One in twenty requests is now taking a second or more. Break the latency down by route and by downstream call to find where the time is going.",
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
const latencyP99Template: ServiceAlertTemplate = buildMetricTemplate({
|
|
770
|
+
id: "service-latency-p99",
|
|
771
|
+
name: "Severe Request Latency (p99)",
|
|
772
|
+
description:
|
|
773
|
+
"Alert when the 99th-percentile server request duration stays at or above 2.5 seconds — the tail users abandon.",
|
|
774
|
+
category: "Latency",
|
|
775
|
+
severity: "Critical",
|
|
776
|
+
metricName: "http.server.request.duration",
|
|
777
|
+
metricAlias: "service_latency_p99",
|
|
778
|
+
aggregationType: MetricsAggregationType.P99,
|
|
779
|
+
legendUnit: "ms",
|
|
780
|
+
threshold: 2500,
|
|
781
|
+
thresholdLabel: "2,500 ms",
|
|
782
|
+
incidentDescription:
|
|
783
|
+
"The slowest one percent of requests are taking 2.5 seconds or more. Look for a saturated dependency, a lock, or a slow query on the affected route.",
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
/*
|
|
787
|
+
* `process.cpu.utilization` and `process.memory.usage` come from the
|
|
788
|
+
* host-metrics / system-metrics instrumentation rather than from any language
|
|
789
|
+
* runtime, which is why they sit in the agnostic set. They are opt-in in every
|
|
790
|
+
* SDK: a service that has not enabled that instrumentation gets a monitor that
|
|
791
|
+
* never fires, which is why both say so in their descriptions.
|
|
792
|
+
*
|
|
793
|
+
* The threshold is a raw [0, 1] ratio, matching how the host templates already
|
|
794
|
+
* express utilization (`0.85 == 85%`) and how the SDKs report it. It is
|
|
795
|
+
* deliberately NOT converted to a percentage: that conversion depends on the
|
|
796
|
+
* metric's declared unit being present, and a missing unit would silently turn
|
|
797
|
+
* "85%" into a threshold no fraction can ever reach.
|
|
798
|
+
*/
|
|
799
|
+
const processCpuTemplate: ServiceAlertTemplate = buildMetricTemplate({
|
|
800
|
+
id: "service-process-cpu-saturation",
|
|
801
|
+
name: "Process CPU Saturation",
|
|
802
|
+
description:
|
|
803
|
+
"Alert when the process stays above 85% CPU utilization. Needs the host-metrics instrumentation enabled in the service's SDK.",
|
|
804
|
+
category: "Saturation",
|
|
805
|
+
severity: "Warning",
|
|
806
|
+
metricName: "process.cpu.utilization",
|
|
807
|
+
metricAlias: "service_process_cpu",
|
|
808
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
809
|
+
threshold: 0.85,
|
|
810
|
+
thresholdLabel: "85%",
|
|
811
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
812
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
813
|
+
incidentDescription:
|
|
814
|
+
"The process has been pinned above 85% CPU for the whole window. Requests will be queueing behind it — check for a hot loop, a retry storm, or a workload that outgrew its CPU allocation.",
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
const processMemoryTemplate: ServiceAlertTemplate = buildMetricTemplate({
|
|
818
|
+
id: "service-process-memory-high",
|
|
819
|
+
name: "Process Memory High",
|
|
820
|
+
description:
|
|
821
|
+
"Alert when process resident memory stays above 1 GB. An absolute budget — retune it to your container limit. Needs the host-metrics instrumentation enabled.",
|
|
822
|
+
category: "Saturation",
|
|
823
|
+
severity: "Warning",
|
|
824
|
+
metricName: "process.memory.usage",
|
|
825
|
+
metricAlias: "service_process_memory",
|
|
826
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
827
|
+
threshold: 1073741824,
|
|
828
|
+
thresholdLabel: "1 GB",
|
|
829
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
830
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
831
|
+
incidentDescription:
|
|
832
|
+
"Process memory has stayed above its budget for the whole window. If it only ever climbs, it is a leak; if it climbs and drops, the workload has outgrown the container limit and will be OOM-killed next.",
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
// --- Java ---
|
|
836
|
+
|
|
837
|
+
const javaTemplates: Array<ServiceAlertTemplate> = [
|
|
838
|
+
buildRatioTemplate({
|
|
839
|
+
id: "service-java-heap-utilization",
|
|
840
|
+
name: "JVM Heap Utilization",
|
|
841
|
+
description:
|
|
842
|
+
"Alert when live heap stays at or above 90% of the JVM's configured heap limit — the leading indicator of GC thrashing and OutOfMemoryError.",
|
|
843
|
+
category: "JVM Runtime",
|
|
844
|
+
severity: "Critical",
|
|
845
|
+
language: "java",
|
|
846
|
+
numeratorMetricName: "jvm.memory.used",
|
|
847
|
+
numeratorAttributes: { "jvm.memory.type": "heap" },
|
|
848
|
+
denominatorMetricName: "jvm.memory.limit",
|
|
849
|
+
denominatorAttributes: { "jvm.memory.type": "heap" },
|
|
850
|
+
numeratorAlias: "jvm_heap_used",
|
|
851
|
+
denominatorAlias: "jvm_heap_limit",
|
|
852
|
+
resultAlias: "jvm_heap_percent",
|
|
853
|
+
thresholdPercent: 90,
|
|
854
|
+
incidentDescription:
|
|
855
|
+
"The JVM heap is at or above 90% of its limit. Expect long GC pauses next and an OutOfMemoryError after that. Take a heap dump before restarting — a restart clears the symptom and destroys the evidence.",
|
|
856
|
+
}),
|
|
857
|
+
buildMetricTemplate({
|
|
858
|
+
id: "service-java-live-heap-after-gc",
|
|
859
|
+
name: "Live Heap After GC",
|
|
860
|
+
description:
|
|
861
|
+
"Alert when the bytes still live after the last collection stay above 2 GB. Unlike heap used, this ignores allocation churn, so a rise here is a genuine leak. Retune to roughly 85% of your -Xmx.",
|
|
862
|
+
category: "JVM Runtime",
|
|
863
|
+
severity: "Warning",
|
|
864
|
+
language: "java",
|
|
865
|
+
metricName: "jvm.memory.used_after_last_gc",
|
|
866
|
+
metricAlias: "jvm_heap_after_gc",
|
|
867
|
+
aggregationType: MetricsAggregationType.Max,
|
|
868
|
+
attributes: { "jvm.memory.type": "heap" },
|
|
869
|
+
threshold: 2147483648,
|
|
870
|
+
thresholdLabel: "2 GB",
|
|
871
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
872
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
873
|
+
incidentDescription:
|
|
874
|
+
"Memory that survives every collection keeps growing, which is what a leak looks like from the outside. Compare heap dumps taken an hour apart and look at which retained set grew.",
|
|
875
|
+
}),
|
|
876
|
+
buildMetricTemplate({
|
|
877
|
+
id: "service-java-gc-pause-p99",
|
|
878
|
+
name: "Long GC Pauses",
|
|
879
|
+
description:
|
|
880
|
+
"Alert when the 99th-percentile garbage collection pause stays at or above one second. Reads jvm.gc.duration; older agents report process.runtime.jvm.gc.duration.",
|
|
881
|
+
category: "JVM Runtime",
|
|
882
|
+
severity: "Critical",
|
|
883
|
+
language: "java",
|
|
884
|
+
metricName: "jvm.gc.duration",
|
|
885
|
+
metricAlias: "jvm_gc_pause_p99",
|
|
886
|
+
aggregationType: MetricsAggregationType.P99,
|
|
887
|
+
legendUnit: "ms",
|
|
888
|
+
threshold: 1000,
|
|
889
|
+
thresholdLabel: "1,000 ms",
|
|
890
|
+
incidentDescription:
|
|
891
|
+
"The JVM is stopping the world for a second or more at the tail. Every request in flight during a pause pays for it, so this shows up to callers as latency with no slow query behind it. Check heap headroom and collector choice.",
|
|
892
|
+
}),
|
|
893
|
+
buildMetricTemplate({
|
|
894
|
+
id: "service-java-cpu-saturation",
|
|
895
|
+
name: "JVM CPU Saturation",
|
|
896
|
+
description:
|
|
897
|
+
"Alert when the JVM's recent CPU utilization stays above 85%. Reads jvm.cpu.recent_utilization; older agents report process.runtime.jvm.cpu.utilization.",
|
|
898
|
+
category: "JVM Runtime",
|
|
899
|
+
severity: "Warning",
|
|
900
|
+
language: "java",
|
|
901
|
+
metricName: "jvm.cpu.recent_utilization",
|
|
902
|
+
metricAlias: "jvm_cpu",
|
|
903
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
904
|
+
threshold: 0.85,
|
|
905
|
+
thresholdLabel: "85%",
|
|
906
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
907
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
908
|
+
incidentDescription:
|
|
909
|
+
"The JVM has been pinned above 85% CPU for the whole window. If GC pause time rose with it, this is GC thrashing rather than application work — check heap utilization first.",
|
|
910
|
+
}),
|
|
911
|
+
buildMetricTemplate({
|
|
912
|
+
id: "service-java-thread-explosion",
|
|
913
|
+
name: "Thread Count Explosion",
|
|
914
|
+
description:
|
|
915
|
+
"Alert when live thread count stays above 500 — an unbounded pool, a leaked executor, or thread-per-request under load. Reads jvm.thread.count; older agents report process.runtime.jvm.threads.count.",
|
|
916
|
+
category: "JVM Runtime",
|
|
917
|
+
severity: "Warning",
|
|
918
|
+
language: "java",
|
|
919
|
+
metricName: "jvm.thread.count",
|
|
920
|
+
metricAlias: "jvm_threads",
|
|
921
|
+
aggregationType: MetricsAggregationType.Max,
|
|
922
|
+
threshold: 500,
|
|
923
|
+
thresholdLabel: "500 threads",
|
|
924
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
925
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
926
|
+
incidentDescription:
|
|
927
|
+
"Live threads have stayed above 500. Each one costs stack memory and scheduler time, and a count that only climbs means threads are being created faster than they finish. Take a thread dump and look for the pool that has no bound.",
|
|
928
|
+
}),
|
|
929
|
+
buildMetricTemplate({
|
|
930
|
+
id: "service-java-non-heap-growth",
|
|
931
|
+
name: "Non-Heap Memory Growth",
|
|
932
|
+
description:
|
|
933
|
+
"Alert when non-heap memory — metaspace, code cache, compressed class space — stays above 512 MB. Usually a classloader leak, dynamic proxy churn, or an agent gone wrong.",
|
|
934
|
+
category: "JVM Runtime",
|
|
935
|
+
severity: "Warning",
|
|
936
|
+
language: "java",
|
|
937
|
+
metricName: "jvm.memory.used",
|
|
938
|
+
metricAlias: "jvm_non_heap_used",
|
|
939
|
+
aggregationType: MetricsAggregationType.Sum,
|
|
940
|
+
attributes: { "jvm.memory.type": "non_heap" },
|
|
941
|
+
threshold: 536870912,
|
|
942
|
+
thresholdLabel: "512 MB",
|
|
943
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
944
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
945
|
+
incidentDescription:
|
|
946
|
+
"Non-heap memory has grown past its budget. Heap dumps will not show this — look at loaded class count over time, and at anything generating classes at runtime.",
|
|
947
|
+
}),
|
|
948
|
+
];
|
|
949
|
+
|
|
950
|
+
// --- .NET ---
|
|
951
|
+
|
|
952
|
+
const dotnetTemplates: Array<ServiceAlertTemplate> = [
|
|
953
|
+
buildMetricTemplate({
|
|
954
|
+
id: "service-dotnet-threadpool-starvation",
|
|
955
|
+
name: "Thread Pool Starvation",
|
|
956
|
+
description:
|
|
957
|
+
"Alert when work items queue behind a saturated thread pool. The classic sync-over-async death spiral, and it presents as latency everywhere at once.",
|
|
958
|
+
category: ".NET Runtime",
|
|
959
|
+
severity: "Critical",
|
|
960
|
+
language: "dotnet",
|
|
961
|
+
metricName: "dotnet.thread_pool.queue.length",
|
|
962
|
+
metricAlias: "dotnet_threadpool_queue",
|
|
963
|
+
aggregationType: MetricsAggregationType.Max,
|
|
964
|
+
threshold: 50,
|
|
965
|
+
thresholdLabel: "50 queued work items",
|
|
966
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
967
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
968
|
+
incidentDescription:
|
|
969
|
+
"Work is queueing because no pool thread is free. Look for blocking calls on pool threads — .Result, .Wait(), or a synchronous I/O call inside an async path — rather than for slow work.",
|
|
970
|
+
}),
|
|
971
|
+
buildMetricTemplate({
|
|
972
|
+
id: "service-dotnet-threadpool-growth",
|
|
973
|
+
name: "Thread Pool Growth",
|
|
974
|
+
description:
|
|
975
|
+
"Alert when the runtime has injected far more pool threads than the machine has cores, which it only does when existing threads are blocked.",
|
|
976
|
+
category: ".NET Runtime",
|
|
977
|
+
severity: "Warning",
|
|
978
|
+
language: "dotnet",
|
|
979
|
+
metricName: "dotnet.thread_pool.thread.count",
|
|
980
|
+
metricAlias: "dotnet_threadpool_threads",
|
|
981
|
+
aggregationType: MetricsAggregationType.Max,
|
|
982
|
+
threshold: 200,
|
|
983
|
+
thresholdLabel: "200 threads",
|
|
984
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
985
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
986
|
+
incidentDescription:
|
|
987
|
+
"The thread pool has injected threads well past core count, which the runtime only does to work around blocked threads. Pairs with queue length — this one rises first.",
|
|
988
|
+
}),
|
|
989
|
+
buildMetricTemplate({
|
|
990
|
+
id: "service-dotnet-gen2-heap-growth",
|
|
991
|
+
name: "Gen 2 Heap Growth",
|
|
992
|
+
description:
|
|
993
|
+
"Alert when the gen 2 heap after the last collection stays above 1 GB. Objects that survive into gen 2 and stay there are the .NET leak signature.",
|
|
994
|
+
category: ".NET Runtime",
|
|
995
|
+
severity: "Warning",
|
|
996
|
+
language: "dotnet",
|
|
997
|
+
metricName: "dotnet.gc.last_collection.heap.size",
|
|
998
|
+
metricAlias: "dotnet_gen2_heap",
|
|
999
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
1000
|
+
attributes: { "dotnet.gc.heap.generation": "gen2" },
|
|
1001
|
+
threshold: 1073741824,
|
|
1002
|
+
thresholdLabel: "1 GB",
|
|
1003
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
1004
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
1005
|
+
incidentDescription:
|
|
1006
|
+
"The gen 2 heap keeps growing across collections. Take two dumps an hour apart and diff the object graph — a static collection or an event handler that is never unsubscribed is the usual cause.",
|
|
1007
|
+
}),
|
|
1008
|
+
buildMetricTemplate({
|
|
1009
|
+
id: "service-dotnet-working-set",
|
|
1010
|
+
name: "Working Set High",
|
|
1011
|
+
description:
|
|
1012
|
+
"Alert when the process working set stays above 1.5 GB. An absolute budget — retune it to your container limit.",
|
|
1013
|
+
category: ".NET Runtime",
|
|
1014
|
+
severity: "Warning",
|
|
1015
|
+
language: "dotnet",
|
|
1016
|
+
metricName: "dotnet.process.memory.working_set",
|
|
1017
|
+
metricAlias: "dotnet_working_set",
|
|
1018
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
1019
|
+
threshold: 1610612736,
|
|
1020
|
+
thresholdLabel: "1.5 GB",
|
|
1021
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
1022
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
1023
|
+
incidentDescription:
|
|
1024
|
+
"The working set has stayed above its budget. If gen 2 heap is flat, the growth is unmanaged — native handles, pinned buffers, or a native library holding memory.",
|
|
1025
|
+
}),
|
|
1026
|
+
];
|
|
1027
|
+
|
|
1028
|
+
// --- Node.js ---
|
|
1029
|
+
|
|
1030
|
+
const nodejsTemplates: Array<ServiceAlertTemplate> = [
|
|
1031
|
+
buildMetricTemplate({
|
|
1032
|
+
id: "service-nodejs-event-loop-saturated",
|
|
1033
|
+
name: "Event Loop Saturated",
|
|
1034
|
+
description:
|
|
1035
|
+
"Alert when the event loop is busy more than 90% of wall time. The single best saturation signal Node has: past this point every new request just queues.",
|
|
1036
|
+
category: "Node.js Runtime",
|
|
1037
|
+
severity: "Critical",
|
|
1038
|
+
language: "nodejs",
|
|
1039
|
+
metricName: "nodejs.eventloop.utilization",
|
|
1040
|
+
metricAlias: "nodejs_eventloop_utilization",
|
|
1041
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
1042
|
+
threshold: 0.9,
|
|
1043
|
+
thresholdLabel: "90%",
|
|
1044
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
1045
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
1046
|
+
incidentDescription:
|
|
1047
|
+
"The event loop has almost no idle time left. One process cannot serve more than this — find the synchronous work blocking the loop (JSON of a huge payload, crypto, a tight loop) or add instances.",
|
|
1048
|
+
}),
|
|
1049
|
+
buildMetricTemplate({
|
|
1050
|
+
id: "service-nodejs-event-loop-lag",
|
|
1051
|
+
name: "Event Loop Lag",
|
|
1052
|
+
description:
|
|
1053
|
+
"Alert when 99th-percentile event-loop scheduling lag stays at or above 200 ms, meaning callbacks and timers are being starved.",
|
|
1054
|
+
category: "Node.js Runtime",
|
|
1055
|
+
severity: "Warning",
|
|
1056
|
+
language: "nodejs",
|
|
1057
|
+
metricName: "nodejs.eventloop.delay.p99",
|
|
1058
|
+
metricAlias: "nodejs_eventloop_delay",
|
|
1059
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
1060
|
+
legendUnit: "ms",
|
|
1061
|
+
threshold: 200,
|
|
1062
|
+
thresholdLabel: "200 ms",
|
|
1063
|
+
incidentDescription:
|
|
1064
|
+
"Callbacks are waiting 200 ms or more just to be scheduled. Every response pays that on top of its real work — look for a synchronous block on the main thread.",
|
|
1065
|
+
}),
|
|
1066
|
+
/*
|
|
1067
|
+
* Deliberately unfiltered by heap space, on both sides.
|
|
1068
|
+
*
|
|
1069
|
+
* `v8js.memory.heap.used` is reported per heap space (the service overview's
|
|
1070
|
+
* own chart labels it "avg across V8 heap spaces"), so the obvious version
|
|
1071
|
+
* of this template filters both queries to `old_space` — that is the space
|
|
1072
|
+
* that actually kills a Node process. Summing every space instead is correct
|
|
1073
|
+
* whichever way the SDK reports it: if the series are split, the sums are
|
|
1074
|
+
* total used over total limit; if they are not, the values pass through
|
|
1075
|
+
* unchanged. Filtering on an attribute the SDK turns out not to emit
|
|
1076
|
+
* produces no data, and a metric monitor with no data never fires — a
|
|
1077
|
+
* silent, permanently-green monitor is a worse outcome than a slightly
|
|
1078
|
+
* blunter one.
|
|
1079
|
+
*/
|
|
1080
|
+
buildRatioTemplate({
|
|
1081
|
+
id: "service-nodejs-heap-pressure",
|
|
1082
|
+
name: "V8 Heap Pressure",
|
|
1083
|
+
description:
|
|
1084
|
+
"Alert when the V8 heap stays at or above 90% of its hard limit — the last warning before a fatal, unrecoverable out-of-memory crash.",
|
|
1085
|
+
category: "Node.js Runtime",
|
|
1086
|
+
severity: "Critical",
|
|
1087
|
+
language: "nodejs",
|
|
1088
|
+
numeratorMetricName: "v8js.memory.heap.used",
|
|
1089
|
+
denominatorMetricName: "v8js.memory.heap.limit",
|
|
1090
|
+
numeratorAlias: "v8_heap_used",
|
|
1091
|
+
denominatorAlias: "v8_heap_limit",
|
|
1092
|
+
resultAlias: "v8_heap_percent",
|
|
1093
|
+
thresholdPercent: 90,
|
|
1094
|
+
incidentDescription:
|
|
1095
|
+
"The V8 heap is nearly full. Node does not degrade here — it aborts the process. Capture a heap snapshot now, and raise --max-old-space-size only as a stopgap.",
|
|
1096
|
+
}),
|
|
1097
|
+
/*
|
|
1098
|
+
* Unfiltered by GC type for the same reason as the template above: a p99
|
|
1099
|
+
* across every collection is dominated by the major ones anyway, and it
|
|
1100
|
+
* cannot be silenced by an attribute the SDK does not emit.
|
|
1101
|
+
*/
|
|
1102
|
+
buildMetricTemplate({
|
|
1103
|
+
id: "service-nodejs-gc-pause-p99",
|
|
1104
|
+
name: "Long GC Pauses",
|
|
1105
|
+
description:
|
|
1106
|
+
"Alert when the 99th-percentile garbage collection pause stays at or above 200 ms. On a single-threaded event loop, a GC pause blocks everything.",
|
|
1107
|
+
category: "Node.js Runtime",
|
|
1108
|
+
severity: "Warning",
|
|
1109
|
+
language: "nodejs",
|
|
1110
|
+
metricName: "v8js.gc.duration",
|
|
1111
|
+
metricAlias: "v8_gc_pause_p99",
|
|
1112
|
+
aggregationType: MetricsAggregationType.P99,
|
|
1113
|
+
legendUnit: "ms",
|
|
1114
|
+
threshold: 200,
|
|
1115
|
+
thresholdLabel: "200 ms",
|
|
1116
|
+
incidentDescription:
|
|
1117
|
+
"Collections are pausing the loop for 200 ms or more at the tail. Usually the tail end of heap pressure — check heap utilization before tuning the collector.",
|
|
1118
|
+
}),
|
|
1119
|
+
];
|
|
1120
|
+
|
|
1121
|
+
// --- Python ---
|
|
1122
|
+
|
|
1123
|
+
const pythonTemplates: Array<ServiceAlertTemplate> = [
|
|
1124
|
+
/*
|
|
1125
|
+
* The only genuinely default-on Python metric here: the ASGI and WSGI
|
|
1126
|
+
* instrumentations emit it, so FastAPI, Django and Flask services get it
|
|
1127
|
+
* without any extra package. The other two need
|
|
1128
|
+
* `opentelemetry-instrumentation-system-metrics`.
|
|
1129
|
+
*/
|
|
1130
|
+
buildMetricTemplate({
|
|
1131
|
+
id: "service-python-request-concurrency",
|
|
1132
|
+
name: "Request Concurrency Saturated",
|
|
1133
|
+
description:
|
|
1134
|
+
"Alert when in-flight requests reach the worker budget, meaning new requests are queueing. Retune the threshold to your workers × threads.",
|
|
1135
|
+
category: "Python Runtime",
|
|
1136
|
+
severity: "Critical",
|
|
1137
|
+
language: "python",
|
|
1138
|
+
metricName: "http.server.active_requests",
|
|
1139
|
+
metricAlias: "python_active_requests",
|
|
1140
|
+
aggregationType: MetricsAggregationType.Max,
|
|
1141
|
+
threshold: 40,
|
|
1142
|
+
thresholdLabel: "40 in-flight requests",
|
|
1143
|
+
incidentDescription:
|
|
1144
|
+
"Every worker is busy and requests are queueing behind them. With a GIL, adding threads rarely helps — look at what the workers are blocked on, and at process count.",
|
|
1145
|
+
}),
|
|
1146
|
+
buildMetricTemplate({
|
|
1147
|
+
id: "service-python-rss-memory",
|
|
1148
|
+
name: "Resident Memory High",
|
|
1149
|
+
description:
|
|
1150
|
+
"Alert when resident memory stays above 1 GB per worker — the signal that precedes an OOM kill. Needs the system-metrics instrumentation enabled.",
|
|
1151
|
+
category: "Python Runtime",
|
|
1152
|
+
severity: "Warning",
|
|
1153
|
+
language: "python",
|
|
1154
|
+
metricName: "process.runtime.cpython.memory",
|
|
1155
|
+
metricAlias: "python_rss",
|
|
1156
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
1157
|
+
attributes: { type: "rss" },
|
|
1158
|
+
threshold: 1073741824,
|
|
1159
|
+
thresholdLabel: "1 GB",
|
|
1160
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
1161
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
1162
|
+
incidentDescription:
|
|
1163
|
+
"A worker's resident memory has stayed above its budget. Python rarely returns memory to the OS, so a plateau after a spike is normal and a steady climb is not — look for an unbounded cache or a growing module-level structure.",
|
|
1164
|
+
}),
|
|
1165
|
+
buildMetricTemplate({
|
|
1166
|
+
id: "service-python-thread-growth",
|
|
1167
|
+
name: "Thread Count Growth",
|
|
1168
|
+
description:
|
|
1169
|
+
"Alert when thread count stays above 200, which usually means a leaked executor or an unclosed client pool. Needs the system-metrics instrumentation enabled.",
|
|
1170
|
+
category: "Python Runtime",
|
|
1171
|
+
severity: "Warning",
|
|
1172
|
+
language: "python",
|
|
1173
|
+
metricName: "process.runtime.cpython.thread_count",
|
|
1174
|
+
metricAlias: "python_threads",
|
|
1175
|
+
aggregationType: MetricsAggregationType.Max,
|
|
1176
|
+
threshold: 200,
|
|
1177
|
+
thresholdLabel: "200 threads",
|
|
1178
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
1179
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
1180
|
+
incidentDescription:
|
|
1181
|
+
"Thread count has stayed above 200. Under a GIL these mostly wait rather than work, so a climbing count is a leak — look for ThreadPoolExecutors created per request and clients never closed.",
|
|
1182
|
+
}),
|
|
1183
|
+
];
|
|
1184
|
+
|
|
1185
|
+
// --- Go ---
|
|
1186
|
+
|
|
1187
|
+
const goTemplates: Array<ServiceAlertTemplate> = [
|
|
1188
|
+
buildMetricTemplate({
|
|
1189
|
+
id: "service-go-goroutine-leak",
|
|
1190
|
+
name: "Goroutine Leak",
|
|
1191
|
+
description:
|
|
1192
|
+
"Alert when live goroutines stay above 10,000 — leaked contexts, unbounded fan-out, or sends on a channel nobody reads. Reads go.goroutine.count; older builds report process.runtime.go.goroutines.",
|
|
1193
|
+
category: "Go Runtime",
|
|
1194
|
+
severity: "Warning",
|
|
1195
|
+
language: "go",
|
|
1196
|
+
metricName: "go.goroutine.count",
|
|
1197
|
+
metricAlias: "go_goroutines",
|
|
1198
|
+
aggregationType: MetricsAggregationType.Max,
|
|
1199
|
+
threshold: 10000,
|
|
1200
|
+
thresholdLabel: "10,000 goroutines",
|
|
1201
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
1202
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
1203
|
+
incidentDescription:
|
|
1204
|
+
"Goroutine count has stayed above 10,000. A count that only climbs is a leak: take a goroutine profile and look at the top stack — it is almost always a channel send or receive with no timeout and no cancelled context.",
|
|
1205
|
+
}),
|
|
1206
|
+
buildMetricTemplate({
|
|
1207
|
+
id: "service-go-heap-memory",
|
|
1208
|
+
name: "Runtime Memory High",
|
|
1209
|
+
description:
|
|
1210
|
+
"Alert when non-stack runtime memory stays above 1 GB. An absolute budget — retune it to your container limit.",
|
|
1211
|
+
category: "Go Runtime",
|
|
1212
|
+
severity: "Warning",
|
|
1213
|
+
language: "go",
|
|
1214
|
+
metricName: "go.memory.used",
|
|
1215
|
+
metricAlias: "go_heap_memory",
|
|
1216
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
1217
|
+
/*
|
|
1218
|
+
* The filter is load-bearing, not a refinement: `go.memory.used` splits
|
|
1219
|
+
* into `stack` and `other` series, so an unfiltered average of the two
|
|
1220
|
+
* reports roughly half the heap and the threshold would be wrong by that
|
|
1221
|
+
* factor. "other" is the heap-dominated half.
|
|
1222
|
+
*/
|
|
1223
|
+
attributes: { "go.memory.type": "other" },
|
|
1224
|
+
threshold: 1073741824,
|
|
1225
|
+
thresholdLabel: "1 GB",
|
|
1226
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
1227
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
1228
|
+
incidentDescription:
|
|
1229
|
+
"Heap memory has stayed above its budget. Take a heap profile and compare allocation sites — in Go this is usually a slice or map that is appended to and never bounded.",
|
|
1230
|
+
}),
|
|
1231
|
+
buildMetricTemplate({
|
|
1232
|
+
id: "service-go-stack-memory",
|
|
1233
|
+
name: "Goroutine Stack Memory",
|
|
1234
|
+
description:
|
|
1235
|
+
"Alert when total goroutine stack memory stays above 256 MB. An independent read on the same leak goroutine count catches, and it often moves first.",
|
|
1236
|
+
category: "Go Runtime",
|
|
1237
|
+
severity: "Warning",
|
|
1238
|
+
language: "go",
|
|
1239
|
+
metricName: "go.memory.used",
|
|
1240
|
+
metricAlias: "go_stack_memory",
|
|
1241
|
+
aggregationType: MetricsAggregationType.Avg,
|
|
1242
|
+
attributes: { "go.memory.type": "stack" },
|
|
1243
|
+
threshold: 268435456,
|
|
1244
|
+
thresholdLabel: "256 MB",
|
|
1245
|
+
unhealthyFilterType: FilterType.GreaterThan,
|
|
1246
|
+
healthyFilterType: FilterType.LessThanOrEqualTo,
|
|
1247
|
+
incidentDescription:
|
|
1248
|
+
"Stack memory has grown past its budget. Either there are far too many goroutines, or some of them recurse deeply — the goroutine profile answers which.",
|
|
1249
|
+
}),
|
|
1250
|
+
buildMetricTemplate({
|
|
1251
|
+
id: "service-go-scheduler-latency-p99",
|
|
1252
|
+
name: "Scheduler Latency",
|
|
1253
|
+
description:
|
|
1254
|
+
"Alert when the 99th-percentile time a runnable goroutine waits to be scheduled stays at or above 50 ms — P contention or a GOMAXPROCS set below the real CPU budget.",
|
|
1255
|
+
category: "Go Runtime",
|
|
1256
|
+
severity: "Warning",
|
|
1257
|
+
language: "go",
|
|
1258
|
+
metricName: "go.schedule.duration",
|
|
1259
|
+
metricAlias: "go_schedule_p99",
|
|
1260
|
+
aggregationType: MetricsAggregationType.P99,
|
|
1261
|
+
legendUnit: "ms",
|
|
1262
|
+
threshold: 50,
|
|
1263
|
+
thresholdLabel: "50 ms",
|
|
1264
|
+
incidentDescription:
|
|
1265
|
+
"Runnable goroutines are waiting 50 ms or more for a processor. Check GOMAXPROCS against the container's real CPU limit, and look for CPU-bound work starving the scheduler.",
|
|
1266
|
+
}),
|
|
1267
|
+
];
|
|
1268
|
+
|
|
1269
|
+
/*
|
|
1270
|
+
* Declaration order is the display order — the recommendations page renders
|
|
1271
|
+
* category sections in the order the templates first mention them, so the
|
|
1272
|
+
* agnostic RED signals come first and the runtime section lands underneath.
|
|
1273
|
+
*/
|
|
1274
|
+
const ALL_SERVICE_ALERT_TEMPLATES: Array<ServiceAlertTemplate> = [
|
|
1275
|
+
failedOperationsTemplate,
|
|
1276
|
+
errorBurstTemplate,
|
|
1277
|
+
unhandledExceptionsTemplate,
|
|
1278
|
+
latencyP95Template,
|
|
1279
|
+
latencyP99Template,
|
|
1280
|
+
trafficStoppedTemplate,
|
|
1281
|
+
processCpuTemplate,
|
|
1282
|
+
processMemoryTemplate,
|
|
1283
|
+
...javaTemplates,
|
|
1284
|
+
...dotnetTemplates,
|
|
1285
|
+
...nodejsTemplates,
|
|
1286
|
+
...pythonTemplates,
|
|
1287
|
+
...goTemplates,
|
|
1288
|
+
];
|
|
1289
|
+
|
|
1290
|
+
export function getAllServiceAlertTemplates(): Array<ServiceAlertTemplate> {
|
|
1291
|
+
return [...ALL_SERVICE_ALERT_TEMPLATES];
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/*
|
|
1295
|
+
* The templates to offer ONE service, given its detected runtime.
|
|
1296
|
+
*
|
|
1297
|
+
* Always the agnostic set, plus the runtime's own set when there is one. The
|
|
1298
|
+
* two rules that matter:
|
|
1299
|
+
*
|
|
1300
|
+
* - An unknown language (null/undefined) yields the agnostic set, never an
|
|
1301
|
+
* empty list and never a guess. A service whose SDK has not reported
|
|
1302
|
+
* `telemetry.sdk.language` yet still has spans and exceptions, so the RED
|
|
1303
|
+
* recommendations are all valid for it.
|
|
1304
|
+
*
|
|
1305
|
+
* - A known language with no templates of its own — Ruby, PHP, Rust, and the
|
|
1306
|
+
* rest — also yields exactly the agnostic set. That is a deliberate
|
|
1307
|
+
* omission rather than a gap to be filled later with plausible-looking
|
|
1308
|
+
* entries: those ecosystems have no default OpenTelemetry runtime-metrics
|
|
1309
|
+
* instrumentation, so any runtime template written for them would query a
|
|
1310
|
+
* metric name nobody emits. A shorter honest list beats a longer list of
|
|
1311
|
+
* monitors that can never fire.
|
|
1312
|
+
*/
|
|
1313
|
+
export function getServiceAlertTemplates(
|
|
1314
|
+
language?: ServiceLanguage | null | undefined,
|
|
1315
|
+
): Array<ServiceAlertTemplate> {
|
|
1316
|
+
return ALL_SERVICE_ALERT_TEMPLATES.filter(
|
|
1317
|
+
(template: ServiceAlertTemplate) => {
|
|
1318
|
+
if (!template.language) {
|
|
1319
|
+
return true;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
return Boolean(language) && template.language === language;
|
|
1323
|
+
},
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
export function getServiceAlertTemplateById(
|
|
1328
|
+
id: string,
|
|
1329
|
+
): ServiceAlertTemplate | undefined {
|
|
1330
|
+
return ALL_SERVICE_ALERT_TEMPLATES.find((template: ServiceAlertTemplate) => {
|
|
1331
|
+
return template.id === id;
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
/*
|
|
1336
|
+
* The languages this module ships runtime templates for.
|
|
1337
|
+
*
|
|
1338
|
+
* Derived from the templates rather than hand-listed, so it cannot drift: a
|
|
1339
|
+
* new runtime template makes its language appear here automatically, and the
|
|
1340
|
+
* tests assert the two agree.
|
|
1341
|
+
*/
|
|
1342
|
+
export function getLanguagesWithServiceAlertTemplates(): Array<ServiceLanguage> {
|
|
1343
|
+
const languages: Array<ServiceLanguage> = [];
|
|
1344
|
+
|
|
1345
|
+
for (const template of ALL_SERVICE_ALERT_TEMPLATES) {
|
|
1346
|
+
if (template.language && !languages.includes(template.language)) {
|
|
1347
|
+
languages.push(template.language);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
return languages;
|
|
1352
|
+
}
|