/* * Copyright to the original author or authors. * * 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. */ package org.rioproject.impl.system.measurable.memory; import com.sun.jini.config.Config; import net.jini.config.Configuration; import net.jini.config.ConfigurationException; import org.rioproject.costmodel.ResourceCostModel; import org.rioproject.costmodel.ZeroCostModel; import org.rioproject.impl.system.measurable.MeasurableCapability; import org.rioproject.impl.system.measurable.MeasurableMonitor; import org.rioproject.system.MeasuredResource; import org.rioproject.system.SystemWatchID; import org.rioproject.system.measurable.memory.CalculableMemory; import org.rioproject.system.measurable.memory.ProcessMemoryUtilization; import org.rioproject.watch.ThresholdValues; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.management.Notification; import javax.management.NotificationEmitter; import javax.management.NotificationListener; import java.lang.management.MemoryNotificationInfo; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; /** * The Memory object is a MeasurableCapability that measures the amount of * memory available for the JVM * * @author Dennis Reedy */ /* Suppress PMD warnings for the invocation of getComponentName() and createMeasurableMonitor() during object * construction. This is by design. */ @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") public class Memory extends MeasurableCapability { /** Iteration value for calculating utilization of sampleSize >1 */ private int count; /** Temporary utilization value */ private double tempUtilization; /** Computed utilization value */ private double utilization; private static final String VIEW = "org.rioproject.system.memory.CalculableMemoryView"; /** Total memory arena */ double totalArena; /** Component for Configuration and Logging */ private static final String COMPONENT = "org.rioproject.system.measurable.memory"; private JMXNotificationListener jmxListener; /** For handling JX event notifications */ private final BlockingQueue<Notification> eventQ = new LinkedBlockingQueue<Notification>(); /** A Logger for this class */ static Logger logger = LoggerFactory.getLogger(COMPONENT); /** * Construct a Memory object * * @param config Configuration object */ public Memory(Configuration config) { this(SystemWatchID.JVM_MEMORY, config); } /** * Construct a Memory object * * @param id Identifier to use * @param config Configuration object */ public Memory(String id, Configuration config) { this(id, COMPONENT, config); } /** * Construct a Memory object * * @param id Identifier to use * @param componentName The configuration component name * @param config Configuration object */ public Memory(String id, String componentName, Configuration config) { super(id, componentName, config); if(!isEnabled()) return; setView(VIEW); try { ThresholdValues tVals = (ThresholdValues)config.getEntry(getComponentName(), "thresholdValues", ThresholdValues.class, new ThresholdValues(0.0, 1.0)); logger.trace("{} threshold values: {}", getId(), tVals.toString()); setThresholdValues(tVals); ResourceCostModel rCostModel = (ResourceCostModel)config.getEntry(getComponentName(), "resourceCost", ResourceCostModel.class, new ZeroCostModel()); setResourceCostModel(rCostModel); sampleSize = Config.getIntEntry(config, getComponentName(), "sampleSize", 1, /* default */ 1, /* min */ 10); /* max */ setSampleSize(sampleSize); setMeasurableMonitor(createMeasurableMonitor(config)); long reportRate = Config.getLongEntry(config, getComponentName(), "reportRate", DEFAULT_PERIOD, /* default */ 5000, /* min */ Integer.MAX_VALUE); /* max */ setPeriod(reportRate); totalArena = Runtime.getRuntime().maxMemory(); } catch (ConfigurationException e) { logger.warn("Getting Memory Configuration", e); } } protected String getComponentName() { return COMPONENT; } protected MeasurableMonitor createMeasurableMonitor(Configuration config) throws ConfigurationException { MeasurableMonitor mMon = (MeasurableMonitor)config.getEntry(getComponentName(), "monitor", MeasurableMonitor.class, new ProcessMemoryMonitor()); if(mMon instanceof ProcessMemoryMonitor) { NotificationEmitter emitter = (NotificationEmitter)((ProcessMemoryMonitor)mMon).getMXBean(); jmxListener = new JMXNotificationListener(); emitter.addNotificationListener(jmxListener, null, null); } return mMon; } public MeasurableMonitor getMeasurableMonitor() { return monitor; } /** * Override PeriodicWatch.start() to get an initial reading prior to * scheduling */ public void start() { checkValue(); super.start(); } @Override public void stop() { super.stop(); if(jmxListener!=null) jmxListener.getExecutorService().shutdownNow(); } /** * Get the computed utilization for this Memory object * * @return The utilization computed for this component */ public double getUtilization() { return(utilization); } /* (non-Javadoc) * @see org.rioproject.watch.PeriodicWatch#checkValue() */ public void checkValue() { addWatchRecord(createCalculableMemory()); } protected double calculateUtilization(MeasuredResource mRes) { count++; tempUtilization += mRes.getValue(); if(count==sampleSize) { utilization = tempUtilization/sampleSize; count = 0; tempUtilization = 0; } logger.trace("Memory : {} utilization={}", getComponentName(), utilization); return utilization; } private CalculableMemory createCalculableMemory() { CalculableMemory calculableMemory ; MeasuredResource mRes = monitor.getMeasuredResource(); utilization = calculateUtilization(mRes); long now = System.currentTimeMillis(); if(mRes instanceof ProcessMemoryUtilization) calculableMemory = new CalculableMemory(getId(), utilization, (ProcessMemoryUtilization)mRes, now); else calculableMemory = new CalculableMemory(getId(), utilization, now); setLastMeasuredResource(mRes); return calculableMemory; } class JMXNotificationListener implements NotificationListener { private final ExecutorService execService = Executors.newSingleThreadExecutor(); JMXNotificationListener() { execService.submit(new JMXNotificationHandler()); } ExecutorService getExecutorService() { return execService; } public void handleNotification(Notification notification, Object handback) { eventQ.add(notification); } } class JMXNotificationHandler implements Runnable { public void run() { while (true) { Notification notification; try { notification = eventQ.take(); } catch (InterruptedException e) { logger.debug("JMXNotificationHandler breaking out of main loop"); break; } if (notification.getType().equals(MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED)) { CalculableMemory calculableMemory = createCalculableMemory(); /* We may have a reading that lags behind the notification memory threshold notification. * Force the value to be greater than the high threshold */ if(calculableMemory.getValue()<=getThresholdValues().getCurrentHighThreshold()) { logger.info("JMX MEMORY_THRESHOLD_EXCEEDED, adjusting CalculableMemory value " + "from [{}] to [{}] to enforce SLA actions to occur.", calculableMemory.getValue(), (getThresholdValues().getCurrentHighThreshold()+0.01)); calculableMemory.setValue(getThresholdValues().getCurrentHighThreshold()+0.01); } addWatchRecord(createCalculableMemory()); } } } } }