All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.brooklyn.feed.jmx.JmxFeed Maven / Gradle / Ivy

There is a newer version: 1.1.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.brooklyn.feed.jmx;

import static com.google.common.base.Preconditions.checkNotNull;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;

import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntityLocal;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.feed.AbstractFeed;
import org.apache.brooklyn.core.feed.AttributePollHandler;
import org.apache.brooklyn.core.feed.DelegatingPollHandler;
import org.apache.brooklyn.core.feed.PollHandler;
import org.apache.brooklyn.core.feed.Poller;
import org.apache.brooklyn.entity.software.base.SoftwareProcessImpl;
import org.apache.brooklyn.util.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.common.reflect.TypeToken;


/**
 * Provides a feed of attribute values, by polling or subscribing over jmx.
 * 
 * Example usage (e.g. in an entity that extends {@link SoftwareProcessImpl}):
 * 
 * {@code
 * private JmxFeed feed;
 * 
 * //@Override
 * protected void connectSensors() {
 *   super.connectSensors();
 *   
 *   feed = JmxFeed.builder()
 *       .entity(this)
 *       .period(500, TimeUnit.MILLISECONDS)
 *       .pollAttribute(new JmxAttributePollConfig(ERROR_COUNT)
 *           .objectName(requestProcessorMbeanName)
 *           .attributeName("errorCount"))
 *       .pollAttribute(new JmxAttributePollConfig(SERVICE_UP)
 *           .objectName(serverMbeanName)
 *           .attributeName("Started")
 *           .onError(Functions.constant(false)))
 *       .build();
 * }
 * 
 * {@literal @}Override
 * protected void disconnectSensors() {
 *   super.disconnectSensors();
 *   if (feed != null) feed.stop();
 * }
 * }
 * 
* * @author aled */ public class JmxFeed extends AbstractFeed { public static final Logger log = LoggerFactory.getLogger(JmxFeed.class); public static final long JMX_CONNECTION_TIMEOUT_MS = 120*1000; public static final ConfigKey HELPER = ConfigKeys.newConfigKey(JmxHelper.class, "helper"); public static final ConfigKey OWN_HELPER = ConfigKeys.newBooleanConfigKey("ownHelper"); public static final ConfigKey JMX_URI = ConfigKeys.newStringConfigKey("jmxUri"); public static final ConfigKey JMX_CONNECTION_TIMEOUT = ConfigKeys.newLongConfigKey("jmxConnectionTimeout"); @SuppressWarnings("serial") public static final ConfigKey>> ATTRIBUTE_POLLS = ConfigKeys.newConfigKey( new TypeToken>>() {}, "attributePolls"); @SuppressWarnings("serial") public static final ConfigKey, JmxOperationPollConfig>> OPERATION_POLLS = ConfigKeys.newConfigKey( new TypeToken, JmxOperationPollConfig>>() {}, "operationPolls"); @SuppressWarnings("serial") public static final ConfigKey>> NOTIFICATION_SUBSCRIPTIONS = ConfigKeys.newConfigKey( new TypeToken>>() {}, "notificationPolls"); public static Builder builder() { return new Builder(); } public static class Builder { private Entity entity; private JmxHelper helper; private long jmxConnectionTimeout = JMX_CONNECTION_TIMEOUT_MS; private long period = 500; private TimeUnit periodUnits = TimeUnit.MILLISECONDS; private List> attributePolls = Lists.newArrayList(); private List> operationPolls = Lists.newArrayList(); private List> notificationSubscriptions = Lists.newArrayList(); private String uniqueTag; private volatile boolean built; public Builder entity(Entity val) { this.entity = val; return this; } public Builder helper(JmxHelper val) { this.helper = val; return this; } public Builder period(Duration duration) { return period(duration.toMilliseconds(), TimeUnit.MILLISECONDS); } public Builder period(long millis) { return period(millis, TimeUnit.MILLISECONDS); } public Builder period(long val, TimeUnit units) { this.period = val; this.periodUnits = units; return this; } public Builder pollAttribute(JmxAttributePollConfig config) { attributePolls.add(config); return this; } public Builder pollOperation(JmxOperationPollConfig config) { operationPolls.add(config); return this; } public Builder subscribeToNotification(JmxNotificationSubscriptionConfig config) { notificationSubscriptions.add(config); return this; } public Builder uniqueTag(String uniqueTag) { this.uniqueTag = uniqueTag; return this; } public JmxFeed build() { built = true; JmxFeed result = new JmxFeed(this); result.setEntity(checkNotNull((EntityLocal)entity, "entity")); result.start(); return result; } @Override protected void finalize() { if (!built) log.warn("JmxFeed.Builder created, but build() never called"); } } private final SetMultimap notificationListeners = HashMultimap.create(); /** * For rebind; do not call directly; use builder */ public JmxFeed() { } protected JmxFeed(Builder builder) { super(); if (builder.helper != null) { JmxHelper helper = builder.helper; config().set(HELPER, helper); config().set(OWN_HELPER, false); config().set(JMX_URI, helper.getUrl()); } config().set(JMX_CONNECTION_TIMEOUT, builder.jmxConnectionTimeout); SetMultimap> attributePolls = HashMultimap.>create(); for (JmxAttributePollConfig config : builder.attributePolls) { if (!config.isEnabled()) continue; @SuppressWarnings({ "rawtypes", "unchecked" }) JmxAttributePollConfig configCopy = new JmxAttributePollConfig(config); if (configCopy.getPeriod() < 0) configCopy.period(builder.period, builder.periodUnits); attributePolls.put(configCopy.getObjectName().getCanonicalName() + configCopy.getAttributeName(), configCopy); } config().set(ATTRIBUTE_POLLS, attributePolls); SetMultimap, JmxOperationPollConfig> operationPolls = HashMultimap.,JmxOperationPollConfig>create(); for (JmxOperationPollConfig config : builder.operationPolls) { if (!config.isEnabled()) continue; @SuppressWarnings({ "rawtypes", "unchecked" }) JmxOperationPollConfig configCopy = new JmxOperationPollConfig(config); if (configCopy.getPeriod() < 0) configCopy.period(builder.period, builder.periodUnits); operationPolls.put(configCopy.buildOperationIdentity(), configCopy); } config().set(OPERATION_POLLS, operationPolls); SetMultimap> notificationSubscriptions = HashMultimap.create(); for (JmxNotificationSubscriptionConfig config : builder.notificationSubscriptions) { if (!config.isEnabled()) continue; notificationSubscriptions.put(config.getNotificationFilter(), config); } config().set(NOTIFICATION_SUBSCRIPTIONS, notificationSubscriptions); initUniqueTag(builder.uniqueTag, attributePolls, operationPolls, notificationSubscriptions); } @Override public void setEntity(EntityLocal entity) { if (getConfig(HELPER) == null) { JmxHelper helper = new JmxHelper(entity); config().set(HELPER, helper); config().set(OWN_HELPER, true); config().set(JMX_URI, helper.getUrl()); } super.setEntity(entity); } public String getJmxUri() { return getConfig(JMX_URI); } protected JmxHelper getHelper() { return getConfig(HELPER); } @Override @SuppressWarnings("unchecked") protected Poller getPoller() { return (Poller) super.getPoller(); } @Override protected boolean isConnected() { return super.isConnected() && getHelper().isConnected(); } @Override protected void preStart() { /* * All actions on the JmxHelper are done async (through the poller's threading) so we don't * block on start/rebind if the entity is unreachable * (without this we get a 120s pause in JmxHelper.connect restarting) */ final SetMultimap> notificationSubscriptions = getConfig(NOTIFICATION_SUBSCRIPTIONS); final SetMultimap, JmxOperationPollConfig> operationPolls = getConfig(OPERATION_POLLS); final SetMultimap> attributePolls = getConfig(ATTRIBUTE_POLLS); getPoller().submit(new Callable() { @Override public Void call() { getHelper().connect(getConfig(JMX_CONNECTION_TIMEOUT)); return null; } @Override public String toString() { return "Connect JMX "+getHelper().getUrl(); } }); for (final NotificationFilter filter : notificationSubscriptions.keySet()) { getPoller().submit(new Callable() { @Override public Void call() { // TODO Could config.getObjectName have wildcards? Is this code safe? Set> configs = notificationSubscriptions.get(filter); NotificationListener listener = registerNotificationListener(configs); ObjectName objectName = Iterables.get(configs, 0).getObjectName(); notificationListeners.put(objectName, listener); return null; } @Override public String toString() { return "Register JMX notifications: "+notificationSubscriptions.get(filter); } }); } // Setup polling of sensors for (final String jmxAttributeName : attributePolls.keys()) { registerAttributePoller(attributePolls.get(jmxAttributeName)); } // Setup polling of operations for (final List operationIdentifier : operationPolls.keys()) { registerOperationPoller(operationPolls.get(operationIdentifier)); } } @Override protected void preStop() { super.preStop(); for (Map.Entry entry : notificationListeners.entries()) { unregisterNotificationListener(entry.getKey(), entry.getValue()); } notificationListeners.clear(); } @Override protected void postStop() { super.postStop(); JmxHelper helper = getHelper(); Boolean ownHelper = getConfig(OWN_HELPER); if (helper != null && ownHelper) helper.terminate(); } /** * Registers to poll a jmx-operation for an ObjectName, where all the given configs are for the same ObjectName + operation + parameters. */ private void registerOperationPoller(Set> configs) { Set> handlers = Sets.newLinkedHashSet(); long minPeriod = Integer.MAX_VALUE; final ObjectName objectName = Iterables.get(configs, 0).getObjectName(); final String operationName = Iterables.get(configs, 0).getOperationName(); final List signature = Iterables.get(configs, 0).getSignature(); final List params = Iterables.get(configs, 0).getParams(); for (JmxOperationPollConfig config : configs) { handlers.add(new AttributePollHandler(config, getEntity(), this)); if (config.getPeriod() > 0) minPeriod = Math.min(minPeriod, config.getPeriod()); } getPoller().scheduleAtFixedRate( new Callable() { @Override public Object call() throws Exception { if (log.isDebugEnabled()) log.debug("jmx operation polling for {} sensors at {} -> {}", new Object[] {getEntity(), getJmxUri(), operationName}); if (signature.size() == params.size()) { return getHelper().operation(objectName, operationName, signature, params); } else { return getHelper().operation(objectName, operationName, params.toArray()); } } }, new DelegatingPollHandler(handlers), minPeriod); } /** * Registers to poll a jmx-attribute for an ObjectName, where all the given configs are for that same ObjectName + attribute. */ private void registerAttributePoller(Set> configs) { Set> handlers = Sets.newLinkedHashSet(); long minPeriod = Integer.MAX_VALUE; final ObjectName objectName = Iterables.get(configs, 0).getObjectName(); final String jmxAttributeName = Iterables.get(configs, 0).getAttributeName(); for (JmxAttributePollConfig config : configs) { handlers.add(new AttributePollHandler(config, getEntity(), this)); if (config.getPeriod() > 0) minPeriod = Math.min(minPeriod, config.getPeriod()); } // TODO Not good calling this holding the synchronization lock getPoller().scheduleAtFixedRate( new Callable() { @Override public Object call() throws Exception { if (log.isTraceEnabled()) log.trace("jmx attribute polling for {} sensors at {} -> {}", new Object[] {getEntity(), getJmxUri(), jmxAttributeName}); return getHelper().getAttribute(objectName, jmxAttributeName); } }, new DelegatingPollHandler(handlers), minPeriod); } /** * Registers to subscribe to notifications for an ObjectName, where all the given configs are for that same ObjectName + filter. */ private NotificationListener registerNotificationListener(Set> configs) { final List> handlers = Lists.newArrayList(); final ObjectName objectName = Iterables.get(configs, 0).getObjectName(); final NotificationFilter filter = Iterables.get(configs, 0).getNotificationFilter(); for (final JmxNotificationSubscriptionConfig config : configs) { AttributePollHandler handler = new AttributePollHandler(config, getEntity(), this) { @Override protected Object transformValueOnSuccess(javax.management.Notification val) { if (config.getOnNotification() != null) { return config.getOnNotification().apply(val); } else { Object result = super.transformValueOnSuccess(val); if (result instanceof javax.management.Notification) return ((javax.management.Notification)result).getUserData(); return result; } } }; handlers.add(handler); } final PollHandler compoundHandler = new DelegatingPollHandler(handlers); NotificationListener listener = new NotificationListener() { @Override public void handleNotification(Notification notification, Object handback) { compoundHandler.onSuccess(notification); } }; getHelper().addNotificationListener(objectName, listener, filter); return listener; } private void unregisterNotificationListener(ObjectName objectName, NotificationListener listener) { try { getHelper().removeNotificationListener(objectName, listener); } catch (RuntimeException e) { log.warn("Failed to unregister listener: "+objectName+", "+listener+"; continuing...", e); } } @Override public String toString() { return "JmxFeed["+(getManagementContext()!=null&&getManagementContext().isRunning()?getJmxUri():"mgmt-not-running")+"]"; } }