@oneuptime/common 11.3.6 → 11.3.8

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.
@@ -31,6 +31,7 @@ import AlertOwnerUser from "../../Models/DatabaseModels/AlertOwnerUser";
31
31
  import AlertStateTimeline from "../../Models/DatabaseModels/AlertStateTimeline";
32
32
  import { IsBillingEnabled } from "../EnvironmentConfig";
33
33
  import logger from "../Utils/Logger";
34
+ import Semaphore from "../Infrastructure/Semaphore";
34
35
  import TelemetryUtil from "../Utils/Telemetry/Telemetry";
35
36
  import MetricService from "./MetricService";
36
37
  import GlobalConfigService from "./GlobalConfigService";
@@ -979,7 +980,7 @@ ${alertSeverity.name}
979
980
  return Service.DEFAULT_METRIC_RETENTION_DAYS;
980
981
  }
981
982
  async refreshAlertMetrics(data) {
982
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7;
983
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11;
983
984
  const alert = await this.findOneById({
984
985
  id: data.alertId,
985
986
  select: {
@@ -1028,181 +1029,274 @@ ${alertSeverity.name}
1028
1029
  },
1029
1030
  });
1030
1031
  const firstAlertStateTimeline = alertStateTimelines[0];
1031
- // delete all the alert metrics with this alert id because it's a refresh
1032
- await MetricService.deleteBy({
1033
- query: {
1034
- primaryEntityId: data.alertId,
1035
- },
1036
- props: {
1037
- isRoot: true,
1038
- },
1039
- });
1040
- const itemsToSave = [];
1041
- const metricTypesMap = {};
1042
- const metricRetentionDays = await this.getMetricRetentionDays();
1043
- const alertMetricRetentionDate = OneUptimeDate.addRemoveDays(OneUptimeDate.getCurrentDate(), metricRetentionDays);
1044
- // now we need to create new metrics for this alert - TimeToAcknowledge, TimeToResolve, AlertCount, AlertDuration
1045
- const alertStartsAt = (firstAlertStateTimeline === null || firstAlertStateTimeline === void 0 ? void 0 : firstAlertStateTimeline.startsAt) ||
1046
- alert.createdAt ||
1047
- OneUptimeDate.getCurrentDate();
1048
- const alertCountMetric = new Metric();
1049
- alertCountMetric.projectId = alert.projectId;
1050
- alertCountMetric.primaryEntityId = alert.id;
1051
- alertCountMetric.primaryEntityType = ServiceType.Alert;
1052
- alertCountMetric.name = AlertMetricType.AlertCount;
1053
- alertCountMetric.value = 1;
1054
- alertCountMetric.attributes = {
1055
- alertId: data.alertId.toString(),
1056
- projectId: alert.projectId.toString(),
1057
- monitorId: (_b = (_a = alert.monitor) === null || _a === void 0 ? void 0 : _a._id) === null || _b === void 0 ? void 0 : _b.toString(),
1058
- monitorName: (_d = (_c = alert.monitor) === null || _c === void 0 ? void 0 : _c.name) === null || _d === void 0 ? void 0 : _d.toString(),
1059
- alertSeverityId: (_f = (_e = alert.alertSeverity) === null || _e === void 0 ? void 0 : _e._id) === null || _f === void 0 ? void 0 : _f.toString(),
1060
- alertSeverityName: (_h = (_g = alert.alertSeverity) === null || _g === void 0 ? void 0 : _g.name) === null || _h === void 0 ? void 0 : _h.toString(),
1061
- };
1062
- alertCountMetric.attributeKeys = TelemetryUtil.getAttributeKeys(alertCountMetric.attributes);
1063
- alertCountMetric.time = alertStartsAt;
1064
- alertCountMetric.timeUnixNano = OneUptimeDate.toUnixNano(alertCountMetric.time);
1065
- alertCountMetric.metricPointType = MetricPointType.Sum;
1066
- alertCountMetric.retentionDate = alertMetricRetentionDate;
1067
- itemsToSave.push(alertCountMetric);
1068
- const metricType = new MetricType();
1069
- metricType.name = alertCountMetric.name;
1070
- metricType.description = "Number of alerts created";
1071
- metricType.unit = "";
1072
- metricType.services = [];
1073
- metricTypesMap[alertCountMetric.name] = metricType;
1074
- // is the alert acknowledged?
1075
- const isAlertAcknowledged = alertStateTimelines.some((timeline) => {
1076
- var _a;
1077
- return (_a = timeline.alertState) === null || _a === void 0 ? void 0 : _a.isAcknowledgedState;
1078
- });
1079
- if (isAlertAcknowledged) {
1080
- const ackAlertStateTimeline = alertStateTimelines.find((timeline) => {
1081
- var _a;
1082
- return (_a = timeline.alertState) === null || _a === void 0 ? void 0 : _a.isAcknowledgedState;
1032
+ /*
1033
+ * Serialize concurrent refreshes for this alert across pods. The
1034
+ * write-once guard below is read-then-insert (findBy existing, then a
1035
+ * conditional createMany). Both call sites fire refresh fire-and-forget
1036
+ * after releasing the per-alert state-timeline mutex, so two
1037
+ * close-together transitions (e.g. auto-acknowledge then auto-resolve)
1038
+ * could interleave: both read a snapshot missing a metric and both
1039
+ * insert it. The Metric table is a plain MergeTree with no row collapse,
1040
+ * so those duplicates would be permanent and re-inflate the very Sum/Avg
1041
+ * widgets this change fixes. A best-effort distributed lock keyed on the
1042
+ * alert makes the read+insert atomic; if the lock can't be taken we still
1043
+ * proceed (the existence check alone covers the common sequential case).
1044
+ */
1045
+ let metricRefreshMutex = null;
1046
+ try {
1047
+ metricRefreshMutex = await Semaphore.lock({
1048
+ key: data.alertId.toString(),
1049
+ namespace: "AlertService.refreshAlertMetrics",
1050
+ lockTimeout: 30000,
1051
+ });
1052
+ }
1053
+ catch (err) {
1054
+ logger.error(err, {
1055
+ projectId: (_a = alert.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
1056
+ alertId: (_b = alert.id) === null || _b === void 0 ? void 0 : _b.toString(),
1083
1057
  });
1084
- if (ackAlertStateTimeline) {
1085
- const timeToAcknowledgeMetric = new Metric();
1086
- timeToAcknowledgeMetric.projectId = alert.projectId;
1087
- timeToAcknowledgeMetric.primaryEntityId = alert.id;
1088
- timeToAcknowledgeMetric.primaryEntityType = ServiceType.Alert;
1089
- timeToAcknowledgeMetric.name = AlertMetricType.TimeToAcknowledge;
1090
- timeToAcknowledgeMetric.value = OneUptimeDate.getDifferenceInSeconds((ackAlertStateTimeline === null || ackAlertStateTimeline === void 0 ? void 0 : ackAlertStateTimeline.startsAt) || OneUptimeDate.getCurrentDate(), alertStartsAt);
1091
- timeToAcknowledgeMetric.attributes = {
1058
+ }
1059
+ try {
1060
+ /*
1061
+ * Write-once metrics — no deletes.
1062
+ *
1063
+ * The raw Metric table (`MetricItemV3`) feeds an AggregatingMergeTree
1064
+ * materialized view (`MetricItemAggMV1m_mv`) that accumulates
1065
+ * `sumState`/`countState` per (projectId, name, primaryEntityId,
1066
+ * minute). The MV trigger fires only on INSERT — an `ALTER ... DELETE`
1067
+ * mutation on the source rolls back nothing in the MV. So the old
1068
+ * "delete every metric for this alert, then re-insert" refresh both
1069
+ * (a) inflated those rollups and (b) issued one heavy `ALTER ... DELETE`
1070
+ * mutation per state transition per alert — a mutation storm.
1071
+ *
1072
+ * Every alert metric has a single eventually-final value: AlertCount is
1073
+ * constant `value = 1`; MTTA/MTTR/duration are each fixed once the
1074
+ * relevant state transition has happened. So we load what we've already
1075
+ * emitted for this alert and insert each metric exactly once, when it
1076
+ * becomes final. Repeat refreshes that find nothing new are no-ops.
1077
+ */
1078
+ const existingMetricNames = new Set((await MetricService.findBy({
1079
+ query: {
1080
+ projectId: alert.projectId,
1081
+ primaryEntityId: data.alertId,
1082
+ },
1083
+ select: {
1084
+ name: true,
1085
+ },
1086
+ skip: 0,
1087
+ limit: LIMIT_PER_PROJECT,
1088
+ props: {
1089
+ isRoot: true,
1090
+ },
1091
+ }))
1092
+ .map((metric) => {
1093
+ return metric.name;
1094
+ })
1095
+ .filter(Boolean));
1096
+ const itemsToSave = [];
1097
+ const metricTypesMap = {};
1098
+ const metricRetentionDays = await this.getMetricRetentionDays();
1099
+ const alertMetricRetentionDate = OneUptimeDate.addRemoveDays(OneUptimeDate.getCurrentDate(), metricRetentionDays);
1100
+ // now we need to create new metrics for this alert - TimeToAcknowledge, TimeToResolve, AlertCount, AlertDuration
1101
+ const alertStartsAt = (firstAlertStateTimeline === null || firstAlertStateTimeline === void 0 ? void 0 : firstAlertStateTimeline.startsAt) ||
1102
+ alert.createdAt ||
1103
+ OneUptimeDate.getCurrentDate();
1104
+ // register the metric type so the catalog stays complete across refreshes.
1105
+ const alertCountMetricType = new MetricType();
1106
+ alertCountMetricType.name = AlertMetricType.AlertCount;
1107
+ alertCountMetricType.description = "Number of alerts created";
1108
+ alertCountMetricType.unit = "";
1109
+ alertCountMetricType.services = [];
1110
+ metricTypesMap[AlertMetricType.AlertCount] = alertCountMetricType;
1111
+ // write-once: AlertCount is a constant value=1 keyed by the alert.
1112
+ if (!existingMetricNames.has(AlertMetricType.AlertCount)) {
1113
+ const alertCountMetric = new Metric();
1114
+ alertCountMetric.projectId = alert.projectId;
1115
+ alertCountMetric.primaryEntityId = alert.id;
1116
+ alertCountMetric.primaryEntityType = ServiceType.Alert;
1117
+ alertCountMetric.name = AlertMetricType.AlertCount;
1118
+ alertCountMetric.value = 1;
1119
+ alertCountMetric.attributes = {
1092
1120
  alertId: data.alertId.toString(),
1093
1121
  projectId: alert.projectId.toString(),
1094
- monitorId: (_k = (_j = alert.monitor) === null || _j === void 0 ? void 0 : _j._id) === null || _k === void 0 ? void 0 : _k.toString(),
1095
- monitorName: (_m = (_l = alert.monitor) === null || _l === void 0 ? void 0 : _l.name) === null || _m === void 0 ? void 0 : _m.toString(),
1096
- alertSeverityId: (_p = (_o = alert.alertSeverity) === null || _o === void 0 ? void 0 : _o._id) === null || _p === void 0 ? void 0 : _p.toString(),
1097
- alertSeverityName: (_r = (_q = alert.alertSeverity) === null || _q === void 0 ? void 0 : _q.name) === null || _r === void 0 ? void 0 : _r.toString(),
1122
+ monitorId: (_d = (_c = alert.monitor) === null || _c === void 0 ? void 0 : _c._id) === null || _d === void 0 ? void 0 : _d.toString(),
1123
+ monitorName: (_f = (_e = alert.monitor) === null || _e === void 0 ? void 0 : _e.name) === null || _f === void 0 ? void 0 : _f.toString(),
1124
+ alertSeverityId: (_h = (_g = alert.alertSeverity) === null || _g === void 0 ? void 0 : _g._id) === null || _h === void 0 ? void 0 : _h.toString(),
1125
+ alertSeverityName: (_k = (_j = alert.alertSeverity) === null || _j === void 0 ? void 0 : _j.name) === null || _k === void 0 ? void 0 : _k.toString(),
1098
1126
  };
1099
- timeToAcknowledgeMetric.attributeKeys = TelemetryUtil.getAttributeKeys(timeToAcknowledgeMetric.attributes);
1100
- timeToAcknowledgeMetric.time =
1101
- (ackAlertStateTimeline === null || ackAlertStateTimeline === void 0 ? void 0 : ackAlertStateTimeline.startsAt) ||
1102
- alert.createdAt ||
1103
- OneUptimeDate.getCurrentDate();
1104
- timeToAcknowledgeMetric.timeUnixNano = OneUptimeDate.toUnixNano(timeToAcknowledgeMetric.time);
1105
- timeToAcknowledgeMetric.metricPointType = MetricPointType.Sum;
1106
- timeToAcknowledgeMetric.retentionDate = alertMetricRetentionDate;
1107
- itemsToSave.push(timeToAcknowledgeMetric);
1108
- const metricType = new MetricType();
1109
- metricType.name = timeToAcknowledgeMetric.name;
1110
- metricType.description = "Time taken to acknowledge the alert";
1111
- metricType.unit = "seconds";
1112
- metricTypesMap[timeToAcknowledgeMetric.name] = metricType;
1127
+ alertCountMetric.attributeKeys = TelemetryUtil.getAttributeKeys(alertCountMetric.attributes);
1128
+ alertCountMetric.time = alertStartsAt;
1129
+ alertCountMetric.timeUnixNano = OneUptimeDate.toUnixNano(alertCountMetric.time);
1130
+ alertCountMetric.metricPointType = MetricPointType.Sum;
1131
+ alertCountMetric.retentionDate = alertMetricRetentionDate;
1132
+ itemsToSave.push(alertCountMetric);
1113
1133
  }
1114
- }
1115
- // time to resolve
1116
- const isAlertResolved = alertStateTimelines.some((timeline) => {
1117
- var _a;
1118
- return (_a = timeline.alertState) === null || _a === void 0 ? void 0 : _a.isResolvedState;
1119
- });
1120
- if (isAlertResolved) {
1134
+ // is the alert acknowledged?
1135
+ const isAlertAcknowledged = alertStateTimelines.some((timeline) => {
1136
+ var _a;
1137
+ return (_a = timeline.alertState) === null || _a === void 0 ? void 0 : _a.isAcknowledgedState;
1138
+ });
1139
+ if (isAlertAcknowledged) {
1140
+ const ackAlertStateTimeline = alertStateTimelines.find((timeline) => {
1141
+ var _a;
1142
+ return (_a = timeline.alertState) === null || _a === void 0 ? void 0 : _a.isAcknowledgedState;
1143
+ });
1144
+ if (ackAlertStateTimeline) {
1145
+ // register the metric type so the catalog stays complete across refreshes.
1146
+ const metricType = new MetricType();
1147
+ metricType.name = AlertMetricType.TimeToAcknowledge;
1148
+ metricType.description = "Time taken to acknowledge the alert";
1149
+ metricType.unit = "seconds";
1150
+ metricTypesMap[AlertMetricType.TimeToAcknowledge] = metricType;
1151
+ // write-once: MTTA is fixed once the alert is first acknowledged.
1152
+ if (!existingMetricNames.has(AlertMetricType.TimeToAcknowledge)) {
1153
+ const timeToAcknowledgeMetric = new Metric();
1154
+ timeToAcknowledgeMetric.projectId = alert.projectId;
1155
+ timeToAcknowledgeMetric.primaryEntityId = alert.id;
1156
+ timeToAcknowledgeMetric.primaryEntityType = ServiceType.Alert;
1157
+ timeToAcknowledgeMetric.name = AlertMetricType.TimeToAcknowledge;
1158
+ timeToAcknowledgeMetric.value =
1159
+ OneUptimeDate.getDifferenceInSeconds((ackAlertStateTimeline === null || ackAlertStateTimeline === void 0 ? void 0 : ackAlertStateTimeline.startsAt) ||
1160
+ OneUptimeDate.getCurrentDate(), alertStartsAt);
1161
+ timeToAcknowledgeMetric.attributes = {
1162
+ alertId: data.alertId.toString(),
1163
+ projectId: alert.projectId.toString(),
1164
+ monitorId: (_m = (_l = alert.monitor) === null || _l === void 0 ? void 0 : _l._id) === null || _m === void 0 ? void 0 : _m.toString(),
1165
+ monitorName: (_p = (_o = alert.monitor) === null || _o === void 0 ? void 0 : _o.name) === null || _p === void 0 ? void 0 : _p.toString(),
1166
+ alertSeverityId: (_r = (_q = alert.alertSeverity) === null || _q === void 0 ? void 0 : _q._id) === null || _r === void 0 ? void 0 : _r.toString(),
1167
+ alertSeverityName: (_t = (_s = alert.alertSeverity) === null || _s === void 0 ? void 0 : _s.name) === null || _t === void 0 ? void 0 : _t.toString(),
1168
+ };
1169
+ timeToAcknowledgeMetric.attributeKeys =
1170
+ TelemetryUtil.getAttributeKeys(timeToAcknowledgeMetric.attributes);
1171
+ timeToAcknowledgeMetric.time =
1172
+ (ackAlertStateTimeline === null || ackAlertStateTimeline === void 0 ? void 0 : ackAlertStateTimeline.startsAt) ||
1173
+ alert.createdAt ||
1174
+ OneUptimeDate.getCurrentDate();
1175
+ timeToAcknowledgeMetric.timeUnixNano = OneUptimeDate.toUnixNano(timeToAcknowledgeMetric.time);
1176
+ timeToAcknowledgeMetric.metricPointType = MetricPointType.Sum;
1177
+ timeToAcknowledgeMetric.retentionDate = alertMetricRetentionDate;
1178
+ itemsToSave.push(timeToAcknowledgeMetric);
1179
+ }
1180
+ }
1181
+ }
1182
+ // time to resolve
1183
+ const isAlertResolved = alertStateTimelines.some((timeline) => {
1184
+ var _a;
1185
+ return (_a = timeline.alertState) === null || _a === void 0 ? void 0 : _a.isResolvedState;
1186
+ });
1121
1187
  const resolvedAlertStateTimeline = alertStateTimelines.find((timeline) => {
1122
1188
  var _a;
1123
1189
  return (_a = timeline.alertState) === null || _a === void 0 ? void 0 : _a.isResolvedState;
1124
1190
  });
1125
- if (resolvedAlertStateTimeline) {
1126
- const timeToResolveMetric = new Metric();
1127
- timeToResolveMetric.projectId = alert.projectId;
1128
- timeToResolveMetric.primaryEntityId = alert.id;
1129
- timeToResolveMetric.primaryEntityType = ServiceType.Alert;
1130
- timeToResolveMetric.name = AlertMetricType.TimeToResolve;
1131
- timeToResolveMetric.value = OneUptimeDate.getDifferenceInSeconds((resolvedAlertStateTimeline === null || resolvedAlertStateTimeline === void 0 ? void 0 : resolvedAlertStateTimeline.startsAt) ||
1132
- OneUptimeDate.getCurrentDate(), alertStartsAt);
1133
- timeToResolveMetric.attributes = {
1134
- alertId: data.alertId.toString(),
1135
- projectId: alert.projectId.toString(),
1136
- monitorId: (_t = (_s = alert.monitor) === null || _s === void 0 ? void 0 : _s._id) === null || _t === void 0 ? void 0 : _t.toString(),
1137
- monitorName: (_v = (_u = alert.monitor) === null || _u === void 0 ? void 0 : _u.name) === null || _v === void 0 ? void 0 : _v.toString(),
1138
- alertSeverityId: (_x = (_w = alert.alertSeverity) === null || _w === void 0 ? void 0 : _w._id) === null || _x === void 0 ? void 0 : _x.toString(),
1139
- alertSeverityName: (_z = (_y = alert.alertSeverity) === null || _y === void 0 ? void 0 : _y.name) === null || _z === void 0 ? void 0 : _z.toString(),
1140
- };
1141
- timeToResolveMetric.attributeKeys = TelemetryUtil.getAttributeKeys(timeToResolveMetric.attributes);
1142
- timeToResolveMetric.time =
1143
- (resolvedAlertStateTimeline === null || resolvedAlertStateTimeline === void 0 ? void 0 : resolvedAlertStateTimeline.startsAt) ||
1144
- alert.createdAt ||
1145
- OneUptimeDate.getCurrentDate();
1146
- timeToResolveMetric.timeUnixNano = OneUptimeDate.toUnixNano(timeToResolveMetric.time);
1147
- timeToResolveMetric.metricPointType = MetricPointType.Sum;
1148
- timeToResolveMetric.retentionDate = alertMetricRetentionDate;
1149
- itemsToSave.push(timeToResolveMetric);
1191
+ if (isAlertResolved && resolvedAlertStateTimeline) {
1192
+ // register the metric type so the catalog stays complete across refreshes.
1150
1193
  const metricType = new MetricType();
1151
- metricType.name = timeToResolveMetric.name;
1194
+ metricType.name = AlertMetricType.TimeToResolve;
1152
1195
  metricType.description = "Time taken to resolve the alert";
1153
1196
  metricType.unit = "seconds";
1154
- metricTypesMap[timeToResolveMetric.name] = metricType;
1197
+ metricTypesMap[AlertMetricType.TimeToResolve] = metricType;
1198
+ // write-once: MTTR is fixed once the alert is first resolved.
1199
+ if (!existingMetricNames.has(AlertMetricType.TimeToResolve)) {
1200
+ const timeToResolveMetric = new Metric();
1201
+ timeToResolveMetric.projectId = alert.projectId;
1202
+ timeToResolveMetric.primaryEntityId = alert.id;
1203
+ timeToResolveMetric.primaryEntityType = ServiceType.Alert;
1204
+ timeToResolveMetric.name = AlertMetricType.TimeToResolve;
1205
+ timeToResolveMetric.value = OneUptimeDate.getDifferenceInSeconds((resolvedAlertStateTimeline === null || resolvedAlertStateTimeline === void 0 ? void 0 : resolvedAlertStateTimeline.startsAt) ||
1206
+ OneUptimeDate.getCurrentDate(), alertStartsAt);
1207
+ timeToResolveMetric.attributes = {
1208
+ alertId: data.alertId.toString(),
1209
+ projectId: alert.projectId.toString(),
1210
+ monitorId: (_v = (_u = alert.monitor) === null || _u === void 0 ? void 0 : _u._id) === null || _v === void 0 ? void 0 : _v.toString(),
1211
+ monitorName: (_x = (_w = alert.monitor) === null || _w === void 0 ? void 0 : _w.name) === null || _x === void 0 ? void 0 : _x.toString(),
1212
+ alertSeverityId: (_z = (_y = alert.alertSeverity) === null || _y === void 0 ? void 0 : _y._id) === null || _z === void 0 ? void 0 : _z.toString(),
1213
+ alertSeverityName: (_1 = (_0 = alert.alertSeverity) === null || _0 === void 0 ? void 0 : _0.name) === null || _1 === void 0 ? void 0 : _1.toString(),
1214
+ };
1215
+ timeToResolveMetric.attributeKeys = TelemetryUtil.getAttributeKeys(timeToResolveMetric.attributes);
1216
+ timeToResolveMetric.time =
1217
+ (resolvedAlertStateTimeline === null || resolvedAlertStateTimeline === void 0 ? void 0 : resolvedAlertStateTimeline.startsAt) ||
1218
+ alert.createdAt ||
1219
+ OneUptimeDate.getCurrentDate();
1220
+ timeToResolveMetric.timeUnixNano = OneUptimeDate.toUnixNano(timeToResolveMetric.time);
1221
+ timeToResolveMetric.metricPointType = MetricPointType.Sum;
1222
+ timeToResolveMetric.retentionDate = alertMetricRetentionDate;
1223
+ itemsToSave.push(timeToResolveMetric);
1224
+ }
1225
+ }
1226
+ /*
1227
+ * Alert duration — write-once, finalized at resolution.
1228
+ *
1229
+ * Previously recomputed as (latest state transition − start) on every
1230
+ * refresh, which grew for open alerts and required delete + re-insert.
1231
+ * With no deletes we emit a single final duration once the alert reaches
1232
+ * a resolved state: (first resolution − start). An alert that is never
1233
+ * resolved has no duration row (its lifetime is not yet final).
1234
+ */
1235
+ if (isAlertResolved && resolvedAlertStateTimeline) {
1236
+ // register the metric type so the catalog stays complete across refreshes.
1237
+ const metricType = new MetricType();
1238
+ metricType.name = AlertMetricType.AlertDuration;
1239
+ metricType.description = "Duration of the alert";
1240
+ metricType.unit = "seconds";
1241
+ metricTypesMap[AlertMetricType.AlertDuration] = metricType;
1242
+ if (!existingMetricNames.has(AlertMetricType.AlertDuration)) {
1243
+ const alertEndsAt = resolvedAlertStateTimeline.startsAt ||
1244
+ OneUptimeDate.getCurrentDate();
1245
+ const alertDurationMetric = new Metric();
1246
+ alertDurationMetric.projectId = alert.projectId;
1247
+ alertDurationMetric.primaryEntityId = alert.id;
1248
+ alertDurationMetric.primaryEntityType = ServiceType.Alert;
1249
+ alertDurationMetric.name = AlertMetricType.AlertDuration;
1250
+ alertDurationMetric.value = OneUptimeDate.getDifferenceInSeconds(alertEndsAt, alertStartsAt);
1251
+ alertDurationMetric.attributes = {
1252
+ alertId: data.alertId.toString(),
1253
+ projectId: alert.projectId.toString(),
1254
+ monitorId: (_3 = (_2 = alert.monitor) === null || _2 === void 0 ? void 0 : _2._id) === null || _3 === void 0 ? void 0 : _3.toString(),
1255
+ monitorName: (_5 = (_4 = alert.monitor) === null || _4 === void 0 ? void 0 : _4.name) === null || _5 === void 0 ? void 0 : _5.toString(),
1256
+ alertSeverityId: (_7 = (_6 = alert.alertSeverity) === null || _6 === void 0 ? void 0 : _6._id) === null || _7 === void 0 ? void 0 : _7.toString(),
1257
+ alertSeverityName: (_9 = (_8 = alert.alertSeverity) === null || _8 === void 0 ? void 0 : _8.name) === null || _9 === void 0 ? void 0 : _9.toString(),
1258
+ };
1259
+ alertDurationMetric.attributeKeys = TelemetryUtil.getAttributeKeys(alertDurationMetric.attributes);
1260
+ alertDurationMetric.time = alertEndsAt;
1261
+ alertDurationMetric.timeUnixNano = OneUptimeDate.toUnixNano(alertDurationMetric.time);
1262
+ alertDurationMetric.metricPointType = MetricPointType.Sum;
1263
+ alertDurationMetric.retentionDate = alertMetricRetentionDate;
1264
+ itemsToSave.push(alertDurationMetric);
1265
+ }
1266
+ }
1267
+ // write-once: a refresh that finds nothing new inserts nothing.
1268
+ if (itemsToSave.length > 0) {
1269
+ await MetricService.createMany({
1270
+ items: itemsToSave,
1271
+ props: {
1272
+ isRoot: true,
1273
+ },
1274
+ });
1155
1275
  }
1276
+ TelemetryUtil.indexMetricNameServiceNameMap({
1277
+ metricNameServiceNameMap: metricTypesMap,
1278
+ projectId: alert.projectId,
1279
+ }).catch((err) => {
1280
+ var _a, _b;
1281
+ logger.error(err, {
1282
+ projectId: (_a = alert.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
1283
+ alertId: (_b = alert.id) === null || _b === void 0 ? void 0 : _b.toString(),
1284
+ });
1285
+ });
1156
1286
  }
1157
- // alert duration
1158
- const alertDurationMetric = new Metric();
1159
- const lastAlertStateTimeline = alertStateTimelines[alertStateTimelines.length - 1];
1160
- if (lastAlertStateTimeline) {
1161
- const alertEndsAt = lastAlertStateTimeline.startsAt || OneUptimeDate.getCurrentDate();
1162
- alertDurationMetric.projectId = alert.projectId;
1163
- alertDurationMetric.primaryEntityId = alert.id;
1164
- alertDurationMetric.primaryEntityType = ServiceType.Alert;
1165
- alertDurationMetric.name = AlertMetricType.AlertDuration;
1166
- alertDurationMetric.value = OneUptimeDate.getDifferenceInSeconds(alertEndsAt, alertStartsAt);
1167
- alertDurationMetric.attributes = {
1168
- alertId: data.alertId.toString(),
1169
- projectId: alert.projectId.toString(),
1170
- monitorId: (_1 = (_0 = alert.monitor) === null || _0 === void 0 ? void 0 : _0._id) === null || _1 === void 0 ? void 0 : _1.toString(),
1171
- monitorName: (_3 = (_2 = alert.monitor) === null || _2 === void 0 ? void 0 : _2.name) === null || _3 === void 0 ? void 0 : _3.toString(),
1172
- alertSeverityId: (_5 = (_4 = alert.alertSeverity) === null || _4 === void 0 ? void 0 : _4._id) === null || _5 === void 0 ? void 0 : _5.toString(),
1173
- alertSeverityName: (_7 = (_6 = alert.alertSeverity) === null || _6 === void 0 ? void 0 : _6.name) === null || _7 === void 0 ? void 0 : _7.toString(),
1174
- };
1175
- alertDurationMetric.attributeKeys = TelemetryUtil.getAttributeKeys(alertDurationMetric.attributes);
1176
- alertDurationMetric.time =
1177
- (lastAlertStateTimeline === null || lastAlertStateTimeline === void 0 ? void 0 : lastAlertStateTimeline.startsAt) ||
1178
- alert.createdAt ||
1179
- OneUptimeDate.getCurrentDate();
1180
- alertDurationMetric.timeUnixNano = OneUptimeDate.toUnixNano(alertDurationMetric.time);
1181
- alertDurationMetric.metricPointType = MetricPointType.Sum;
1182
- alertDurationMetric.retentionDate = alertMetricRetentionDate;
1183
- itemsToSave.push(alertDurationMetric);
1184
- const metricType = new MetricType();
1185
- metricType.name = alertDurationMetric.name;
1186
- metricType.description = "Duration of the alert";
1187
- metricType.unit = "seconds";
1188
- metricTypesMap[alertDurationMetric.name] = metricType;
1287
+ finally {
1288
+ if (metricRefreshMutex) {
1289
+ try {
1290
+ await Semaphore.release(metricRefreshMutex);
1291
+ }
1292
+ catch (err) {
1293
+ logger.error(err, {
1294
+ projectId: (_10 = alert.projectId) === null || _10 === void 0 ? void 0 : _10.toString(),
1295
+ alertId: (_11 = alert.id) === null || _11 === void 0 ? void 0 : _11.toString(),
1296
+ });
1297
+ }
1298
+ }
1189
1299
  }
1190
- await MetricService.createMany({
1191
- items: itemsToSave,
1192
- props: {
1193
- isRoot: true,
1194
- },
1195
- });
1196
- TelemetryUtil.indexMetricNameServiceNameMap({
1197
- metricNameServiceNameMap: metricTypesMap,
1198
- projectId: alert.projectId,
1199
- }).catch((err) => {
1200
- var _a, _b;
1201
- logger.error(err, {
1202
- projectId: (_a = alert.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
1203
- alertId: (_b = alert.id) === null || _b === void 0 ? void 0 : _b.toString(),
1204
- });
1205
- });
1206
1300
  }
1207
1301
  async isAlertResolved(data) {
1208
1302
  const alert = await this.findOneBy({