diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/PrometheusMetricsSink.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/PrometheusMetricsSink.java index 3a6a079a056d..e77553ea3a41 100644 --- a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/PrometheusMetricsSink.java +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/PrometheusMetricsSink.java @@ -24,9 +24,11 @@ import java.util.Collections; import java.util.Map; import java.util.TreeMap; +import java.util.List; import java.util.regex.Pattern; import org.apache.commons.configuration2.SubsetConfiguration; import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.hdds.utils.DecayRpcSchedulerUtil; import org.apache.hadoop.metrics2.AbstractMetric; import org.apache.hadoop.metrics2.MetricType; import org.apache.hadoop.metrics2.MetricsRecord; @@ -61,11 +63,17 @@ public void putMetrics(MetricsRecord metricsRecord) { if (metrics.type() == MetricType.COUNTER || metrics.type() == MetricType.GAUGE) { + String metricName = DecayRpcSchedulerUtil + .splitMetricNameIfNeeded(metricsRecord.name(), metrics.name()); + // If there is no username this should be null + String username = DecayRpcSchedulerUtil + .checkMetricNameForUsername(metricsRecord.name(), metrics.name()); + String key = prometheusName( - metricsRecord.name(), metrics.name()); + metricsRecord.name(), metricName); String prometheusMetricKeyAsString = - getPrometheusMetricKeyAsString(metricsRecord, key); + getPrometheusMetricKeyAsString(metricsRecord, key, username); String metricKey = "# TYPE " + key @@ -80,14 +88,20 @@ public void putMetrics(MetricsRecord metricsRecord) { } private String getPrometheusMetricKeyAsString(MetricsRecord metricsRecord, - String key) { + String key, String username) { StringBuilder prometheusMetricKey = new StringBuilder(); prometheusMetricKey.append(key) .append("{"); String sep = ""; + // tagListWithUsernameIfNeeded() checks if username is null. + // If it's not then it returns a list with the existing + // metric tags and a username tag. + List metricTagList = DecayRpcSchedulerUtil + .tagListWithUsernameIfNeeded(metricsRecord, username); + //add tags - for (MetricsTag tag : metricsRecord.tags()) { + for (MetricsTag tag : metricTagList) { String tagName = tag.name().toLowerCase(); //ignore specific tag which includes sub-hierarchy diff --git a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/DecayRpcSchedulerUtil.java b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/DecayRpcSchedulerUtil.java new file mode 100644 index 000000000000..e0fe8be8a8cd --- /dev/null +++ b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/utils/DecayRpcSchedulerUtil.java @@ -0,0 +1,121 @@ +/** + * 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.hadoop.hdds.utils; + +import com.google.common.base.Strings; +import org.apache.hadoop.metrics2.MetricsInfo; +import org.apache.hadoop.metrics2.MetricsRecord; +import org.apache.hadoop.metrics2.MetricsTag; + +import java.util.ArrayList; +import java.util.List; + +/** + * Helper functions for DecayRpcScheduler + * metrics for Prometheus. + */ +public final class DecayRpcSchedulerUtil { + + private DecayRpcSchedulerUtil() { + } + + private static final MetricsInfo USERNAME_INFO = new MetricsInfo() { + @Override + public String name() { + return "username"; + } + + @Override + public String description() { + return "caller username"; + } + }; + + /** + * For Decay_Rpc_Scheduler, the metric name is in format + * "Caller().Volume" + * or + * "Caller().Priority" + * Split it and return the metric. + * + * If the recordName doesn't belong to Decay_Rpc_Scheduler, + * then return the metricName as it is without making + * any changes to it. + * + * @param recordName + * @param metricName "Caller(xyz).Volume" or "Caller(xyz).Priority" + * @return "Volume" or "Priority" or metricName(unchanged) + */ + public static String splitMetricNameIfNeeded(String recordName, + String metricName) { + if (recordName.toLowerCase().contains("decayrpcscheduler") && + metricName.toLowerCase().contains("caller(")) { + // names will contain ["Caller(xyz)", "Volume" / "Priority"] + String[] names = metricName.split("[.]"); + + // "Volume" or "Priority" + return names[1]; + } + return metricName; + } + + /** + * For Decay_Rpc_Scheduler, split the metric name + * and then get the part that is in the format "Caller()" + * and split it to return the username. + * @param recordName + * @param metricName + * @return caller username or null if not present + */ + public static String checkMetricNameForUsername(String recordName, + String metricName) { + if (recordName.toLowerCase().contains("decayrpcscheduler") && + metricName.toLowerCase().contains("caller(")) { + // names will contain ["Caller(xyz)", "Volume" / "Priority"] + String[] names = metricName.split("[.]"); + + // Caller(xyz) + String caller = names[0]; + + // subStrings will contain ["Caller", "xyz"] + String[] subStrings = caller.split("[()]"); + + String username = subStrings[1]; + + return username; + } + return null; + } + + /** + * MetricRecord.tags() is an unmodifiable collection of tags. + * Store it in a list, to modify it and add a username tag. + * @param metricsRecord + * @return the new list with the metric tags and the username tag + */ + public static List tagListWithUsernameIfNeeded( + MetricsRecord metricsRecord, String username) { + List list = new ArrayList<>(metricsRecord.tags()); + + if (!Strings.isNullOrEmpty(username)) { + MetricsTag tag = new MetricsTag(USERNAME_INFO, username); + list.add(tag); + } + return list; + } +} diff --git a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/TestDecayRpcSchedulerUtil.java b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/TestDecayRpcSchedulerUtil.java new file mode 100644 index 000000000000..b3ccbafca625 --- /dev/null +++ b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/utils/TestDecayRpcSchedulerUtil.java @@ -0,0 +1,75 @@ +/* + * 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.hadoop.hdds.utils; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Test class for DecayRpcSchedulerUtil. + */ +public class TestDecayRpcSchedulerUtil { + + private static final String USERNAME = "testUser"; + private static final String METRIC_NAME_VOLUME = "Volume"; + + private static final String RECORD_NAME = + "org.apache.hadoop.ipc.DecayRpcScheduler"; + private static final String METRIC_NAME = + "Caller(" + USERNAME + ")." + METRIC_NAME_VOLUME; + + private static final String RANDOM_RECORD_NAME = "JvmMetrics"; + private static final String RANDOM_METRIC_NAME = "ThreadsNew"; + + @Test + public void testSplitMetricNameIfNeeded() { + // Split the metric name and return only the + // name of the metric type. + String splitName = DecayRpcSchedulerUtil + .splitMetricNameIfNeeded(RECORD_NAME, METRIC_NAME); + + assertEquals(METRIC_NAME_VOLUME, splitName); + + // This metric name should remain the same. + String unchangedName = DecayRpcSchedulerUtil + .splitMetricNameIfNeeded(RANDOM_RECORD_NAME, RANDOM_METRIC_NAME); + + assertEquals(RANDOM_METRIC_NAME, unchangedName); + } + + @Test + public void testCheckMetricNameForUsername() { + // Get the username from the metric name. + String decayRpcSchedulerUsername = DecayRpcSchedulerUtil + .checkMetricNameForUsername(RECORD_NAME, METRIC_NAME); + + assertEquals(USERNAME, decayRpcSchedulerUsername); + + // This metric doesn't contain a username in the metric name. + // DecayRpcSchedulerUtil.checkMetricNameForUsername() + // should return null. + String nullUsername = DecayRpcSchedulerUtil + .checkMetricNameForUsername(RANDOM_RECORD_NAME, RANDOM_METRIC_NAME); + + assertNull(nullUsername); + } +}