All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.prometheus.metrics.model.snapshots.PrometheusNaming Maven / Gradle / Ivy

There is a newer version: 1.3.1
Show newest version
package io.prometheus.metrics.model.snapshots;

import java.util.regex.Pattern;

/**
 * Utility for Prometheus Metric and Label naming.
 * 

* Note that this library allows dots in metric and label names. Dots will automatically be replaced with underscores * in Prometheus exposition formats. However, if metrics are exposed in OpenTelemetry format the dots are retained. */ public class PrometheusNaming { /** * Legal characters for metric names, including dot. */ private static final Pattern METRIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z_.:][a-zA-Z0-9_.:]+$"); /** * Legal characters for label names, including dot. */ private static final Pattern LABEL_NAME_PATTERN = Pattern.compile("^[a-zA-Z_.][a-zA-Z0-9_.]*$"); /** * According to OpenMetrics {@code _count} and {@code _sum} (and {@code _gcount}, {@code _gsum}) should also be * reserved metric name suffixes. However, popular instrumentation libraries have Gauges with names * ending in {@code _count}. * Examples: *

    *
  • Micrometer: {@code jvm_buffer_count}
  • *
  • OpenTelemetry: {@code process_runtime_jvm_buffer_count}
  • *
* We do not treat {@code _count} and {@code _sum} as reserved suffixes here for compatibility with these libraries. * However, there is a risk of name conflict if someone creates a gauge named {@code my_data_count} and a * histogram or summary named {@code my_data}, because the histogram or summary will implicitly have a sample * named {@code my_data_count}. */ private static final String[] RESERVED_METRIC_NAME_SUFFIXES = { "_total", "_created", "_bucket", "_info", ".total", ".created", ".bucket", ".info" }; /** * Test if a metric name is valid. Rules: *
    *
  • The name must match {@link #METRIC_NAME_PATTERN}.
  • *
  • The name MUST NOT end with one of the {@link #RESERVED_METRIC_NAME_SUFFIXES}.
  • *
* If a metric has a {@link Unit}, the metric name SHOULD end with the unit as a suffix. * Note that OpenMetrics requires metric names to have their unit as suffix, * and we implement this in {@code prometheus-metrics-core}. However, {@code prometheus-metrics-model} * does not enforce Unit suffixes. *

* Example: If you create a Counter for a processing time with Unit {@link Unit#SECONDS SECONDS}, * the name should be {@code processing_time_seconds}. When exposed in OpenMetrics Text format, * this will be represented as two values: {@code processing_time_seconds_total} for the counter value, * and the optional {@code processing_time_seconds_created} timestamp. *

* Use {@link #sanitizeMetricName(String)} to convert arbitrary Strings to valid metric names. */ public static boolean isValidMetricName(String name) { return validateMetricName(name) == null; } /** * Same as {@link #isValidMetricName(String)}, but produces an error message. *

* The name is valid if the error message is {@code null}. */ static String validateMetricName(String name) { for (String reservedSuffix : RESERVED_METRIC_NAME_SUFFIXES) { if (name.endsWith(reservedSuffix)) { return "The metric name must not include the '" + reservedSuffix + "' suffix."; } } if (!METRIC_NAME_PATTERN.matcher(name).matches()) { return "The metric name contains unsupported characters"; } return null; } public static boolean isValidLabelName(String name) { return LABEL_NAME_PATTERN.matcher(name).matches() && !(name.startsWith("__") || name.startsWith("._") || name.startsWith("..") || name.startsWith("_.")); } /** * Get the metric or label name that is used in Prometheus exposition format. * * @param name must be a valid metric or label name, * i.e. {@link #isValidMetricName(String) isValidMetricName(name)} * or {@link #isValidLabelName(String) isValidLabelName(name)} must be true. * @return the name with dots replaced by underscores. */ public static String prometheusName(String name) { return name.replace(".", "_"); } /** * Convert an arbitrary string to a name where {@link #isValidMetricName(String) isValidMetricName(name)} is true. */ public static String sanitizeMetricName(String metricName) { if (metricName.isEmpty()) { throw new IllegalArgumentException("Cannot convert an empty string to a valid metric name."); } String sanitizedName = replaceIllegalCharsInMetricName(metricName); boolean modified = true; while (modified) { modified = false; for (String reservedSuffix : RESERVED_METRIC_NAME_SUFFIXES) { if (sanitizedName.equals(reservedSuffix)) { // This is for the corner case when you call sanitizeMetricName("_total"). // In that case the result will be "total". return reservedSuffix.substring(1); } if (sanitizedName.endsWith(reservedSuffix)) { sanitizedName = sanitizedName.substring(0, sanitizedName.length() - reservedSuffix.length()); modified = true; } } } return sanitizedName; } /** * Convert an arbitrary string to a name where {@link #isValidLabelName(String) isValidLabelName(name)} is true. */ public static String sanitizeLabelName(String labelName) { if (labelName.isEmpty()) { throw new IllegalArgumentException("Cannot convert an empty string to a valid label name."); } String sanitizedName = replaceIllegalCharsInLabelName(labelName); while (sanitizedName.startsWith("__") || sanitizedName.startsWith("_.") || sanitizedName.startsWith("._") || sanitizedName.startsWith("..")) { sanitizedName = sanitizedName.substring(1); } return sanitizedName; } /** * Returns a string that matches {@link #METRIC_NAME_PATTERN}. */ private static String replaceIllegalCharsInMetricName(String name) { int length = name.length(); char[] sanitized = new char[length]; for (int i = 0; i < length; i++) { char ch = name.charAt(i); if (ch == ':' || ch == '.' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (i > 0 && ch >= '0' && ch <= '9')) { sanitized[i] = ch; } else { sanitized[i] = '_'; } } return new String(sanitized); } /** * Returns a string that matches {@link #LABEL_NAME_PATTERN}. */ private static String replaceIllegalCharsInLabelName(String name) { int length = name.length(); char[] sanitized = new char[length]; for (int i = 0; i < length; i++) { char ch = name.charAt(i); if (ch == '.' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (i > 0 && ch >= '0' && ch <= '9')) { sanitized[i] = ch; } else { sanitized[i] = '_'; } } return new String(sanitized); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy