package com.linkedin.databus.core.monitoring.mbean; /* * * Copyright 2013 LinkedIn Corp. All rights reserved * * Licensed 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. * */ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Hashtable; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.regex.Pattern; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import org.apache.avro.io.JsonEncoder; import org.apache.avro.specific.SpecificDatumWriter; import org.apache.log4j.Logger; import com.linkedin.databus.core.util.JmxUtil; import com.linkedin.databus.core.util.ReadWriteSyncedObject; public abstract class AbstractMonitoringMBean<T> extends ReadWriteSyncedObject implements DatabusMonitoringMBean<T> { public static final String MODULE = AbstractMonitoringMBean.class.getName(); public static final Logger LOG = Logger.getLogger(MODULE); public static final String JMX_DOMAIN = "com.linkedin.databus2"; private static final Pattern BAD_CHARS_PATTERN = Pattern.compile("[:,?=]"); // we use -1 in both cases(instead of real MAX and MIN) because we want to // avoid spikes on the graphs. public static final long DEFAULT_MAX_LONG_VALUE = -1; public static final long DEFAULT_MIN_LONG_VALUE = -1; protected final AtomicBoolean _enabled; protected T _event; protected final SpecificDatumWriter<T> _avroWriter; protected AbstractMonitoringMBean(boolean enabled, boolean threadSafe, T initEvent) { super(threadSafe); _enabled = new AtomicBoolean(enabled); _event = null == initEvent ? newDataEvent() : initEvent; _avroWriter = getAvroWriter(); } @Override public boolean isEnabled() { return _enabled.get(); } @Override public void reset() { Lock writeLock = acquireWriteLock(); try { resetData(); } finally { releaseLock(writeLock); } } @Override public void setEnabled(boolean enabled) { _enabled.set(enabled); } @Override public T getStatistics(T reuse) { if (null == reuse) reuse = newDataEvent(); Lock readLock = acquireReadLock(); try { cloneData(reuse); } finally { releaseLock(readLock); } return reuse; } @Override public String toJson() { ByteArrayOutputStream out = new ByteArrayOutputStream(10000); try { JsonEncoder jsonEncoder = createJsonEncoder(out); toJson(jsonEncoder, null); } catch (IOException ioe) { LOG.error("JSON serialization error", ioe); out.reset(); } return out.toString(); } @Override public T toJson(JsonEncoder jsonEncoder, T eventReuse) throws IOException { eventReuse = getStatistics(eventReuse); _avroWriter.write(eventReuse, jsonEncoder); jsonEncoder.flush(); return eventReuse; } @SuppressWarnings("unchecked") @Override public void mergeStats(DatabusMonitoringMBean<T> other) { if (! (other instanceof AbstractMonitoringMBean<?>)) return; AbstractMonitoringMBean<Object> otherObj = (AbstractMonitoringMBean<Object>)other; if (! _event.getClass().isInstance(otherObj._event)) return; Object otherEvent = otherObj._event; Lock otherReadLock = otherObj.acquireReadLock(); Lock thisWriteLock = null; try { thisWriteLock = acquireWriteLock(otherReadLock); doMergeStats(otherEvent); } finally { releaseLock(thisWriteLock); releaseLock(otherReadLock); } } public boolean registerAsMbean(MBeanServer mbeanServer) { boolean success = false; if (null != mbeanServer) { try { ObjectName objName = generateObjectName(); if (mbeanServer.isRegistered(objName)) { if (LOG.isDebugEnabled()) { LOG.debug("unregistering old MBean: " + objName); } mbeanServer.unregisterMBean(objName); } mbeanServer.registerMBean(this, objName); if (LOG.isDebugEnabled()) { LOG.debug("MBean registered " + objName); } success = true; } catch (Exception e) { LOG.error("Unable to register to mbean server", e); } } return success; } public boolean unregisterMbean(MBeanServer mbeanServer) { boolean success = false; if (null != mbeanServer) { try { ObjectName objName = generateObjectName(); if (!isThreadSafe()) { // For e.g., ConsumerCallbackStats uses thread unsafe mode by design if (LOG.isDebugEnabled()) { LOG.debug("The mbean " + objName.toString() + " is not thread-safe which is probably not a good idea."); } } JmxUtil.unregisterMBeanSafely(mbeanServer, objName, LOG); if (LOG.isDebugEnabled()) { LOG.debug("MBean unregistered " + objName); } success = true; } catch (Exception e) { LOG.error("Unable to register to mbean server", e); } } return success; } public static String sanitizeString(String s) { return BAD_CHARS_PATTERN.matcher(s).replaceAll("_"); } public abstract ObjectName generateObjectName() throws MalformedObjectNameException; /** Resets the actual event data; no need to be synchronized */ protected abstract void resetData(); /** Clones the actual event data; no need to be synchronized */ protected abstract void cloneData(T eventObj); /** Creates ant empty event data object */ protected abstract T newDataEvent(); /** Constructs an avro writer object */ protected abstract SpecificDatumWriter<T> getAvroWriter(); /** Merges the stats from another event; no need to be synchronized */ protected abstract void doMergeStats(Object eventData); /** * Creates a hash table with the base mbean properties to be shared across all mbeans * @return the hash table */ protected Hashtable<String, String> generateBaseMBeanProps() { Hashtable<String, String> mbeanProps = new Hashtable<String, String>(5); mbeanProps.put("type", this.getClass().getSimpleName()); return mbeanProps; } /** * @param val1 * @param val2 * @return max of two (takes into account default value) */ protected long maxValue(long val1, long val2) { if(val1 == DEFAULT_MAX_LONG_VALUE) return val2; if(val2 == DEFAULT_MAX_LONG_VALUE) return val1; return Math.max(val1, val2); } /** * * @param val1 * @param val2 * @return min of two (takes into account default value) */ protected long minValue(long val1, long val2) { if(val1 == DEFAULT_MIN_LONG_VALUE) return val2; if(val2 == DEFAULT_MIN_LONG_VALUE) return val1; return Math.min(val1, val2); } @Override public String toString() { return "AbstractMonitoringMBean [_event=" + _event + "]"; } }