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.hdfs.server.common;
019
020import java.lang.management.ManagementFactory;
021import java.util.Collections;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Set;
025
026import javax.management.Attribute;
027import javax.management.AttributeList;
028import javax.management.MBeanAttributeInfo;
029import javax.management.MBeanInfo;
030import javax.management.MBeanServer;
031import javax.management.MalformedObjectNameException;
032import javax.management.ObjectName;
033
034import org.apache.commons.logging.Log;
035import org.apache.commons.logging.LogFactory;
036import org.apache.commons.logging.impl.Log4JLogger;
037import org.apache.hadoop.metrics2.util.MBeans;
038import org.apache.log4j.Appender;
039import org.apache.log4j.AsyncAppender;
040
041/**
042 * MetricsLoggerTask can be used as utility to dump metrics to log.
043 */
044public class MetricsLoggerTask implements Runnable {
045
046  public static final Log LOG = LogFactory.getLog(MetricsLoggerTask.class);
047
048  private static ObjectName objectName = null;
049
050  static {
051    try {
052      objectName = new ObjectName("Hadoop:*");
053    } catch (MalformedObjectNameException m) {
054      // This should not occur in practice since we pass
055      // a valid pattern to the constructor above.
056    }
057  }
058
059  private Log metricsLog;
060  private String nodeName;
061  private short maxLogLineLength;
062
063  public MetricsLoggerTask(Log metricsLog, String nodeName,
064      short maxLogLineLength) {
065    this.metricsLog = metricsLog;
066    this.nodeName = nodeName;
067    this.maxLogLineLength = maxLogLineLength;
068  }
069
070  /**
071   * Write metrics to the metrics appender when invoked.
072   */
073  @Override
074  public void run() {
075    // Skip querying metrics if there are no known appenders.
076    if (!metricsLog.isInfoEnabled() || !hasAppenders(metricsLog)
077        || objectName == null) {
078      return;
079    }
080
081    metricsLog.info(" >> Begin " + nodeName + " metrics dump");
082    final MBeanServer server = ManagementFactory.getPlatformMBeanServer();
083
084    // Iterate over each MBean.
085    for (final ObjectName mbeanName : server.queryNames(objectName, null)) {
086      try {
087        MBeanInfo mBeanInfo = server.getMBeanInfo(mbeanName);
088        final String mBeanNameName = MBeans.getMbeanNameName(mbeanName);
089        final Set<String> attributeNames = getFilteredAttributes(mBeanInfo);
090
091        final AttributeList attributes = server.getAttributes(mbeanName,
092            attributeNames.toArray(new String[attributeNames.size()]));
093
094        for (Object o : attributes) {
095          final Attribute attribute = (Attribute) o;
096          final Object value = attribute.getValue();
097          final String valueStr = (value != null) ? value.toString() : "null";
098          // Truncate the value if it is too long
099          metricsLog.info(mBeanNameName + ":" + attribute.getName() + "="
100              + trimLine(valueStr));
101        }
102      } catch (Exception e) {
103        metricsLog.error("Failed to get " + nodeName + " metrics for mbean "
104            + mbeanName.toString(), e);
105      }
106    }
107    metricsLog.info(" << End " + nodeName + " metrics dump");
108  }
109
110  private String trimLine(String valueStr) {
111    if (maxLogLineLength <= 0) {
112      return valueStr;
113    }
114
115    return (valueStr.length() < maxLogLineLength ? valueStr : valueStr
116        .substring(0, maxLogLineLength) + "...");
117  }
118
119  private static boolean hasAppenders(Log logger) {
120    if (!(logger instanceof Log4JLogger)) {
121      // Don't bother trying to determine the presence of appenders.
122      return true;
123    }
124    Log4JLogger log4JLogger = ((Log4JLogger) logger);
125    return log4JLogger.getLogger().getAllAppenders().hasMoreElements();
126  }
127
128  /**
129   * Get the list of attributes for the MBean, filtering out a few attribute
130   * types.
131   */
132  private static Set<String> getFilteredAttributes(MBeanInfo mBeanInfo) {
133    Set<String> attributeNames = new HashSet<>();
134    for (MBeanAttributeInfo attributeInfo : mBeanInfo.getAttributes()) {
135      if (!attributeInfo.getType().equals(
136          "javax.management.openmbean.TabularData")
137          && !attributeInfo.getType().equals(
138              "javax.management.openmbean.CompositeData")
139          && !attributeInfo.getType().equals(
140              "[Ljavax.management.openmbean.CompositeData;")) {
141        attributeNames.add(attributeInfo.getName());
142      }
143    }
144    return attributeNames;
145  }
146
147  /**
148   * Make the metrics logger async and add all pre-existing appenders to the
149   * async appender.
150   */
151  public static void makeMetricsLoggerAsync(Log metricsLog) {
152    if (!(metricsLog instanceof Log4JLogger)) {
153      LOG.warn("Metrics logging will not be async since "
154          + "the logger is not log4j");
155      return;
156    }
157    org.apache.log4j.Logger logger = ((Log4JLogger) metricsLog).getLogger();
158    logger.setAdditivity(false); // Don't pollute actual logs with metrics dump
159
160    @SuppressWarnings("unchecked")
161    List<Appender> appenders = Collections.list(logger.getAllAppenders());
162    // failsafe against trying to async it more than once
163    if (!appenders.isEmpty() && !(appenders.get(0) instanceof AsyncAppender)) {
164      AsyncAppender asyncAppender = new AsyncAppender();
165      // change logger to have an async appender containing all the
166      // previously configured appenders
167      for (Appender appender : appenders) {
168        logger.removeAppender(appender);
169        asyncAppender.addAppender(appender);
170      }
171      logger.addAppender(asyncAppender);
172    }
173  }
174}