Package org.helios.netty.jmx

Source Code of org.helios.netty.jmx.MetricCollector

/**
* Helios, OpenSource Monitoring
* Brought to you by the Helios Development Group
*
* Copyright 2007, Helios Development Group and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
*/
package org.helios.netty.jmx;

import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.OK;
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;

import java.lang.Thread.State;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.net.SocketAddress;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.ObjectName;

import org.apache.log4j.Logger;
import org.helios.netty.ajax.PipelineModifier;
import org.helios.netty.ajax.SharedChannelGroup;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelDownstreamHandler;
import org.jboss.netty.channel.ChannelEvent;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelUpstreamHandler;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.DownstreamMessageEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.util.CharsetUtil;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
* <p>Title: MetricCollector</p>
* <p>Description: Background task processor that periodically collects metrics and sends them to all active channels as a JSON object</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.netty.jmx.MetricCollector</code></p>
*/

public class MetricCollector extends NotificationBroadcasterSupport implements MetricCollectorMXBean, Runnable, PipelineModifier, ChannelDownstreamHandler, ChannelUpstreamHandler {
  /** The memory mx bean */
  public static final MemoryMXBean memMxBean = ManagementFactory.getMemoryMXBean();
  /** The thread mx bean */
  public static final ThreadMXBean threadMxBean = ManagementFactory.getThreadMXBean();
  /** The NIO Direct MXBean ObjectName */
  public static final ObjectName directNio = JMXHelper.objectName("java.nio:type=BufferPool,name=direct");
 
  /** The NIO attributes we are interested in */
  public static final String[] NIO_ATTRS = new String[]{"Count", "MemoryUsed", "TotalCapacity"};
  /** Indicates if we have the NIO MXBean */
  protected final boolean haveNioMXBean;
  /** The period between collections */
  protected long period = 5000;
  /** Serial number factory for thread names */
  protected final AtomicInteger serial = new AtomicInteger(0);
  /** Serial number factory for notifications */
  protected final AtomicLong tick = new AtomicLong(0);
  /** The ObjectName for the metric collector */
  public static final ObjectName OBJECT_NAME = JMXHelper.objectName("org.helios.netty.jmx:service=MetricCollector");
  /** A set of the unique metric names */
  protected final Set<String> metricNames = new CopyOnWriteArraySet<String>();
  /** Instance logger */
  protected final Logger log = Logger.getLogger(getClass());
  /** The schedule handle */
  protected ScheduledFuture<?> handle = null;
  /** The scheduler */
  protected final ScheduledThreadPoolExecutor scheduler;
  /**  A map of remotely submitted metrics keyed by the address that they came from */
  protected final Map<SocketAddress, Map<String, Long>> pendingRemoteMetrics = new ConcurrentHashMap<SocketAddress, Map<String, Long>> ();
  /** Dropped metric counter */
  protected final AtomicLong dropCounter = new AtomicLong(0);
 
  /** The singleton instance */
  private static volatile MetricCollector instance = null;
  /** The singleton ctor lock */
  private static final Object lock = new Object();
 
  /**
   * Acquires the singleton instance. First call wins on the period.
   * @param period The ms. between each metric collect.
   * @return The metric collector singleton
   */
  public static MetricCollector getInstance(long period) {
    if(instance==null) {
      synchronized(lock) {
        if(instance==null) {
          instance = new MetricCollector(period);
        }
      }
    }
    return instance;
  }
 
  /**
   * Acquires the singleton instance.
   * @return The metric collector singleton
   */
  public static MetricCollector getInstance() {
    if(instance==null) {
      throw new RuntimeException("The metric collector has not been initialized", new Throwable());
    }
    return instance;
  }
 
  /**
   * Creates a new MetricCollector
   * @param period The period of collection
   */
  private MetricCollector(long period) {
    super();
    haveNioMXBean = ManagementFactory.getPlatformMBeanServer().isRegistered(directNio);
    this.period = period;
    try {     
      ManagementFactory.getPlatformMBeanServer().registerMBean(this, OBJECT_NAME);
      scheduler = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(2, new ThreadFactory(){
        public Thread newThread(Runnable r) {
          Thread t = new Thread(r, OBJECT_NAME.getKeyProperty("service") + "Thread#" + serial.incrementAndGet());
          t.setDaemon(true);
          return t;
        }
      });
      initMetricNames();
      scheduler.schedule(this, period, TimeUnit.MILLISECONDS);
      log.info("Started MetricCollector with a period of " + period + " ms.");
    } catch (Exception e) {
      throw new RuntimeException("Failed to create MetricCollector", e);
    }
  }
 
