001package io.prometheus.metrics.model.snapshots;
002
003import io.prometheus.metrics.config.EscapingScheme;
004import javax.annotation.Nullable;
005
006/** Immutable container for metric metadata: name, help, unit. */
007public final class MetricMetadata {
008
009  /**
010   * Name without suffix.
011   *
012   * <p>For example, the name for a counter "http_requests_total" is "http_requests". The name of an
013   * info called "jvm_info" is "jvm".
014   *
015   * <p>We allow dots in label names. Dots are automatically replaced with underscores in Prometheus
016   * exposition formats. However, if metrics from this library are exposed in OpenTelemetry format
017   * dots are retained.
018   *
019   * <p>See {@link #MetricMetadata(String, String, Unit)} for more info on naming conventions.
020   */
021  private final String name;
022
023  /**
024   * Same as name that all invalid char (without Unicode support) are replaced by _
025   *
026   * <p>Multiple metrics with the same prometheusName are not allowed, because they would end up in
027   * the same time series in Prometheus if {@link EscapingScheme#UNDERSCORE_ESCAPING} or {@link
028   * EscapingScheme#DOTS_ESCAPING} is used.
029   */
030  private final String prometheusName;
031
032  @Nullable private final String help;
033  @Nullable private final Unit unit;
034
035  /** See {@link #MetricMetadata(String, String, Unit)} */
036  public MetricMetadata(String name) {
037    this(name, null, null);
038  }
039
040  /** See {@link #MetricMetadata(String, String, Unit)} */
041  public MetricMetadata(String name, String help) {
042    this(name, help, null);
043  }
044
045  /**
046   * Constructor.
047   *
048   * @param name must not be {@code null}. {@link PrometheusNaming#isValidMetricName(String)
049   *     isValidMetricName(name)} must be {@code true}. Use {@link
050   *     PrometheusNaming#sanitizeMetricName(String)} to convert arbitrary strings into valid names.
051   * @param help optional. May be {@code null}.
052   * @param unit optional. May be {@code null}.
053   */
054  public MetricMetadata(String name, @Nullable String help, @Nullable Unit unit) {
055    this.name = name;
056    this.help = help;
057    this.unit = unit;
058    validate();
059    this.prometheusName = PrometheusNaming.prometheusName(name);
060  }
061
062  /**
063   * The name does not include the {@code _total} suffix for counter metrics or the {@code _info}
064   * suffix for Info metrics.
065   *
066   * <p>The name may contain any Unicode chars. Use {@link #getPrometheusName()} to get the name in
067   * legacy Prometheus format, i.e. with all dots and all invalid chars replaced by underscores.
068   */
069  public String getName() {
070    return name;
071  }
072
073  /**
074   * Same as {@link #getName()} but with all invalid characters and dots replaced by underscores.
075   *
076   * <p>This is used by Prometheus exposition formats.
077   */
078  public String getPrometheusName() {
079    return prometheusName;
080  }
081
082  @Nullable
083  public String getHelp() {
084    return help;
085  }
086
087  public boolean hasUnit() {
088    return unit != null;
089  }
090
091  @Nullable
092  public Unit getUnit() {
093    return unit;
094  }
095
096  private void validate() {
097    if (name == null) {
098      throw new IllegalArgumentException("Missing required field: name is null");
099    }
100    String error = PrometheusNaming.validateMetricName(name);
101    if (error != null) {
102      throw new IllegalArgumentException(
103          "'"
104              + name
105              + "': Illegal metric name. "
106              + error
107              + " Call "
108              + PrometheusNaming.class.getSimpleName()
109              + ".sanitizeMetricName(name) to avoid this error.");
110    }
111    if (hasUnit()) {
112      if (!name.endsWith("_" + unit) && !name.endsWith("." + unit)) {
113        throw new IllegalArgumentException(
114            "'"
115                + name
116                + "': Illegal metric name. If the unit is non-null, "
117                + "the name must end with the unit: _"
118                + unit
119                + "."
120                + " Call "
121                + PrometheusNaming.class.getSimpleName()
122                + ".sanitizeMetricName(name, unit) to avoid this error.");
123      }
124    }
125  }
126
127  MetricMetadata escape(EscapingScheme escapingScheme) {
128    return new MetricMetadata(PrometheusNaming.escapeName(name, escapingScheme), help, unit);
129  }
130}