@oneuptime/common 10.0.70 → 10.0.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/Alert.ts +55 -0
- package/Models/DatabaseModels/Incident.ts +55 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +6 -4
- package/Models/DatabaseModels/Project.ts +5 -5
- package/Models/DatabaseModels/StatusPage.ts +80 -0
- package/Server/API/StatusPageAPI.ts +4 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1776881254913-DedupeKubernetesClustersAndAddUniqueIndex.ts +6 -3
- package/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1776940714709-MigrationName.ts +41 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1776971364783-AddStatusPageLanguageSettings.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/AIBillingService.ts +2 -2
- package/Server/Services/AnalyticsDatabaseService.ts +17 -7
- package/Server/Services/BillingService.ts +116 -48
- package/Server/Services/NotificationService.ts +2 -2
- package/Server/Types/Database/QueryUtil.ts +13 -7
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +175 -29
- package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +71 -0
- package/Server/Utils/Monitor/MonitorAlert.ts +170 -7
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +171 -2
- package/Server/Utils/Monitor/MonitorIncident.ts +212 -8
- package/Server/Utils/Monitor/MonitorMetricUtil.ts +423 -1
- package/Server/Utils/Monitor/MonitorResource.ts +2 -0
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +99 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +268 -0
- package/Types/BaseDatabase/IncludesNone.ts +1 -4
- package/Types/Email.ts +50 -0
- package/Types/Infrastructure/BasicMetrics.ts +75 -0
- package/Types/Metrics/MetricQueryData.ts +11 -0
- package/Types/Monitor/CriteriaFilter.ts +10 -0
- package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +11 -0
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +10 -0
- package/Types/Monitor/MetricMonitor/MetricSeriesResult.ts +20 -0
- package/Types/Monitor/MonitorMetricType.ts +34 -0
- package/Types/Monitor/ServerMonitor/ServerMonitorResponse.ts +8 -0
- package/Types/Probe/ProbeApiIngestResponse.ts +25 -0
- package/Types/StatusPage/StatusPageLanguage.ts +29 -0
- package/UI/Components/Charts/Area/AreaChart.tsx +17 -12
- package/UI/Components/Charts/Bar/BarChart.tsx +16 -11
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +23 -0
- package/UI/Components/Charts/Line/LineChart.tsx +16 -11
- package/UI/Components/Filters/DateFilter.tsx +16 -8
- package/UI/Components/Filters/EntityFilter.tsx +33 -18
- package/UI/Components/Filters/FilterViewer.tsx +7 -5
- package/UI/Components/Filters/FiltersForm.tsx +27 -5
- package/UI/Components/Filters/NumberFilter.tsx +3 -2
- package/UI/Components/Filters/TextFilter.tsx +5 -4
- package/UI/Components/ModelTable/BaseModelTable.tsx +5 -3
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +453 -0
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.tsx +229 -0
- package/Utils/Metrics/MetricSeriesFingerprint.ts +97 -0
- package/Utils/Monitor/MonitorMetricType.ts +309 -19
- package/build/dist/Models/DatabaseModels/Alert.js +57 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +57 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +6 -4
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +5 -5
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +82 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +4 -0
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776881254913-DedupeKubernetesClustersAndAddUniqueIndex.js +4 -2
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776881254913-DedupeKubernetesClustersAndAddUniqueIndex.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776940714709-MigrationName.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776940714709-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776971364783-AddStatusPageLanguageSettings.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776971364783-AddStatusPageLanguageSettings.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AIBillingService.js +2 -2
- package/build/dist/Server/Services/AIBillingService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +14 -4
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +99 -39
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/NotificationService.js +2 -2
- package/build/dist/Server/Services/NotificationService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +13 -7
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +132 -30
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +58 -7
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +134 -12
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +112 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +159 -15
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +373 -0
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +2 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +65 -0
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.js +199 -0
- package/build/dist/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.js.map +1 -1
- package/build/dist/Types/BaseDatabase/IncludesNone.js.map +1 -1
- package/build/dist/Types/Email.js +42 -0
- package/build/dist/Types/Email.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +10 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/MetricMonitor/MetricSeriesResult.js +2 -0
- package/build/dist/Types/Monitor/MetricMonitor/MetricSeriesResult.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js +28 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Types/StatusPage/StatusPageLanguage.js +21 -0
- package/build/dist/Types/StatusPage/StatusPageLanguage.js.map +1 -0
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +13 -12
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +12 -11
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +11 -3
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +12 -11
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Filters/DateFilter.js +1 -4
- package/build/dist/UI/Components/Filters/DateFilter.js.map +1 -1
- package/build/dist/UI/Components/Filters/EntityFilter.js +21 -14
- package/build/dist/UI/Components/Filters/EntityFilter.js.map +1 -1
- package/build/dist/UI/Components/Filters/FilterViewer.js +1 -2
- package/build/dist/UI/Components/Filters/FilterViewer.js.map +1 -1
- package/build/dist/UI/Components/Filters/FiltersForm.js +7 -3
- package/build/dist/UI/Components/Filters/FiltersForm.js.map +1 -1
- package/build/dist/UI/Components/Filters/NumberFilter.js +0 -1
- package/build/dist/UI/Components/Filters/NumberFilter.js.map +1 -1
- package/build/dist/UI/Components/Filters/TextFilter.js +5 -4
- package/build/dist/UI/Components/Filters/TextFilter.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +5 -3
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +383 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js +109 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js.map +1 -0
- package/build/dist/Utils/Metrics/MetricSeriesFingerprint.js +81 -0
- package/build/dist/Utils/Metrics/MetricSeriesFingerprint.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorMetricType.js +287 -19
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/package.json +1 -1
|
@@ -208,6 +208,77 @@ export default class ServerMonitorCriteria {
|
|
|
208
208
|
});
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
if (
|
|
212
|
+
!(input.dataToProcess as ServerMonitorResponse)
|
|
213
|
+
.onlyCheckRequestReceivedAt &&
|
|
214
|
+
(input.criteriaFilter.checkOn === CheckOn.LoadAverage1Min ||
|
|
215
|
+
input.criteriaFilter.checkOn === CheckOn.LoadAverage5Min ||
|
|
216
|
+
input.criteriaFilter.checkOn === CheckOn.LoadAverage15Min)
|
|
217
|
+
) {
|
|
218
|
+
threshold = CompareCriteria.convertToNumber(threshold);
|
|
219
|
+
|
|
220
|
+
const loadMetrics:
|
|
221
|
+
| { load1: number; load5: number; load15: number }
|
|
222
|
+
| undefined = (input.dataToProcess as ServerMonitorResponse)
|
|
223
|
+
.basicInfrastructureMetrics?.loadMetrics;
|
|
224
|
+
|
|
225
|
+
let currentLoad: number | undefined = undefined;
|
|
226
|
+
if (input.criteriaFilter.checkOn === CheckOn.LoadAverage1Min) {
|
|
227
|
+
currentLoad = loadMetrics?.load1;
|
|
228
|
+
} else if (input.criteriaFilter.checkOn === CheckOn.LoadAverage5Min) {
|
|
229
|
+
currentLoad = loadMetrics?.load5;
|
|
230
|
+
} else if (input.criteriaFilter.checkOn === CheckOn.LoadAverage15Min) {
|
|
231
|
+
currentLoad = loadMetrics?.load15;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const value: number | Array<number> =
|
|
235
|
+
(overTimeValue as Array<number>) || currentLoad || 0;
|
|
236
|
+
|
|
237
|
+
return CompareCriteria.compareCriteriaNumbers({
|
|
238
|
+
value: value,
|
|
239
|
+
threshold: threshold as number,
|
|
240
|
+
criteriaFilter: input.criteriaFilter,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (
|
|
245
|
+
input.criteriaFilter.checkOn === CheckOn.SwapUsagePercent &&
|
|
246
|
+
!(input.dataToProcess as ServerMonitorResponse).onlyCheckRequestReceivedAt
|
|
247
|
+
) {
|
|
248
|
+
threshold = CompareCriteria.convertToNumber(threshold);
|
|
249
|
+
|
|
250
|
+
const swapPercent: number | Array<number> =
|
|
251
|
+
(overTimeValue as Array<number>) ||
|
|
252
|
+
(input.dataToProcess as ServerMonitorResponse)
|
|
253
|
+
.basicInfrastructureMetrics?.memoryMetrics?.swapPercentUsed ||
|
|
254
|
+
0;
|
|
255
|
+
|
|
256
|
+
return CompareCriteria.compareCriteriaNumbers({
|
|
257
|
+
value: swapPercent,
|
|
258
|
+
threshold: threshold as number,
|
|
259
|
+
criteriaFilter: input.criteriaFilter,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (
|
|
264
|
+
input.criteriaFilter.checkOn === CheckOn.CPUIoWaitPercent &&
|
|
265
|
+
!(input.dataToProcess as ServerMonitorResponse).onlyCheckRequestReceivedAt
|
|
266
|
+
) {
|
|
267
|
+
threshold = CompareCriteria.convertToNumber(threshold);
|
|
268
|
+
|
|
269
|
+
const ioWaitPercent: number | Array<number> =
|
|
270
|
+
(overTimeValue as Array<number>) ||
|
|
271
|
+
(input.dataToProcess as ServerMonitorResponse)
|
|
272
|
+
.basicInfrastructureMetrics?.cpuMetrics?.timeIoWaitPercent ||
|
|
273
|
+
0;
|
|
274
|
+
|
|
275
|
+
return CompareCriteria.compareCriteriaNumbers({
|
|
276
|
+
value: ioWaitPercent,
|
|
277
|
+
threshold: threshold as number,
|
|
278
|
+
criteriaFilter: input.criteriaFilter,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
211
282
|
if (
|
|
212
283
|
input.criteriaFilter.checkOn === CheckOn.ServerProcessName &&
|
|
213
284
|
threshold &&
|
|
@@ -16,7 +16,9 @@ import { TelemetryQuery } from "../../../Types/Telemetry/TelemetryQuery";
|
|
|
16
16
|
import { DisableAutomaticAlertCreation } from "../../EnvironmentConfig";
|
|
17
17
|
import AlertService from "../../Services/AlertService";
|
|
18
18
|
import AlertSeverityService from "../../Services/AlertSeverityService";
|
|
19
|
+
import AlertStateService from "../../Services/AlertStateService";
|
|
19
20
|
import AlertStateTimelineService from "../../Services/AlertStateTimelineService";
|
|
21
|
+
import AlertState from "../../../Models/DatabaseModels/AlertState";
|
|
20
22
|
import logger, { LogAttributes } from "../Logger";
|
|
21
23
|
import CaptureSpan from "../Telemetry/CaptureSpan";
|
|
22
24
|
import DataToProcess from "./DataToProcess";
|
|
@@ -24,6 +26,7 @@ import MonitorTemplateUtil from "./MonitorTemplateUtil";
|
|
|
24
26
|
import { JSONObject } from "../../../Types/JSON";
|
|
25
27
|
import OneUptimeDate from "../../../Types/Date";
|
|
26
28
|
import MonitorEvaluationSummary from "../../../Types/Monitor/MonitorEvaluationSummary";
|
|
29
|
+
import { PerSeriesCriteriaMatch } from "../../../Types/Probe/ProbeApiIngestResponse";
|
|
27
30
|
|
|
28
31
|
export default class MonitorAlert {
|
|
29
32
|
@CaptureSpan()
|
|
@@ -34,6 +37,7 @@ export default class MonitorAlert {
|
|
|
34
37
|
criteriaInstance: MonitorCriteriaInstance | null;
|
|
35
38
|
dataToProcess: DataToProcess;
|
|
36
39
|
evaluationSummary?: MonitorEvaluationSummary | undefined;
|
|
40
|
+
breachingSeriesFingerprints?: Set<string> | undefined;
|
|
37
41
|
}): Promise<Array<Alert>> {
|
|
38
42
|
// check active alerts and if there are open alerts, do not cretae anothr alert.
|
|
39
43
|
const openAlerts: Array<Alert> = await AlertService.findBy({
|
|
@@ -47,10 +51,14 @@ export default class MonitorAlert {
|
|
|
47
51
|
limit: LIMIT_PER_PROJECT,
|
|
48
52
|
select: {
|
|
49
53
|
_id: true,
|
|
54
|
+
title: true,
|
|
50
55
|
createdCriteriaId: true,
|
|
51
56
|
projectId: true,
|
|
52
57
|
alertNumber: true,
|
|
53
58
|
alertNumberWithPrefix: true,
|
|
59
|
+
currentAlertStateId: true,
|
|
60
|
+
seriesFingerprint: true,
|
|
61
|
+
seriesLabels: true,
|
|
54
62
|
},
|
|
55
63
|
props: {
|
|
56
64
|
isRoot: true,
|
|
@@ -65,6 +73,7 @@ export default class MonitorAlert {
|
|
|
65
73
|
autoResolveCriteriaInstanceIdAlertIdsDictionary:
|
|
66
74
|
input.autoResolveCriteriaInstanceIdAlertIdsDictionary,
|
|
67
75
|
criteriaInstance: input.criteriaInstance,
|
|
76
|
+
breachingSeriesFingerprints: input.breachingSeriesFingerprints,
|
|
68
77
|
});
|
|
69
78
|
|
|
70
79
|
if (shouldClose) {
|
|
@@ -103,6 +112,7 @@ export default class MonitorAlert {
|
|
|
103
112
|
props: {
|
|
104
113
|
telemetryQuery?: TelemetryQuery | undefined;
|
|
105
114
|
};
|
|
115
|
+
matchesPerSeries?: Array<PerSeriesCriteriaMatch> | undefined;
|
|
106
116
|
}): Promise<void> {
|
|
107
117
|
const alertLogAttributes: LogAttributes = {
|
|
108
118
|
projectId: input.monitor.projectId?.toString(),
|
|
@@ -113,6 +123,16 @@ export default class MonitorAlert {
|
|
|
113
123
|
`${input.monitor.id?.toString()} - Check open alerts.`,
|
|
114
124
|
alertLogAttributes,
|
|
115
125
|
);
|
|
126
|
+
|
|
127
|
+
const breachingSeriesFingerprints: Set<string> | undefined =
|
|
128
|
+
input.matchesPerSeries
|
|
129
|
+
? new Set<string>(
|
|
130
|
+
input.matchesPerSeries.map((m: PerSeriesCriteriaMatch) => {
|
|
131
|
+
return m.fingerprint;
|
|
132
|
+
}),
|
|
133
|
+
)
|
|
134
|
+
: undefined;
|
|
135
|
+
|
|
116
136
|
// check active alerts and if there are open alerts, do not cretae anothr alert.
|
|
117
137
|
const openAlerts: Array<Alert> =
|
|
118
138
|
await this.checkOpenAlertsAndCloseIfResolved({
|
|
@@ -123,19 +143,31 @@ export default class MonitorAlert {
|
|
|
123
143
|
criteriaInstance: input.criteriaInstance,
|
|
124
144
|
dataToProcess: input.dataToProcess,
|
|
125
145
|
evaluationSummary: input.evaluationSummary,
|
|
146
|
+
breachingSeriesFingerprints,
|
|
126
147
|
});
|
|
127
148
|
|
|
128
|
-
if (input.criteriaInstance.data?.createAlerts) {
|
|
129
|
-
|
|
149
|
+
if (!input.criteriaInstance.data?.createAlerts) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const seriesToProcess: Array<PerSeriesCriteriaMatch | undefined> =
|
|
154
|
+
input.matchesPerSeries && input.matchesPerSeries.length > 0
|
|
155
|
+
? input.matchesPerSeries
|
|
156
|
+
: [undefined];
|
|
130
157
|
|
|
131
|
-
|
|
132
|
-
|
|
158
|
+
for (const criteriaAlert of input.criteriaInstance.data?.alerts || []) {
|
|
159
|
+
for (const seriesMatch of seriesToProcess) {
|
|
160
|
+
const seriesFingerprint: string | undefined = seriesMatch?.fingerprint;
|
|
161
|
+
const seriesLabels: JSONObject | undefined = seriesMatch?.labels;
|
|
162
|
+
const seriesRootCause: string =
|
|
163
|
+
seriesMatch?.rootCause || input.rootCause;
|
|
133
164
|
|
|
134
165
|
const alreadyOpenAlert: Alert | undefined = openAlerts.find(
|
|
135
166
|
(alert: Alert) => {
|
|
136
167
|
return (
|
|
137
168
|
alert.createdCriteriaId ===
|
|
138
|
-
|
|
169
|
+
input.criteriaInstance.data?.id.toString() &&
|
|
170
|
+
(alert.seriesFingerprint || undefined) === seriesFingerprint
|
|
139
171
|
);
|
|
140
172
|
},
|
|
141
173
|
);
|
|
@@ -153,9 +185,12 @@ export default class MonitorAlert {
|
|
|
153
185
|
);
|
|
154
186
|
|
|
155
187
|
if (hasAlreadyOpenAlert) {
|
|
188
|
+
const renderedAlertTitle: string =
|
|
189
|
+
alreadyOpenAlert?.title || criteriaAlert.title;
|
|
190
|
+
|
|
156
191
|
input.evaluationSummary?.events.push({
|
|
157
192
|
type: "alert-skipped",
|
|
158
|
-
title: `Alert already active: ${
|
|
193
|
+
title: `Alert already active: ${renderedAlertTitle}`,
|
|
159
194
|
message:
|
|
160
195
|
"Skipped creating a new alert because an active alert exists for this criteria.",
|
|
161
196
|
relatedCriteriaId: input.criteriaInstance.data?.id,
|
|
@@ -180,6 +215,8 @@ export default class MonitorAlert {
|
|
|
180
215
|
MonitorTemplateUtil.buildTemplateStorageMap({
|
|
181
216
|
monitorType: input.monitor.monitorType!,
|
|
182
217
|
dataToProcess: input.dataToProcess,
|
|
218
|
+
monitor: input.monitor,
|
|
219
|
+
seriesLabels,
|
|
183
220
|
});
|
|
184
221
|
|
|
185
222
|
alert.title = MonitorTemplateUtil.processTemplateString({
|
|
@@ -221,13 +258,20 @@ export default class MonitorAlert {
|
|
|
221
258
|
|
|
222
259
|
alert.monitor = input.monitor;
|
|
223
260
|
alert.projectId = input.monitor.projectId!;
|
|
224
|
-
alert.rootCause =
|
|
261
|
+
alert.rootCause = seriesRootCause;
|
|
225
262
|
alert.createdStateLog = JSON.parse(
|
|
226
263
|
JSON.stringify(input.dataToProcess, null, 2),
|
|
227
264
|
);
|
|
228
265
|
|
|
229
266
|
alert.createdCriteriaId = input.criteriaInstance.data.id.toString();
|
|
230
267
|
|
|
268
|
+
if (seriesFingerprint) {
|
|
269
|
+
alert.seriesFingerprint = seriesFingerprint;
|
|
270
|
+
}
|
|
271
|
+
if (seriesLabels && Object.keys(seriesLabels).length > 0) {
|
|
272
|
+
alert.seriesLabels = seriesLabels;
|
|
273
|
+
}
|
|
274
|
+
|
|
231
275
|
alert.onCallDutyPolicies =
|
|
232
276
|
criteriaAlert.onCallPolicyIds?.map((id: ObjectID) => {
|
|
233
277
|
const onCallPolicy: OnCallDutyPolicy = new OnCallDutyPolicy();
|
|
@@ -328,6 +372,82 @@ export default class MonitorAlert {
|
|
|
328
372
|
input.openAlert.projectId!,
|
|
329
373
|
);
|
|
330
374
|
|
|
375
|
+
/*
|
|
376
|
+
* Skip the Resolved insert if the alert's timeline is already at or past
|
|
377
|
+
* the Resolved state in the project's workflow order. Two cases:
|
|
378
|
+
* 1. Latest timeline state is Resolved but Alert.currentAlertStateId is
|
|
379
|
+
* stuck on an earlier state (partial-failure from a prior resolve).
|
|
380
|
+
* Re-inserting Resolved would throw "Alert state cannot be same as
|
|
381
|
+
* previous state" from AlertStateTimelineService.onBeforeCreate.
|
|
382
|
+
* 2. The project defines a custom state after Resolved (e.g. Closed) and
|
|
383
|
+
* the alert has moved into it. Inserting Resolved would throw
|
|
384
|
+
* "cannot transition to Resolved from Closed because Resolved is
|
|
385
|
+
* before Closed in the order of alert states."
|
|
386
|
+
* Either failure bubbles up through ingest workers and causes monitors to
|
|
387
|
+
* flap. Reconcile Alert.currentAlertStateId if out of sync with the
|
|
388
|
+
* timeline, then return.
|
|
389
|
+
*/
|
|
390
|
+
const [resolvedState, latestTimeline]: [
|
|
391
|
+
AlertState | null,
|
|
392
|
+
AlertStateTimeline | null,
|
|
393
|
+
] = await Promise.all([
|
|
394
|
+
AlertStateService.findOneBy({
|
|
395
|
+
query: {
|
|
396
|
+
_id: resolvedStateId.toString(),
|
|
397
|
+
},
|
|
398
|
+
select: {
|
|
399
|
+
order: true,
|
|
400
|
+
},
|
|
401
|
+
props: {
|
|
402
|
+
isRoot: true,
|
|
403
|
+
},
|
|
404
|
+
}),
|
|
405
|
+
AlertStateTimelineService.findOneBy({
|
|
406
|
+
query: {
|
|
407
|
+
alertId: input.openAlert.id!,
|
|
408
|
+
},
|
|
409
|
+
sort: {
|
|
410
|
+
startsAt: SortOrder.Descending,
|
|
411
|
+
},
|
|
412
|
+
select: {
|
|
413
|
+
alertStateId: true,
|
|
414
|
+
alertState: {
|
|
415
|
+
order: true,
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
props: {
|
|
419
|
+
isRoot: true,
|
|
420
|
+
},
|
|
421
|
+
}),
|
|
422
|
+
]);
|
|
423
|
+
|
|
424
|
+
const latestOrder: number | undefined | null =
|
|
425
|
+
latestTimeline?.alertState?.order;
|
|
426
|
+
const resolvedOrder: number | undefined | null = resolvedState?.order;
|
|
427
|
+
|
|
428
|
+
if (
|
|
429
|
+
latestTimeline?.alertStateId &&
|
|
430
|
+
typeof latestOrder === "number" &&
|
|
431
|
+
typeof resolvedOrder === "number" &&
|
|
432
|
+
latestOrder >= resolvedOrder
|
|
433
|
+
) {
|
|
434
|
+
if (
|
|
435
|
+
input.openAlert.currentAlertStateId?.toString() !==
|
|
436
|
+
latestTimeline.alertStateId.toString()
|
|
437
|
+
) {
|
|
438
|
+
await AlertService.updateOneById({
|
|
439
|
+
id: input.openAlert.id!,
|
|
440
|
+
data: {
|
|
441
|
+
currentAlertStateId: latestTimeline.alertStateId,
|
|
442
|
+
},
|
|
443
|
+
props: {
|
|
444
|
+
isRoot: true,
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
331
451
|
const alertStateTimeline: AlertStateTimeline = new AlertStateTimeline();
|
|
332
452
|
alertStateTimeline.alertId = input.openAlert.id!;
|
|
333
453
|
alertStateTimeline.alertStateId = resolvedStateId;
|
|
@@ -357,7 +477,50 @@ export default class MonitorAlert {
|
|
|
357
477
|
openAlert: Alert;
|
|
358
478
|
autoResolveCriteriaInstanceIdAlertIdsDictionary: Dictionary<Array<string>>;
|
|
359
479
|
criteriaInstance: MonitorCriteriaInstance | null; // null if no criteia met.
|
|
480
|
+
breachingSeriesFingerprints?: Set<string> | undefined;
|
|
360
481
|
}): boolean {
|
|
482
|
+
const openSeriesFingerprint: string | undefined =
|
|
483
|
+
input.openAlert.seriesFingerprint || undefined;
|
|
484
|
+
|
|
485
|
+
/*
|
|
486
|
+
* Per-series auto-resolve: when a breaching-series set is given and
|
|
487
|
+
* this alert has a fingerprint, resolve whenever the fingerprint is
|
|
488
|
+
* no longer in the set, independent of whether other series on the
|
|
489
|
+
* same monitor are still breaching.
|
|
490
|
+
*/
|
|
491
|
+
if (
|
|
492
|
+
input.breachingSeriesFingerprints !== undefined &&
|
|
493
|
+
openSeriesFingerprint
|
|
494
|
+
) {
|
|
495
|
+
const stillBreaching: boolean = input.breachingSeriesFingerprints.has(
|
|
496
|
+
openSeriesFingerprint,
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
if (stillBreaching) {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (!input.openAlert.createdCriteriaId?.toString()) {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const autoResolveTemplates: Array<string> | undefined =
|
|
508
|
+
input.autoResolveCriteriaInstanceIdAlertIdsDictionary[
|
|
509
|
+
input.openAlert.createdCriteriaId.toString()
|
|
510
|
+
];
|
|
511
|
+
|
|
512
|
+
/*
|
|
513
|
+
* Alert auto-resolve lists templates by criteria; presence of any
|
|
514
|
+
* template for this criteria means "this criteria's alerts are
|
|
515
|
+
* configured to auto-resolve", so resolve this series.
|
|
516
|
+
*/
|
|
517
|
+
if (autoResolveTemplates && autoResolveTemplates.length > 0) {
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
|
|
361
524
|
if (
|
|
362
525
|
input.openAlert.createdCriteriaId?.toString() ===
|
|
363
526
|
input.criteriaInstance?.data?.id.toString()
|
|
@@ -9,7 +9,9 @@ import SSLMonitorCriteria from "./Criteria/SSLMonitorCriteria";
|
|
|
9
9
|
import ServerMonitorCriteria from "./Criteria/ServerMonitorCriteria";
|
|
10
10
|
import SyntheticMonitoringCriteria from "./Criteria/SyntheticMonitor";
|
|
11
11
|
import LogMonitorCriteria from "./Criteria/LogMonitorCriteria";
|
|
12
|
-
import MetricMonitorCriteria
|
|
12
|
+
import MetricMonitorCriteria, {
|
|
13
|
+
MetricSeriesEvaluationResult,
|
|
14
|
+
} from "./Criteria/MetricMonitorCriteria";
|
|
13
15
|
import TraceMonitorCriteria from "./Criteria/TraceMonitorCriteria";
|
|
14
16
|
import ExceptionMonitorCriteria from "./Criteria/ExceptionMonitorCriteria";
|
|
15
17
|
import ProfileMonitorCriteria from "./Criteria/ProfileMonitorCriteria";
|
|
@@ -31,7 +33,9 @@ import MonitorEvaluationSummary, {
|
|
|
31
33
|
MonitorEvaluationEvent,
|
|
32
34
|
MonitorEvaluationFilterResult,
|
|
33
35
|
} from "../../../Types/Monitor/MonitorEvaluationSummary";
|
|
34
|
-
import ProbeApiIngestResponse
|
|
36
|
+
import ProbeApiIngestResponse, {
|
|
37
|
+
PerSeriesCriteriaMatch,
|
|
38
|
+
} from "../../../Types/Probe/ProbeApiIngestResponse";
|
|
35
39
|
import ProbeMonitorResponse from "../../../Types/Probe/ProbeMonitorResponse";
|
|
36
40
|
import RequestFailedDetails from "../../../Types/Probe/RequestFailedDetails";
|
|
37
41
|
import IncomingMonitorRequest from "../../../Types/Monitor/IncomingMonitor/IncomingMonitorRequest";
|
|
@@ -50,6 +54,7 @@ import MetricMonitorResponse, {
|
|
|
50
54
|
KubernetesAffectedResource,
|
|
51
55
|
KubernetesResourceBreakdown,
|
|
52
56
|
} from "../../../Types/Monitor/MetricMonitor/MetricMonitorResponse";
|
|
57
|
+
import MetricSeriesResult from "../../../Types/Monitor/MetricMonitor/MetricSeriesResult";
|
|
53
58
|
import MetricCriteriaContext, {
|
|
54
59
|
MetricBreachingSample,
|
|
55
60
|
MetricComponent,
|
|
@@ -156,6 +161,26 @@ ${contextBlock}
|
|
|
156
161
|
**Cause**: ${(input.dataToProcess as ProbeMonitorResponse).failureCause || ""}
|
|
157
162
|
`;
|
|
158
163
|
}
|
|
164
|
+
|
|
165
|
+
/*
|
|
166
|
+
* When this is a metric-style monitor with per-series results,
|
|
167
|
+
* compute the per-series match list so MonitorResource can fan
|
|
168
|
+
* out one incident per affected series. We do this *after* the
|
|
169
|
+
* scalar criteriaMetId/rootCause is populated so legacy readers
|
|
170
|
+
* still see a usable response if perSeriesMatches is ignored.
|
|
171
|
+
*/
|
|
172
|
+
const perSeriesMatches: Array<PerSeriesCriteriaMatch> =
|
|
173
|
+
MonitorCriteriaEvaluator.collectPerSeriesMatches({
|
|
174
|
+
dataToProcess: input.dataToProcess,
|
|
175
|
+
monitor: input.monitor,
|
|
176
|
+
monitorStep: input.monitorStep,
|
|
177
|
+
criteriaInstance,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
if (perSeriesMatches.length > 0) {
|
|
181
|
+
input.probeApiIngestResponse.perSeriesMatches = perSeriesMatches;
|
|
182
|
+
}
|
|
183
|
+
|
|
159
184
|
break;
|
|
160
185
|
}
|
|
161
186
|
}
|
|
@@ -163,6 +188,150 @@ ${contextBlock}
|
|
|
163
188
|
return input.probeApiIngestResponse;
|
|
164
189
|
}
|
|
165
190
|
|
|
191
|
+
/**
|
|
192
|
+
* For metric-backed monitors (Metrics/Kubernetes/Docker) with
|
|
193
|
+
* per-series aggregated results, re-evaluate the matched criteria
|
|
194
|
+
* once per series and return one entry per series that breached.
|
|
195
|
+
* Returns an empty array when the monitor is not series-aware or
|
|
196
|
+
* no series matched — the caller falls back to the single-incident
|
|
197
|
+
* path in that case.
|
|
198
|
+
*/
|
|
199
|
+
private static collectPerSeriesMatches(input: {
|
|
200
|
+
dataToProcess: DataToProcess;
|
|
201
|
+
monitor: Monitor;
|
|
202
|
+
monitorStep: MonitorStep;
|
|
203
|
+
criteriaInstance: MonitorCriteriaInstance;
|
|
204
|
+
}): Array<PerSeriesCriteriaMatch> {
|
|
205
|
+
if (
|
|
206
|
+
input.monitor.monitorType !== MonitorType.Metrics &&
|
|
207
|
+
input.monitor.monitorType !== MonitorType.Kubernetes &&
|
|
208
|
+
input.monitor.monitorType !== MonitorType.Docker
|
|
209
|
+
) {
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const metricResponse: MetricMonitorResponse =
|
|
214
|
+
input.dataToProcess as MetricMonitorResponse;
|
|
215
|
+
|
|
216
|
+
const seriesBreakdown: Array<MetricSeriesResult> | undefined =
|
|
217
|
+
metricResponse.seriesBreakdown;
|
|
218
|
+
|
|
219
|
+
if (!seriesBreakdown || seriesBreakdown.length === 0) {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const criteriaId: string | undefined = input.criteriaInstance.data?.id;
|
|
224
|
+
if (!criteriaId) {
|
|
225
|
+
return [];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const filterCondition: FilterCondition =
|
|
229
|
+
input.criteriaInstance.data?.filterCondition || FilterCondition.All;
|
|
230
|
+
|
|
231
|
+
const filters: Array<CriteriaFilter> =
|
|
232
|
+
input.criteriaInstance.data?.filters || [];
|
|
233
|
+
|
|
234
|
+
/*
|
|
235
|
+
* Evaluate every metric-value filter against every series. We only
|
|
236
|
+
* handle CheckOn.MetricValue for now — the criteria evaluator's
|
|
237
|
+
* other filter types don't carry series information.
|
|
238
|
+
*/
|
|
239
|
+
const metricFilters: Array<CriteriaFilter> = filters.filter(
|
|
240
|
+
(f: CriteriaFilter) => {
|
|
241
|
+
return f.checkOn === CheckOn.MetricValue;
|
|
242
|
+
},
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
if (metricFilters.length === 0) {
|
|
246
|
+
return [];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/*
|
|
250
|
+
* Key: fingerprint. Value: results of each metric filter for that
|
|
251
|
+
* series, tracked so we can apply FilterCondition.All/Any per series.
|
|
252
|
+
*/
|
|
253
|
+
const resultsByFingerprint: Map<
|
|
254
|
+
string,
|
|
255
|
+
{
|
|
256
|
+
labels: JSONObject;
|
|
257
|
+
filterResults: Array<MetricSeriesEvaluationResult>;
|
|
258
|
+
}
|
|
259
|
+
> = new Map();
|
|
260
|
+
|
|
261
|
+
for (const criteriaFilter of metricFilters) {
|
|
262
|
+
const evaluations: Array<MetricSeriesEvaluationResult> =
|
|
263
|
+
MetricMonitorCriteria.evaluateAllSeries({
|
|
264
|
+
dataToProcess: input.dataToProcess,
|
|
265
|
+
criteriaFilter,
|
|
266
|
+
monitorStep: input.monitorStep,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
for (const evaluation of evaluations) {
|
|
270
|
+
const fingerprint: string | undefined = evaluation.fingerprint;
|
|
271
|
+
if (!fingerprint) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const existing:
|
|
276
|
+
| {
|
|
277
|
+
labels: JSONObject;
|
|
278
|
+
filterResults: Array<MetricSeriesEvaluationResult>;
|
|
279
|
+
}
|
|
280
|
+
| undefined = resultsByFingerprint.get(fingerprint);
|
|
281
|
+
|
|
282
|
+
if (existing) {
|
|
283
|
+
existing.filterResults.push(evaluation);
|
|
284
|
+
} else {
|
|
285
|
+
resultsByFingerprint.set(fingerprint, {
|
|
286
|
+
labels: evaluation.labels,
|
|
287
|
+
filterResults: [evaluation],
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const matches: Array<PerSeriesCriteriaMatch> = [];
|
|
294
|
+
|
|
295
|
+
for (const [fingerprint, entry] of resultsByFingerprint) {
|
|
296
|
+
// Were all/any of the metric filters met for this series?
|
|
297
|
+
const matched: Array<MetricSeriesEvaluationResult> =
|
|
298
|
+
entry.filterResults.filter((r: MetricSeriesEvaluationResult) => {
|
|
299
|
+
return r.rootCause !== null;
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const seriesMatched: boolean =
|
|
303
|
+
filterCondition === FilterCondition.All
|
|
304
|
+
? matched.length === entry.filterResults.length &&
|
|
305
|
+
entry.filterResults.length > 0
|
|
306
|
+
: matched.length > 0;
|
|
307
|
+
|
|
308
|
+
if (!seriesMatched) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/*
|
|
313
|
+
* For the per-series rootCause, concatenate the matched filter
|
|
314
|
+
* messages so the incident message reflects exactly what
|
|
315
|
+
* breached on this specific series.
|
|
316
|
+
*/
|
|
317
|
+
const rootCauseLines: Array<string> = matched.map(
|
|
318
|
+
(r: MetricSeriesEvaluationResult) => {
|
|
319
|
+
return `- ${r.rootCause}`;
|
|
320
|
+
},
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
matches.push({
|
|
324
|
+
criteriaMetId: criteriaId,
|
|
325
|
+
fingerprint,
|
|
326
|
+
labels: entry.labels,
|
|
327
|
+
rootCause: rootCauseLines.join("\n"),
|
|
328
|
+
metricContext: matched[0]?.context,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return matches;
|
|
333
|
+
}
|
|
334
|
+
|
|
166
335
|
private static async processMonitorCriteriaInstance(input: {
|
|
167
336
|
dataToProcess: DataToProcess;
|
|
168
337
|
monitorStep: MonitorStep;
|