  /**
   * Submits a new metric and value
   * @param metricName The metric name
   * @param value The metric value
   */
  public void submitMetric(String metricName, long value) {
    if(metricNames.add(metricName)) {
      JSONObject envelope = new JSONObject();
      try {
        envelope.put("metric-names", new JSONArray(new String[]{metricName}));
        SharedChannelGroup.getInstance().write(envelope);
        SharedChannelGroup.getInstance().write(packageMetricUpdate(Collections.singletonMap(metricName, value)));
      } catch (JSONException e) {
        log.error("Failed to submit metric", e);
      }     
    }
  }
 
 
  /**
   * Submits a map of metrics from a remote socket submitter
   * @param clientSocket The address of the submitting socket
   * @param metrics A map of metric values keyed by metric name
   */
  public void submitMetrics(SocketAddress clientSocket , Map<String, Long> metrics) {
    submitMetrics(metrics);
    if(pendingRemoteMetrics.put(clientSocket, metrics)!=null) {
      dropCounter.incrementAndGet();
    }
  }

  /**
   * Returns the number of dropped metrics
   * @return the number of dropped metrics
   */
  public long getDroppedMetricCount() {
    return dropCounter.get();
  }
 
  /**
   * Submits a map of metrics
   * @param metrics A map of metric values keyed by metric name
   */
  public void submitMetrics(Map<String, Long> metrics) {
    Set<String> newNames = new HashSet<String>();
    for(String s: metrics.keySet()) {
      if(metricNames.add(s)) {
        newNames.add(s);
      }
    }
    try {
      JSONObject envelope = new JSONObject();
      envelope.put("metric-names", new JSONArray(newNames.toArray(new String[newNames.size()])));
      SharedChannelGroup.getInstance().write(envelope);
    } catch (JSONException e) {
      log.error("Failed to submit metric names", e);
    }   
    try {
      SharedChannelGroup.getInstance().write(packageMetricUpdate(metrics));
    } catch (Exception e) {
      log.error("Failed to send metrics", e);
    }         
  }
 
 
  /**
   * Packages the passed map of metrics into a JSONObject
   * @param metrics A map of metric values keyed by metric name
   * @return The JSONObject with the metrics
   */
  protected JSONObject packageMetricUpdate(Map<String, Long> metrics) {
    try {
      final JSONObject top = new JSONObject();
      final JSONObject envelope = new JSONObject();
      envelope.put("metrics", top);
      for(Map.Entry<String, Long> entry: metrics.entrySet()) {
        insertMetric(entry.getKey(), entry.getValue(), top);
      }
      return envelope;
    } catch (Exception e) {
      log.error("Failed to package metric updates", e);
      return null;
    }
  }
 
  /**
   * Inserts the metric name keys and value into the passed JSONObject
   * @param metricName The metric name
   * @param value The metric value
   * @param top The JSONObject to insert into
   * @throws JSONException
   */
  protected void insertMetric(String metricName, long value, JSONObject top) throws JSONException {
    String[] frags = metricName.split("\\.");
    int fc = frags.length;
    JSONObject current = top;
    for(int i = 0; i < fc; i++) {
      if(i==fc-1) {
        current.put(frags[i], value);
      } else {
        JSONObject tmp = null;
        if(current.has(frags[i])) {
          tmp = current.getJSONObject(frags[i]);
        } else {
          tmp = new JSONObject();
          current.put(frags[i], tmp);         
        }
        current = tmp;
      }
    }
  }
 
 
  /**
   * The MetricCollector is not a real MetricProvider, but it needs to supply the names of the metrics
   * it published, so we add them to the registry here.
   */
  protected void initMetricNames() {
    String[] memMetrics = new String[]{
        "[capacity(%)]", "[committed]", "[init]", "[max]", "[used]", "[consumed(%)]"
    };
    for(String m: memMetrics) {
      metricNames.add("[heap]." + m);
      metricNames.add("[non-heap]." + m);
    }
    if(haveNioMXBean) {
      for(String s: NIO_ATTRS) {
        metricNames.add("direct-nio." + s);
      }
    }
    metricNames.add("thread-states*");
  }
 
