@oneuptime/common 14.0.5 → 14.0.7
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/InventoryItem.ts +23 -12
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.ts +217 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/AlertService.ts +32 -36
- package/Server/Services/IncidentAlertService.ts +609 -2
- package/Server/Services/IncidentService.ts +134 -44
- package/Server/Services/InventoryItemService.ts +38 -0
- package/Server/Services/IoTDeviceCredentialService.ts +13 -4
- package/Server/Services/MetricService.ts +1 -1
- package/Server/Services/ScheduledMaintenanceService.ts +50 -46
- package/Server/Services/UserNotificationRuleService.ts +55 -53
- package/Server/Services/WorkspaceNotificationSummaryService.ts +38 -20
- package/Server/Types/Database/QueryHelper.ts +54 -1
- package/Server/Utils/AI/Toolbox/MetricTools.ts +13 -2
- package/Server/Utils/AI/Toolbox/ResourceTools.ts +12 -1
- package/Server/Utils/AffectedResources/LinkedAffectedResources.ts +576 -0
- package/Server/Utils/Alert/AlertStateChangeAuthorization.ts +129 -0
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +1 -1
- package/Server/Utils/Inventory/InventoryOverviewAggregation.ts +111 -0
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +33 -0
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +4 -3
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +3 -1
- package/Server/Utils/Response.ts +31 -0
- package/Server/Utils/Topology/TopologyConcurrencyLimiter.ts +229 -0
- package/Server/Utils/Topology/TopologyQueries.ts +1350 -0
- package/Server/Utils/Topology/TopologyRequest.ts +463 -0
- package/Server/Utils/Topology/TopologyResponseCache.ts +183 -0
- package/Server/Utils/Topology/TopologySql.ts +1024 -0
- package/Tests/App/Dashboard/AcknowledgeAlertsOnDeclare.test.ts +369 -0
- package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +1 -1
- package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +510 -123
- package/Tests/App/Dashboard/AlertsTableAffectedResources.test.tsx +490 -0
- package/Tests/App/Dashboard/ChangeAlertStateDeclareIncident.test.tsx +886 -0
- package/Tests/App/Dashboard/CreateReviewStepRoleSummary.test.tsx +1250 -0
- package/Tests/App/Dashboard/CreateReviewStepSelectionSummary.test.tsx +738 -0
- package/Tests/App/Dashboard/DeclareIncidentFromAlert.test.ts +417 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +1948 -229
- package/Tests/App/Dashboard/EntityDetailPanelLifecycle.test.tsx +493 -0
- package/Tests/App/Dashboard/EntityDetailPanelNavigation.test.tsx +439 -0
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +292 -1
- package/Tests/App/Dashboard/EventStatusPanel.test.tsx +798 -2
- package/Tests/App/Dashboard/FetchStateAndSeverityElements.test.tsx +669 -0
- package/Tests/App/Dashboard/FetchUsers.test.tsx +230 -0
- package/Tests/App/Dashboard/IncidentCreateAcknowledgeAlerts.test.tsx +1611 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +1365 -57
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +964 -23
- package/Tests/App/Dashboard/InventoryRelationships.test.tsx +885 -0
- package/Tests/App/Dashboard/NetworkTopologyExplorerSearch.test.tsx +1267 -0
- package/Tests/App/Dashboard/ResourceOverviewTileTooltips.test.tsx +4 -1
- package/Tests/App/Dashboard/RumOverviewPage.test.tsx +127 -0
- package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +201 -15
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +508 -36
- package/Tests/App/Dashboard/SiteSearchBox.test.tsx +2110 -0
- package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +168 -8
- package/Tests/App/Dashboard/TopologyDataLifecycle.test.tsx +583 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +982 -218
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +818 -114
- package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +90 -21
- package/Tests/Server/Services/DomainService.test.ts +525 -0
- package/Tests/Server/Services/IncidentAlertAcknowledgeOnDeclare.test.ts +2630 -0
- package/Tests/Server/Services/IncidentAlertPostgres.test.ts +1625 -1
- package/Tests/Server/Services/IncidentDeclareAcknowledgeAlerts.test.ts +2189 -0
- package/Tests/Server/Services/InventoryItemServiceOverviewCounts.test.ts +345 -0
- package/Tests/Server/Services/InventoryOverviewPostgres.test.ts +254 -0
- package/Tests/Server/Services/IoTDeviceCredentialService.test.ts +819 -0
- package/Tests/Server/Services/LinkedAffectedResourcesCreatedFeed.test.ts +420 -0
- package/Tests/Server/Services/MetricServiceAggregate.test.ts +12 -0
- package/Tests/Server/Services/SavedViewDefaults.test.ts +325 -0
- package/Tests/Server/Services/SloAffectedResourceCreatedFeed.test.ts +92 -36
- package/Tests/Server/Services/StatusPageCertificateService.test.ts +169 -0
- package/Tests/Server/Services/TeamComplianceSettingService.test.ts +191 -0
- package/Tests/Server/Types/Database/QueryHelperSearchAllWords.test.ts +651 -0
- package/Tests/Server/Utils/AI/AlertWriteTools.test.ts +256 -0
- package/Tests/Server/Utils/AI/IncidentWriteTools.test.ts +482 -0
- package/Tests/Server/Utils/AffectedResources/LinkedAffectedResources.test.ts +858 -0
- package/Tests/Server/Utils/Alert/AlertStateChangeAuthorization.test.ts +1172 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +54 -0
- package/Tests/Server/Utils/Monitor/MetricDecimalThreshold.test.ts +417 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorAffectedResourceEdgeCases.test.ts +96 -0
- package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigration.test.ts +386 -0
- package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigrationPostgres.test.ts +430 -0
- package/Tests/Server/Utils/Topology/TopologyActivityParityPostgres.test.ts +279 -0
- package/Tests/Server/Utils/Topology/TopologyConcurrencyLimiter.test.ts +534 -0
- package/Tests/Server/Utils/Topology/TopologyEstateGenerator.ts +624 -0
- package/Tests/Server/Utils/Topology/TopologyJsonResponse.test.ts +90 -0
- package/Tests/Server/Utils/Topology/TopologyQueries.test.ts +1634 -0
- package/Tests/Server/Utils/Topology/TopologyQueriesDifferentialPostgres.test.ts +683 -0
- package/Tests/Server/Utils/Topology/TopologyQueriesPostgres.test.ts +2624 -0
- package/Tests/Server/Utils/Topology/TopologyReference.ts +1018 -0
- package/Tests/Server/Utils/Topology/TopologyRequest.test.ts +643 -0
- package/Tests/Server/Utils/Topology/TopologyResponseCache.test.ts +305 -0
- package/Tests/Server/Utils/Topology/TopologySql.test.ts +973 -0
- package/Tests/Types/BaseDatabase/AggregationType.test.ts +5 -1
- package/Tests/Types/Dashboard/KubernetesDashboardTemplate.test.ts +184 -0
- package/Tests/Types/Dashboard/KubernetesDashboardTemplates.test.ts +107 -0
- package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +8 -14
- package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +4 -3
- package/Tests/Types/Monitor/Utils/KubernetesAgentEmittedMetrics.ts +27 -6
- package/Tests/Types/Topology/TopologyTypeRules.test.ts +176 -0
- package/Tests/UI/Rum/ChunkLoader.test.ts +76 -0
- package/Tests/UI/Rum/ReplayRailDetail.test.tsx +53 -0
- package/Tests/UI/Rum/ReplaySignals.test.ts +54 -0
- package/Tests/Utils/Dashboard/KubernetesClusterCpuTileRepair.test.ts +370 -0
- package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +302 -0
- package/Tests/Utils/Rules/MonitorTypeRuleCriteria.test.ts +181 -0
- package/Types/BaseDatabase/AggregationType.ts +7 -0
- package/Types/Dashboard/DashboardTemplates.ts +20 -7
- package/Types/Incident/IncidentAlertLink.ts +19 -0
- package/Types/Rum/SessionReplayCustomEvents.ts +29 -3
- package/Types/Rum/WebVitals.ts +13 -0
- package/Types/Telemetry/InventoryLiveness.ts +26 -0
- package/Types/Topology/TopologyApi.ts +428 -0
- package/Types/Topology/TopologyTypeRules.ts +191 -0
- package/UI/Components/Input/Input.tsx +11 -0
- package/Utils/Dashboard/KubernetesClusterCpuTileRepair.ts +147 -0
- package/Utils/Incident/IncidentFromAlerts.ts +78 -0
- package/build/dist/Models/DatabaseModels/InventoryItem.js +20 -11
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js +165 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +21 -33
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentAlertService.js +457 -3
- package/build/dist/Server/Services/IncidentAlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +96 -42
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +31 -0
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceCredentialService.js +12 -5
- package/build/dist/Server/Services/IoTDeviceCredentialService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +37 -40
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +55 -51
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +42 -25
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +47 -1
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js +13 -2
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +12 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
- package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js +405 -0
- package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js.map +1 -0
- package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js +112 -0
- package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js.map +1 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +1 -1
- package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js +81 -0
- package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +31 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Response.js +30 -0
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js +178 -0
- package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyQueries.js +884 -0
- package/build/dist/Server/Utils/Topology/TopologyQueries.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyRequest.js +275 -0
- package/build/dist/Server/Utils/Topology/TopologyRequest.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyResponseCache.js +146 -0
- package/build/dist/Server/Utils/Topology/TopologyResponseCache.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologySql.js +688 -0
- package/build/dist/Server/Utils/Topology/TopologySql.js.map +1 -0
- package/build/dist/Types/BaseDatabase/AggregationType.js +7 -0
- package/build/dist/Types/BaseDatabase/AggregationType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js +20 -7
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Incident/IncidentAlertLink.js +17 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayCustomEvents.js.map +1 -1
- package/build/dist/Types/Rum/WebVitals.js +12 -0
- package/build/dist/Types/Rum/WebVitals.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +23 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -1
- package/build/dist/Types/Topology/TopologyApi.js +55 -0
- package/build/dist/Types/Topology/TopologyApi.js.map +1 -0
- package/build/dist/Types/Topology/TopologyTypeRules.js +165 -0
- package/build/dist/Types/Topology/TopologyTypeRules.js.map +1 -0
- package/build/dist/UI/Components/Input/Input.js +1 -1
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/Utils/Dashboard/KubernetesClusterCpuTileRepair.js +110 -0
- package/build/dist/Utils/Dashboard/KubernetesClusterCpuTileRepair.js.map +1 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js +38 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -1
- package/package.json +1 -1
|
@@ -1938,7 +1938,7 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> {
|
|
|
1938
1938
|
* EXAMPLE:
|
|
1939
1939
|
* SELECT sum(Metric.value) as avg_value, date_trunc('hour', toStartOfInterval(createdAt, INTERVAL 1 hour)) as createdAt
|
|
1940
1940
|
*
|
|
1941
|
-
* Percentile aggregations (P50/P90/P95/P99) compile to ClickHouse's
|
|
1941
|
+
* Percentile aggregations (P50/P75/P90/P95/P99) compile to ClickHouse's
|
|
1942
1942
|
* `quantile(level)(column)`. This is the right thing for scalar
|
|
1943
1943
|
* columns (Span.duration, Metric.value when the metric is a Sum or
|
|
1944
1944
|
* Gauge, etc.). MetricService overrides this method when it has
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AggregateColumn,
|
|
3
|
+
AggregateRow,
|
|
4
|
+
} from "../../Types/Database/AggregateBy";
|
|
5
|
+
import AggregateResultUtil from "../../Types/Database/AggregateResultUtil";
|
|
6
|
+
import Dictionary from "../../../Types/Dictionary";
|
|
7
|
+
import EntitySource from "../../../Types/Telemetry/EntitySource";
|
|
8
|
+
import {
|
|
9
|
+
InventoryLiveness,
|
|
10
|
+
getInventoryLivenessSql,
|
|
11
|
+
} from "../../../Types/Telemetry/InventoryLiveness";
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* The Inventory Overview's numbers: the five summary tiles and the per-type
|
|
15
|
+
* breakdown.
|
|
16
|
+
*
|
|
17
|
+
* These used to be folded in the browser from one list read capped at
|
|
18
|
+
* LIMIT_PER_PROJECT, so a project past ten thousand items saw tiles and a
|
|
19
|
+
* breakdown describing whichever ten thousand had been seen most recently,
|
|
20
|
+
* with nothing on the page saying so. They are counted in Postgres instead,
|
|
21
|
+
* in ONE grouped statement: the tiles are sums over the same per-type groups
|
|
22
|
+
* the breakdown lists, so the two are views of one snapshot and cannot
|
|
23
|
+
* disagree.
|
|
24
|
+
*
|
|
25
|
+
* Every tile drills into the Items list, and each filter below is the exact
|
|
26
|
+
* narrowing that tile's list applies — the source facet, or the source facet
|
|
27
|
+
* plus the Stale status facet, whose SQL is the same getInventoryLivenessSql
|
|
28
|
+
* the `inventoryStatus` virtual column is built from.
|
|
29
|
+
*
|
|
30
|
+
* Kept apart from InventoryItemService so the dashboard's tile tests and the
|
|
31
|
+
* Postgres test can use the same columns without loading the service.
|
|
32
|
+
*/
|
|
33
|
+
export const INVENTORY_OVERVIEW_GROUP_BY: Array<AggregateColumn> = [
|
|
34
|
+
{ expression: `"InventoryItem"."entityType"`, alias: "entityType" },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
export const INVENTORY_OVERVIEW_SELECT: Array<AggregateColumn> = [
|
|
38
|
+
{ expression: `COUNT(*)`, alias: "itemCount" },
|
|
39
|
+
{
|
|
40
|
+
expression: `COUNT(*) FILTER (WHERE "InventoryItem"."source" = '${EntitySource.Discovered}')`,
|
|
41
|
+
alias: "discoveredCount",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
expression: `COUNT(*) FILTER (WHERE "InventoryItem"."source" = '${EntitySource.Inventory}')`,
|
|
45
|
+
alias: "mirroredCount",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
expression: `COUNT(*) FILTER (WHERE "InventoryItem"."source" = '${EntitySource.Manual}')`,
|
|
49
|
+
alias: "manualCount",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
// Only discovered rows can be stale; the shared rule says NotTracked for the rest.
|
|
53
|
+
expression: `COUNT(*) FILTER (WHERE (${getInventoryLivenessSql(`"InventoryItem"`)}) = '${InventoryLiveness.Stale}')`,
|
|
54
|
+
alias: "staleCount",
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
export interface InventoryOverviewCounts {
|
|
59
|
+
total: number;
|
|
60
|
+
discovered: number;
|
|
61
|
+
mirrored: number;
|
|
62
|
+
manual: number;
|
|
63
|
+
stale: number;
|
|
64
|
+
// Items per entity type, for the category breakdown. Types that counted zero are absent.
|
|
65
|
+
countsByType: Dictionary<number>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Sum the per-type groups into the tile counts, and key the groups by type
|
|
70
|
+
* for the breakdown.
|
|
71
|
+
*
|
|
72
|
+
* `total` counts every row, including rows whose source this build does not
|
|
73
|
+
* recognise — they land in none of the three source buckets, which is why
|
|
74
|
+
* those are not expected to sum to the total. A group with no type still
|
|
75
|
+
* counts towards every tile; it is only left out of the breakdown, which has
|
|
76
|
+
* no row to put it in.
|
|
77
|
+
*/
|
|
78
|
+
export function readInventoryOverviewGroups(
|
|
79
|
+
rows: Array<AggregateRow>,
|
|
80
|
+
): InventoryOverviewCounts {
|
|
81
|
+
const counts: InventoryOverviewCounts = {
|
|
82
|
+
total: 0,
|
|
83
|
+
discovered: 0,
|
|
84
|
+
mirrored: 0,
|
|
85
|
+
manual: 0,
|
|
86
|
+
stale: 0,
|
|
87
|
+
countsByType: {},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
for (const row of rows) {
|
|
91
|
+
const itemCount: number = AggregateResultUtil.toNumber(row, "itemCount");
|
|
92
|
+
|
|
93
|
+
counts.total += itemCount;
|
|
94
|
+
counts.discovered += AggregateResultUtil.toNumber(row, "discoveredCount");
|
|
95
|
+
counts.mirrored += AggregateResultUtil.toNumber(row, "mirroredCount");
|
|
96
|
+
counts.manual += AggregateResultUtil.toNumber(row, "manualCount");
|
|
97
|
+
counts.stale += AggregateResultUtil.toNumber(row, "staleCount");
|
|
98
|
+
|
|
99
|
+
const entityType: string | null = AggregateResultUtil.toStringOrNull(
|
|
100
|
+
row,
|
|
101
|
+
"entityType",
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
if (entityType && itemCount > 0) {
|
|
105
|
+
counts.countsByType[entityType] =
|
|
106
|
+
(counts.countsByType[entityType] || 0) + itemCount;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return counts;
|
|
111
|
+
}
|
|
@@ -280,6 +280,39 @@ export default class CompareCriteria {
|
|
|
280
280
|
return threshold as number;
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
/*
|
|
284
|
+
* A metric-value threshold as a number, fraction included.
|
|
285
|
+
*
|
|
286
|
+
* The criteria form saves what the user typed as a STRING, and
|
|
287
|
+
* convertToNumber's parseInt truncated it: "0.9" on a ratio metric was
|
|
288
|
+
* compared as 0 and fired on every non-zero sample, and "85.5" was
|
|
289
|
+
* compared as 85. Metric values are where a fractional threshold is the
|
|
290
|
+
* natural one to type (ratios, seconds, load averages), so they are
|
|
291
|
+
* parsed with parseFloat. Every other checkOn keeps convertToNumber.
|
|
292
|
+
*
|
|
293
|
+
* Anything that does not parse to a finite number is treated as missing,
|
|
294
|
+
* so the caller ignores the filter instead of comparing against NaN.
|
|
295
|
+
*/
|
|
296
|
+
@CaptureSpan()
|
|
297
|
+
public static convertMetricThresholdToNumber(
|
|
298
|
+
threshold: string | number | undefined,
|
|
299
|
+
): number | null {
|
|
300
|
+
if (threshold === undefined || threshold === null) {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const parsed: number =
|
|
305
|
+
typeof threshold === Typeof.String
|
|
306
|
+
? parseFloat(threshold as string)
|
|
307
|
+
: (threshold as number);
|
|
308
|
+
|
|
309
|
+
if (typeof parsed !== Typeof.Number || !Number.isFinite(parsed)) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return parsed;
|
|
314
|
+
}
|
|
315
|
+
|
|
283
316
|
@CaptureSpan()
|
|
284
317
|
public static checkEqualToOrNotEqualTo(data: {
|
|
285
318
|
value: string | number;
|
|
@@ -180,9 +180,10 @@ export default class MetricMonitorCriteria {
|
|
|
180
180
|
projectId: { toString(): string } | undefined;
|
|
181
181
|
nativeUnitsByMetricName?: { [key: string]: string } | undefined;
|
|
182
182
|
}): Promise<MetricSeriesEvaluationResult> {
|
|
183
|
-
const rawThreshold: number | null =
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
const rawThreshold: number | null =
|
|
184
|
+
CompareCriteria.convertMetricThresholdToNumber(
|
|
185
|
+
input.criteriaFilter.value,
|
|
186
|
+
);
|
|
186
187
|
|
|
187
188
|
const metricAlias: string =
|
|
188
189
|
input.criteriaFilter.metricMonitorOptions?.metricAlias || "";
|
|
@@ -4659,7 +4659,9 @@ ${contextBlock}
|
|
|
4659
4659
|
.map((f: CriteriaFilter) => {
|
|
4660
4660
|
return {
|
|
4661
4661
|
filter: f,
|
|
4662
|
-
threshold: CompareCriteria.
|
|
4662
|
+
threshold: CompareCriteria.convertMetricThresholdToNumber(
|
|
4663
|
+
f.value,
|
|
4664
|
+
) as number,
|
|
4663
4665
|
};
|
|
4664
4666
|
})
|
|
4665
4667
|
.filter((f: { filter: CriteriaFilter; threshold: number | null }) => {
|
package/Server/Utils/Response.ts
CHANGED
|
@@ -451,6 +451,37 @@ export default class Response {
|
|
|
451
451
|
.send(item);
|
|
452
452
|
}
|
|
453
453
|
|
|
454
|
+
/*
|
|
455
|
+
* Send a body that is ALREADY serialized JSON, as is.
|
|
456
|
+
*
|
|
457
|
+
* For responses built once and handed out many times (the Topology maps
|
|
458
|
+
* cache their serialized payload): sendJsonObjectResponse would parse
|
|
459
|
+
* nothing but re-stringify a multi-megabyte object on every hit, and
|
|
460
|
+
* res.send would hash the whole body for an ETag nobody can use on a POST.
|
|
461
|
+
* writeHead + end skips both, the way sendHtmlResponse does.
|
|
462
|
+
*
|
|
463
|
+
* no-store: the body is one project's inventory, read under the caller's
|
|
464
|
+
* permissions. The audit log gets its size, never the payload.
|
|
465
|
+
*/
|
|
466
|
+
@CaptureSpan()
|
|
467
|
+
public static sendJsonStringResponse(
|
|
468
|
+
_req: ExpressRequest,
|
|
469
|
+
res: ExpressResponse,
|
|
470
|
+
json: string,
|
|
471
|
+
): void {
|
|
472
|
+
const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse;
|
|
473
|
+
|
|
474
|
+
Response.setNoCacheHeaders(oneUptimeResponse);
|
|
475
|
+
oneUptimeResponse.logBody = {
|
|
476
|
+
contentType: "application/json",
|
|
477
|
+
characters: json.length,
|
|
478
|
+
};
|
|
479
|
+
oneUptimeResponse.writeHead(200, {
|
|
480
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
481
|
+
});
|
|
482
|
+
oneUptimeResponse.end(json);
|
|
483
|
+
}
|
|
484
|
+
|
|
454
485
|
@CaptureSpan()
|
|
455
486
|
public static sendTextResponse(
|
|
456
487
|
_req: ExpressRequest,
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import TooManyRequestsException from "../../../Types/Exception/TooManyRequestsException";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Bounds the Topology API's database work in one API process.
|
|
5
|
+
*
|
|
6
|
+
* Every Topology request reads in its own snapshot on one pooled connection
|
|
7
|
+
* (TopologyQueries.inReadOnlySnapshot), and a map build is among the heaviest
|
|
8
|
+
* reads in the app — a whole-inventory pass that can take seconds on a large
|
|
9
|
+
* project. The response cache shares a build between viewers of the same
|
|
10
|
+
* (project, minute), but callers choose the minute and the drawer and
|
|
11
|
+
* collection reads are not cached at all, so nothing else stops a burst of
|
|
12
|
+
* them from taking the connection pool (shared with ingest, alerting and
|
|
13
|
+
* every other request on the process) and the database CPU with it.
|
|
14
|
+
*
|
|
15
|
+
* So at most `maxRunning` of them run at once per process, and at most
|
|
16
|
+
* `maxRunningPerProject` for any one project, so one busy project cannot
|
|
17
|
+
* occupy every slot. The rest wait in arrival order WITHOUT holding a
|
|
18
|
+
* connection: a released slot goes to the first waiter whose project is
|
|
19
|
+
* under its own limit, so a project at its limit never holds up another
|
|
20
|
+
* project queued behind it. The queue is bounded too — per process and per
|
|
21
|
+
* project — and a request that finds it full, or waits longer than
|
|
22
|
+
* `maxWaitMs`, is answered 429 (TooManyRequestsException) at once: the
|
|
23
|
+
* Dashboard shows "busy, try again" rather than a request that hangs.
|
|
24
|
+
*
|
|
25
|
+
* A request whose client has gone (the drawer aborts the previous entity's
|
|
26
|
+
* load on every click) gives up its queue place at once and never starts:
|
|
27
|
+
* otherwise one person clicking through the drawer would queue dead reads
|
|
28
|
+
* ahead of the one they are waiting for and fill their project's queue.
|
|
29
|
+
*
|
|
30
|
+
* Per process, like the response cache; there is no cross-process
|
|
31
|
+
* coordination, so the cluster-wide bound is this times the API replicas.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
export const TOPOLOGY_BUSY_MESSAGE: string =
|
|
35
|
+
"The topology service is busy. Try again in a moment.";
|
|
36
|
+
|
|
37
|
+
/* Sent as Retry-After with a 429 from the Topology API. */
|
|
38
|
+
export const TOPOLOGY_BUSY_RETRY_AFTER_SECONDS: number = 5;
|
|
39
|
+
|
|
40
|
+
export interface TopologyConcurrencyLimits {
|
|
41
|
+
/* Topology reads (one snapshot each) running at once in this process. */
|
|
42
|
+
maxRunning: number;
|
|
43
|
+
/* Of those, at most this many for one project. */
|
|
44
|
+
maxRunningPerProject: number;
|
|
45
|
+
/* Requests waiting for a slot in this process; one more is a 429. */
|
|
46
|
+
maxWaiting: number;
|
|
47
|
+
/* Of those, at most this many for one project, so it cannot fill the queue. */
|
|
48
|
+
maxWaitingPerProject: number;
|
|
49
|
+
/* Longest a request waits for a slot before it is answered 429. */
|
|
50
|
+
maxWaitMs: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const TOPOLOGY_CONCURRENCY_LIMITS: TopologyConcurrencyLimits = {
|
|
54
|
+
maxRunning: 4,
|
|
55
|
+
maxRunningPerProject: 2,
|
|
56
|
+
maxWaiting: 64,
|
|
57
|
+
maxWaitingPerProject: 16,
|
|
58
|
+
maxWaitMs: 30_000,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/* The caller went away before its read started; nobody is waiting for it. */
|
|
62
|
+
export class TopologyRequestAbandonedError extends Error {
|
|
63
|
+
public constructor() {
|
|
64
|
+
super("The Topology request was abandoned by its client.");
|
|
65
|
+
this.name = "TopologyRequestAbandonedError";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface Waiter {
|
|
70
|
+
projectId: string;
|
|
71
|
+
start: () => void;
|
|
72
|
+
timer: ReturnType<typeof setTimeout> | null;
|
|
73
|
+
detach: () => void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class TopologyConcurrencyLimiter {
|
|
77
|
+
private running: number = 0;
|
|
78
|
+
private runningByProject: Map<string, number> = new Map<string, number>();
|
|
79
|
+
/*
|
|
80
|
+
* Arrival order. Invariant between calls: no waiter here could start (the
|
|
81
|
+
* process is full, or its project is), so a new request that can start
|
|
82
|
+
* never overtakes one that could have.
|
|
83
|
+
*/
|
|
84
|
+
private waiting: Array<Waiter> = [];
|
|
85
|
+
|
|
86
|
+
public constructor(
|
|
87
|
+
private limits: TopologyConcurrencyLimits = TOPOLOGY_CONCURRENCY_LIMITS,
|
|
88
|
+
) {}
|
|
89
|
+
|
|
90
|
+
/*
|
|
91
|
+
* Runs `work` once a slot for `projectId` is free, and frees it when the
|
|
92
|
+
* work settles, however it settles. Throws TooManyRequestsException
|
|
93
|
+
* without running `work` when the queue is full or the wait too long, and
|
|
94
|
+
* TopologyRequestAbandonedError without running it when `signal` fires
|
|
95
|
+
* first (a read that has started runs to completion).
|
|
96
|
+
*/
|
|
97
|
+
public async run<T>(
|
|
98
|
+
projectId: string,
|
|
99
|
+
work: () => Promise<T>,
|
|
100
|
+
signal?: AbortSignal | undefined,
|
|
101
|
+
): Promise<T> {
|
|
102
|
+
await this.acquire(projectId, signal);
|
|
103
|
+
try {
|
|
104
|
+
if (signal?.aborted) {
|
|
105
|
+
throw new TopologyRequestAbandonedError();
|
|
106
|
+
}
|
|
107
|
+
return await work();
|
|
108
|
+
} finally {
|
|
109
|
+
this.release(projectId);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public runningCount(projectId?: string): number {
|
|
114
|
+
if (projectId === undefined) {
|
|
115
|
+
return this.running;
|
|
116
|
+
}
|
|
117
|
+
return this.runningByProject.get(projectId) || 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public waitingCount(projectId?: string): number {
|
|
121
|
+
if (projectId === undefined) {
|
|
122
|
+
return this.waiting.length;
|
|
123
|
+
}
|
|
124
|
+
return this.waiting.filter((waiter: Waiter): boolean => {
|
|
125
|
+
return waiter.projectId === projectId;
|
|
126
|
+
}).length;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private canStart(projectId: string): boolean {
|
|
130
|
+
return (
|
|
131
|
+
this.running < this.limits.maxRunning &&
|
|
132
|
+
this.runningCount(projectId) < this.limits.maxRunningPerProject
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private take(projectId: string): void {
|
|
137
|
+
this.running++;
|
|
138
|
+
this.runningByProject.set(projectId, this.runningCount(projectId) + 1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private acquire(
|
|
142
|
+
projectId: string,
|
|
143
|
+
signal: AbortSignal | undefined,
|
|
144
|
+
): Promise<void> {
|
|
145
|
+
if (signal?.aborted) {
|
|
146
|
+
return Promise.reject(new TopologyRequestAbandonedError());
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (this.canStart(projectId)) {
|
|
150
|
+
this.take(projectId);
|
|
151
|
+
return Promise.resolve();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (
|
|
155
|
+
this.waiting.length >= this.limits.maxWaiting ||
|
|
156
|
+
this.waitingCount(projectId) >= this.limits.maxWaitingPerProject
|
|
157
|
+
) {
|
|
158
|
+
return Promise.reject(
|
|
159
|
+
new TooManyRequestsException(TOPOLOGY_BUSY_MESSAGE),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return new Promise<void>(
|
|
164
|
+
(resolve: () => void, reject: (error: Error) => void): void => {
|
|
165
|
+
/* Leaves the queue (if still in it) and rejects with `error`. */
|
|
166
|
+
const leave: (error: Error) => void = (error: Error): void => {
|
|
167
|
+
const index: number = this.waiting.indexOf(waiter);
|
|
168
|
+
if (index >= 0) {
|
|
169
|
+
this.waiting.splice(index, 1);
|
|
170
|
+
waiter.detach();
|
|
171
|
+
reject(error);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const onAbort: () => void = (): void => {
|
|
175
|
+
leave(new TopologyRequestAbandonedError());
|
|
176
|
+
};
|
|
177
|
+
const waiter: Waiter = {
|
|
178
|
+
projectId,
|
|
179
|
+
start: resolve,
|
|
180
|
+
timer: null,
|
|
181
|
+
detach: (): void => {
|
|
182
|
+
if (waiter.timer) {
|
|
183
|
+
clearTimeout(waiter.timer);
|
|
184
|
+
}
|
|
185
|
+
signal?.removeEventListener("abort", onAbort);
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
if (Number.isFinite(this.limits.maxWaitMs)) {
|
|
189
|
+
waiter.timer = setTimeout((): void => {
|
|
190
|
+
leave(new TooManyRequestsException(TOPOLOGY_BUSY_MESSAGE));
|
|
191
|
+
}, this.limits.maxWaitMs);
|
|
192
|
+
}
|
|
193
|
+
signal?.addEventListener("abort", onAbort);
|
|
194
|
+
this.waiting.push(waiter);
|
|
195
|
+
},
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private release(projectId: string): void {
|
|
200
|
+
this.running--;
|
|
201
|
+
const remaining: number = this.runningCount(projectId) - 1;
|
|
202
|
+
if (remaining > 0) {
|
|
203
|
+
this.runningByProject.set(projectId, remaining);
|
|
204
|
+
} else {
|
|
205
|
+
this.runningByProject.delete(projectId);
|
|
206
|
+
}
|
|
207
|
+
this.startWaiters();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* Hands free slots to waiters in arrival order, skipping full projects. */
|
|
211
|
+
private startWaiters(): void {
|
|
212
|
+
if (this.waiting.length === 0) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const stillWaiting: Array<Waiter> = [];
|
|
216
|
+
for (const waiter of this.waiting) {
|
|
217
|
+
if (this.canStart(waiter.projectId)) {
|
|
218
|
+
this.take(waiter.projectId);
|
|
219
|
+
waiter.detach();
|
|
220
|
+
waiter.start();
|
|
221
|
+
} else {
|
|
222
|
+
stillWaiting.push(waiter);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
this.waiting = stillWaiting;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export default new TopologyConcurrencyLimiter();
|