diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java index 99fe67e5ba..e57f6739df 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java @@ -27,6 +27,7 @@ import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.core.GraphManager; import org.apache.hugegraph.define.Checkable; +import org.apache.hugegraph.exception.NotFoundException; import org.apache.hugegraph.metrics.MetricsUtil; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.InsertionOrderUtil; @@ -38,7 +39,6 @@ import com.google.common.collect.ImmutableMap; import jakarta.ws.rs.ForbiddenException; -import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.NotSupportedException; import jakarta.ws.rs.core.MediaType; @@ -49,6 +49,7 @@ public class API { public static final String APPLICATION_JSON = MediaType.APPLICATION_JSON; public static final String APPLICATION_JSON_WITH_CHARSET = APPLICATION_JSON + ";charset=" + CHARSET; + public static final String APPLICATION_TEXT_WITH_CHARSET = MediaType.TEXT_PLAIN + ";charset=" + CHARSET; public static final String JSON = MediaType.APPLICATION_JSON_TYPE .getSubtype(); public static final String ACTION_APPEND = "append"; diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/filter/AccessLogFilter.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/filter/AccessLogFilter.java new file mode 100644 index 0000000000..ba9c981186 --- /dev/null +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/filter/AccessLogFilter.java @@ -0,0 +1,82 @@ +/* + * 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.hugegraph.api.filter; + +import static org.apache.hugegraph.api.filter.PathFilter.REQUEST_TIME; +import static org.apache.hugegraph.metrics.MetricsUtil.METRICS_PATH_FAILED_COUNTER; +import static org.apache.hugegraph.metrics.MetricsUtil.METRICS_PATH_RESPONSE_TIME_HISTOGRAM; +import static org.apache.hugegraph.metrics.MetricsUtil.METRICS_PATH_SUCCESS_COUNTER; +import static org.apache.hugegraph.metrics.MetricsUtil.METRICS_PATH_TOTAL_COUNTER; + +import java.io.IOException; + +import org.apache.hugegraph.metrics.MetricsUtil; + +import jakarta.inject.Singleton; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ContainerResponseFilter; +import jakarta.ws.rs.ext.Provider; + + +@Provider +@Singleton +public class AccessLogFilter implements ContainerResponseFilter { + + private static final String DELIMETER = "/"; + + /** + * Use filter to log request info + * + * @param requestContext requestContext + * @param responseContext responseContext + */ + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { + // Grab corresponding request / response info from context; + String method = requestContext.getRequest().getMethod(); + String path = requestContext.getUriInfo().getPath(); + String metricsName = join(path, method); + + MetricsUtil.registerCounter(join(metricsName, METRICS_PATH_TOTAL_COUNTER)).inc(); + if (statusOk(responseContext.getStatus())) { + MetricsUtil.registerCounter(join(metricsName, METRICS_PATH_SUCCESS_COUNTER)).inc(); + } else { + MetricsUtil.registerCounter(join(metricsName, METRICS_PATH_FAILED_COUNTER)).inc(); + } + + // get responseTime + Object requestTime = requestContext.getProperty(REQUEST_TIME); + if(requestTime!=null){ + long now = System.currentTimeMillis(); + long responseTime = (now - (long)requestTime); + + MetricsUtil.registerHistogram( + join(metricsName, METRICS_PATH_RESPONSE_TIME_HISTOGRAM)) + .update(responseTime); + } + } + + private String join(String path1, String path2) { + return String.join(DELIMETER, path1, path2); + } + + private boolean statusOk(int status){ + return status == 200 || status == 201 || status == 202; + } +} diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/filter/PathFilter.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/filter/PathFilter.java new file mode 100644 index 0000000000..3414d6831b --- /dev/null +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/filter/PathFilter.java @@ -0,0 +1,40 @@ +/* + * 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.hugegraph.api.filter; + +import java.io.IOException; + +import jakarta.inject.Singleton; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.PreMatching; +import jakarta.ws.rs.ext.Provider; + +@Provider +@Singleton +@PreMatching +public class PathFilter implements ContainerRequestFilter { + + public static final String REQUEST_TIME = "request_time"; + + @Override + public void filter(ContainerRequestContext context) + throws IOException { + context.setProperty(REQUEST_TIME, System.currentTimeMillis()); + } +} diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/metrics/MetricsAPI.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/metrics/MetricsAPI.java index 6df4f6453b..f74286b5f8 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/metrics/MetricsAPI.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/metrics/MetricsAPI.java @@ -19,33 +19,66 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.apache.hugegraph.metrics.MetricsUtil.COUNT_ATTR; +import static org.apache.hugegraph.metrics.MetricsUtil.END_LSTR; +import static org.apache.hugegraph.metrics.MetricsUtil.FIFT_MIN_RATE_ATRR; +import static org.apache.hugegraph.metrics.MetricsUtil.FIVE_MIN_RATE_ATRR; +import static org.apache.hugegraph.metrics.MetricsUtil.GAUGE_TYPE; +import static org.apache.hugegraph.metrics.MetricsUtil.HISTOGRAM_TYPE; +import static org.apache.hugegraph.metrics.MetricsUtil.LEFT_NAME_STR; +import static org.apache.hugegraph.metrics.MetricsUtil.MEAN_RATE_ATRR; +import static org.apache.hugegraph.metrics.MetricsUtil.METRICS_PATH_FAILED_COUNTER; +import static org.apache.hugegraph.metrics.MetricsUtil.METRICS_PATH_RESPONSE_TIME_HISTOGRAM; +import static org.apache.hugegraph.metrics.MetricsUtil.METRICS_PATH_SUCCESS_COUNTER; +import static org.apache.hugegraph.metrics.MetricsUtil.METRICS_PATH_TOTAL_COUNTER; +import static org.apache.hugegraph.metrics.MetricsUtil.ONE_MIN_RATE_ATRR; +import static org.apache.hugegraph.metrics.MetricsUtil.PROM_HELP_NAME; +import static org.apache.hugegraph.metrics.MetricsUtil.RIGHT_NAME_STR; +import static org.apache.hugegraph.metrics.MetricsUtil.SPACE_STR; +import static org.apache.hugegraph.metrics.MetricsUtil.STR_HELP; +import static org.apache.hugegraph.metrics.MetricsUtil.STR_TYPE; +import static org.apache.hugegraph.metrics.MetricsUtil.UNTYPED; +import static org.apache.hugegraph.metrics.MetricsUtil.VERSION_STR; +import static org.apache.hugegraph.metrics.MetricsUtil.exportSnapshot; +import static org.apache.hugegraph.metrics.MetricsUtil.replaceDotDashInKey; +import static org.apache.hugegraph.metrics.MetricsUtil.replaceSlashInKey; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.security.RolesAllowed; -import jakarta.inject.Singleton; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.Context; - +import org.apache.hugegraph.HugeGraph; +import org.apache.hugegraph.api.API; +import org.apache.hugegraph.backend.store.BackendMetrics; import org.apache.hugegraph.core.GraphManager; +import org.apache.hugegraph.metrics.MetricsKeys; import org.apache.hugegraph.metrics.MetricsModule; +import org.apache.hugegraph.metrics.MetricsUtil; import org.apache.hugegraph.metrics.ServerReporter; import org.apache.hugegraph.metrics.SystemMetrics; -import org.slf4j.Logger; - -import org.apache.hugegraph.HugeGraph; -import org.apache.hugegraph.api.API; -import org.apache.hugegraph.backend.store.BackendMetrics; import org.apache.hugegraph.util.InsertionOrderUtil; import org.apache.hugegraph.util.JsonUtil; import org.apache.hugegraph.util.Log; +import org.apache.hugegraph.version.ApiVersion; +import org.apache.tinkerpop.gremlin.server.util.MetricManager; +import org.slf4j.Logger; + +import com.codahale.metrics.Counter; +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Histogram; +import com.codahale.metrics.Meter; import com.codahale.metrics.Metric; import com.codahale.metrics.annotation.Timed; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Singleton; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; + @Singleton @Path("metrics") @Tag(name = "MetricsAPI") @@ -53,12 +86,14 @@ public class MetricsAPI extends API { private static final Logger LOG = Log.logger(MetricsAPI.class); - private SystemMetrics systemMetrics; + private static final String JSON_STR = "json"; static { JsonUtil.registerModule(new MetricsModule(SECONDS, MILLISECONDS, false)); } + private final SystemMetrics systemMetrics; + public MetricsAPI() { this.systemMetrics = new SystemMetrics(); } @@ -94,21 +129,6 @@ public String backend(@Context GraphManager manager) { return JsonUtil.toJson(results); } - @GET - @Timed - @Produces(APPLICATION_JSON_WITH_CHARSET) - @RolesAllowed({"admin", "$owner= $action=metrics_read"}) - public String all() { - ServerReporter reporter = ServerReporter.instance(); - Map> result = new LinkedHashMap<>(); - result.put("gauges", reporter.gauges()); - result.put("counters", reporter.counters()); - result.put("histograms", reporter.histograms()); - result.put("meters", reporter.meters()); - result.put("timers", reporter.timers()); - return JsonUtil.toJson(result); - } - @GET @Timed @Path("gauges") @@ -158,4 +178,242 @@ public String timers() { ServerReporter reporter = ServerReporter.instance(); return JsonUtil.toJson(reporter.timers()); } + + @GET + @Timed + @Produces(APPLICATION_TEXT_WITH_CHARSET) + @RolesAllowed({"admin", "$owner= $action=metrics_read"}) + public String all(@Context GraphManager manager, + @QueryParam("type") String type) { + if (type != null && type.equals(JSON_STR)) { + return baseMetricAll(); + } else { + return baseMetricPrometheusAll(); + } + } + + @GET + @Path("statistics") + @Timed + @Produces(APPLICATION_TEXT_WITH_CHARSET) + @RolesAllowed({"admin", "$owner= $action=metrics_read"}) + public String statistics(@QueryParam("type") String type) { + Map> metricMap = statistics(); + + if (type != null && type.equals(JSON_STR)) { + return JsonUtil.toJson(metricMap); + } + return statisticsProm(metricMap); + } + + public String baseMetricAll() { + ServerReporter reporter = ServerReporter.instance(); + Map> result = new LinkedHashMap<>(); + result.put("gauges", reporter.gauges()); + result.put("counters", reporter.counters()); + result.put("histograms", reporter.histograms()); + result.put("meters", reporter.meters()); + result.put("timers", reporter.timers()); + return JsonUtil.toJson(result); + } + + private String baseMetricPrometheusAll() { + StringBuilder promMetric = new StringBuilder(); + ServerReporter reporter = ServerReporter.instance(); + String helpName = PROM_HELP_NAME; + // build version info + promMetric.append(STR_HELP) + .append(helpName).append(END_LSTR); + promMetric.append(STR_TYPE) + .append(helpName) + .append(SPACE_STR + UNTYPED + END_LSTR); + promMetric.append(helpName) + .append(VERSION_STR) + .append(ApiVersion.VERSION.toString()).append("\",}") + .append(SPACE_STR + "1.0" + END_LSTR); + + // build gauges metric info + for (String key : reporter.gauges().keySet()) { + final Gauge gauge + = reporter.gauges().get(key); + if (gauge != null) { + helpName = replaceDotDashInKey(key); + promMetric.append(STR_HELP) + .append(helpName).append(END_LSTR); + promMetric.append(STR_TYPE) + .append(helpName).append(SPACE_STR + GAUGE_TYPE + END_LSTR); + promMetric.append(helpName) + .append(SPACE_STR + gauge.getValue() + END_LSTR); + } + } + + // build histograms metric info + for (String histogramkey : reporter.histograms().keySet()) { + final Histogram histogram = reporter.histograms().get(histogramkey); + if (histogram != null) { + helpName = replaceDotDashInKey(histogramkey); + promMetric.append(STR_HELP) + .append(helpName).append(END_LSTR); + promMetric.append(STR_TYPE) + .append(helpName) + .append(SPACE_STR + HISTOGRAM_TYPE + END_LSTR); + + promMetric.append(helpName) + .append(COUNT_ATTR) + .append(histogram.getCount() + END_LSTR); + promMetric.append( + exportSnapshot(helpName, histogram.getSnapshot())); + } + } + + // build meters metric info + for (String meterkey : reporter.meters().keySet()) { + final Meter metric = reporter.meters().get(meterkey); + if (metric != null) { + helpName = replaceDotDashInKey(meterkey); + promMetric.append(STR_HELP) + .append(helpName).append(END_LSTR); + promMetric.append(STR_TYPE) + .append(helpName) + .append(SPACE_STR + HISTOGRAM_TYPE + END_LSTR); + + promMetric.append(helpName) + .append(COUNT_ATTR) + .append(metric.getCount() + END_LSTR); + promMetric.append(helpName) + .append(MEAN_RATE_ATRR) + .append(metric.getMeanRate() + END_LSTR); + promMetric.append(helpName) + .append(ONE_MIN_RATE_ATRR) + .append(metric.getOneMinuteRate() + END_LSTR); + promMetric.append(helpName) + .append(FIVE_MIN_RATE_ATRR) + .append(metric.getFiveMinuteRate() + END_LSTR); + promMetric.append(helpName) + .append(FIFT_MIN_RATE_ATRR) + .append(metric.getFifteenMinuteRate() + END_LSTR); + } + } + + // build timer metric info + for (String timerkey : reporter.timers().keySet()) { + final com.codahale.metrics.Timer timer = reporter.timers() + .get(timerkey); + if (timer != null) { + helpName = replaceDotDashInKey(timerkey); + promMetric.append(STR_HELP) + .append(helpName).append(END_LSTR); + promMetric.append(STR_TYPE) + .append(helpName) + .append(SPACE_STR + HISTOGRAM_TYPE + END_LSTR); + + promMetric.append(helpName) + .append(COUNT_ATTR) + .append(timer.getCount() + END_LSTR); + promMetric.append(helpName) + .append(ONE_MIN_RATE_ATRR) + .append(timer.getOneMinuteRate() + END_LSTR); + promMetric.append(helpName) + .append(FIVE_MIN_RATE_ATRR) + .append(timer.getFiveMinuteRate() + END_LSTR); + promMetric.append(helpName) + .append(FIFT_MIN_RATE_ATRR) + .append(timer.getFifteenMinuteRate() + END_LSTR); + promMetric.append( + exportSnapshot(helpName, timer.getSnapshot())); + } + } + + MetricsUtil.writePrometheusFormat(promMetric, MetricManager.INSTANCE.getRegistry()); + + return promMetric.toString(); + } + + private Map> statistics() { + Map> metricsMap = new HashMap<>(); + ServerReporter reporter = ServerReporter.instance(); + for (Map.Entry entry : reporter.histograms().entrySet()) { + // entryKey = path/method/responseTimeHistogram + String entryKey = entry.getKey(); + String[] split = entryKey.split("/"); + String lastWord = split[split.length - 1]; + if (!lastWord.equals(METRICS_PATH_RESPONSE_TIME_HISTOGRAM)) { + // original metrics dont report + continue; + } + // metricsName = path/method + String metricsName = + entryKey.substring(0, entryKey.length() - lastWord.length() - 1); + + Counter totalCounter = reporter.counters().get( + joinWithSlash(metricsName, METRICS_PATH_TOTAL_COUNTER)); + Counter failedCounter = reporter.counters().get( + joinWithSlash(metricsName, METRICS_PATH_FAILED_COUNTER)); + Counter successCounter = reporter.counters().get( + joinWithSlash(metricsName, METRICS_PATH_SUCCESS_COUNTER)); + + + Histogram histogram = entry.getValue(); + Map entryMetricsMap = new HashMap<>(); + entryMetricsMap.put(MetricsKeys.MAX_RESPONSE_TIME.name(), + histogram.getSnapshot().getMax()); + entryMetricsMap.put(MetricsKeys.MEAN_RESPONSE_TIME.name(), + histogram.getSnapshot().getMean()); + + entryMetricsMap.put(MetricsKeys.TOTAL_REQUEST.name(), + totalCounter.getCount()); + + if (failedCounter == null) { + entryMetricsMap.put(MetricsKeys.FAILED_REQUEST.name(), 0); + } else { + entryMetricsMap.put(MetricsKeys.FAILED_REQUEST.name(), + failedCounter.getCount()); + } + + if (successCounter == null) { + entryMetricsMap.put(MetricsKeys.SUCCESS_REQUEST.name(), 0); + } else { + entryMetricsMap.put(MetricsKeys.SUCCESS_REQUEST.name(), + successCounter.getCount()); + } + + metricsMap.put(metricsName, entryMetricsMap); + + } + return metricsMap; + } + + private String statisticsProm(Map> metricMap) { + StringBuilder promMetric = new StringBuilder(); + + // build version info + promMetric.append(STR_HELP) + .append(PROM_HELP_NAME).append(END_LSTR); + promMetric.append(STR_TYPE) + .append(PROM_HELP_NAME) + .append(SPACE_STR + UNTYPED + END_LSTR); + promMetric.append(PROM_HELP_NAME) + .append(VERSION_STR) + .append(ApiVersion.VERSION.toString()).append("\",}") + .append(SPACE_STR + "1.0" + END_LSTR); + + for (String methodKey : metricMap.keySet()) { + String metricName = replaceSlashInKey(methodKey); + promMetric.append(STR_HELP) + .append(metricName).append(END_LSTR); + promMetric.append(STR_TYPE) + .append(metricName).append(SPACE_STR + GAUGE_TYPE + END_LSTR); + Map itemMetricMap = metricMap.get(methodKey); + for (String labelName : itemMetricMap.keySet()) { + promMetric.append(metricName).append(LEFT_NAME_STR).append(labelName) + .append(RIGHT_NAME_STR).append(itemMetricMap.get(labelName)) + .append(END_LSTR); + } + } + return promMetric.toString(); + } + + private String joinWithSlash(String path1, String path2) { + return String.join("/", path1, path2); + } } diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/metrics/MetricsKeys.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/metrics/MetricsKeys.java new file mode 100644 index 0000000000..1cda15c829 --- /dev/null +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/metrics/MetricsKeys.java @@ -0,0 +1,40 @@ +/* + * 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.hugegraph.metrics; + +public enum MetricsKeys { + + MAX_RESPONSE_TIME(1, "max_response_time"), + + MEAN_RESPONSE_TIME(2, "mean_response_time"), + + TOTAL_REQUEST(3, "total_request"), + + FAILED_REQUEST(4, "failed_request"), + + SUCCESS_REQUEST(5, "success_request"); + + private final byte code; + private final String name; + + MetricsKeys(int code, String name) { + assert code < 256; + this.code = (byte) code; + this.name = name; + } +} diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/metrics/MetricsUtil.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/metrics/MetricsUtil.java index fabd1df7b9..bb411f9276 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/metrics/MetricsUtil.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/metrics/MetricsUtil.java @@ -24,12 +24,45 @@ import com.codahale.metrics.Histogram; import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Snapshot; import com.codahale.metrics.Timer; public class MetricsUtil { - private static final MetricRegistry REGISTRY = - MetricManager.INSTANCE.getRegistry(); + public static final String METRICS_PATH_TOTAL_COUNTER = "TOTAL_COUNTER"; + public static final String METRICS_PATH_FAILED_COUNTER = "FAILED_COUNTER"; + public static final String METRICS_PATH_SUCCESS_COUNTER = "SUCCESS_COUNTER"; + public static final String METRICS_PATH_RESPONSE_TIME_HISTOGRAM = + "RESPONSE_TIME_HISTOGRAM"; + public static final String P75_ATTR = "{name=\"p75\",} "; + public static final String P95_ATTR = "{name=\"p95\",} "; + public static final String P98_ATTR = "{name=\"p98\",} "; + public static final String P99_ATTR = "{name=\"p99\",} "; + public static final String P999_ATTR = "{name=\"p999\",} "; + public static final String MEAN_RATE_ATRR = "{name=\"mean_rate\",} "; + public static final String ONE_MIN_RATE_ATRR = "{name=\"m1_rate\",} "; + public static final String FIVE_MIN_RATE_ATRR = "{name=\"m5_rate\",} "; + public static final String FIFT_MIN_RATE_ATRR = "{name=\"m15_rate\",} "; + public static final MetricRegistry REGISTRY = MetricManager.INSTANCE.getRegistry(); + public static final String STR_HELP = "# HELP "; + public static final String STR_TYPE = "# TYPE "; + public static final String HISTOGRAM_TYPE = "histogram"; + public static final String UNTYPED = "untyped"; + public static final String GAUGE_TYPE = "gauge"; + public static final String END_LSTR = "\n"; + public static final String SPACE_STR = " "; + public static final String VERSION_STR = "{version=\""; + public static final String COUNT_ATTR = "{name=\"count\",} "; + public static final String MIN_ATTR = "{name=\"min\",} "; + public static final String MAX_ATTR = "{name=\"max\",} "; + public static final String MEAN_ATTR = "{name=\"mean\",} "; + public static final String STDDEV_ATTR = "{name=\"stddev\",} "; + public static final String P50_ATTR = "{name=\"p50\",} "; + + public static final String LEFT_NAME_STR = "{name="; + public static final String RIGHT_NAME_STR = ",} "; + public static final String PROM_HELP_NAME = "hugegraph_info"; + public static Gauge registerGauge(Class clazz, String name, Gauge gauge) { @@ -40,10 +73,18 @@ public static Counter registerCounter(Class clazz, String name) { return REGISTRY.counter(MetricRegistry.name(clazz, name)); } + public static Counter registerCounter(String name) { + return REGISTRY.counter(MetricRegistry.name(name)); + } + public static Histogram registerHistogram(Class clazz, String name) { return REGISTRY.histogram(MetricRegistry.name(clazz, name)); } + public static Histogram registerHistogram(String name) { + return REGISTRY.histogram(name); + } + public static Meter registerMeter(Class clazz, String name) { return REGISTRY.meter(MetricRegistry.name(clazz, name)); } @@ -51,4 +92,124 @@ public static Meter registerMeter(Class clazz, String name) { public static Timer registerTimer(Class clazz, String name) { return REGISTRY.timer(MetricRegistry.name(clazz, name)); } + + public static String replaceDotDashInKey(String orgKey) { + return orgKey.replace(".", "_").replace("-", "_"); + } + + public static String replaceSlashInKey(String orgKey) { + return orgKey.replace("/", "_"); + } + + public static void writePrometheusFormat(StringBuilder promeMetrics, MetricRegistry registry) { + // gauges + registry.getGauges().forEach((key, gauge) -> { + if (gauge != null) { + String helpName = replaceDotDashInKey(key); + promeMetrics.append(STR_HELP) + .append(helpName).append(END_LSTR); + promeMetrics.append(STR_TYPE) + .append(helpName).append(SPACE_STR + GAUGE_TYPE + END_LSTR); + promeMetrics.append(helpName).append(SPACE_STR).append(gauge.getValue()) + .append(END_LSTR); + } + }); + + // histograms + registry.getHistograms().forEach((key, histogram) -> { + if (histogram != null) { + String helpName = replaceDotDashInKey(key); + promeMetrics.append(STR_HELP) + .append(helpName).append(END_LSTR); + promeMetrics.append(STR_TYPE) + .append(helpName) + .append(SPACE_STR + HISTOGRAM_TYPE + END_LSTR); + + promeMetrics.append(helpName) + .append(COUNT_ATTR).append(histogram.getCount()).append(END_LSTR); + promeMetrics.append( + exportSnapshot(helpName, histogram.getSnapshot())); + } + }); + + // meters + registry.getMeters().forEach((key, metric) -> { + if (metric != null) { + String helpName = replaceDotDashInKey(key); + promeMetrics.append(STR_HELP) + .append(helpName).append(END_LSTR); + promeMetrics.append(STR_TYPE) + .append(helpName) + .append(SPACE_STR + HISTOGRAM_TYPE + END_LSTR); + + promeMetrics.append(helpName) + .append(COUNT_ATTR).append(metric.getCount()).append(END_LSTR); + promeMetrics.append(helpName) + .append(MEAN_RATE_ATRR).append(metric.getMeanRate()).append(END_LSTR); + promeMetrics.append(helpName) + .append(ONE_MIN_RATE_ATRR).append(metric.getOneMinuteRate()) + .append(END_LSTR); + promeMetrics.append(helpName) + .append(FIVE_MIN_RATE_ATRR).append(metric.getFiveMinuteRate()) + .append(END_LSTR); + promeMetrics.append(helpName) + .append(FIFT_MIN_RATE_ATRR).append(metric.getFifteenMinuteRate()) + .append(END_LSTR); + } + }); + + // timer + registry.getTimers().forEach((key, timer) -> { + if (timer != null) { + String helpName = replaceDotDashInKey(key); + promeMetrics.append(STR_HELP) + .append(helpName).append(END_LSTR); + promeMetrics.append(STR_TYPE) + .append(helpName) + .append(SPACE_STR + HISTOGRAM_TYPE + END_LSTR); + + promeMetrics.append(helpName) + .append(COUNT_ATTR).append(timer.getCount()).append(END_LSTR); + promeMetrics.append(helpName) + .append(ONE_MIN_RATE_ATRR).append(timer.getOneMinuteRate()) + .append(END_LSTR); + promeMetrics.append(helpName) + .append(FIVE_MIN_RATE_ATRR).append(timer.getFiveMinuteRate()) + .append(END_LSTR); + promeMetrics.append(helpName) + .append(FIFT_MIN_RATE_ATRR).append(timer.getFifteenMinuteRate()) + .append(END_LSTR); + promeMetrics.append( + exportSnapshot(helpName, timer.getSnapshot())); + } + }); + } + + public static String exportSnapshot(final String helpName, final Snapshot snapshot) { + if (snapshot == null) { + return ""; + } + StringBuilder snapMetrics = new StringBuilder(); + snapMetrics.append(helpName) + .append(MIN_ATTR).append(snapshot.getMin()).append(END_LSTR); + snapMetrics.append(helpName) + .append(MAX_ATTR).append(snapshot.getMax()).append(END_LSTR); + snapMetrics.append(helpName) + .append(MEAN_ATTR).append(snapshot.getMean()).append(END_LSTR); + snapMetrics.append(helpName) + .append(STDDEV_ATTR).append(snapshot.getStdDev()).append(END_LSTR); + snapMetrics.append(helpName) + .append(P50_ATTR).append(snapshot.getMedian()).append(END_LSTR); + snapMetrics.append(helpName) + .append(P75_ATTR).append(snapshot.get75thPercentile()).append(END_LSTR); + snapMetrics.append(helpName) + .append(P95_ATTR).append(snapshot.get95thPercentile()).append(END_LSTR); + snapMetrics.append(helpName) + .append(P98_ATTR).append(snapshot.get98thPercentile()).append(END_LSTR); + snapMetrics.append(helpName) + .append(P99_ATTR).append(snapshot.get99thPercentile()).append(END_LSTR); + snapMetrics.append(helpName) + .append(P999_ATTR).append(snapshot.get999thPercentile()).append(END_LSTR); + return snapMetrics.toString(); + } } diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/MetricsApiTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/MetricsApiTest.java index 499103c174..cce5af30cc 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/MetricsApiTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/MetricsApiTest.java @@ -17,20 +17,24 @@ package org.apache.hugegraph.api; +import java.util.HashMap; import java.util.Map; -import jakarta.ws.rs.core.Response; +import org.apache.hugegraph.testutil.Assert; import org.junit.Test; -import org.apache.hugegraph.testutil.Assert; +import jakarta.ws.rs.core.Response; public class MetricsApiTest extends BaseApiTest { - private static String path = "/metrics"; + private static final String path = "/metrics"; + private static final String statisticsPath = path + "/statistics"; @Test - public void testMetricsAll() { - Response r = client().get(path); + public void testBaseMetricsAll() { + Map params = new HashMap<>(); + params.put("type", "json"); + Response r = client().get(path, params); String result = assertResponseStatus(200, r); assertJsonContains(result, "gauges"); assertJsonContains(result, "counters"); @@ -39,6 +43,26 @@ public void testMetricsAll() { assertJsonContains(result, "timers"); } + @Test + public void testBaseMetricsPromAll() { + Response r = client().get(path); + assertResponseStatus(200, r); + } + + @Test + public void testStatisticsMetricsAll() { + Map params = new HashMap<>(); + params.put("type", "json"); + Response r = client().get(path); + assertResponseStatus(200, r); + } + + @Test + public void testStatisticsMetricsPromAll() { + Response r = client().get(statisticsPath); + assertResponseStatus(200, r); + } + @Test public void testMetricsSystem() { Response r = client().get(path, "system");