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