001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.metrics2.lib;
019
020import java.lang.reflect.Field;
021import java.lang.reflect.Method;
022import java.util.HashMap;
023import org.apache.yetus.audience.InterfaceAudience;
024import org.slf4j.Logger;
025import org.slf4j.LoggerFactory;
026
027@InterfaceAudience.Private
028public class DefaultMetricsSystemHelper {
029
030  private static final Logger LOG = LoggerFactory.getLogger(DefaultMetricsSystemHelper.class);
031  private final Method removeObjectMethod;
032  private final Field sourceNamesField;
033  private final Field mapField;
034
035  public DefaultMetricsSystemHelper() {
036    @SuppressWarnings("GetClassOnEnum")
037    Class<? extends DefaultMetricsSystem> clazz = DefaultMetricsSystem.INSTANCE.getClass();
038    Method m;
039    try {
040      m = clazz.getDeclaredMethod("removeObjectName", String.class);
041      m.setAccessible(true);
042    } catch (NoSuchMethodException e) {
043      m = null;
044    }
045    removeObjectMethod = m;
046
047    Field f1, f2;
048    try {
049      f1 = clazz.getDeclaredField("sourceNames");
050      f1.setAccessible(true);
051      f2 = UniqueNames.class.getDeclaredField("map");
052      f2.setAccessible(true);
053    } catch (NoSuchFieldException e) {
054      LOG.trace(e.toString(), e);
055      f1 = null;
056      f2 = null;
057    }
058    sourceNamesField = f1;
059    mapField = f2;
060  }
061
062  public boolean removeObjectName(final String name) {
063    if (removeObjectMethod != null) {
064      try {
065        removeObjectMethod.invoke(DefaultMetricsSystem.INSTANCE, name);
066        return true;
067      } catch (Exception e) {
068        if (LOG.isTraceEnabled()) {
069          LOG.trace("Unable to remove object name from cache: " + name, e);
070        }
071      }
072    }
073    return false;
074  }
075
076  /**
077   * Unfortunately Hadoop tries to be too-clever and permanently keeps track of all names registered
078   * so far as a Source, thus preventing further re-registration of the source with the same name.
079   * In case of dynamic metrics tied to region-lifecycles, this becomes a problem because we would
080   * like to be able to re-register and remove with the same name. Otherwise, it is resource leak.
081   * This ugly code manually removes the name from the UniqueNames map. TODO: May not be needed for
082   * Hadoop versions after YARN-5190.
083   */
084  public void removeSourceName(String name) {
085    if (sourceNamesField == null || mapField == null) {
086      return;
087    }
088    try {
089      Object sourceNames = sourceNamesField.get(DefaultMetricsSystem.INSTANCE);
090      HashMap map = (HashMap) mapField.get(sourceNames);
091      synchronized (sourceNames) {
092        map.remove(name);
093      }
094    } catch (Exception ex) {
095      if (LOG.isTraceEnabled()) {
096        LOG.trace(
097          "Received exception while trying to access Hadoop Metrics classes via " + "reflection.",
098          ex);
099      }
100    }
101  }
102}