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

io.helidon.metrics.prometheus.PrometheusSupport Maven / Gradle / Ivy

There is a newer version: 4.1.6
Show newest version
/*
 * Copyright (c) 2017, 2023 Oracle and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.helidon.metrics.prometheus;

import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import io.helidon.http.HttpMediaType;
import io.helidon.webserver.http.HttpRouting;
import io.helidon.webserver.http.HttpRules;
import io.helidon.webserver.http.HttpService;
import io.helidon.webserver.http.ServerRequest;
import io.helidon.webserver.http.ServerResponse;
import io.helidon.webserver.servicecommon.HelidonFeatureSupport;

import io.prometheus.client.Collector;
import io.prometheus.client.CollectorRegistry;

/**
 * Support for Prometheus client endpoint.
 * 

* Default and simplest use on {@link HttpRouting} creates {@code /metrics} endpoint * for {@link CollectorRegistry default CollectorRegistry}. *

{@code
 * HttpRouting.builder()
 *        ..addFeature(PrometheusSupport.create())
 * }
*/ public final class PrometheusSupport extends HelidonFeatureSupport { private static final System.Logger LOGGER = System.getLogger(PrometheusSupport.class.getName()); /** * Standard path of Prometheus client resource: {@code /metrics}. */ private static final String DEFAULT_PATH = "/metrics"; private static final HttpMediaType CONTENT_TYPE = HttpMediaType.create("text/plain; version=0.0.4; charset=utf-8"); private final CollectorRegistry collectorRegistry; private final String path; private PrometheusSupport(Builder builder) { super(LOGGER, builder, "prometheus"); this.collectorRegistry = builder.registry; this.path = builder.path; } private void configureRoutes(HttpRules rules) { rules.get(path, this::process); } @Override public Optional service() { return Optional.of(this::configureRoutes); } private void process(ServerRequest req, ServerResponse res) { // Recent releases of the Prometheus client append suffixes such as "_total" to the meter names. To preserve the // outward behavior of this endpoint where the query parameter specifies the base name (without the suffix), // pass a predicate for filtering rather than just the set of strings of base names. Set filters = new HashSet<>(req.query().all("name[]", List::of)); Enumeration mfs = collectorRegistry.filteredMetricFamilySamples(candidate -> filters.isEmpty() || filters.stream().anyMatch(candidate::startsWith)); res.headers().contentType(CONTENT_TYPE); res.send(compose(mfs)); } /** * Compose the text version 0.0.4 of the given MetricFamilySamples. */ private static String compose(Enumeration mfs) { /* See http://prometheus.io/docs/instrumenting/exposition_formats/ * for the output format specification. */ StringBuilder result = new StringBuilder(); while (mfs.hasMoreElements()) { Collector.MetricFamilySamples metricFamilySamples = mfs.nextElement(); result.append("# HELP ") .append(metricFamilySamples.name) .append(' '); appendEscapedHelp(result, metricFamilySamples.help); result.append('\n'); result.append("# TYPE ") .append(metricFamilySamples.name) .append(' ') .append(typeString(metricFamilySamples.type)) .append('\n'); for (Collector.MetricFamilySamples.Sample sample: metricFamilySamples.samples) { result.append(sample.name); if (!sample.labelNames.isEmpty()) { result.append('{'); for (int i = 0; i < sample.labelNames.size(); ++i) { result.append(sample.labelNames.get(i)) .append("=\""); appendEscapedLabelValue(result, sample.labelValues.get(i)); result.append("\","); } result.append('}'); } result.append(' ') .append(Collector.doubleToGoString(sample.value)) .append('\n'); } } return result.toString(); } private static void appendEscapedHelp(StringBuilder sb, String s) { for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); switch (c) { case '\\': sb.append("\\\\"); break; case '\n': sb.append("\\n"); break; default: sb.append(c); } } } private static void appendEscapedLabelValue(StringBuilder sb, String s) { for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); switch (c) { case '\\': sb.append("\\\\"); break; case '\"': sb.append("\\\""); break; case '\n': sb.append("\\n"); break; default: sb.append(c); } } } private static String typeString(Collector.Type t) { switch (t) { case GAUGE: return "gauge"; case COUNTER: return "counter"; case SUMMARY: return "summary"; case HISTOGRAM: return "histogram"; default: return "untyped"; } } /** * Creates new instance using specified Prometheus {@link CollectorRegistry}. * * @param collectorRegistry a registry to use * @return new instance * @see #create() * @see #builder() */ public static PrometheusSupport create(CollectorRegistry collectorRegistry) { return builder().collectorRegistry(collectorRegistry).build(); } /** * Creates new instance using default Prometheus {@link CollectorRegistry}. * * @return new instance * @see CollectorRegistry * @see #create(CollectorRegistry) * @see #builder() */ public static PrometheusSupport create() { return builder().build(); } /** * Creates new {@code Builder} instance. * * @return the new instance * @see #create() * @see #create(CollectorRegistry) */ public static Builder builder() { return new Builder(); } /** * A builder of {@link PrometheusSupport}. */ public static final class Builder extends HelidonFeatureSupport.Builder { private CollectorRegistry registry = CollectorRegistry.defaultRegistry; private String path = DEFAULT_PATH; private Builder() { super("/"); } /** * Sets collector registry to use, default is {@link CollectorRegistry#defaultRegistry}. * * @param registry a registry to use * @return updated builder */ public Builder collectorRegistry(CollectorRegistry registry) { this.registry = registry; return this; } /** * Sets path of the metrics resource, default is {@code /metrics}. * * @param path a resource path * @return updated builder */ public Builder path(String path) { if (path == null || path.isEmpty()) { this.path = "/"; } else { this.path = path; } return this; } @Override public PrometheusSupport build() { return new PrometheusSupport(this); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy