@oneuptime/common 10.0.71 → 10.0.73
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/Project.ts +26 -0
- package/Models/DatabaseModels/Service.ts +2 -2
- package/Models/DatabaseModels/StatusPage.ts +80 -0
- package/Server/API/StatusPageAPI.ts +4 -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/1777018175127-AddTelemetryRetentionSettings.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Services/AnalyticsDatabaseService.ts +17 -7
- package/Server/Services/OpenTelemetryIngestService.ts +36 -5
- package/Server/Services/ServiceService.ts +27 -1
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +175 -29
- package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +71 -0
- package/Server/Utils/Monitor/MonitorAlert.ts +91 -7
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +171 -2
- package/Server/Utils/Monitor/MonitorIncident.ts +133 -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/AdminDashboard/AdminDashboardLanguage.ts +33 -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 +30 -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/FiltersForm.tsx +26 -2
- 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/Project.js +27 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Service.js +1 -2
- package/build/dist/Models/DatabaseModels/Service.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/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/1777018175127-AddTelemetryRetentionSettings.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1777018175127-AddTelemetryRetentionSettings.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/Services/AnalyticsDatabaseService.js +14 -4
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +30 -4
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +22 -1
- package/build/dist/Server/Services/ServiceService.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 +66 -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 +91 -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/AdminDashboard/AdminDashboardLanguage.js +22 -0
- package/build/dist/Types/AdminDashboard/AdminDashboardLanguage.js.map +1 -0
- 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 +22 -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/FiltersForm.js +6 -2
- package/build/dist/UI/Components/Filters/FiltersForm.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
|
@@ -8,6 +8,7 @@ import MetricCriteriaContext, {
|
|
|
8
8
|
MetricComponent,
|
|
9
9
|
MetricComponentValue,
|
|
10
10
|
} from "../../../../Types/Monitor/MetricMonitor/MetricCriteriaContext";
|
|
11
|
+
import MetricSeriesResult from "../../../../Types/Monitor/MetricMonitor/MetricSeriesResult";
|
|
11
12
|
import MonitorStep from "../../../../Types/Monitor/MonitorStep";
|
|
12
13
|
import { JSONObject } from "../../../../Types/JSON";
|
|
13
14
|
import DataToProcess from "../DataToProcess";
|
|
@@ -23,6 +24,20 @@ import CaptureSpan from "../../Telemetry/CaptureSpan";
|
|
|
23
24
|
import MetricUnitUtil from "../../../../Utils/MetricUnitUtil";
|
|
24
25
|
import MetricFormulaEvaluator from "../../../../Utils/Metrics/MetricFormulaEvaluator";
|
|
25
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Result of evaluating a single criteria filter against a single metric
|
|
29
|
+
* series. `rootCause` is null when the filter did not match; otherwise
|
|
30
|
+
* it's the human-readable comparison message. `context` always reflects
|
|
31
|
+
* the metric identity for this series (used to render the metric
|
|
32
|
+
* details + breaching samples section of the incident root cause).
|
|
33
|
+
*/
|
|
34
|
+
export interface MetricSeriesEvaluationResult {
|
|
35
|
+
fingerprint: string | undefined;
|
|
36
|
+
labels: JSONObject;
|
|
37
|
+
rootCause: string | null;
|
|
38
|
+
context: MetricCriteriaContext;
|
|
39
|
+
}
|
|
40
|
+
|
|
26
41
|
export default class MetricMonitorCriteria {
|
|
27
42
|
@CaptureSpan()
|
|
28
43
|
public static async isMonitorInstanceCriteriaFilterMet(input: {
|
|
@@ -30,8 +45,50 @@ export default class MetricMonitorCriteria {
|
|
|
30
45
|
criteriaFilter: CriteriaFilter;
|
|
31
46
|
monitorStep: MonitorStep;
|
|
32
47
|
}): Promise<string | null> {
|
|
33
|
-
|
|
48
|
+
const evaluations: Array<MetricSeriesEvaluationResult> =
|
|
49
|
+
MetricMonitorCriteria.evaluateAllSeries(input);
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
* Backwards-compat: the scalar entrypoint collapses per-series
|
|
53
|
+
* evaluation down to the first matching series so existing callers
|
|
54
|
+
* (single-incident path) keep working. The per-series code path uses
|
|
55
|
+
* `evaluateAllSeries` directly.
|
|
56
|
+
*/
|
|
57
|
+
const match: MetricSeriesEvaluationResult | undefined = evaluations.find(
|
|
58
|
+
(e: MetricSeriesEvaluationResult) => {
|
|
59
|
+
return e.rootCause !== null;
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
* Always populate the legacy single-context field so the root-cause
|
|
65
|
+
* renderer can still read metric identity from the criteria filter
|
|
66
|
+
* even when nothing matched. Pick the first evaluation's context.
|
|
67
|
+
*/
|
|
68
|
+
if (evaluations.length > 0) {
|
|
69
|
+
input.criteriaFilter.metricCriteriaContext = (
|
|
70
|
+
match || evaluations[0]!
|
|
71
|
+
).context;
|
|
72
|
+
}
|
|
34
73
|
|
|
74
|
+
return match ? match.rootCause : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Evaluate a single criteria filter against every series produced by
|
|
79
|
+
* the monitor. For monitors without group-by, this returns a single
|
|
80
|
+
* evaluation covering all aggregated results (legacy behavior). For
|
|
81
|
+
* monitors with group-by attributes set, it returns one evaluation
|
|
82
|
+
* per unique series fingerprint — each with its own
|
|
83
|
+
* `MetricCriteriaContext` carrying that series' breaching samples
|
|
84
|
+
* and labels. The caller fans this out into one incident per
|
|
85
|
+
* breaching series.
|
|
86
|
+
*/
|
|
87
|
+
public static evaluateAllSeries(input: {
|
|
88
|
+
dataToProcess: DataToProcess;
|
|
89
|
+
criteriaFilter: CriteriaFilter;
|
|
90
|
+
monitorStep: MonitorStep;
|
|
91
|
+
}): Array<MetricSeriesEvaluationResult> {
|
|
35
92
|
if (
|
|
36
93
|
input.criteriaFilter.metricMonitorOptions &&
|
|
37
94
|
!input.criteriaFilter.metricMonitorOptions.metricAggregationType
|
|
@@ -41,20 +98,14 @@ export default class MetricMonitorCriteria {
|
|
|
41
98
|
}
|
|
42
99
|
|
|
43
100
|
if (input.criteriaFilter.checkOn !== CheckOn.MetricValue) {
|
|
44
|
-
return
|
|
101
|
+
return [];
|
|
45
102
|
}
|
|
46
103
|
|
|
47
|
-
const rawThreshold: number | null = CompareCriteria.convertToNumber(
|
|
48
|
-
input.criteriaFilter.value,
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
const metricAlias: string =
|
|
52
|
-
input.criteriaFilter.metricMonitorOptions?.metricAlias || "";
|
|
53
|
-
|
|
54
104
|
const metricResponse: MetricMonitorResponse =
|
|
55
105
|
input.dataToProcess as MetricMonitorResponse;
|
|
56
|
-
|
|
57
|
-
|
|
106
|
+
|
|
107
|
+
const seriesBreakdown: Array<MetricSeriesResult> | undefined =
|
|
108
|
+
metricResponse.seriesBreakdown;
|
|
58
109
|
|
|
59
110
|
const queryConfigs: Array<MetricQueryConfigData> =
|
|
60
111
|
input.monitorStep.data?.metricMonitor?.metricViewConfig?.queryConfigs ||
|
|
@@ -63,6 +114,60 @@ export default class MetricMonitorCriteria {
|
|
|
63
114
|
input.monitorStep.data?.metricMonitor?.metricViewConfig?.formulaConfigs ||
|
|
64
115
|
[];
|
|
65
116
|
|
|
117
|
+
/*
|
|
118
|
+
* Series-less path: one synthetic "all-series" evaluation over the
|
|
119
|
+
* flat metricResult. Preserves the pre-group-by behavior exactly.
|
|
120
|
+
*/
|
|
121
|
+
if (!seriesBreakdown || seriesBreakdown.length === 0) {
|
|
122
|
+
const result: MetricSeriesEvaluationResult =
|
|
123
|
+
MetricMonitorCriteria.evaluateOneSeries({
|
|
124
|
+
criteriaFilter: input.criteriaFilter,
|
|
125
|
+
aggregatedResults: metricResponse.metricResult || [],
|
|
126
|
+
queryConfigs,
|
|
127
|
+
formulaConfigs,
|
|
128
|
+
seriesFingerprint: undefined,
|
|
129
|
+
seriesLabels: {},
|
|
130
|
+
});
|
|
131
|
+
return [result];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return seriesBreakdown.map((series: MetricSeriesResult) => {
|
|
135
|
+
return MetricMonitorCriteria.evaluateOneSeries({
|
|
136
|
+
criteriaFilter: input.criteriaFilter,
|
|
137
|
+
aggregatedResults: series.aggregatedResults,
|
|
138
|
+
queryConfigs,
|
|
139
|
+
formulaConfigs,
|
|
140
|
+
seriesFingerprint: series.fingerprint,
|
|
141
|
+
seriesLabels: series.labels,
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Core evaluation loop: compare the samples for one metric series
|
|
148
|
+
* against the criteria threshold. Builds the metric identity context,
|
|
149
|
+
* identifies breaching samples, and assembles the human-readable
|
|
150
|
+
* root-cause message. Factored out so `evaluateAllSeries` can invoke
|
|
151
|
+
* it once per series without duplicating logic.
|
|
152
|
+
*/
|
|
153
|
+
private static evaluateOneSeries(input: {
|
|
154
|
+
criteriaFilter: CriteriaFilter;
|
|
155
|
+
aggregatedResults: Array<AggregatedResult>;
|
|
156
|
+
queryConfigs: Array<MetricQueryConfigData>;
|
|
157
|
+
formulaConfigs: Array<MetricFormulaConfigData>;
|
|
158
|
+
seriesFingerprint: string | undefined;
|
|
159
|
+
seriesLabels: JSONObject;
|
|
160
|
+
}): MetricSeriesEvaluationResult {
|
|
161
|
+
const rawThreshold: number | null = CompareCriteria.convertToNumber(
|
|
162
|
+
input.criteriaFilter.value,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const metricAlias: string =
|
|
166
|
+
input.criteriaFilter.metricMonitorOptions?.metricAlias || "";
|
|
167
|
+
|
|
168
|
+
const metricAggregatedResult: Array<AggregatedResult> =
|
|
169
|
+
input.aggregatedResults;
|
|
170
|
+
|
|
66
171
|
/*
|
|
67
172
|
* Resolve which query/formula the alias refers to. Use explicit index
|
|
68
173
|
* checks (not `findIndex() || -1`, which incorrectly falls back to -1
|
|
@@ -73,25 +178,25 @@ export default class MetricMonitorCriteria {
|
|
|
73
178
|
let aliasIndex: number = -1;
|
|
74
179
|
|
|
75
180
|
if (metricAlias) {
|
|
76
|
-
const qIdx: number = queryConfigs.findIndex(
|
|
181
|
+
const qIdx: number = input.queryConfigs.findIndex(
|
|
77
182
|
(q: MetricQueryConfigData) => {
|
|
78
183
|
return q.metricAliasData?.metricVariable === metricAlias;
|
|
79
184
|
},
|
|
80
185
|
);
|
|
81
186
|
|
|
82
187
|
if (qIdx >= 0) {
|
|
83
|
-
matchedQuery = queryConfigs[qIdx] || null;
|
|
188
|
+
matchedQuery = input.queryConfigs[qIdx] || null;
|
|
84
189
|
aliasIndex = qIdx;
|
|
85
190
|
} else {
|
|
86
|
-
const fIdx: number = formulaConfigs.findIndex(
|
|
191
|
+
const fIdx: number = input.formulaConfigs.findIndex(
|
|
87
192
|
(f: MetricFormulaConfigData) => {
|
|
88
193
|
return f.metricAliasData?.metricVariable === metricAlias;
|
|
89
194
|
},
|
|
90
195
|
);
|
|
91
196
|
|
|
92
197
|
if (fIdx >= 0) {
|
|
93
|
-
matchedFormula = formulaConfigs[fIdx] || null;
|
|
94
|
-
aliasIndex = queryConfigs.length + fIdx;
|
|
198
|
+
matchedFormula = input.formulaConfigs[fIdx] || null;
|
|
199
|
+
aliasIndex = input.queryConfigs.length + fIdx;
|
|
95
200
|
}
|
|
96
201
|
}
|
|
97
202
|
}
|
|
@@ -105,8 +210,8 @@ export default class MetricMonitorCriteria {
|
|
|
105
210
|
? metricAggregatedResult[aliasIndex]
|
|
106
211
|
: metricAggregatedResult[0];
|
|
107
212
|
|
|
108
|
-
if (!matchedQuery && !matchedFormula && queryConfigs[0]) {
|
|
109
|
-
matchedQuery = queryConfigs[0];
|
|
213
|
+
if (!matchedQuery && !matchedFormula && input.queryConfigs[0]) {
|
|
214
|
+
matchedQuery = input.queryConfigs[0];
|
|
110
215
|
}
|
|
111
216
|
|
|
112
217
|
/*
|
|
@@ -119,14 +224,24 @@ export default class MetricMonitorCriteria {
|
|
|
119
224
|
matchedFormula,
|
|
120
225
|
metricAlias,
|
|
121
226
|
criteriaFilter: input.criteriaFilter,
|
|
122
|
-
queryConfigs,
|
|
123
|
-
formulaConfigs,
|
|
227
|
+
queryConfigs: input.queryConfigs,
|
|
228
|
+
formulaConfigs: input.formulaConfigs,
|
|
124
229
|
});
|
|
125
230
|
|
|
126
|
-
input.
|
|
231
|
+
if (input.seriesFingerprint) {
|
|
232
|
+
metricContext.seriesFingerprint = input.seriesFingerprint;
|
|
233
|
+
}
|
|
234
|
+
if (input.seriesLabels && Object.keys(input.seriesLabels).length > 0) {
|
|
235
|
+
metricContext.seriesLabels = input.seriesLabels;
|
|
236
|
+
}
|
|
127
237
|
|
|
128
238
|
if (rawThreshold === null) {
|
|
129
|
-
return
|
|
239
|
+
return {
|
|
240
|
+
fingerprint: input.seriesFingerprint,
|
|
241
|
+
labels: input.seriesLabels,
|
|
242
|
+
rootCause: null,
|
|
243
|
+
context: metricContext,
|
|
244
|
+
};
|
|
130
245
|
}
|
|
131
246
|
|
|
132
247
|
/*
|
|
@@ -180,11 +295,21 @@ export default class MetricMonitorCriteria {
|
|
|
180
295
|
NoDataPolicy.Ignore;
|
|
181
296
|
|
|
182
297
|
if (policy === NoDataPolicy.Ignore) {
|
|
183
|
-
return
|
|
298
|
+
return {
|
|
299
|
+
fingerprint: input.seriesFingerprint,
|
|
300
|
+
labels: input.seriesLabels,
|
|
301
|
+
rootCause: null,
|
|
302
|
+
context: metricContext,
|
|
303
|
+
};
|
|
184
304
|
}
|
|
185
305
|
|
|
186
306
|
if (policy === NoDataPolicy.Trigger) {
|
|
187
|
-
return
|
|
307
|
+
return {
|
|
308
|
+
fingerprint: input.seriesFingerprint,
|
|
309
|
+
labels: input.seriesLabels,
|
|
310
|
+
rootCause: `No data received for ${metricContext.metricName} in the evaluation window — triggering per no-data policy.`,
|
|
311
|
+
context: metricContext,
|
|
312
|
+
};
|
|
188
313
|
}
|
|
189
314
|
|
|
190
315
|
// TreatAsZero: fall through to the comparator with value 0.
|
|
@@ -206,7 +331,12 @@ export default class MetricMonitorCriteria {
|
|
|
206
331
|
});
|
|
207
332
|
|
|
208
333
|
if (!comparisonMessage) {
|
|
209
|
-
return
|
|
334
|
+
return {
|
|
335
|
+
fingerprint: input.seriesFingerprint,
|
|
336
|
+
labels: input.seriesLabels,
|
|
337
|
+
rootCause: null,
|
|
338
|
+
context: metricContext,
|
|
339
|
+
};
|
|
210
340
|
}
|
|
211
341
|
|
|
212
342
|
/*
|
|
@@ -232,8 +362,8 @@ export default class MetricMonitorCriteria {
|
|
|
232
362
|
matchedFormula
|
|
233
363
|
? MetricMonitorCriteria.buildComponentValueLookup({
|
|
234
364
|
components: metricContext.components || [],
|
|
235
|
-
queryConfigs,
|
|
236
|
-
formulaConfigs,
|
|
365
|
+
queryConfigs: input.queryConfigs,
|
|
366
|
+
formulaConfigs: input.formulaConfigs,
|
|
237
367
|
metricAggregatedResult,
|
|
238
368
|
})
|
|
239
369
|
: null;
|
|
@@ -276,7 +406,12 @@ export default class MetricMonitorCriteria {
|
|
|
276
406
|
metricContext.breachingSamples = breachingSamples;
|
|
277
407
|
}
|
|
278
408
|
|
|
279
|
-
return
|
|
409
|
+
return {
|
|
410
|
+
fingerprint: input.seriesFingerprint,
|
|
411
|
+
labels: input.seriesLabels,
|
|
412
|
+
rootCause: comparisonMessage,
|
|
413
|
+
context: metricContext,
|
|
414
|
+
};
|
|
280
415
|
}
|
|
281
416
|
|
|
282
417
|
private static buildComponentValueLookup(input: {
|
|
@@ -438,6 +573,17 @@ export default class MetricMonitorCriteria {
|
|
|
438
573
|
? Object.keys(q.metricQueryData.groupBy as Record<string, unknown>)
|
|
439
574
|
: [];
|
|
440
575
|
|
|
576
|
+
/*
|
|
577
|
+
* Include user-selected attribute keys as part of the groupBy view
|
|
578
|
+
* so the root-cause block shows "Grouped By: host.name" not just the
|
|
579
|
+
* raw columns ClickHouse was asked to partition on.
|
|
580
|
+
*/
|
|
581
|
+
const groupByAttributeKeys: Array<string> =
|
|
582
|
+
q?.metricQueryData?.groupByAttributeKeys || [];
|
|
583
|
+
const allGroupBy: Array<string> = Array.from(
|
|
584
|
+
new Set([...groupBy, ...groupByAttributeKeys]),
|
|
585
|
+
);
|
|
586
|
+
|
|
441
587
|
const components: Array<MetricComponent> | undefined = f
|
|
442
588
|
? MetricMonitorCriteria.buildFormulaComponents({
|
|
443
589
|
formulaConfig: f,
|
|
@@ -454,7 +600,7 @@ export default class MetricMonitorCriteria {
|
|
|
454
600
|
isFormula: Boolean(f),
|
|
455
601
|
formulaExpression: f?.metricFormulaData?.metricFormula,
|
|
456
602
|
filterAttributes,
|
|
457
|
-
groupBy,
|
|
603
|
+
groupBy: allGroupBy,
|
|
458
604
|
timeWindowMinutes:
|
|
459
605
|
input.criteriaFilter.evaluateOverTimeOptions?.timeValueInMinutes,
|
|
460
606
|
...(components && components.length > 0 ? { components } : {}),
|
|
@@ -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 &&
|
|
@@ -26,6 +26,7 @@ import MonitorTemplateUtil from "./MonitorTemplateUtil";
|
|
|
26
26
|
import { JSONObject } from "../../../Types/JSON";
|
|
27
27
|
import OneUptimeDate from "../../../Types/Date";
|
|
28
28
|
import MonitorEvaluationSummary from "../../../Types/Monitor/MonitorEvaluationSummary";
|
|
29
|
+
import { PerSeriesCriteriaMatch } from "../../../Types/Probe/ProbeApiIngestResponse";
|
|
29
30
|
|
|
30
31
|
export default class MonitorAlert {
|
|
31
32
|
@CaptureSpan()
|
|
@@ -36,6 +37,7 @@ export default class MonitorAlert {
|
|
|
36
37
|
criteriaInstance: MonitorCriteriaInstance | null;
|
|
37
38
|
dataToProcess: DataToProcess;
|
|
38
39
|
evaluationSummary?: MonitorEvaluationSummary | undefined;
|
|
40
|
+
breachingSeriesFingerprints?: Set<string> | undefined;
|
|
39
41
|
}): Promise<Array<Alert>> {
|
|
40
42
|
// check active alerts and if there are open alerts, do not cretae anothr alert.
|
|
41
43
|
const openAlerts: Array<Alert> = await AlertService.findBy({
|
|
@@ -49,11 +51,14 @@ export default class MonitorAlert {
|
|
|
49
51
|
limit: LIMIT_PER_PROJECT,
|
|
50
52
|
select: {
|
|
51
53
|
_id: true,
|
|
54
|
+
title: true,
|
|
52
55
|
createdCriteriaId: true,
|
|
53
56
|
projectId: true,
|
|
54
57
|
alertNumber: true,
|
|
55
58
|
alertNumberWithPrefix: true,
|
|
56
59
|
currentAlertStateId: true,
|
|
60
|
+
seriesFingerprint: true,
|
|
61
|
+
seriesLabels: true,
|
|
57
62
|
},
|
|
58
63
|
props: {
|
|
59
64
|
isRoot: true,
|
|
@@ -68,6 +73,7 @@ export default class MonitorAlert {
|
|
|
68
73
|
autoResolveCriteriaInstanceIdAlertIdsDictionary:
|
|
69
74
|
input.autoResolveCriteriaInstanceIdAlertIdsDictionary,
|
|
70
75
|
criteriaInstance: input.criteriaInstance,
|
|
76
|
+
breachingSeriesFingerprints: input.breachingSeriesFingerprints,
|
|
71
77
|
});
|
|
72
78
|
|
|
73
79
|
if (shouldClose) {
|
|
@@ -106,6 +112,7 @@ export default class MonitorAlert {
|
|
|
106
112
|
props: {
|
|
107
113
|
telemetryQuery?: TelemetryQuery | undefined;
|
|
108
114
|
};
|
|
115
|
+
matchesPerSeries?: Array<PerSeriesCriteriaMatch> | undefined;
|
|
109
116
|
}): Promise<void> {
|
|
110
117
|
const alertLogAttributes: LogAttributes = {
|
|
111
118
|
projectId: input.monitor.projectId?.toString(),
|
|
@@ -116,6 +123,16 @@ export default class MonitorAlert {
|
|
|
116
123
|
`${input.monitor.id?.toString()} - Check open alerts.`,
|
|
117
124
|
alertLogAttributes,
|
|
118
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
|
+
|
|
119
136
|
// check active alerts and if there are open alerts, do not cretae anothr alert.
|
|
120
137
|
const openAlerts: Array<Alert> =
|
|
121
138
|
await this.checkOpenAlertsAndCloseIfResolved({
|
|
@@ -126,19 +143,31 @@ export default class MonitorAlert {
|
|
|
126
143
|
criteriaInstance: input.criteriaInstance,
|
|
127
144
|
dataToProcess: input.dataToProcess,
|
|
128
145
|
evaluationSummary: input.evaluationSummary,
|
|
146
|
+
breachingSeriesFingerprints,
|
|
129
147
|
});
|
|
130
148
|
|
|
131
|
-
if (input.criteriaInstance.data?.createAlerts) {
|
|
132
|
-
|
|
149
|
+
if (!input.criteriaInstance.data?.createAlerts) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
133
152
|
|
|
134
|
-
|
|
135
|
-
|
|
153
|
+
const seriesToProcess: Array<PerSeriesCriteriaMatch | undefined> =
|
|
154
|
+
input.matchesPerSeries && input.matchesPerSeries.length > 0
|
|
155
|
+
? input.matchesPerSeries
|
|
156
|
+
: [undefined];
|
|
157
|
+
|
|
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;
|
|
136
164
|
|
|
137
165
|
const alreadyOpenAlert: Alert | undefined = openAlerts.find(
|
|
138
166
|
(alert: Alert) => {
|
|
139
167
|
return (
|
|
140
168
|
alert.createdCriteriaId ===
|
|
141
|
-
|
|
169
|
+
input.criteriaInstance.data?.id.toString() &&
|
|
170
|
+
(alert.seriesFingerprint || undefined) === seriesFingerprint
|
|
142
171
|
);
|
|
143
172
|
},
|
|
144
173
|
);
|
|
@@ -156,9 +185,12 @@ export default class MonitorAlert {
|
|
|
156
185
|
);
|
|
157
186
|
|
|
158
187
|
if (hasAlreadyOpenAlert) {
|
|
188
|
+
const renderedAlertTitle: string =
|
|
189
|
+
alreadyOpenAlert?.title || criteriaAlert.title;
|
|
190
|
+
|
|
159
191
|
input.evaluationSummary?.events.push({
|
|
160
192
|
type: "alert-skipped",
|
|
161
|
-
title: `Alert already active: ${
|
|
193
|
+
title: `Alert already active: ${renderedAlertTitle}`,
|
|
162
194
|
message:
|
|
163
195
|
"Skipped creating a new alert because an active alert exists for this criteria.",
|
|
164
196
|
relatedCriteriaId: input.criteriaInstance.data?.id,
|
|
@@ -183,6 +215,8 @@ export default class MonitorAlert {
|
|
|
183
215
|
MonitorTemplateUtil.buildTemplateStorageMap({
|
|
184
216
|
monitorType: input.monitor.monitorType!,
|
|
185
217
|
dataToProcess: input.dataToProcess,
|
|
218
|
+
monitor: input.monitor,
|
|
219
|
+
seriesLabels,
|
|
186
220
|
});
|
|
187
221
|
|
|
188
222
|
alert.title = MonitorTemplateUtil.processTemplateString({
|
|
@@ -224,13 +258,20 @@ export default class MonitorAlert {
|
|
|
224
258
|
|
|
225
259
|
alert.monitor = input.monitor;
|
|
226
260
|
alert.projectId = input.monitor.projectId!;
|
|
227
|
-
alert.rootCause =
|
|
261
|
+
alert.rootCause = seriesRootCause;
|
|
228
262
|
alert.createdStateLog = JSON.parse(
|
|
229
263
|
JSON.stringify(input.dataToProcess, null, 2),
|
|
230
264
|
);
|
|
231
265
|
|
|
232
266
|
alert.createdCriteriaId = input.criteriaInstance.data.id.toString();
|
|
233
267
|
|
|
268
|
+
if (seriesFingerprint) {
|
|
269
|
+
alert.seriesFingerprint = seriesFingerprint;
|
|
270
|
+
}
|
|
271
|
+
if (seriesLabels && Object.keys(seriesLabels).length > 0) {
|
|
272
|
+
alert.seriesLabels = seriesLabels;
|
|
273
|
+
}
|
|
274
|
+
|
|
234
275
|
alert.onCallDutyPolicies =
|
|
235
276
|
criteriaAlert.onCallPolicyIds?.map((id: ObjectID) => {
|
|
236
277
|
const onCallPolicy: OnCallDutyPolicy = new OnCallDutyPolicy();
|
|
@@ -436,7 +477,50 @@ export default class MonitorAlert {
|
|
|
436
477
|
openAlert: Alert;
|
|
437
478
|
autoResolveCriteriaInstanceIdAlertIdsDictionary: Dictionary<Array<string>>;
|
|
438
479
|
criteriaInstance: MonitorCriteriaInstance | null; // null if no criteia met.
|
|
480
|
+
breachingSeriesFingerprints?: Set<string> | undefined;
|
|
439
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
|
+
|
|
440
524
|
if (
|
|
441
525
|
input.openAlert.createdCriteriaId?.toString() ===
|
|
442
526
|
input.criteriaInstance?.data?.id.toString()
|