Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
Expand Down Expand Up @@ -186,6 +185,13 @@ public void saveData(CollectRep.MetricsData metricsData) {
metricsData.getId(), metricsData.getApp(), metricsData.getMetrics());
return;
}
var managedLabelCollisions =
VictoriaMetricsDataStorage.findManagedLabelCollisions(metricsData.getLabels());
if (!managedLabelCollisions.isEmpty()) {
log.error("[warehouse victoria-metrics] reject metrics data {} because custom labels contain "
+ "HertzBeat-managed keys {}.", metricsData.getId(), managedLabelCollisions);
return;
}
Map<String, String> defaultLabels = Maps.newHashMapWithExpectedSize(8);
defaultLabels.put(MONITOR_METRICS_KEY, metricsData.getMetrics());
boolean isPrometheusAuto;
Expand Down Expand Up @@ -243,10 +249,7 @@ public void saveData(CollectRep.MetricsData metricsData) {
}
labels.put(LABEL_KEY_MONITOR_ID, String.valueOf(metricsData.getId()));
// add customized labels as identifier
var customizedLabels = metricsData.getLabels();
if (!ObjectUtils.isEmpty(customizedLabels)) {
labels.putAll(customizedLabels);
}
VictoriaMetricsDataStorage.addCustomizedLabels(labels, metricsData.getLabels());
VictoriaMetricsDataStorage.VictoriaMetricsContent content = VictoriaMetricsDataStorage.VictoriaMetricsContent.builder()
.metric(new HashMap<>(labels))
.values(new Double[]{entry.getValue()})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -98,6 +100,11 @@ public class VictoriaMetricsDataStorage extends AbstractHistoryDataStorage {
private static final String SPILT = "_";
private static final String MONITOR_METRICS_KEY = "__metrics__";
private static final String MONITOR_METRIC_KEY = "__metric__";
private static final Set<String> MANAGED_LABEL_KEYS = Set.of(
LABEL_KEY_NAME,
LABEL_KEY_MONITOR_ID,
MONITOR_METRICS_KEY,
MONITOR_METRIC_KEY);
private static final long MAX_WAIT_MS = 500L;
private static final int MAX_RETRIES = 3;

Expand Down Expand Up @@ -170,6 +177,12 @@ public void saveData(CollectRep.MetricsData metricsData) {
metricsData.getId(), metricsData.getApp(), metricsData.getMetrics());
return;
}
Set<String> managedLabelCollisions = findManagedLabelCollisions(metricsData.getLabels());
if (!managedLabelCollisions.isEmpty()) {
log.error("[warehouse victoria-metrics] reject metrics data {} because custom labels contain "
+ "HertzBeat-managed keys {}.", metricsData.getId(), managedLabelCollisions);
return;
}
Map<String, String> defaultLabels = Maps.newHashMapWithExpectedSize(8);
defaultLabels.put(MONITOR_METRICS_KEY, metricsData.getMetrics());
boolean isPrometheusAuto = false;
Expand Down Expand Up @@ -226,10 +239,7 @@ public void saveData(CollectRep.MetricsData metricsData) {
}
labels.put(LABEL_KEY_MONITOR_ID, String.valueOf(metricsData.getId()));
// add customized labels as identifier
var customizedLabels = metricsData.getLabels();
if (!ObjectUtils.isEmpty(customizedLabels)) {
labels.putAll(customizedLabels);
}
addCustomizedLabels(labels, metricsData.getLabels());
VictoriaMetricsContent content = VictoriaMetricsContent.builder()
.metric(new HashMap<>(labels))
.values(new Double[]{entry.getValue()})
Expand All @@ -255,6 +265,27 @@ public void saveData(CollectRep.MetricsData metricsData) {
sendVictoriaMetrics(contentList);
}

static void addCustomizedLabels(Map<String, String> labels, Map<String, String> customizedLabels) {
if (ObjectUtils.isEmpty(customizedLabels)) {
return;
}
Set<String> managedLabelCollisions = findManagedLabelCollisions(customizedLabels);
if (!managedLabelCollisions.isEmpty()) {
throw new IllegalArgumentException(
"Custom labels contain HertzBeat-managed keys " + managedLabelCollisions);
}
labels.putAll(customizedLabels);
}

static Set<String> findManagedLabelCollisions(Map<String, String> customizedLabels) {
if (ObjectUtils.isEmpty(customizedLabels)) {
return Set.of();
}
Set<String> collisions = new TreeSet<>(customizedLabels.keySet());
collisions.retainAll(MANAGED_LABEL_KEYS);
return collisions;
}