  /**
   * Executes the collection
   * {@inheritDoc}
   * @see java.lang.Runnable#run()
   */
  public void run() {
    try {
      updateMetricNames();
      long channelCount = SharedChannelGroup.getInstance().size();
      //log.info("\n\tChanne,l Count:" + channelCount);
      submitMetric("netty.channels.count", channelCount);
      Notification notif = new Notification(MetricProvider.METRIC_NOTIFICATION, OBJECT_NAME, tick.incrementAndGet(), System.currentTimeMillis());
      final JSONObject json = new JSONObject();
      final JSONObject envelope = new JSONObject();
      envelope.put("metrics", json);
      notif.setUserData(json);
      pushRemoteMetrics(json);
      json.put("ts", System.currentTimeMillis());     
      json.put("heap", processMemoryUsage(memMxBean.getHeapMemoryUsage()));
      json.put("non-heap", processMemoryUsage(memMxBean.getNonHeapMemoryUsage()))
      json.put("thread-states*", new JSONObject(getThreadStates()));
      if(haveNioMXBean) {
        json.put("direct-nio", new JSONObject(getNio()));       
      }
      sendNotification(notif);
      SharedChannelGroup.getInstance().write(envelope);
    } catch (Exception e) {
      e.printStackTrace(System.err);
    } finally {
      scheduler.schedule(this, period, TimeUnit.MILLISECONDS);
    }
  }
 
  /**
   * Retrieves the unsubmitted remote metrics and packages them into the passed json object
   * @param json The JSON Object to package the metrics into
   */
  protected void pushRemoteMetrics(final JSONObject json) {
    Map<SocketAddress, Map<String, Long>> tmpMap = new HashMap<SocketAddress, Map<String, Long>>(pendingRemoteMetrics);
    pendingRemoteMetrics.clear();
    for(Map.Entry<SocketAddress, Map<String, Long>> entry: tmpMap.entrySet()) {
      for(Map.Entry<String, Long> mentry: entry.getValue().entrySet()) {
        try {
          insertMetric(mentry.getKey(), mentry.getValue(), json);
        } catch (JSONException e) {
        }
      }
    }
    try {
      insertMetric("metriccollector.drops", getDroppedMetricCount(), json);
    } catch (Exception e) {}
    tmpMap.clear();
  }
  /**
   * Collects metric names from participating metric providers
   * @throws JSONException thrown on any json exception
   */
  protected void updateMetricNames() throws JSONException {
    Notification notif = new Notification(MetricProvider.METRIC_NAME_NOTIFICATION, OBJECT_NAME, tick.incrementAndGet(), System.currentTimeMillis());
    final Set<String> names = new HashSet<String>();
    final Set<String> newNames = new HashSet<String>();
    notif.setUserData(names);
    sendNotification(notif);
    for(String s: names) {
      if(metricNames.add(s)) {
        newNames.add(s);
      }
    }
    if(!newNames.isEmpty()) {
      JSONObject envelope = new JSONObject();
      envelope.put("metric-names", new JSONArray(newNames));
      SharedChannelGroup.getInstance().write(envelope);
    }
  }
 
  /**
   * Generates a {@link JSONObject} representing memory usage, plus the percentage usage of:<ul>
   * <li>Memory Allocated</li>
   * <li>Memory Maximum Capacity</li>
   * </ul>
   * @param usage The memory usage provided by the {@link MemoryMXBean}
   * @return A {@link JSONObject}
   * @throws JSONException I have no idea why this would be thrown
   */
  protected JSONObject processMemoryUsage(MemoryUsage usage) throws JSONException {
    JSONObject json = new JSONObject(usage);
    json.put("consumed(%)", calcPercent(usage.getUsed(), usage.getCommitted()));
    json.put("capacity(%)", calcPercent(usage.getUsed(), usage.getMax()));   
    return json;
  }
 
  /**
   * Returns a JSONArray of all the registered metric names
   * @return the metricNames
   */
  public JSONObject getMetricNamesJSON() {
    JSONArray arr = new JSONArray(metricNames);
    JSONObject mn = new JSONObject();
    try {
      mn.put("metric-names", arr);
    } catch (JSONException e) {
      e.printStackTrace();
    }
    return mn;
  }
 
  /**
   * Returns a JSON string of all the registered metric names
   * @return the metricNames
   */
  public String getMetricNames() {
    JSONArray arr = new JSONArray(metricNames);
    JSONObject mn = new JSONObject();
    try {
      mn.put("metric-names", arr);
    } catch (JSONException e) {
      e.printStackTrace();
    }
    return mn.toString();
  }
 
 
 
