/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2000-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
/*
* @(#)BrokerMonitor.java 1.31 06/28/07
*/
package com.sun.messaging.jmq.jmsserver.core;
import com.sun.messaging.jmq.util.DestType;
import com.sun.messaging.jmq.util.timer.*;
import com.sun.messaging.jmq.util.selector.*;
import com.sun.messaging.jmq.jmsserver.util.*;
import com.sun.messaging.jmq.util.log.*;
import com.sun.messaging.jmq.io.*;
import com.sun.messaging.jmq.jmsserver.Globals;
import com.sun.messaging.jmq.util.admin.MessageType;
import com.sun.messaging.jmq.jmsserver.config.ConfigListener;
import com.sun.messaging.jmq.jmsserver.resources.BrokerResources;
import com.sun.messaging.jmq.jmsserver.config.PropertyUpdateException;
import com.sun.messaging.jmq.jmsserver.config.BrokerConfig;
import java.util.*;
import java.io.*;
import java.net.*;
public class BrokerMonitor
{
private static final long DEFAULT_INTERVAL = 60;
private static final boolean DEFAULT_PERSIST = false;
private static final long DEFAULT_TTL = 5 * DEFAULT_INTERVAL;
private static final boolean DEFAULT_ENABLED = true;
/*
* Broker monitoring property names
*/
private static String METRICS_PROP_PREFIX =
Globals.IMQ + ".metrics.topic.";
private static String METRICS_TIME_PROP =
METRICS_PROP_PREFIX + "interval";
private static String PERSIST_PROP =
METRICS_PROP_PREFIX + "persist";
private static String TTL_PROP =
METRICS_PROP_PREFIX + "timetolive";
private static String ENABLED_PROP =
METRICS_PROP_PREFIX + "enabled";
/*
* Broker monitoring property values
*/
public static long METRICS_TIME =
Globals.getConfig().getLongProperty(
METRICS_TIME_PROP,
DEFAULT_INTERVAL) * 1000;
public static boolean PERSIST =
Globals.getConfig().getBooleanProperty(
PERSIST_PROP, DEFAULT_PERSIST);
public static long TTL =
Globals.getConfig().getLongProperty(
TTL_PROP, DEFAULT_TTL) * 1000;
public static boolean ENABLED =
Globals.getConfig().getBooleanProperty(
ENABLED_PROP, DEFAULT_ENABLED);
private static MQTimer timer = Globals.getTimer();
Logger logger = Globals.getLogger();
Monitor monitor = null;
private static HashSet active = new HashSet();
private static TimerTask task = null;
boolean valid = true;
boolean started = false;
public static void shutdownMonitor() {
if (task != null)
task.cancel();
active.clear();
BrokerConfig cfg = Globals.getConfig();
cfg.removeListener(METRICS_TIME_PROP, cl);
cfg.removeListener(PERSIST_PROP, cl);
cfg.removeListener(TTL_PROP, cl);
cl = null;
}
private static ConfigListener cl = new ConfigListener() {
public void validate(String name, String value)
throws PropertyUpdateException {
}
public boolean update(String name, String value) {
BrokerConfig cfg = Globals.getConfig();
if (name.equals(METRICS_TIME_PROP)) {
METRICS_TIME = Globals.getConfig().getLongProperty(
METRICS_TIME_PROP, DEFAULT_INTERVAL) * 1000;
synchronized (active) {
if (task != null) {
task.cancel();
task = new NotificationTask();
try {
timer.schedule(task, METRICS_TIME, METRICS_TIME);
} catch (IllegalStateException ex) {
Globals.getLogger().log(Logger.INFO,"Internal Error: Shutting down metrics, timer has been canceled", ex);
}
}
}
} else if (name.equals(PERSIST_PROP)) {
PERSIST = Globals.getConfig().getBooleanProperty(
PERSIST_PROP, DEFAULT_PERSIST);
} else if (name.equals(TTL_PROP)) {
TTL = Globals.getConfig().getLongProperty(
TTL_PROP, DEFAULT_TTL) * 1000;
} else if (name.equals(ENABLED_PROP)) {
ENABLED = Globals.getConfig().getBooleanProperty(
ENABLED_PROP, DEFAULT_ENABLED);
}
return true;
}
};
static class NotificationTask extends TimerTask
{
public void run() {
Iterator itr = null;
synchronized(active) {
itr = (new HashSet(active)).iterator();
}
while (itr.hasNext()) {
Monitor m = (Monitor)itr.next();
m.run();
}
}
}
public static void init() {
/*
* The static listener 'cl' updates the
* static variables METRICS_TIME, PERSIST,
* and TTL when their corresponding
* properties are updated.
*/
BrokerConfig cfg = Globals.getConfig();
cfg.addListener(METRICS_TIME_PROP, cl);
cfg.addListener(PERSIST_PROP, cl);
cfg.addListener(TTL_PROP, cl);
cfg.addListener(ENABLED_PROP, cl);
}
public BrokerMonitor(Destination d)
throws IllegalArgumentException, BrokerException
{
monitor = createMonitor(d);
}
public static boolean isInternal(String dest)
{
return DestType.destNameIsInternal(dest);
}
public void start() {
synchronized (this) {
if (!valid) {
return;
}
if (!started) {
started = true;
} else {
return;
}
}
synchronized (active) {
active.add(monitor);
if (task == null) {
task = new NotificationTask();
try {
timer.schedule(task, METRICS_TIME, METRICS_TIME);
} catch (IllegalStateException ex) {
logger.log(Logger.INFO,"InternalError: Shutting down metrics, timer has been canceled", ex);
}
}
}
}
public void stop() {
synchronized (this) {
if (!valid) return;
if (started) {
started = false;
} else {
return;
}
}
synchronized (active) {
active.remove(monitor);
if (active.size() == 0) {
task.cancel();
task = null;
}
}
}
public void destroy() {
stop();
synchronized(this) {
valid = false;
started = false;
monitor = null;
}
}
private Monitor createMonitor(Destination d)
throws IllegalArgumentException, BrokerException
{
String destination = d.getDestinationName();
//parse it
if (!DestType.destNameIsInternal(destination)) {
throw new IllegalArgumentException("Illegal Internal Name"
+ destination);
}
String substring = destination.substring(
DestType.INTERNAL_DEST_PREFIX.length());
StringTokenizer tk = new StringTokenizer(
substring, ".");
if (!tk.hasMoreElements()) {
throw new IllegalArgumentException("Missing type "
+ " for monitoring " + destination);
}
String type=(String)tk.nextElement();
if (!type.equals("metrics")) {
throw new IllegalArgumentException("Illegal type " + type
+ " for monitoring. Only Metrics is valid ["
+ destination + "]");
}
if (!tk.hasMoreElements()) {
throw new IllegalArgumentException("Missing area "
+ " for monitoring " + destination);
}
String area=(String)tk.nextElement();
// parse
if (area.equals("broker")) {
if (tk.hasMoreElements()) {
throw new IllegalArgumentException("Bad name "
+ " for broker monitoring " + destination
+ " should be " + DestType.INTERNAL_DEST_PREFIX
+ "broker");
}
monitor = new BrokerMetricsMonitor(d);
} else if (area.equals("jvm")) {
if (tk.hasMoreElements()) {
throw new IllegalArgumentException("Bad name "
+ " for broker monitoring " + destination
+ " should be " + DestType.INTERNAL_DEST_PREFIX
+ "jvm");
}
monitor = new JVMMonitor(d);
} else if (area.equals("destination")) {
if (!tk.hasMoreElements()) {
throw new IllegalArgumentException(
"Missing destination type or list for broker destination monitoring "
+ destination);
}
String destArea=(String)tk.nextElement();
if (destArea.equals("queue")) {
if (!tk.hasMoreElements()) {
throw new IllegalArgumentException("Missing name "
+ " for broker queue monitoring " + destination);
}
String prefix = "metrics.destination.queue.";
String name = substring.substring(prefix.length());
DestinationUID duid =DestinationUID.getUID(name, true);
if (!Destination.canAutoCreate(true) &&
(Destination.getDestination(duid) == null)) {
throw new BrokerException(
Globals.getBrokerResources().getKString(
BrokerResources.E_MONITOR_DEST_DISALLOWED,
duid.getName(),
duid.getDestType()),Status.FORBIDDEN);
}
monitor = new DestMonitor(d, duid);
} else if (destArea.equals("topic")) {
if (!tk.hasMoreElements()) {
throw new IllegalArgumentException("Missing name "
+ " for broker topic monitoring " + destination);
}
String prefix = "metrics.destination.topic.";
String name = substring.substring(prefix.length());
DestinationUID duid =DestinationUID.getUID(name, false);
if (!Destination.canAutoCreate(false) &&
(Destination.getDestination(duid) == null)) {
throw new BrokerException(
Globals.getBrokerResources().getKString(
BrokerResources.E_MONITOR_DEST_DISALLOWED,
duid.getName(),
duid.getDestType()),Status.FORBIDDEN);
}
monitor = new DestMonitor(d, duid);
}
} else if (area.equals("destination_list")) {
monitor = new DestListMonitor(d);
} else {
throw new IllegalArgumentException("Illegal area "
+ area + " for monitoring " + destination);
}
return monitor;
}
public void updateNewConsumer(Consumer c) {
monitor.writeToSpecificMonitorConsumer(c);
}
}
abstract class Monitor
{
Destination d = null;
public Monitor(Destination d)
{
this.d = d;
}
protected abstract Hashtable getMonitorData();
public void run() {
Packet p = new Packet(false);
Hashtable entries = getMonitorData();
if (entries == null) {
return;
}
if (writeMap(p, entries, d.getDestinationName())) {
try {
PacketReference ref = PacketReference.createReference(
p, null);
d.queueMessage(ref, false);
Set s = d.routeNewMessage(ref);
d.forwardMessage(s, ref);
} catch (BrokerException ex) {
Globals.getLogger().log(Logger.DEBUG,"Unable to writeMap for "
+ " metrics" + d, ex);
} catch (SelectorFormatException ex) {
Globals.getLogger().log(Logger.DEBUG,"Internal Error ", ex);
}
} else {
Globals.getLogger().log(Logger.DEBUG,"Unable to writeMap for "
+ " metrics" + d);
}
}
public void writeToSpecificMonitorConsumer(Consumer c)
{
Packet p = new Packet(true);
Hashtable entries = getMonitorData();
if (entries == null) {
return;
}
if (c == null) {
return;
}
if (writeMap(p, entries, d.getDestinationName())) {
try {
PacketReference ref = PacketReference.createReference(
p, null);
d.queueMessage(ref, false);
ArrayList arl = new ArrayList(1);
arl.add(c);
ref.store(arl);
c.routeMessage(ref, false);
} catch (BrokerException ex) {
Globals.getLogger().log(Logger.DEBUG,"Unable to writeMap for "
+ " metrics " + d + " : targeted for " + c.getConsumerUID(), ex);
}
} else {
Globals.getLogger().log(Logger.DEBUG,"Unable to writeMap for "
+ " metrics" + d+ " : targeted for " + c.getConsumerUID());
}
}
private static boolean writeMap(Packet pkt, Hashtable entries,
String destination)
{
try {
// write header information
Hashtable props = new Hashtable();
Long curTime = new Long(System.currentTimeMillis());
props.put("type",destination);
props.put("timestamp", curTime);
MQAddress addr = Globals.getMQAddress();
props.put("brokerAddress", addr.toString());
props.put("brokerHost", addr.getHostName());
props.put("brokerPort", new Integer(addr.getPort()));
pkt.setProperties(props);
pkt.setPacketType(PacketType.MAP_MESSAGE);
pkt.setDestination(destination);
pkt.setPriority(5);
pkt.setIP(InetAddress.getLocalHost().getAddress());
pkt.updateSequenceNumber();
pkt.updateTimestamp();
pkt.generateSequenceNumber(false);
pkt.generateTimestamp(false);
// should also set port, but I'm not sure how
pkt.setIsQueue(false);
pkt.setTransactionID(0);
pkt.setSendAcknowledge(false);
pkt.setPersistent(BrokerMonitor.PERSIST);
pkt.setExpiration(BrokerMonitor.TTL == 0 ? (long)0
: (curTime.longValue()
+ BrokerMonitor.TTL));
ByteArrayOutputStream byteArrayOutputStream =
new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream =
new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(entries);
objectOutputStream.flush();
byteArrayOutputStream.flush();
byte[] messageBody = byteArrayOutputStream.toByteArray();
objectOutputStream.close();
byteArrayOutputStream.close();
pkt.setMessageBody(messageBody);
return true;
} catch (Exception e) {
Globals.getLogger().log(Logger.ERROR,
"Error sending metrics data",e);
return false;
}
}
}
class JVMMonitor extends Monitor
{
public JVMMonitor(Destination d) {
super(d);
}
protected Hashtable getMonitorData() {
Hashtable mapMessage = new Hashtable();
MetricManager mm = Globals.getMetricManager();
MetricData md = mm.getMetrics();
mapMessage.put("freeMemory",
new Long(md.freeMemory));
mapMessage.put("maxMemory",
new Long(Runtime.getRuntime().maxMemory()));
mapMessage.put("totalMemory",
new Long(md.totalMemory));
return mapMessage;
}
}
class DestListMonitor extends Monitor
{
public DestListMonitor(Destination d) {
super(d);
}
protected Hashtable getMonitorData() {
Hashtable mapMessages = new Hashtable();
Iterator itr = Destination.getAllDestinations();
while (itr.hasNext()) {
Destination oneDest = (Destination)itr.next();
Hashtable values;
String key;
if (oneDest.isInternal() ||
oneDest.isAdmin() ||
(oneDest.getDestinationName().equals(MessageType.JMQ_ADMIN_DEST)) ||
(oneDest.getDestinationName().equals(MessageType.JMQ_BRIDGE_ADMIN_DEST))) {
continue;
}
values = new Hashtable();
if (oneDest.isQueue()) {
key = "mq.metrics.destination.queue." + oneDest.getDestinationName();
values.put("type", "queue");
} else {
key = "mq.metrics.destination.topic." + oneDest.getDestinationName();
values.put("type", "topic");
}
values.put("name", oneDest.getDestinationName());
values.put("isTemporary", Boolean.valueOf(oneDest.isTemporary()));
mapMessages.put(key, values);
}
return mapMessages;
}
}
class DestMonitor extends Monitor
{
DestinationUID target = null;
public DestMonitor(Destination d,
DestinationUID target) {
super(d);
this.target = target;
}
protected Hashtable getMonitorData() {
if (target == null) {
return (null);
}
Destination td = Destination.getDestination(target);
if (td == null) {
return (null);
}
Hashtable values = new Hashtable(td.getMetrics());
return values;
}
}
class BrokerMetricsMonitor extends Monitor
{
public BrokerMetricsMonitor(Destination d) {
super(d);
}
protected Hashtable getMonitorData() {
Hashtable mapMessage = new Hashtable();
MetricManager mm = Globals.getMetricManager();
MetricData md = mm.getMetrics();
mapMessage.put("numConnections",
new Long((long)md.nConnections));
mapMessage.put("numMsgsIn",
new Long((long)md.totals.messagesIn));
mapMessage.put("numMsgsOut",
new Long((long)md.totals.messagesOut));
mapMessage.put("numMsgs",
new Long((long)Destination.totalCount()));
mapMessage.put("msgBytesIn",
new Long((long)md.totals.messageBytesIn));
mapMessage.put("msgBytesOut",
new Long((long)md.totals.messageBytesOut));
mapMessage.put("numPktsIn",
new Long((long)md.totals.packetsIn));
mapMessage.put("numPktsOut",
new Long((long)md.totals.packetsOut));
mapMessage.put("pktBytesIn",
new Long((long)md.totals.packetBytesIn));
mapMessage.put("pktBytesOut",
new Long((long)md.totals.packetBytesOut));
mapMessage.put("totalMsgBytes",
new Long(Destination.totalBytes()));
/*
* Calculate number of destinations.
* We cannot use Destination.destinationsSize() here because
* that includes all destinations. We need to filter out
* internal/admin destinations - basically what is returned
* by DestListMonitor.
*/
Iterator itr = Destination.getAllDestinations();
long numDests = 0;
while (itr.hasNext()) {
Destination oneDest = (Destination)itr.next();
if (oneDest.isInternal() ||
oneDest.isAdmin() ||
(oneDest.getDestinationName().equals(MessageType.JMQ_BRIDGE_ADMIN_DEST)) ||
(oneDest.getDestinationName().equals(MessageType.JMQ_ADMIN_DEST))) {
continue;
}
numDests++;
}
mapMessage.put("numDestinations", new Long(numDests));
return mapMessage;
}
}