This is an automated email from the ASF dual-hosted git repository.
Duansg pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
The following commit(s) were added to refs/heads/master by this push:
new 4c9ebb7d0a maintenance: canonicalize alert label fingerprints (#4283)
4c9ebb7d0a is described below
commit 4c9ebb7d0a448dd03acd901fb2c67cecb2049661
Author: Logic <[email protected]>
AuthorDate: Mon Aug 3 13:51:02 2026 +0800
maintenance: canonicalize alert label fingerprints (#4283)
Co-authored-by: Duansg <[email protected]>
---
.../org/apache/hertzbeat/alert/util/AlertUtil.java | 34 ++++++++----
.../alert/calculate/AlarmCacheManagerTest.java | 27 +++++++++-
.../alert/reduce/AlarmCommonReduceTest.java | 24 +++++++++
.../apache/hertzbeat/alert/util/AlertUtilTest.java | 61 ++++++++++++++++++++++
4 files changed, 136 insertions(+), 10 deletions(-)
diff --git
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/util/AlertUtil.java
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/util/AlertUtil.java
index a1ed180c3f..06cc39600a 100644
---
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/util/AlertUtil.java
+++
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/util/AlertUtil.java
@@ -17,10 +17,8 @@
package org.apache.hertzbeat.alert.util;
-import java.util.Arrays;
-import java.util.List;
+import java.util.Comparator;
import java.util.Map;
-import java.util.Objects;
/**
* alert util
@@ -28,13 +26,31 @@ import java.util.Objects;
public class AlertUtil {
/**
- * calculate fingerprint
- * @param fingerPrints finger prints
+ * Calculate an in-memory alert cache coordinate.
+ *
+ * <p>This value is rebuilt from persisted alert labels when the process
+ * starts. It is not the durable {@code SingleAlert.fingerprint} used by
+ * persistence, grouping, silence, or inhibition.</p>
+ *
+ * @param fingerPrints labels used by the calculator cache
+ * @return deterministic cache coordinate
*/
public static String calculateFingerprint(Map<String, String>
fingerPrints) {
- List<String> keyList =
fingerPrints.keySet().stream().filter(Objects::nonNull).sorted().toList();
- List<String> valueList =
fingerPrints.values().stream().filter(Objects::nonNull).sorted().toList();
- return Arrays.hashCode(keyList.toArray(new String[0])) + "-"
- + Arrays.hashCode(valueList.toArray(new String[0]));
+ StringBuilder canonicalLabels = new StringBuilder();
+ fingerPrints.entrySet().stream()
+
.sorted(Map.Entry.comparingByKey(Comparator.nullsFirst(Comparator.naturalOrder())))
+ .forEach(entry -> {
+ appendLengthPrefixed(canonicalLabels, entry.getKey());
+ appendLengthPrefixed(canonicalLabels, entry.getValue());
+ });
+ return CryptoUtils.sha256Hex(canonicalLabels.toString());
+ }
+
+ private static void appendLengthPrefixed(StringBuilder target, String
value) {
+ if (value == null) {
+ target.append("-1:");
+ return;
+ }
+ target.append(value.length()).append(':').append(value);
}
}
diff --git
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/calculate/AlarmCacheManagerTest.java
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/calculate/AlarmCacheManagerTest.java
index 95a43ffbfa..c2a76092e3 100644
---
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/calculate/AlarmCacheManagerTest.java
+++
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/calculate/AlarmCacheManagerTest.java
@@ -34,6 +34,7 @@ import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.Mockito.when;
/**
@@ -132,4 +133,28 @@ public class AlarmCacheManagerTest {
historicalSingleAlert = alarmCacheManager.getFiring(4L, fingerprint);
assertNull(historicalSingleAlert);
}
-}
\ No newline at end of file
+
+ @Test
+ void restartShouldRebuildCacheKeyWithoutChangingPersistedFingerprint() {
+ Map<String, String> labels = Map.of(
+ CommonConstants.LABEL_DEFINE_ID, "7",
+ CommonConstants.LABEL_ALERT_NAME, "disk_full",
+ "instance", "db-1");
+ SingleAlert persistedAlert = SingleAlert.builder()
+ .id(99L)
+ .fingerprint("alertname:disk_full,define_id:7,instance:db-1")
+ .labels(labels)
+ .status(CommonConstants.ALERT_STATUS_FIRING)
+ .build();
+
when(singleAlertDao.querySingleAlertsByStatus(CommonConstants.ALERT_STATUS_FIRING))
+ .thenReturn(Collections.singletonList(persistedAlert));
+
+ alarmCacheManager = new AlarmCacheManager(singleAlertDao);
+ String rebuiltCacheKey = AlertUtil.calculateFingerprint(labels);
+ SingleAlert resolved = alarmCacheManager.removeFiring(7L,
rebuiltCacheKey);
+
+ assertSame(persistedAlert, resolved);
+ assertEquals("alertname:disk_full,define_id:7,instance:db-1",
resolved.getFingerprint());
+ assertNull(alarmCacheManager.getFiring(7L, rebuiltCacheKey));
+ }
+}
diff --git
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/reduce/AlarmCommonReduceTest.java
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/reduce/AlarmCommonReduceTest.java
index 0208188529..bd8cdfe217 100644
---
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/reduce/AlarmCommonReduceTest.java
+++
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/reduce/AlarmCommonReduceTest.java
@@ -17,6 +17,7 @@
package org.apache.hertzbeat.alert.reduce;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -28,6 +29,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
import org.junit.jupiter.api.AfterEach;
@@ -85,6 +87,28 @@ class AlarmCommonReduceTest {
assertTrue(virtualThread.get());
}
+ @Test
+ void durableFingerprintShouldRemainIndependentFromCalculatorCacheKey()
throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference<String> durableFingerprint = new AtomicReference<>();
+ doAnswer(invocation -> {
+ durableFingerprint.set(invocation.getArgument(0,
SingleAlert.class).getFingerprint());
+ latch.countDown();
+ return null;
+ }).when(alarmGroupReduce).processGroupAlert(any(SingleAlert.class));
+ SingleAlert alert = SingleAlert.builder()
+ .labels(new HashMap<>(Map.of(
+ "instance", "db-1",
+ "alertname", "disk_full",
+ "timestamp", "not-part-of-identity")))
+ .build();
+
+ alarmCommonReduce.reduceAndSendAlarm(alert);
+
+ assertTrue(latch.await(5, TimeUnit.SECONDS));
+ assertEquals("alertname:disk_full,instance:db-1",
durableFingerprint.get());
+ }
+
@Test
void testReduceAndSendAlarmQueuesWhenConcurrencyLimitReached() throws
Exception {
VirtualThreadProperties properties = new VirtualThreadProperties(
diff --git
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/util/AlertUtilTest.java
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/util/AlertUtilTest.java
new file mode 100644
index 0000000000..b634713068
--- /dev/null
+++
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/util/AlertUtilTest.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test case for {@link AlertUtil}.
+ */
+class AlertUtilTest {
+
+ @Test
+ void calculateFingerprintPreservesLabelPairing() {
+ Map<String, String> first = new LinkedHashMap<>();
+ first.put("environment", "production");
+ first.put("team", "payments");
+
+ Map<String, String> swapped = new LinkedHashMap<>();
+ swapped.put("environment", "payments");
+ swapped.put("team", "production");
+
+ assertNotEquals(
+ AlertUtil.calculateFingerprint(first),
+ AlertUtil.calculateFingerprint(swapped));
+ }
+
+ @Test
+ void calculateFingerprintIsIndependentOfMapIterationOrder() {
+ Map<String, String> first = new LinkedHashMap<>();
+ first.put("environment", "production");
+ first.put("team", "payments");
+
+ Map<String, String> reversed = new LinkedHashMap<>();
+ reversed.put("team", "payments");
+ reversed.put("environment", "production");
+
+ assertEquals(
+ AlertUtil.calculateFingerprint(first),
+ AlertUtil.calculateFingerprint(reversed));
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]