From 70d88101b2cb7b893e63f775b63fcd0a91e9d9b7 Mon Sep 17 00:00:00 2001 From: Keith Laban Date: Mon, 8 May 2017 16:41:22 -0400 Subject: [PATCH] SOLR-10654 - Expose Prometheus metrics under /admin/metrics with wt=prometheus --- lucene/ivy-versions.properties | 5 ++ .../java/org/apache/solr/core/SolrCore.java | 3 ++ .../solr/handler/admin/MetricsHandler.java | 41 +++++++++++--- .../response/PrometheusMetricsWriter.java | 53 +++++++++++++++++++ .../handler/admin/MetricsHandlerTest.java | 27 ++++++++++ solr/server/ivy.xml | 4 ++ 6 files changed, 126 insertions(+), 7 deletions(-) create mode 100644 solr/core/src/java/org/apache/solr/response/PrometheusMetricsWriter.java diff --git a/lucene/ivy-versions.properties b/lucene/ivy-versions.properties index 2ef7ad852764..35a713eef915 100644 --- a/lucene/ivy-versions.properties +++ b/lucene/ivy-versions.properties @@ -62,6 +62,11 @@ io.dropwizard.metrics.version = 3.2.2 /io.dropwizard.metrics/metrics-jetty9 = ${io.dropwizard.metrics.version} /io.dropwizard.metrics/metrics-jvm = ${io.dropwizard.metrics.version} +io.prometheus.version = 0.0.21 +/io.prometheus/simpleclient = ${io.prometheus.version} +/io.prometheus/simpleclient_common = ${io.prometheus.version} +/io.prometheus/simpleclient_dropwizard = ${io.prometheus.version} + io.netty.netty-all.version = 4.0.36.Final /io.netty/netty-all = ${io.netty.netty-all.version} diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java index f0bb88b97594..7bfe38f0ed4c 100644 --- a/solr/core/src/java/org/apache/solr/core/SolrCore.java +++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java @@ -101,6 +101,7 @@ import org.apache.solr.handler.ReplicationHandler; import org.apache.solr.handler.RequestHandlerBase; import org.apache.solr.handler.SolrConfigHandler; +import org.apache.solr.handler.admin.MetricsHandler; import org.apache.solr.handler.component.HighlightComponent; import org.apache.solr.handler.component.SearchComponent; import org.apache.solr.logging.MDCLoggingContext; @@ -116,6 +117,7 @@ import org.apache.solr.response.JSONResponseWriter; import org.apache.solr.response.PHPResponseWriter; import org.apache.solr.response.PHPSerializedResponseWriter; +import org.apache.solr.response.PrometheusMetricsWriter; import org.apache.solr.response.PythonResponseWriter; import org.apache.solr.response.QueryResponseWriter; import org.apache.solr.response.RawResponseWriter; @@ -2586,6 +2588,7 @@ public PluginBag getResponseWriters() { m.put("python", new PythonResponseWriter()); m.put("php", new PHPResponseWriter()); m.put("phps", new PHPSerializedResponseWriter()); + m.put(MetricsHandler.PROMETHEUS_METRICS_WT, new PrometheusMetricsWriter()); m.put("ruby", new RubyResponseWriter()); m.put("raw", new RawResponseWriter()); m.put(CommonParams.JAVABIN, new BinaryResponseWriter()); diff --git a/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java index 11f68212e222..80afd50e1676 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java @@ -25,14 +25,8 @@ import java.util.Set; import java.util.stream.Collectors; -import com.codahale.metrics.Counter; -import com.codahale.metrics.Gauge; -import com.codahale.metrics.Histogram; -import com.codahale.metrics.Meter; -import com.codahale.metrics.MetricFilter; -import com.codahale.metrics.MetricRegistry; -import com.codahale.metrics.Timer; import org.apache.solr.common.SolrException; +import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.SimpleOrderedMap; import org.apache.solr.common.util.StrUtils; @@ -45,6 +39,17 @@ import org.apache.solr.security.PermissionNameProvider; import org.apache.solr.util.stats.MetricUtils; +import com.codahale.metrics.Counter; +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Histogram; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricFilter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; + +import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.dropwizard.DropwizardExports; + /** * Request handler to return metrics */ @@ -52,6 +57,8 @@ public class MetricsHandler extends RequestHandlerBase implements PermissionName final CoreContainer container; final SolrMetricManager metricManager; + public static final String PROMETHEUS_METRICS_WT = "prometheus"; + public static final String COMPACT_PARAM = "compact"; public static final String PREFIX_PARAM = "prefix"; public static final String REGEX_PARAM = "regex"; @@ -83,6 +90,14 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw throw new SolrException(SolrException.ErrorCode.INVALID_STATE, "Core container instance not initialized"); } + if(PROMETHEUS_METRICS_WT.equals(req.getParams().get(CommonParams.WT))) { + handlePrometheusMetricsRequest(req, rsp); + } else { + handleMetricsRequest(req, rsp); + } + } + + private void handleMetricsRequest(SolrQueryRequest req, SolrQueryResponse rsp) { boolean compact = req.getParams().getBool(COMPACT_PARAM, true); MetricFilter mustMatchFilter = parseMustMatchFilter(req); MetricUtils.PropertyFilter propertyFilter = parsePropertyFilter(req); @@ -103,6 +118,18 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw rsp.getValues().add("metrics", response); } + private void handlePrometheusMetricsRequest(SolrQueryRequest req, SolrQueryResponse rsp) { + Set requestedRegistries = parseRegistries(req); + CollectorRegistry collector = new CollectorRegistry(); + for (String registryName : requestedRegistries) { + MetricRegistry registry = metricManager.registry(registryName); + collector.register(new DropwizardExports(registry)); + } + + rsp.getValues().add(PROMETHEUS_METRICS_WT, collector); + } + + private MetricFilter parseMustMatchFilter(SolrQueryRequest req) { String[] prefixes = req.getParams().getParams(PREFIX_PARAM); MetricFilter prefixFilter = null; diff --git a/solr/core/src/java/org/apache/solr/response/PrometheusMetricsWriter.java b/solr/core/src/java/org/apache/solr/response/PrometheusMetricsWriter.java new file mode 100644 index 000000000000..17c4ff96d4fa --- /dev/null +++ b/solr/core/src/java/org/apache/solr/response/PrometheusMetricsWriter.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.solr.response; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; + +import org.apache.solr.common.SolrException; +import org.apache.solr.common.SolrException.ErrorCode; +import org.apache.solr.handler.admin.MetricsHandler; +import org.apache.solr.request.SolrQueryRequest; +import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.exporter.common.TextFormat; + +public class PrometheusMetricsWriter extends BinaryResponseWriter { + + @Override + public void write(OutputStream out, SolrQueryRequest req, SolrQueryResponse response) throws IOException { + Writer writer = new OutputStreamWriter(out); + Object collectorRegistry = response.getValues().get(MetricsHandler.PROMETHEUS_METRICS_WT); + + if(null == collectorRegistry || !(collectorRegistry instanceof CollectorRegistry)) { + throw new SolrException(ErrorCode.INVALID_STATE, "I was expecting a CollectorRegistry but got null or something else"); + } + + TextFormat.write004(writer, ((CollectorRegistry)collectorRegistry).metricFamilySamples()); + + writer.flush(); + writer.close(); + } + + @Override + public String getContentType(SolrQueryRequest request, SolrQueryResponse response) { + return TextFormat.CONTENT_TYPE_004; + } + +} diff --git a/solr/core/src/test/org/apache/solr/handler/admin/MetricsHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/MetricsHandlerTest.java index 402cc250565e..35354a981d15 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/MetricsHandlerTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/MetricsHandlerTest.java @@ -17,16 +17,20 @@ package org.apache.solr.handler.admin; +import java.util.Arrays; import java.util.Map; import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.SimpleOrderedMap; +import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; import org.junit.BeforeClass; import org.junit.Test; +import io.prometheus.client.CollectorRegistry; + /** * Test for {@link MetricsHandler} */ @@ -186,6 +190,29 @@ public void testCompact() throws Exception { assertNotNull(o); // counter type assertTrue(o instanceof Number); } + + @Test + public void testPrometheusOutput() throws Exception { + MetricsHandler handler = new MetricsHandler(h.getCoreContainer()); + + SolrQueryResponse resp = new SolrQueryResponse(); + SolrQueryRequest req = req(CommonParams.QT, "/admin/metrics", CommonParams.WT, MetricsHandler.PROMETHEUS_METRICS_WT, "registry", "solr.core.collection1"); + + req.getCore().getRequestHandlers().put("/admin/metrics", handler); + + handler.handleRequestBody(req, resp); + + NamedList values = resp.getValues(); + Object o = values.get(MetricsHandler.PROMETHEUS_METRICS_WT); + + assertNotNull(o); + assertTrue(o instanceof CollectorRegistry); + + String queryResp = h.query(req); + assertFalse(queryResp.isEmpty()); + + assertTrue(Arrays.stream(queryResp.split("[\\r\\n]+")).filter(l -> !l.startsWith("#")).count() > 0); + } @Test public void testPropertyFilter() throws Exception { diff --git a/solr/server/ivy.xml b/solr/server/ivy.xml index c9b3a7301431..c9d19ed6bac1 100644 --- a/solr/server/ivy.xml +++ b/solr/server/ivy.xml @@ -40,6 +40,10 @@ + + + +