This is an automated email from the ASF dual-hosted git repository.
gavinchou pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 493259c368f [improvement](metrics) Encode version metrics with
fixed-width components (#66666)
493259c368f is described below
commit 493259c368f630406b51dfe0379875a1f50f35d6
Author: Gavin Chou <[email protected]>
AuthorDate: Tue Oct 20 15:27:59 2026 +0800
[improvement](metrics) Encode version metrics with fixed-width components
(#66666)
Related PR: #68655
Problem Summary:
The existing version gauges concatenate variable-width decimal
components with `0`. Their numeric ordering can disagree with version
ordering; for example, `4.99.0` is encoded as `409900`, which is greater
than `5.0.0` encoded as `50000`. Appending hotfix only when it is
positive also changes the value's magnitude.
This change treats `major`, `minor`, `patch`, and `hotfix` as four
base-100 digits. Every component therefore occupies exactly two decimal
positions: `5.1.2.3` is logically encoded as `05|01|02|03` and exported
as the numeric value `5010203`. Source generation rejects components
outside `0..99`, so the encoding remains unambiguous and
order-preserving.
BE and Cloud share one C++ encoder, FE applies the equivalent
calculation, and the existing metric names and build labels remain
unchanged. Unit tests cover representative values, ordering, and bounds;
the cross-process regression reconstructs the expected value from labels
for FE, BE, and Cloud.
### Prometheus exposition examples
The following examples assume build version `doris-5.1.2.3` and short
Git hash `abc1234`. Each version component occupies two decimal
positions, so `5.1.2.3` is logically encoded as `05|01|02|03` and
exported as the numeric value `5010203`.
FE (`/metrics`):
```text
# HELP doris_fe_version
# TYPE doris_fe_version gauge
doris_fe_version{version="doris-5.1.2.3", major="5", minor="1", patch="2",
hotfix="3", short_hash="abc1234"} 5010203
```
BE (`/brpc_metrics`):
```text
# HELP doris_be_version
# TYPE doris_be_version gauge
doris_be_version{version="doris-5.1.2.3",major="5",minor="1",patch="2",hotfix="3",short_hash="abc1234"}
5010203
```
Cloud meta-service/recycler (`/brpc_metrics`):
```text
# HELP doris_cloud_version
# TYPE doris_cloud_version gauge
doris_cloud_version{version="doris-5.1.2.3",major="5",minor="1",patch="2",hotfix="3",short_hash="abc1234"}
5010203
```
### Release note
Encode FE, BE, and Cloud version metric values with fixed-width base-100
components.
---
be/src/util/debug_util.cpp | 21 ++++++-----
be/test/util/debug_util_test.cpp | 20 ++++++-----
cloud/src/common/version_metrics.cpp | 17 +++++----
cloud/test/metric_test.cpp | 18 ++++++----
common/cpp/version_metrics.h | 41 ++++++++++++++++++++++
.../java/org/apache/doris/metric/MetricRepo.java | 23 ++++++++----
.../java/org/apache/doris/metric/MetricsTest.java | 19 +++++-----
gensrc/script/gen_build_version.sh | 17 +++++++++
.../suites/metrics_p0/test_version_metrics.groovy | 7 ++++
9 files changed, 136 insertions(+), 47 deletions(-)
diff --git a/be/src/util/debug_util.cpp b/be/src/util/debug_util.cpp
index 7362788c169..dd1ee2c7dc0 100644
--- a/be/src/util/debug_util.cpp
+++ b/be/src/util/debug_util.cpp
@@ -24,7 +24,6 @@
#include <gflags/gflags.h>
#include <stdint.h>
-#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <list>
@@ -36,6 +35,7 @@
#include "common/check.h"
#include "common/config.h"
#include "common/version_internal.h"
+#include "cpp/version_metrics.h"
#include "fmt/core.h"
#include "util/uid_util.h"
@@ -119,13 +119,18 @@ std::string hexdump(const char* buf, int len) {
namespace {
uint64_t get_be_version_metric_value() {
- std::stringstream ss;
- ss << version::doris_build_version_major() << 0 <<
version::doris_build_version_minor() << 0
- << version::doris_build_version_patch();
- if (version::doris_build_version_hotfix() > 0) {
- ss << 0 << version::doris_build_version_hotfix();
- }
- return std::strtoul(ss.str().c_str(), nullptr, 10);
+ const int64_t major = version::doris_build_version_major();
+ const int64_t minor = version::doris_build_version_minor();
+ const int64_t patch = version::doris_build_version_patch();
+ const int64_t hotfix = version::doris_build_version_hotfix();
+
+ // A component outside 0..99 would carry into its neighbor in the base-100
encoding. For
+ // example, patch 100 would be indistinguishable from incrementing minor
and using patch 0.
+ DORIS_CHECK(is_valid_version_metric_component(major));
+ DORIS_CHECK(is_valid_version_metric_component(minor));
+ DORIS_CHECK(is_valid_version_metric_component(patch));
+ DORIS_CHECK(is_valid_version_metric_component(hotfix));
+ return encode_version_metric_value(major, minor, patch, hotfix);
}
} // namespace
diff --git a/be/test/util/debug_util_test.cpp b/be/test/util/debug_util_test.cpp
index 983268a4282..cd1fe8ca11e 100644
--- a/be/test/util/debug_util_test.cpp
+++ b/be/test/util/debug_util_test.cpp
@@ -23,16 +23,22 @@
// brpc's header uses the butil types above without including their
definitions.
#include <brpc/builtin/prometheus_metrics_service.h>
-#include <cstdlib>
+#include <cstdint>
#include <sstream>
#include <string>
#include "common/config.h"
#include "common/version_internal.h"
+#include "cpp/version_metrics.h"
namespace doris {
TEST(DebugUtilTest, BeVersionMetricIsExportedWithBuildLabels) {
+ EXPECT_EQ(26000100ULL, encode_version_metric_value(26, 0, 1, 0));
+ EXPECT_EQ(26000101ULL, encode_version_metric_value(26, 0, 1, 1));
+ EXPECT_LT(encode_version_metric_value(4, 99, 0, 0),
encode_version_metric_value(5, 0, 0, 0));
+ EXPECT_FALSE(is_valid_version_metric_component(100));
+
config::bvar_max_dump_multi_dimension_metric_num = "5000";
init_be_version_metrics();
@@ -40,12 +46,9 @@ TEST(DebugUtilTest,
BeVersionMetricIsExportedWithBuildLabels) {
ASSERT_EQ(0, brpc::DumpPrometheusMetricsToIOBuf(&output));
const std::string body = output.to_string();
- std::stringstream value;
- value << version::doris_build_version_major() << 0 <<
version::doris_build_version_minor() << 0
- << version::doris_build_version_patch();
- if (version::doris_build_version_hotfix() > 0) {
- value << 0 << version::doris_build_version_hotfix();
- }
+ const uint64_t value = encode_version_metric_value(
+ version::doris_build_version_major(),
version::doris_build_version_minor(),
+ version::doris_build_version_patch(),
version::doris_build_version_hotfix());
std::stringstream sample;
sample << "doris_be_version{version=\"" << version::doris_build_version()
<< "\",major=\""
@@ -53,8 +56,7 @@ TEST(DebugUtilTest, BeVersionMetricIsExportedWithBuildLabels)
{
<< version::doris_build_version_minor() << "\",patch=\""
<< version::doris_build_version_patch() << "\",hotfix=\""
<< version::doris_build_version_hotfix() << "\",short_hash=\""
- << version::doris_build_short_hash() << "\"} "
- << std::strtoull(value.str().c_str(), nullptr, 10);
+ << version::doris_build_short_hash() << "\"} " << value;
EXPECT_NE(std::string::npos, body.find("# TYPE doris_be_version gauge"))
<< body;
EXPECT_NE(std::string::npos, body.find(sample.str())) << body;
diff --git a/cloud/src/common/version_metrics.cpp
b/cloud/src/common/version_metrics.cpp
index e627c853d3e..d60226afe4e 100644
--- a/cloud/src/common/version_metrics.cpp
+++ b/cloud/src/common/version_metrics.cpp
@@ -23,24 +23,23 @@
#include <gflags/gflags.h>
#include <cstdint>
-#include <cstdlib>
#include <list>
-#include <sstream>
#include <string>
#include "common/config.h"
+#include "cpp/version_metrics.h"
namespace doris::cloud {
namespace {
uint64_t get_doris_cloud_version_metric_value() {
- std::stringstream ss;
- ss << DORIS_CLOUD_BUILD_VERSION_MAJOR << 0 <<
DORIS_CLOUD_BUILD_VERSION_MINOR << 0
- << DORIS_CLOUD_BUILD_VERSION_PATCH;
- if (DORIS_CLOUD_BUILD_VERSION_HOTFIX > 0) {
- ss << 0 << DORIS_CLOUD_BUILD_VERSION_HOTFIX;
- }
- return std::strtoul(ss.str().c_str(), nullptr, 10);
+ CHECK(is_valid_version_metric_component(DORIS_CLOUD_BUILD_VERSION_MAJOR));
+ CHECK(is_valid_version_metric_component(DORIS_CLOUD_BUILD_VERSION_MINOR));
+ CHECK(is_valid_version_metric_component(DORIS_CLOUD_BUILD_VERSION_PATCH));
+ CHECK(is_valid_version_metric_component(DORIS_CLOUD_BUILD_VERSION_HOTFIX));
+ return encode_version_metric_value(
+ DORIS_CLOUD_BUILD_VERSION_MAJOR, DORIS_CLOUD_BUILD_VERSION_MINOR,
+ DORIS_CLOUD_BUILD_VERSION_PATCH, DORIS_CLOUD_BUILD_VERSION_HOTFIX);
}
} // namespace
diff --git a/cloud/test/metric_test.cpp b/cloud/test/metric_test.cpp
index 9bbd2410cd0..f4252dbdcca 100644
--- a/cloud/test/metric_test.cpp
+++ b/cloud/test/metric_test.cpp
@@ -35,6 +35,7 @@
#include "common/bvars.h"
#include "common/config.h"
#include "common/version_metrics.h"
+#include "cpp/version_metrics.h"
#include "meta-store/mem_txn_kv.h"
#include "meta-store/txn_kv.h"
#include "meta-store/txn_kv_error.h"
@@ -47,6 +48,12 @@ int main(int argc, char** argv) {
TEST(MetricTest, CloudVersionMetricIsExportedWithBuildLabels) {
using namespace doris::cloud;
+ EXPECT_EQ(26000100ULL, doris::encode_version_metric_value(26, 0, 1, 0));
+ EXPECT_EQ(26000101ULL, doris::encode_version_metric_value(26, 0, 1, 1));
+ EXPECT_LT(doris::encode_version_metric_value(4, 99, 0, 0),
+ doris::encode_version_metric_value(5, 0, 0, 0));
+ EXPECT_FALSE(doris::is_valid_version_metric_component(100));
+
config::bvar_max_dump_multi_dimension_metric_num = "5000";
init_doris_cloud_version_metrics();
@@ -54,19 +61,16 @@ TEST(MetricTest,
CloudVersionMetricIsExportedWithBuildLabels) {
ASSERT_EQ(0, brpc::DumpPrometheusMetricsToIOBuf(&output));
const std::string body = output.to_string();
- std::stringstream value;
- value << DORIS_CLOUD_BUILD_VERSION_MAJOR << 0 <<
DORIS_CLOUD_BUILD_VERSION_MINOR << 0
- << DORIS_CLOUD_BUILD_VERSION_PATCH;
- if (DORIS_CLOUD_BUILD_VERSION_HOTFIX > 0) {
- value << 0 << DORIS_CLOUD_BUILD_VERSION_HOTFIX;
- }
+ const uint64_t value = doris::encode_version_metric_value(
+ DORIS_CLOUD_BUILD_VERSION_MAJOR, DORIS_CLOUD_BUILD_VERSION_MINOR,
+ DORIS_CLOUD_BUILD_VERSION_PATCH, DORIS_CLOUD_BUILD_VERSION_HOTFIX);
std::stringstream sample;
sample << "doris_cloud_version{version=\"" << DORIS_CLOUD_BUILD_VERSION <<
"\",major=\""
<< DORIS_CLOUD_BUILD_VERSION_MAJOR << "\",minor=\"" <<
DORIS_CLOUD_BUILD_VERSION_MINOR
<< "\",patch=\"" << DORIS_CLOUD_BUILD_VERSION_PATCH <<
"\",hotfix=\""
<< DORIS_CLOUD_BUILD_VERSION_HOTFIX << "\",short_hash=\"" <<
DORIS_CLOUD_BUILD_SHORT_HASH
- << "\"} " << std::strtoull(value.str().c_str(), nullptr, 10);
+ << "\"} " << value;
EXPECT_NE(std::string::npos, body.find("# TYPE doris_cloud_version
gauge")) << body;
EXPECT_NE(std::string::npos, body.find(sample.str())) << body;
diff --git a/common/cpp/version_metrics.h b/common/cpp/version_metrics.h
new file mode 100644
index 00000000000..a15c34b3e33
--- /dev/null
+++ b/common/cpp/version_metrics.h
@@ -0,0 +1,41 @@
+// 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.
+
+#pragma once
+
+#include <cstdint>
+
+namespace doris {
+
+inline constexpr uint64_t VERSION_METRIC_COMPONENT_BASE = 100;
+
+inline constexpr bool is_valid_version_metric_component(int64_t component) {
+ return component >= 0 && component <
static_cast<int64_t>(VERSION_METRIC_COMPONENT_BASE);
+}
+
+// Treat each component as one base-100 digit so numeric ordering matches
version ordering.
+// For example, 26.0.1.0 is encoded as 26000100 and 26.0.1.1 as 26000101.
+// Callers must validate every component with
is_valid_version_metric_component first.
+inline constexpr uint64_t encode_version_metric_value(int64_t major, int64_t
minor, int64_t patch,
+ int64_t hotfix) {
+ uint64_t encoded = static_cast<uint64_t>(major);
+ encoded = encoded * VERSION_METRIC_COMPONENT_BASE +
static_cast<uint64_t>(minor);
+ encoded = encoded * VERSION_METRIC_COMPONENT_BASE +
static_cast<uint64_t>(patch);
+ return encoded * VERSION_METRIC_COMPONENT_BASE +
static_cast<uint64_t>(hotfix);
+}
+
+} // namespace doris
diff --git a/fe/fe-core/src/main/java/org/apache/doris/metric/MetricRepo.java
b/fe/fe-core/src/main/java/org/apache/doris/metric/MetricRepo.java
index e6ea188c24d..6318771caef 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/metric/MetricRepo.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/metric/MetricRepo.java
@@ -324,6 +324,20 @@ public final class MetricRepo {
private static final ScheduledThreadPoolExecutor metricTimer =
ThreadPoolManager.newDaemonScheduledThreadPool(1,
"metric-timer-pool", true);
private static final MetricCalculator metricCalculator = new
MetricCalculator();
+ private static final long VERSION_METRIC_COMPONENT_BASE = 100L;
+
+ static long encodeVersionMetricValue(int major, int minor, int patch, int
hotfix) {
+ int[] components = {major, minor, patch, hotfix};
+ for (int component : components) {
+ if (component < 0 || component >= VERSION_METRIC_COMPONENT_BASE) {
+ throw new IllegalArgumentException("version metric components
must be between 0 and 99");
+ }
+ }
+
+ // Each component is one base-100 digit: 26.0.1.0 becomes 26000100.
+ return (((major * VERSION_METRIC_COMPONENT_BASE + minor) *
VERSION_METRIC_COMPONENT_BASE + patch)
+ * VERSION_METRIC_COMPONENT_BASE + hotfix);
+ }
// init() should only be called after catalog is constructed.
public static synchronized void init() {
@@ -336,12 +350,9 @@ public final class MetricRepo {
@Override
public Long getValue() {
try {
- String verStr = Version.DORIS_BUILD_VERSION_MAJOR + "0" +
Version.DORIS_BUILD_VERSION_MINOR + "0"
- + Version.DORIS_BUILD_VERSION_PATCH;
- if (Version.DORIS_BUILD_VERSION_HOTFIX > 0) {
- verStr += ("0" + Version.DORIS_BUILD_VERSION_HOTFIX);
- }
- return Long.parseLong(verStr);
+ return
encodeVersionMetricValue(Version.DORIS_BUILD_VERSION_MAJOR,
+ Version.DORIS_BUILD_VERSION_MINOR,
Version.DORIS_BUILD_VERSION_PATCH,
+ Version.DORIS_BUILD_VERSION_HOTFIX);
} catch (Throwable t) {
LOG.warn("failed to init version metrics", t);
return 0L;
diff --git a/fe/fe-core/src/test/java/org/apache/doris/metric/MetricsTest.java
b/fe/fe-core/src/test/java/org/apache/doris/metric/MetricsTest.java
index afe7e614f8c..33fc70c2f90 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/metric/MetricsTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/metric/MetricsTest.java
@@ -74,13 +74,16 @@ public class MetricsTest {
@Test
public void testVersionMetricIsExportedWithBuildLabels() {
- StringBuilder value = new StringBuilder()
- .append(Version.DORIS_BUILD_VERSION_MAJOR).append(0)
- .append(Version.DORIS_BUILD_VERSION_MINOR).append(0)
- .append(Version.DORIS_BUILD_VERSION_PATCH);
- if (Version.DORIS_BUILD_VERSION_HOTFIX > 0) {
- value.append(0).append(Version.DORIS_BUILD_VERSION_HOTFIX);
- }
+ Assertions.assertEquals(26000100L,
MetricRepo.encodeVersionMetricValue(26, 0, 1, 0));
+ Assertions.assertEquals(26000101L,
MetricRepo.encodeVersionMetricValue(26, 0, 1, 1));
+ Assertions.assertTrue(MetricRepo.encodeVersionMetricValue(4, 99, 0, 0)
+ < MetricRepo.encodeVersionMetricValue(5, 0, 0, 0));
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> MetricRepo.encodeVersionMetricValue(26, 0, 100, 0));
+
+ long value =
MetricRepo.encodeVersionMetricValue(Version.DORIS_BUILD_VERSION_MAJOR,
+ Version.DORIS_BUILD_VERSION_MINOR,
Version.DORIS_BUILD_VERSION_PATCH,
+ Version.DORIS_BUILD_VERSION_HOTFIX);
String expectedSample = "doris_fe_version{version=\"" +
Version.DORIS_BUILD_VERSION
+ "\", major=\"" + Version.DORIS_BUILD_VERSION_MAJOR
@@ -88,7 +91,7 @@ public class MetricsTest {
+ "\", patch=\"" + Version.DORIS_BUILD_VERSION_PATCH
+ "\", hotfix=\"" + Version.DORIS_BUILD_VERSION_HOTFIX
+ "\", short_hash=\"" + Version.DORIS_BUILD_SHORT_HASH
- + "\"} " + Long.parseLong(value.toString());
+ + "\"} " + value;
String metricResult = getPrometheusMetrics();
Assertions.assertTrue(metricResult.contains("# TYPE doris_fe_version
gauge"), metricResult);
diff --git a/gensrc/script/gen_build_version.sh
b/gensrc/script/gen_build_version.sh
index 5b6699f045d..f5be09bc78e 100755
--- a/gensrc/script/gen_build_version.sh
+++ b/gensrc/script/gen_build_version.sh
@@ -34,6 +34,23 @@ build_version_patch="${DORIS_BUILD_VERSION_PATCH-0}"
build_version_hotfix="${DORIS_BUILD_VERSION_HOTFIX-0}"
build_version_rc_version="${DORIS_BUILD_VERSION_RC_VERSION-""}"
+validate_build_version_component() {
+ local component_name="$1"
+ local component_value="$2"
+
+ # Version metrics encode each component as one base-100 digit. Reject
values such as 100
+ # instead of silently carrying them into the adjacent component and
creating collisions.
+ if [[ ! "${component_value}" =~ ^(0|[1-9][0-9]?)$ ]]; then
+ echo "${component_name} must be a decimal integer between 0 and 99,
got: ${component_value}" >&2
+ exit 1
+ fi
+}
+
+validate_build_version_component "DORIS_BUILD_VERSION_MAJOR"
"${build_version_major}"
+validate_build_version_component "DORIS_BUILD_VERSION_MINOR"
"${build_version_minor}"
+validate_build_version_component "DORIS_BUILD_VERSION_PATCH"
"${build_version_patch}"
+validate_build_version_component "DORIS_BUILD_VERSION_HOTFIX"
"${build_version_hotfix}"
+
build_version="${build_version_prefix}-${build_version_major}.${build_version_minor}.${build_version_patch}"
if [[ ${build_version_hotfix} -gt 0 ]]; then
build_version+=".${build_version_hotfix}"
diff --git a/regression-test/suites/metrics_p0/test_version_metrics.groovy
b/regression-test/suites/metrics_p0/test_version_metrics.groovy
index 39112ec6e77..df82290fcce 100644
--- a/regression-test/suites/metrics_p0/test_version_metrics.groovy
+++ b/regression-test/suites/metrics_p0/test_version_metrics.groovy
@@ -38,6 +38,13 @@ suite("test_version_metrics") {
}
assertEquals(requiredVersionLabels, labels.keySet())
parsedMetric = [labels: labels, value:
Long.parseLong(matcher.group(2))]
+ def components = [labels.major, labels.minor, labels.patch,
labels.hotfix]
+ .collect { Long.parseLong(it) }
+ components.each { component ->
+ assertTrue(component >= 0 && component < 100)
+ }
+ def expectedValue = components.inject(0L) { encoded, component ->
encoded * 100 + component }
+ assertEquals(expectedValue, parsedMetric.value)
break
}
assertNotNull(parsedMetric)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]