  /**
   * Simple percentage calculator
   * @param part The part value
   * @param whole The whole value
   * @return The percentage that the part is of the whole as a long
   */
  protected long calcPercent(double part, double whole) {
    if(part<1 || whole<1) return 0L;
    double d = part/whole*100;
    return (long)d;
  }
 
 
  /**
   * Returns a simple map of NIO metrics
   * @return a simple map of NIO metrics
   */
  protected Map<String, Long> getNio() {
    Map<String, Long> map = new HashMap<String, Long>(NIO_ATTRS.length);
    try {
      AttributeList attrs = ManagementFactory.getPlatformMBeanServer().getAttributes(directNio, NIO_ATTRS);
      for(Attribute attr: attrs.asList()) {
        map.put(attr.getName(), (Long)attr.getValue());
      }
    } catch (Exception e) {
      e.printStackTrace(System.err);
    }
    return map;
  }
 
  /**
   * Collects the number of threads in each thread state
   * @return an EnumMap with Thread states as the key and the number of threads in that state as the value
   */
  public EnumMap<Thread.State, AtomicInteger> getThreadStates() {
    EnumMap<Thread.State, AtomicInteger> map = new EnumMap<State, AtomicInteger>(Thread.State.class);
    for(ThreadInfo ti : threadMxBean.getThreadInfo(threadMxBean.getAllThreadIds())) {
      State st = ti.getThreadState();
      AtomicInteger ai = map.get(st);
      if(ai==null) {
        ai = new AtomicInteger(0);
        map.put(st, ai);
      }
      ai.incrementAndGet();
    }
    return map;
  }

  /**
   * Returns the collection period in ms.
   * @return the period
   */
  public long getPeriod() {
    return period;
  }

  /**
   * Sets the collection period in ms.
   * @param period the period to set
   */
  public void setPeriod(long period) {
    this.period = period;
  }

  /**
   * {@inheritDoc}
   * @see org.helios.netty.ajax.PipelineModifier#modifyPipeline(org.jboss.netty.channel.ChannelPipeline)
   */
  @Override
  public void modifyPipeline(ChannelPipeline pipeline) {
    if(pipeline.get("metricnames")==null) {
      pipeline.addLast("metricnames", this);
    }   
  }

  /**
   * {@inheritDoc}
   * @see org.helios.netty.ajax.PipelineModifier#getName()
   */
  @Override
  public String getName() {
    return "metricnames";
  }

  /**
   * {@inheritDoc}
   * @see org.helios.netty.ajax.PipelineModifier#getChannelHandler()
   */
  @Override
  public ChannelHandler getChannelHandler() {
    return this;
  }

  /**
   * {@inheritDoc}
   * @see org.jboss.netty.channel.ChannelDownstreamHandler#handleDownstream(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.ChannelEvent)
   */
  @Override
  public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
    final Channel channel = e.getChannel();
    if(!channel.isOpen()) return;
    if(!(e instanceof MessageEvent)) {
            ctx.sendDownstream(e);
            return;
        }
    Object message = ((MessageEvent)e).getMessage();
    if(!(message instanceof JSONObject) && !(message instanceof CharSequence)) {
            ctx.sendDownstream(e);
            return;     
    }
   
    HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
    response.setContent(ChannelBuffers.copiedBuffer("\n" + message.toString() + "\n", CharsetUtil.UTF_8));
    response.setHeader(CONTENT_TYPE, "application/json");
    ChannelFuture cf = Channels.future(channel);
    cf.addListener(new ChannelFutureListener(){
      public void operationComplete(ChannelFuture f) throws Exception {
        channel.close();
      }
    });
    ctx.sendDownstream(new DownstreamMessageEvent(channel, cf, response, channel.getRemoteAddress()));
  }

  /**
   * {@inheritDoc}
   * @see org.jboss.netty.channel.ChannelUpstreamHandler#handleUpstream(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.ChannelEvent)
   */
  @Override
  public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
    if(e instanceof MessageEvent) {
      Object msg = ((MessageEvent)e).getMessage();
      if(msg instanceof HttpRequest) {
        //ctx.sendDownstream(new DownstreamMessageEvent(e.getChannel(), Channels.future(e.getChannel()), getMetricNamesJSON(), ((MessageEvent) e).getRemoteAddress()));
        e.getChannel().write(getMetricNamesJSON());
      }
    }
    ctx.sendUpstream(e);
  }

}
TOP

Related Classes of org.helios.netty.jmx.MetricCollector

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.