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
17 changes: 16 additions & 1 deletion docs/apidiffs/current_vs_latest/prometheus-metrics-model.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ protected MetricWithFixedMetadata(Builder<?, ?> builder) {
String originalName = builder.originalName;
String expositionBaseName = makeExpositionBaseName(originalName, builder.unit);
this.metadata =
new MetricMetadata(name, expositionBaseName, originalName, builder.help, builder.unit);
MetricMetadata.builder()
.name(name)
.expositionBaseName(expositionBaseName)
.originalName(originalName)
.help(builder.help)
.unit(builder.unit)
.build();
this.labelNames = Arrays.copyOf(builder.labelNames, builder.labelNames.length);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,111 @@ public MetricMetadata(String name, @Nullable String help, @Nullable Unit unit) {
this(name, name, help, unit);
}

/**
* Creates a builder for {@link MetricMetadata}.
*
* <p>Use the builder instead of the multi-arg constructors for cleaner, more readable code:
*
* <pre>{@code
* MetricMetadata.builder()
* .name("http_requests")
* .help("Total HTTP requests")
* .unit(Unit.BYTES)
* .counterSuffix(true)
* .build();
* }</pre>
*/
@StableApi
public static Builder builder() {
return new Builder();
}

/** Builder for {@link MetricMetadata}. */
public static final class Builder {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this solve the use cases in this repo that we now have the suppressions on? i'm not sure it allows us to actually fully deprecate the constructors as-is.

For example, for MetricWithFixedMetadata i think we'd need:

    this.metadata = MetricMetadata.builder()
      .name(name)
      .expositionBaseName(expositionBaseName)
      .originalName(originalName)
      .help(builder.help)
      .unit(builder.unit)
      .build();

It might be worth trying to migrate any of the ones where we suppress the deprecation warning, just to see if it's possible

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea - done

@Nullable private String name;
@Nullable private String expositionBaseName;
@Nullable private String originalName;
@Nullable private String help;
@Nullable private Unit unit;
private boolean counterSuffix;

private Builder() {}

/** Required. The base metric name (without type suffix like {@code _total}). */
@StableApi
public Builder name(String name) {
this.name = name;
if (originalName == null) {
this.originalName = name;
}
return this;
}

/**
* Internal use only. Not part of the stable API.
*
* <p>Allows internal callers to preserve a separate exposition base name.
*/
public Builder expositionBaseName(String expositionBaseName) {
this.expositionBaseName = expositionBaseName;
return this;
}

/**
* Internal use only. Not part of the stable API.
*
* <p>Allows internal callers to preserve the raw name before normalization.
*/
public Builder originalName(String originalName) {
this.originalName = originalName;
return this;
}

/** Optional. Human-readable description of the metric. */
@StableApi
public Builder help(@Nullable String help) {
this.help = help;
return this;
}

/** Optional. The unit of measurement. Appended to the name if not already present. */
@StableApi
public Builder unit(@Nullable Unit unit) {
this.unit = unit;
return this;
}

/**
* Optional. When {@code true}, the writer appends {@code _total} to the exposition name. Use
* this for counter metrics, especially UTF-8 names where the writer cannot infer it from the
* snapshot type alone.
*/
@StableApi
public Builder counterSuffix(boolean counterSuffix) {
this.counterSuffix = counterSuffix;
return this;
}

/** Builds the {@link MetricMetadata}. Throws if {@code name} was not set. */
@StableApi
public MetricMetadata build() {
if (name == null) {
throw new IllegalArgumentException("name is required");
}
String baseName = appendUnitIfMissing(name, unit);
String originalName = this.originalName == null ? name : this.originalName;
String expositionBaseName =
appendUnitIfMissing(
this.expositionBaseName == null ? baseName : this.expositionBaseName, unit);
if (counterSuffix
&& !expositionBaseName.endsWith("_total")
&& !expositionBaseName.endsWith(".total")) {
expositionBaseName = expositionBaseName + "_total";
}
return new MetricMetadata(baseName, expositionBaseName, originalName, help, unit);
}
}

/**
* Constructor with exposition base name.
*
Expand All @@ -82,7 +187,9 @@ public MetricMetadata(String name, @Nullable String help, @Nullable Unit unit) {
* format writers for smart-append logic
* @param help optional. May be {@code null}.
* @param unit optional. May be {@code null}.
* @deprecated Use {@link #builder()} instead.
*/
@Deprecated
public MetricMetadata(
String name, String expositionBaseName, @Nullable String help, @Nullable Unit unit) {
this(name, expositionBaseName, expositionBaseName, help, unit);
Expand All @@ -97,7 +204,9 @@ public MetricMetadata(
* @param originalName the raw name as provided by the user, before any modification
* @param help optional. May be {@code null}.
* @param unit optional. May be {@code null}.
* @deprecated Use {@link #builder()} instead.
*/
@Deprecated
public MetricMetadata(
String name,
String expositionBaseName,
Expand Down Expand Up @@ -174,6 +283,13 @@ public Unit getUnit() {
return unit;
}

private static String appendUnitIfMissing(String name, @Nullable Unit unit) {
if (unit != null && !name.endsWith("_" + unit) && !name.endsWith("." + unit)) {
return name + "_" + unit;
}
return name;
}

private void validate() {
if (name == null) {
throw new IllegalArgumentException("Missing required field: name is null");
Expand Down Expand Up @@ -206,11 +322,12 @@ private void validate() {
}

MetricMetadata escape(EscapingScheme escapingScheme) {
return new MetricMetadata(
PrometheusNaming.escapeName(name, escapingScheme),
PrometheusNaming.escapeName(expositionBaseName, escapingScheme),
PrometheusNaming.escapeName(originalName, escapingScheme),
help,
unit);
return MetricMetadata.builder()
.name(PrometheusNaming.escapeName(name, escapingScheme))
.expositionBaseName(PrometheusNaming.escapeName(expositionBaseName, escapingScheme))
.originalName(PrometheusNaming.escapeName(originalName, escapingScheme))
.help(help)
.unit(unit)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ private static MetricMetadata typedMetadata(
String suffix,
String dotSuffix) {
String baseName = stripSuffix(originalName, suffix, dotSuffix);
return new MetricMetadata(
appendUnitIfMissing(baseName, unit),
appendUnitIfMissing(originalName, unit),
originalName,
help,
unit);
return MetricMetadata.builder()
.name(appendUnitIfMissing(baseName, unit))
.expositionBaseName(appendUnitIfMissing(originalName, unit))
.originalName(originalName)
.help(help)
.unit(unit)
.build();
}

private static String appendUnitIfMissing(String name, @Nullable Unit unit) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ void testUnitNotDuplicated() {
assertThat(sanitizeMetricName("my_counter_bytes", Unit.BYTES)).isEqualTo("my_counter_bytes");
}

@SuppressWarnings("deprecation")
@Test
void testFiveArgConstructor() {
MetricMetadata metadata =
Expand All @@ -94,6 +95,7 @@ void testFiveArgConstructor() {
assertThat(metadata.getUnit()).isEqualTo(Unit.BYTES);
}

@SuppressWarnings("deprecation")
@Test
void testFourArgConstructorDefaultsOriginalName() {
MetricMetadata metadata = new MetricMetadata("req_bytes", "req_bytes", "help", Unit.BYTES);
Expand All @@ -107,4 +109,86 @@ void testThreeArgConstructorDefaultsOriginalName() {
assertThat(metadata.getOriginalName()).isEqualTo("req_bytes");
assertThat(metadata.getExpositionBaseName()).isEqualTo("req_bytes");
}

@Test
void builder_noUnit() {
MetricMetadata m = MetricMetadata.builder().name("requests").help("total requests").build();
assertThat(m.getName()).isEqualTo("requests");
assertThat(m.getExpositionBaseName()).isEqualTo("requests");
assertThat(m.getOriginalName()).isEqualTo("requests");
assertThat(m.getHelp()).isEqualTo("total requests");
assertThat(m.getUnit()).isNull();
}

@Test
void builder_unitAppendedWhenAbsent() {
MetricMetadata m = MetricMetadata.builder().name("requests").unit(Unit.BYTES).build();
assertThat(m.getName()).isEqualTo("requests_bytes");
assertThat(m.getExpositionBaseName()).isEqualTo("requests_bytes");
assertThat(m.getOriginalName()).isEqualTo("requests");
}

@Test
void builder_unitNotDuplicatedWhenPresent() {
MetricMetadata m = MetricMetadata.builder().name("requests_bytes").unit(Unit.BYTES).build();
assertThat(m.getName()).isEqualTo("requests_bytes");
assertThat(m.getExpositionBaseName()).isEqualTo("requests_bytes");
assertThat(m.getOriginalName()).isEqualTo("requests_bytes");
}

@Test
void builder_counterSuffixAppended() {
MetricMetadata m = MetricMetadata.builder().name("requests").counterSuffix(true).build();
assertThat(m.getName()).isEqualTo("requests");
assertThat(m.getExpositionBaseName()).isEqualTo("requests_total");
assertThat(m.getOriginalName()).isEqualTo("requests");
}

@Test
void builder_counterSuffixAndUnit() {
MetricMetadata m =
MetricMetadata.builder().name("requests").unit(Unit.BYTES).counterSuffix(true).build();
assertThat(m.getName()).isEqualTo("requests_bytes");
assertThat(m.getExpositionBaseName()).isEqualTo("requests_bytes_total");
assertThat(m.getOriginalName()).isEqualTo("requests");
}

@Test
void builder_utf8NameWithCounterSuffix() {
MetricMetadata m = MetricMetadata.builder().name("my.requests").counterSuffix(true).build();
assertThat(m.getName()).isEqualTo("my.requests");
assertThat(m.getExpositionBaseName()).isEqualTo("my.requests_total");
assertThat(m.getPrometheusName()).isEqualTo("my_requests");
assertThat(m.getExpositionBasePrometheusName()).isEqualTo("my_requests_total");
}

@Test
void builder_customOriginalAndExpositionBaseName() {
MetricMetadata m =
MetricMetadata.builder()
.name("requests_bytes")
.expositionBaseName("requests_total_bytes")
.originalName("requests_total")
.help("help")
.unit(Unit.BYTES)
.build();
assertThat(m.getName()).isEqualTo("requests_bytes");
assertThat(m.getExpositionBaseName()).isEqualTo("requests_total_bytes");
assertThat(m.getOriginalName()).isEqualTo("requests_total");
assertThat(m.getHelp()).isEqualTo("help");
assertThat(m.getUnit()).isEqualTo(Unit.BYTES);
}

@Test
void builder_nonCounterExpositionBaseEqualsName() {
MetricMetadata m = MetricMetadata.builder().name("active_connections").build();
assertThat(m.getExpositionBaseName()).isEqualTo(m.getName());
}

@Test
void builder_nameRequired() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> MetricMetadata.builder().help("help").build())
.withMessage("name is required");
}
}
Loading