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    Class<? extends DefaultMetricsSystem> clazz = DefaultMetricsSystem.INSTANCE.getClass();
037    Method m;
038    try {
039      m = clazz.getDeclaredMethod("removeObjectName", String.class);
040      m.setAccessible(true);
041    } catch (NoSuchMethodException e) {
042      m = null;
043    }
044    removeObjectMethod = m;
045
046    Field f1, f2;
047    try {
048      f1 = clazz.getDeclaredField("sourceNames");
049      f1.setAccessible(true);
050      f2 = UniqueNames.class.getDeclaredField("map");
051      f2.setAccessible(true);
052    } catch (NoSuchFieldException e) {
053      LOG.trace(e.toString(), e);
054      f1 = null;
055      f2 = null;
056    }
057    sourceNamesField = f1;
058    mapField = f2;
059  }
060
061  public boolean removeObjectName(final String name) {
062    if (removeObjectMethod != null) {
063      try {
064        removeObjectMethod.invoke(DefaultMetricsSystem.INSTANCE, name);
065        return true;
066      } catch (Exception e) {
067        if (LOG.isTraceEnabled()) {
068          LOG.trace("Unable to remove object name from cache: " + name, e);
069        }
070      }
071    }
072    return false;
073  }
074
075  /**
076   * Unfortunately Hadoop tries to be too-clever and permanently keeps track of all names registered
077   * so far as a Source, thus preventing further re-registration of the source with the same name.
078   * In case of dynamic metrics tied to region-lifecycles, this becomes a problem because we would
079   * like to be able to re-register and remove with the same name. Otherwise, it is resource leak.
080   * This ugly code manually removes the name from the UniqueNames map.
081   * TODO: May not be needed for Hadoop versions after YARN-5190.
082   */
083  public void removeSourceName(String name) {
084    if (sourceNamesField == null || mapField == null) {
085      return;
086    }
087    try {
088      Object sourceNames = sourceNamesField.get(DefaultMetricsSystem.INSTANCE);
089      HashMap map = (HashMap) mapField.get(sourceNames);
090      synchronized (sourceNames) {
091        map.remove(name);
092      }
093    } catch (Exception ex) {
094      if (LOG.isTraceEnabled()) {
095        LOG.trace("Received exception while trying to access Hadoop Metrics classes via " +
096                        "reflection.", ex);
097      }
098    }
099  }
100}