@Override
public void destroy() {
if (metricsFlushTimer != null && !metricsFlushTimer.isStop()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.apache.hertzbeat.common.entity.arrow.ArrowCell;
import org.apache.hertzbeat.common.entity.arrow.RowWrapper;
import org.apache.hertzbeat.common.entity.message.CollectRep;
import org.apache.hertzbeat.common.util.JsonUtil;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
Expand All @@ -48,16 +49,20 @@
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/**
* Test case for {@link VictoriaMetricsDataStorage}
*/
@ExtendWith(MockitoExtension.class)
@ExtendWith({MockitoExtension.class, OutputCaptureExtension.class})
@MockitoSettings(strictness = Strictness.LENIENT)
class VictoriaMetricsDataStorageTest {

Expand All @@ -73,6 +78,7 @@ class VictoriaMetricsDataStorageTest {
private VictoriaMetricsDataStorage victoriaMetricsDataStorage;

private final AtomicInteger postForEntityCount = new AtomicInteger(0);
private final AtomicReference<String> lastPayload = new AtomicReference<>();

@BeforeEach
void setUp() {
Expand All @@ -97,6 +103,10 @@ void setUp() {
eq(String.class)
)).thenAnswer(invocation -> {
postForEntityCount.incrementAndGet();
HttpEntity<?> httpEntity = invocation.getArgument(1);
if (httpEntity.getBody() instanceof String payload) {
lastPayload.set(payload);
}
return responseEntity;
});
}
Expand Down Expand Up @@ -184,6 +194,50 @@ void testMultiThreadSaveDataBySize() {
.isGreaterThanOrEqualTo(threadCount * writeSize / bufferSize));
}

@Test
void existingJobAndInstanceLabelsKeepTheirSeriesIdentity() {
when(victoriaMetricsProperties.insert()).thenReturn(new VictoriaMetricsProperties.InsertConfig(
1, Integer.MAX_VALUE, new VictoriaMetricsProperties.Compression(false)));
CollectRep.MetricsData metricsData = generateMockedMetricsData();
when(metricsData.getLabels()).thenReturn(Map.of(
"job", "custom-job",
"instance", "custom-instance",
"region", "west"));
victoriaMetricsDataStorage = new VictoriaMetricsDataStorage(victoriaMetricsProperties, restTemplate);

victoriaMetricsDataStorage.saveData(metricsData);

Awaitility.await()
.atMost(5, TimeUnit.SECONDS)
.untilAsserted(() -> assertThat(postForEntityCount.get()).isEqualTo(1));
VictoriaMetricsDataStorage.VictoriaMetricsContent content =
JsonUtil.fromJson(lastPayload.get().trim(), VictoriaMetricsDataStorage.VictoriaMetricsContent.class);
assertThat(content.getMetric())
.containsEntry("job", "custom-job")
.containsEntry("instance", "custom-instance")
.containsEntry("region", "west");
}

@Test
void managedLabelCollisionsRejectTheBatchWithDiagnostics(CapturedOutput output) {
when(victoriaMetricsProperties.insert()).thenReturn(new VictoriaMetricsProperties.InsertConfig(
1, Integer.MAX_VALUE, new VictoriaMetricsProperties.Compression(false)));
CollectRep.MetricsData metricsData = generateMockedMetricsData();
when(metricsData.getLabels()).thenReturn(Map.of(
"__name__", "custom-name",
"__monitor_id__", "custom-monitor"));
victoriaMetricsDataStorage = new VictoriaMetricsDataStorage(victoriaMetricsProperties, restTemplate);

victoriaMetricsDataStorage.saveData(metricsData);

assertThat(postForEntityCount.get()).isZero();
assertThat(output.getAll())
.contains("__name__")
.contains("__monitor_id__")
.doesNotContain("custom-name")
.doesNotContain("custom-monitor");
}

@AfterEach
void stop() {
if (victoriaMetricsDataStorage != null) {
Expand All @@ -200,6 +254,8 @@ public static CollectRep.MetricsData generateMockedMetricsData() {
when(mockMetricsData.getTime()).thenReturn(System.currentTimeMillis());
when(mockMetricsData.getCode()).thenReturn(CollectRep.Code.SUCCESS);
when(mockMetricsData.getApp()).thenReturn("app");
when(mockMetricsData.getInstance()).thenReturn("storage-instance");
when(mockMetricsData.getLabels()).thenReturn(Map.of());

CollectRep.ValueRow mockValueRow = Mockito.mock(CollectRep.ValueRow.class);
List<String> columnValues = List.of("server-test-01", "68.7");
Expand Down
18 changes: 18 additions & 0 deletions home/docs/start/victoria-metrics-init.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,24 @@ warehouse:

Once configured, restart HertzBeat to connect to the VictoriaMetrics cluster.

### Custom Label Collision Policy

Monitor custom labels keep their existing Prometheus semantics when HertzBeat
writes to VictoriaMetrics:

- `job`, `instance`, and ordinary custom labels continue to use the configured
custom values. Upgrading does not rename these labels or move new samples to
a different label set.
- `__name__`, `__monitor_id__`, `__metrics__`, and `__metric__` are managed by
HertzBeat and cannot be used as monitor custom-label keys. If one is present,
HertzBeat rejects that metrics batch and logs the conflicting key names
without logging their values.

Before upgrading, inspect monitor custom labels and rename any of the four
HertzBeat-managed keys. Existing VictoriaMetrics series are not rewritten.
No migration is needed for monitors that use `job`, `instance`, or other
custom labels.

### FAQ

1. Do both the time series databases need to be configured? Can they both be used?
Expand Down
Loading