@oneuptime/common 11.3.6 → 11.3.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/Server/Services/AlertService.ts +302 -202
- package/Server/Services/IncidentService.ts +410 -306
- package/build/dist/Server/Services/AlertService.js +255 -161
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +322 -236
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/package.json +1 -1
|
@@ -33,6 +33,7 @@ import AlertStateTimeline from "../../Models/DatabaseModels/AlertStateTimeline";
|
|
|
33
33
|
import User from "../../Models/DatabaseModels/User";
|
|
34
34
|
import { IsBillingEnabled } from "../EnvironmentConfig";
|
|
35
35
|
import logger, { LogAttributes } from "../Utils/Logger";
|
|
36
|
+
import Semaphore, { SemaphoreMutex } from "../Infrastructure/Semaphore";
|
|
36
37
|
import TelemetryUtil from "../Utils/Telemetry/Telemetry";
|
|
37
38
|
import MetricService from "./MetricService";
|
|
38
39
|
import GlobalConfigService from "./GlobalConfigService";
|
|
@@ -1335,91 +1336,111 @@ ${alertSeverity.name}
|
|
|
1335
1336
|
const firstAlertStateTimeline: AlertStateTimeline | undefined =
|
|
1336
1337
|
alertStateTimelines[0];
|
|
1337
1338
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1339
|
+
/*
|
|
1340
|
+
* Serialize concurrent refreshes for this alert across pods. The
|
|
1341
|
+
* write-once guard below is read-then-insert (findBy existing, then a
|
|
1342
|
+
* conditional createMany). Both call sites fire refresh fire-and-forget
|
|
1343
|
+
* after releasing the per-alert state-timeline mutex, so two
|
|
1344
|
+
* close-together transitions (e.g. auto-acknowledge then auto-resolve)
|
|
1345
|
+
* could interleave: both read a snapshot missing a metric and both
|
|
1346
|
+
* insert it. The Metric table is a plain MergeTree with no row collapse,
|
|
1347
|
+
* so those duplicates would be permanent and re-inflate the very Sum/Avg
|
|
1348
|
+
* widgets this change fixes. A best-effort distributed lock keyed on the
|
|
1349
|
+
* alert makes the read+insert atomic; if the lock can't be taken we still
|
|
1350
|
+
* proceed (the existence check alone covers the common sequential case).
|
|
1351
|
+
*/
|
|
1352
|
+
let metricRefreshMutex: SemaphoreMutex | null = null;
|
|
1353
|
+
try {
|
|
1354
|
+
metricRefreshMutex = await Semaphore.lock({
|
|
1355
|
+
key: data.alertId.toString(),
|
|
1356
|
+
namespace: "AlertService.refreshAlertMetrics",
|
|
1357
|
+
lockTimeout: 30000,
|
|
1358
|
+
});
|
|
1359
|
+
} catch (err) {
|
|
1360
|
+
logger.error(
|
|
1361
|
+
err as Error,
|
|
1362
|
+
{
|
|
1363
|
+
projectId: alert.projectId?.toString(),
|
|
1364
|
+
alertId: alert.id?.toString(),
|
|
1365
|
+
} as LogAttributes,
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1356
1368
|
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1369
|
+
try {
|
|
1370
|
+
/*
|
|
1371
|
+
* Write-once metrics — no deletes.
|
|
1372
|
+
*
|
|
1373
|
+
* The raw Metric table (`MetricItemV3`) feeds an AggregatingMergeTree
|
|
1374
|
+
* materialized view (`MetricItemAggMV1m_mv`) that accumulates
|
|
1375
|
+
* `sumState`/`countState` per (projectId, name, primaryEntityId,
|
|
1376
|
+
* minute). The MV trigger fires only on INSERT — an `ALTER ... DELETE`
|
|
1377
|
+
* mutation on the source rolls back nothing in the MV. So the old
|
|
1378
|
+
* "delete every metric for this alert, then re-insert" refresh both
|
|
1379
|
+
* (a) inflated those rollups and (b) issued one heavy `ALTER ... DELETE`
|
|
1380
|
+
* mutation per state transition per alert — a mutation storm.
|
|
1381
|
+
*
|
|
1382
|
+
* Every alert metric has a single eventually-final value: AlertCount is
|
|
1383
|
+
* constant `value = 1`; MTTA/MTTR/duration are each fixed once the
|
|
1384
|
+
* relevant state transition has happened. So we load what we've already
|
|
1385
|
+
* emitted for this alert and insert each metric exactly once, when it
|
|
1386
|
+
* becomes final. Repeat refreshes that find nothing new are no-ops.
|
|
1387
|
+
*/
|
|
1388
|
+
const existingMetricNames: Set<string> = new Set<string>(
|
|
1389
|
+
(
|
|
1390
|
+
await MetricService.findBy({
|
|
1391
|
+
query: {
|
|
1392
|
+
projectId: alert.projectId,
|
|
1393
|
+
primaryEntityId: data.alertId,
|
|
1394
|
+
},
|
|
1395
|
+
select: {
|
|
1396
|
+
name: true,
|
|
1397
|
+
},
|
|
1398
|
+
skip: 0,
|
|
1399
|
+
limit: LIMIT_PER_PROJECT,
|
|
1400
|
+
props: {
|
|
1401
|
+
isRoot: true,
|
|
1402
|
+
},
|
|
1403
|
+
})
|
|
1404
|
+
)
|
|
1405
|
+
.map((metric: Metric) => {
|
|
1406
|
+
return metric.name;
|
|
1407
|
+
})
|
|
1408
|
+
.filter(Boolean) as Array<string>,
|
|
1409
|
+
);
|
|
1381
1410
|
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
alertCountMetric.time,
|
|
1385
|
-
);
|
|
1386
|
-
alertCountMetric.metricPointType = MetricPointType.Sum;
|
|
1387
|
-
alertCountMetric.retentionDate = alertMetricRetentionDate;
|
|
1388
|
-
|
|
1389
|
-
itemsToSave.push(alertCountMetric);
|
|
1390
|
-
|
|
1391
|
-
const metricType: MetricType = new MetricType();
|
|
1392
|
-
metricType.name = alertCountMetric.name;
|
|
1393
|
-
metricType.description = "Number of alerts created";
|
|
1394
|
-
metricType.unit = "";
|
|
1395
|
-
metricType.services = [];
|
|
1396
|
-
metricTypesMap[alertCountMetric.name] = metricType;
|
|
1397
|
-
|
|
1398
|
-
// is the alert acknowledged?
|
|
1399
|
-
const isAlertAcknowledged: boolean = alertStateTimelines.some(
|
|
1400
|
-
(timeline: AlertStateTimeline) => {
|
|
1401
|
-
return timeline.alertState?.isAcknowledgedState;
|
|
1402
|
-
},
|
|
1403
|
-
);
|
|
1411
|
+
const itemsToSave: Array<Metric> = [];
|
|
1412
|
+
const metricTypesMap: Dictionary<MetricType> = {};
|
|
1404
1413
|
|
|
1405
|
-
|
|
1406
|
-
const
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1414
|
+
const metricRetentionDays: number = await this.getMetricRetentionDays();
|
|
1415
|
+
const alertMetricRetentionDate: Date = OneUptimeDate.addRemoveDays(
|
|
1416
|
+
OneUptimeDate.getCurrentDate(),
|
|
1417
|
+
metricRetentionDays,
|
|
1418
|
+
);
|
|
1410
1419
|
|
|
1411
|
-
|
|
1412
|
-
|
|
1420
|
+
// now we need to create new metrics for this alert - TimeToAcknowledge, TimeToResolve, AlertCount, AlertDuration
|
|
1421
|
+
const alertStartsAt: Date =
|
|
1422
|
+
firstAlertStateTimeline?.startsAt ||
|
|
1423
|
+
alert.createdAt ||
|
|
1424
|
+
OneUptimeDate.getCurrentDate();
|
|
1413
1425
|
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1426
|
+
// register the metric type so the catalog stays complete across refreshes.
|
|
1427
|
+
const alertCountMetricType: MetricType = new MetricType();
|
|
1428
|
+
alertCountMetricType.name = AlertMetricType.AlertCount;
|
|
1429
|
+
alertCountMetricType.description = "Number of alerts created";
|
|
1430
|
+
alertCountMetricType.unit = "";
|
|
1431
|
+
alertCountMetricType.services = [];
|
|
1432
|
+
metricTypesMap[AlertMetricType.AlertCount] = alertCountMetricType;
|
|
1433
|
+
|
|
1434
|
+
// write-once: AlertCount is a constant value=1 keyed by the alert.
|
|
1435
|
+
if (!existingMetricNames.has(AlertMetricType.AlertCount)) {
|
|
1436
|
+
const alertCountMetric: Metric = new Metric();
|
|
1437
|
+
|
|
1438
|
+
alertCountMetric.projectId = alert.projectId;
|
|
1439
|
+
alertCountMetric.primaryEntityId = alert.id!;
|
|
1440
|
+
alertCountMetric.primaryEntityType = ServiceType.Alert;
|
|
1441
|
+
alertCountMetric.name = AlertMetricType.AlertCount;
|
|
1442
|
+
alertCountMetric.value = 1;
|
|
1443
|
+
alertCountMetric.attributes = {
|
|
1423
1444
|
alertId: data.alertId.toString(),
|
|
1424
1445
|
projectId: alert.projectId.toString(),
|
|
1425
1446
|
monitorId: alert.monitor?._id?.toString(),
|
|
@@ -1427,152 +1448,231 @@ ${alertSeverity.name}
|
|
|
1427
1448
|
alertSeverityId: alert.alertSeverity?._id?.toString(),
|
|
1428
1449
|
alertSeverityName: alert.alertSeverity?.name?.toString(),
|
|
1429
1450
|
};
|
|
1430
|
-
|
|
1431
|
-
|
|
1451
|
+
alertCountMetric.attributeKeys = TelemetryUtil.getAttributeKeys(
|
|
1452
|
+
alertCountMetric.attributes,
|
|
1432
1453
|
);
|
|
1433
1454
|
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
OneUptimeDate.getCurrentDate();
|
|
1438
|
-
timeToAcknowledgeMetric.timeUnixNano = OneUptimeDate.toUnixNano(
|
|
1439
|
-
timeToAcknowledgeMetric.time,
|
|
1455
|
+
alertCountMetric.time = alertStartsAt;
|
|
1456
|
+
alertCountMetric.timeUnixNano = OneUptimeDate.toUnixNano(
|
|
1457
|
+
alertCountMetric.time,
|
|
1440
1458
|
);
|
|
1441
|
-
|
|
1442
|
-
|
|
1459
|
+
alertCountMetric.metricPointType = MetricPointType.Sum;
|
|
1460
|
+
alertCountMetric.retentionDate = alertMetricRetentionDate;
|
|
1443
1461
|
|
|
1444
|
-
itemsToSave.push(
|
|
1462
|
+
itemsToSave.push(alertCountMetric);
|
|
1463
|
+
}
|
|
1445
1464
|
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1465
|
+
// is the alert acknowledged?
|
|
1466
|
+
const isAlertAcknowledged: boolean = alertStateTimelines.some(
|
|
1467
|
+
(timeline: AlertStateTimeline) => {
|
|
1468
|
+
return timeline.alertState?.isAcknowledgedState;
|
|
1469
|
+
},
|
|
1470
|
+
);
|
|
1471
|
+
|
|
1472
|
+
if (isAlertAcknowledged) {
|
|
1473
|
+
const ackAlertStateTimeline: AlertStateTimeline | undefined =
|
|
1474
|
+
alertStateTimelines.find((timeline: AlertStateTimeline) => {
|
|
1475
|
+
return timeline.alertState?.isAcknowledgedState;
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
if (ackAlertStateTimeline) {
|
|
1479
|
+
// register the metric type so the catalog stays complete across refreshes.
|
|
1480
|
+
const metricType: MetricType = new MetricType();
|
|
1481
|
+
metricType.name = AlertMetricType.TimeToAcknowledge;
|
|
1482
|
+
metricType.description = "Time taken to acknowledge the alert";
|
|
1483
|
+
metricType.unit = "seconds";
|
|
1484
|
+
metricTypesMap[AlertMetricType.TimeToAcknowledge] = metricType;
|
|
1485
|
+
|
|
1486
|
+
// write-once: MTTA is fixed once the alert is first acknowledged.
|
|
1487
|
+
if (!existingMetricNames.has(AlertMetricType.TimeToAcknowledge)) {
|
|
1488
|
+
const timeToAcknowledgeMetric: Metric = new Metric();
|
|
1489
|
+
|
|
1490
|
+
timeToAcknowledgeMetric.projectId = alert.projectId;
|
|
1491
|
+
timeToAcknowledgeMetric.primaryEntityId = alert.id!;
|
|
1492
|
+
timeToAcknowledgeMetric.primaryEntityType = ServiceType.Alert;
|
|
1493
|
+
timeToAcknowledgeMetric.name = AlertMetricType.TimeToAcknowledge;
|
|
1494
|
+
timeToAcknowledgeMetric.value =
|
|
1495
|
+
OneUptimeDate.getDifferenceInSeconds(
|
|
1496
|
+
ackAlertStateTimeline?.startsAt ||
|
|
1497
|
+
OneUptimeDate.getCurrentDate(),
|
|
1498
|
+
alertStartsAt,
|
|
1499
|
+
);
|
|
1500
|
+
timeToAcknowledgeMetric.attributes = {
|
|
1501
|
+
alertId: data.alertId.toString(),
|
|
1502
|
+
projectId: alert.projectId.toString(),
|
|
1503
|
+
monitorId: alert.monitor?._id?.toString(),
|
|
1504
|
+
monitorName: alert.monitor?.name?.toString(),
|
|
1505
|
+
alertSeverityId: alert.alertSeverity?._id?.toString(),
|
|
1506
|
+
alertSeverityName: alert.alertSeverity?.name?.toString(),
|
|
1507
|
+
};
|
|
1508
|
+
timeToAcknowledgeMetric.attributeKeys =
|
|
1509
|
+
TelemetryUtil.getAttributeKeys(
|
|
1510
|
+
timeToAcknowledgeMetric.attributes,
|
|
1511
|
+
);
|
|
1512
|
+
|
|
1513
|
+
timeToAcknowledgeMetric.time =
|
|
1514
|
+
ackAlertStateTimeline?.startsAt ||
|
|
1515
|
+
alert.createdAt ||
|
|
1516
|
+
OneUptimeDate.getCurrentDate();
|
|
1517
|
+
timeToAcknowledgeMetric.timeUnixNano = OneUptimeDate.toUnixNano(
|
|
1518
|
+
timeToAcknowledgeMetric.time,
|
|
1519
|
+
);
|
|
1520
|
+
timeToAcknowledgeMetric.metricPointType = MetricPointType.Sum;
|
|
1521
|
+
timeToAcknowledgeMetric.retentionDate = alertMetricRetentionDate;
|
|
1522
|
+
|
|
1523
|
+
itemsToSave.push(timeToAcknowledgeMetric);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1451
1526
|
}
|
|
1452
|
-
}
|
|
1453
1527
|
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1528
|
+
// time to resolve
|
|
1529
|
+
const isAlertResolved: boolean = alertStateTimelines.some(
|
|
1530
|
+
(timeline: AlertStateTimeline) => {
|
|
1531
|
+
return timeline.alertState?.isResolvedState;
|
|
1532
|
+
},
|
|
1533
|
+
);
|
|
1460
1534
|
|
|
1461
|
-
if (isAlertResolved) {
|
|
1462
1535
|
const resolvedAlertStateTimeline: AlertStateTimeline | undefined =
|
|
1463
1536
|
alertStateTimelines.find((timeline: AlertStateTimeline) => {
|
|
1464
1537
|
return timeline.alertState?.isResolvedState;
|
|
1465
1538
|
});
|
|
1466
1539
|
|
|
1467
|
-
if (resolvedAlertStateTimeline) {
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
timeToResolveMetric.projectId = alert.projectId;
|
|
1471
|
-
timeToResolveMetric.primaryEntityId = alert.id!;
|
|
1472
|
-
timeToResolveMetric.primaryEntityType = ServiceType.Alert;
|
|
1473
|
-
timeToResolveMetric.name = AlertMetricType.TimeToResolve;
|
|
1474
|
-
timeToResolveMetric.value = OneUptimeDate.getDifferenceInSeconds(
|
|
1475
|
-
resolvedAlertStateTimeline?.startsAt ||
|
|
1476
|
-
OneUptimeDate.getCurrentDate(),
|
|
1477
|
-
alertStartsAt,
|
|
1478
|
-
);
|
|
1479
|
-
timeToResolveMetric.attributes = {
|
|
1480
|
-
alertId: data.alertId.toString(),
|
|
1481
|
-
projectId: alert.projectId.toString(),
|
|
1482
|
-
monitorId: alert.monitor?._id?.toString(),
|
|
1483
|
-
monitorName: alert.monitor?.name?.toString(),
|
|
1484
|
-
alertSeverityId: alert.alertSeverity?._id?.toString(),
|
|
1485
|
-
alertSeverityName: alert.alertSeverity?.name?.toString(),
|
|
1486
|
-
};
|
|
1487
|
-
timeToResolveMetric.attributeKeys = TelemetryUtil.getAttributeKeys(
|
|
1488
|
-
timeToResolveMetric.attributes,
|
|
1489
|
-
);
|
|
1490
|
-
|
|
1491
|
-
timeToResolveMetric.time =
|
|
1492
|
-
resolvedAlertStateTimeline?.startsAt ||
|
|
1493
|
-
alert.createdAt ||
|
|
1494
|
-
OneUptimeDate.getCurrentDate();
|
|
1495
|
-
timeToResolveMetric.timeUnixNano = OneUptimeDate.toUnixNano(
|
|
1496
|
-
timeToResolveMetric.time,
|
|
1497
|
-
);
|
|
1498
|
-
timeToResolveMetric.metricPointType = MetricPointType.Sum;
|
|
1499
|
-
timeToResolveMetric.retentionDate = alertMetricRetentionDate;
|
|
1500
|
-
|
|
1501
|
-
itemsToSave.push(timeToResolveMetric);
|
|
1502
|
-
|
|
1540
|
+
if (isAlertResolved && resolvedAlertStateTimeline) {
|
|
1541
|
+
// register the metric type so the catalog stays complete across refreshes.
|
|
1503
1542
|
const metricType: MetricType = new MetricType();
|
|
1504
|
-
metricType.name =
|
|
1543
|
+
metricType.name = AlertMetricType.TimeToResolve;
|
|
1505
1544
|
metricType.description = "Time taken to resolve the alert";
|
|
1506
1545
|
metricType.unit = "seconds";
|
|
1507
|
-
metricTypesMap[
|
|
1508
|
-
|
|
1509
|
-
|
|
1546
|
+
metricTypesMap[AlertMetricType.TimeToResolve] = metricType;
|
|
1547
|
+
|
|
1548
|
+
// write-once: MTTR is fixed once the alert is first resolved.
|
|
1549
|
+
if (!existingMetricNames.has(AlertMetricType.TimeToResolve)) {
|
|
1550
|
+
const timeToResolveMetric: Metric = new Metric();
|
|
1551
|
+
|
|
1552
|
+
timeToResolveMetric.projectId = alert.projectId;
|
|
1553
|
+
timeToResolveMetric.primaryEntityId = alert.id!;
|
|
1554
|
+
timeToResolveMetric.primaryEntityType = ServiceType.Alert;
|
|
1555
|
+
timeToResolveMetric.name = AlertMetricType.TimeToResolve;
|
|
1556
|
+
timeToResolveMetric.value = OneUptimeDate.getDifferenceInSeconds(
|
|
1557
|
+
resolvedAlertStateTimeline?.startsAt ||
|
|
1558
|
+
OneUptimeDate.getCurrentDate(),
|
|
1559
|
+
alertStartsAt,
|
|
1560
|
+
);
|
|
1561
|
+
timeToResolveMetric.attributes = {
|
|
1562
|
+
alertId: data.alertId.toString(),
|
|
1563
|
+
projectId: alert.projectId.toString(),
|
|
1564
|
+
monitorId: alert.monitor?._id?.toString(),
|
|
1565
|
+
monitorName: alert.monitor?.name?.toString(),
|
|
1566
|
+
alertSeverityId: alert.alertSeverity?._id?.toString(),
|
|
1567
|
+
alertSeverityName: alert.alertSeverity?.name?.toString(),
|
|
1568
|
+
};
|
|
1569
|
+
timeToResolveMetric.attributeKeys = TelemetryUtil.getAttributeKeys(
|
|
1570
|
+
timeToResolveMetric.attributes,
|
|
1571
|
+
);
|
|
1510
1572
|
|
|
1511
|
-
|
|
1512
|
-
|
|
1573
|
+
timeToResolveMetric.time =
|
|
1574
|
+
resolvedAlertStateTimeline?.startsAt ||
|
|
1575
|
+
alert.createdAt ||
|
|
1576
|
+
OneUptimeDate.getCurrentDate();
|
|
1577
|
+
timeToResolveMetric.timeUnixNano = OneUptimeDate.toUnixNano(
|
|
1578
|
+
timeToResolveMetric.time,
|
|
1579
|
+
);
|
|
1580
|
+
timeToResolveMetric.metricPointType = MetricPointType.Sum;
|
|
1581
|
+
timeToResolveMetric.retentionDate = alertMetricRetentionDate;
|
|
1513
1582
|
|
|
1514
|
-
|
|
1515
|
-
|
|
1583
|
+
itemsToSave.push(timeToResolveMetric);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1516
1586
|
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1587
|
+
/*
|
|
1588
|
+
* Alert duration — write-once, finalized at resolution.
|
|
1589
|
+
*
|
|
1590
|
+
* Previously recomputed as (latest state transition − start) on every
|
|
1591
|
+
* refresh, which grew for open alerts and required delete + re-insert.
|
|
1592
|
+
* With no deletes we emit a single final duration once the alert reaches
|
|
1593
|
+
* a resolved state: (first resolution − start). An alert that is never
|
|
1594
|
+
* resolved has no duration row (its lifetime is not yet final).
|
|
1595
|
+
*/
|
|
1596
|
+
if (isAlertResolved && resolvedAlertStateTimeline) {
|
|
1597
|
+
// register the metric type so the catalog stays complete across refreshes.
|
|
1598
|
+
const metricType: MetricType = new MetricType();
|
|
1599
|
+
metricType.name = AlertMetricType.AlertDuration;
|
|
1600
|
+
metricType.description = "Duration of the alert";
|
|
1601
|
+
metricType.unit = "seconds";
|
|
1602
|
+
metricTypesMap[AlertMetricType.AlertDuration] = metricType;
|
|
1603
|
+
|
|
1604
|
+
if (!existingMetricNames.has(AlertMetricType.AlertDuration)) {
|
|
1605
|
+
const alertEndsAt: Date =
|
|
1606
|
+
resolvedAlertStateTimeline.startsAt ||
|
|
1607
|
+
OneUptimeDate.getCurrentDate();
|
|
1608
|
+
|
|
1609
|
+
const alertDurationMetric: Metric = new Metric();
|
|
1610
|
+
|
|
1611
|
+
alertDurationMetric.projectId = alert.projectId;
|
|
1612
|
+
alertDurationMetric.primaryEntityId = alert.id!;
|
|
1613
|
+
alertDurationMetric.primaryEntityType = ServiceType.Alert;
|
|
1614
|
+
alertDurationMetric.name = AlertMetricType.AlertDuration;
|
|
1615
|
+
alertDurationMetric.value = OneUptimeDate.getDifferenceInSeconds(
|
|
1616
|
+
alertEndsAt,
|
|
1617
|
+
alertStartsAt,
|
|
1618
|
+
);
|
|
1619
|
+
alertDurationMetric.attributes = {
|
|
1620
|
+
alertId: data.alertId.toString(),
|
|
1621
|
+
projectId: alert.projectId.toString(),
|
|
1622
|
+
monitorId: alert.monitor?._id?.toString(),
|
|
1623
|
+
monitorName: alert.monitor?.name?.toString(),
|
|
1624
|
+
alertSeverityId: alert.alertSeverity?._id?.toString(),
|
|
1625
|
+
alertSeverityName: alert.alertSeverity?.name?.toString(),
|
|
1626
|
+
};
|
|
1627
|
+
alertDurationMetric.attributeKeys = TelemetryUtil.getAttributeKeys(
|
|
1628
|
+
alertDurationMetric.attributes,
|
|
1629
|
+
);
|
|
1520
1630
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
alertStartsAt,
|
|
1528
|
-
);
|
|
1529
|
-
alertDurationMetric.attributes = {
|
|
1530
|
-
alertId: data.alertId.toString(),
|
|
1531
|
-
projectId: alert.projectId.toString(),
|
|
1532
|
-
monitorId: alert.monitor?._id?.toString(),
|
|
1533
|
-
monitorName: alert.monitor?.name?.toString(),
|
|
1534
|
-
alertSeverityId: alert.alertSeverity?._id?.toString(),
|
|
1535
|
-
alertSeverityName: alert.alertSeverity?.name?.toString(),
|
|
1536
|
-
};
|
|
1537
|
-
alertDurationMetric.attributeKeys = TelemetryUtil.getAttributeKeys(
|
|
1538
|
-
alertDurationMetric.attributes,
|
|
1539
|
-
);
|
|
1631
|
+
alertDurationMetric.time = alertEndsAt;
|
|
1632
|
+
alertDurationMetric.timeUnixNano = OneUptimeDate.toUnixNano(
|
|
1633
|
+
alertDurationMetric.time,
|
|
1634
|
+
);
|
|
1635
|
+
alertDurationMetric.metricPointType = MetricPointType.Sum;
|
|
1636
|
+
alertDurationMetric.retentionDate = alertMetricRetentionDate;
|
|
1540
1637
|
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
OneUptimeDate.getCurrentDate();
|
|
1545
|
-
alertDurationMetric.timeUnixNano = OneUptimeDate.toUnixNano(
|
|
1546
|
-
alertDurationMetric.time,
|
|
1547
|
-
);
|
|
1548
|
-
alertDurationMetric.metricPointType = MetricPointType.Sum;
|
|
1549
|
-
alertDurationMetric.retentionDate = alertMetricRetentionDate;
|
|
1638
|
+
itemsToSave.push(alertDurationMetric);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1550
1641
|
|
|
1551
|
-
|
|
1642
|
+
// write-once: a refresh that finds nothing new inserts nothing.
|
|
1643
|
+
if (itemsToSave.length > 0) {
|
|
1644
|
+
await MetricService.createMany({
|
|
1645
|
+
items: itemsToSave,
|
|
1646
|
+
props: {
|
|
1647
|
+
isRoot: true,
|
|
1648
|
+
},
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1552
1651
|
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1652
|
+
TelemetryUtil.indexMetricNameServiceNameMap({
|
|
1653
|
+
metricNameServiceNameMap: metricTypesMap,
|
|
1654
|
+
projectId: alert.projectId,
|
|
1655
|
+
}).catch((err: Error) => {
|
|
1656
|
+
logger.error(err, {
|
|
1657
|
+
projectId: alert.projectId?.toString(),
|
|
1658
|
+
alertId: alert.id?.toString(),
|
|
1659
|
+
} as LogAttributes);
|
|
1660
|
+
});
|
|
1661
|
+
} finally {
|
|
1662
|
+
if (metricRefreshMutex) {
|
|
1663
|
+
try {
|
|
1664
|
+
await Semaphore.release(metricRefreshMutex);
|
|
1665
|
+
} catch (err) {
|
|
1666
|
+
logger.error(
|
|
1667
|
+
err as Error,
|
|
1668
|
+
{
|
|
1669
|
+
projectId: alert.projectId?.toString(),
|
|
1670
|
+
alertId: alert.id?.toString(),
|
|
1671
|
+
} as LogAttributes,
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1558
1675
|
}
|
|
1559
|
-
|
|
1560
|
-
await MetricService.createMany({
|
|
1561
|
-
items: itemsToSave,
|
|
1562
|
-
props: {
|
|
1563
|
-
isRoot: true,
|
|
1564
|
-
},
|
|
1565
|
-
});
|
|
1566
|
-
|
|
1567
|
-
TelemetryUtil.indexMetricNameServiceNameMap({
|
|
1568
|
-
metricNameServiceNameMap: metricTypesMap,
|
|
1569
|
-
projectId: alert.projectId,
|
|
1570
|
-
}).catch((err: Error) => {
|
|
1571
|
-
logger.error(err, {
|
|
1572
|
-
projectId: alert.projectId?.toString(),
|
|
1573
|
-
alertId: alert.id?.toString(),
|
|
1574
|
-
} as LogAttributes);
|
|
1575
|
-
});
|
|
1576
1676
|
}
|
|
1577
1677
|
|
|
1578
1678
|
@CaptureSpan()
|