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