/*
* Quasar: lightweight threads and actors for the JVM.
* Copyright (c) 2013-2014, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
package co.paralleluniverse.actors;
import co.paralleluniverse.common.monitoring.Counter;
import co.paralleluniverse.common.monitoring.MonitoringServices;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import jsr166e.LongAdder;
import org.cliffc.high_scale_lib.NonBlockingHashMapLong;
import org.cliffc.high_scale_lib.NonBlockingHashMapLong.IteratorLong;
/**
*
* @author pron
*/
class JMXActorsMonitor implements NotificationListener, ActorsMXBean {
private static final JMXActorsMonitor instance = new JMXActorsMonitor();
public static JMXActorsMonitor getInstance() {
return instance;
}
private final String mbeanName;
private boolean registered;
private final NonBlockingHashMapLong<ActorRef<?>> actors = new NonBlockingHashMapLong<ActorRef<?>>();
private final NonBlockingHashMapLong<SmallActorMonitor> watchedActors = new NonBlockingHashMapLong<SmallActorMonitor>();
private long lastCollectTime;
private final LongAdder activeCount = new LongAdder();
private JMXActorsMonitor() {
this.mbeanName = "co.paralleluniverse:type=Actors";
registerMBean();
lastCollectTime = nanoTime();
}
@SuppressWarnings({"CallToPrintStackTrace", "CallToThreadDumpStack"})
private void registerMBean() {
try {
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
final ObjectName mxbeanName = new ObjectName(mbeanName);
mbs.registerMBean(this, mxbeanName);
this.registered = true;
} catch (InstanceAlreadyExistsException ex) {
throw new RuntimeException(ex);
} catch (MBeanRegistrationException ex) {
ex.printStackTrace();
} catch (NotCompliantMBeanException ex) {
throw new AssertionError(ex);
} catch (MalformedObjectNameException ex) {
throw new AssertionError(ex);
}
MonitoringServices.getInstance().addPerfNotificationListener(this, mbeanName);
}
@SuppressWarnings({"CallToPrintStackTrace", "CallToThreadDumpStack"})
private void unregister() {
try {
if (registered) {
MonitoringServices.getInstance().removePerfNotificationListener(this);
ManagementFactory.getPlatformMBeanServer().unregisterMBean(new ObjectName(mbeanName));
}
this.registered = false;
} catch (InstanceNotFoundException ex) {
ex.printStackTrace();
} catch (MBeanRegistrationException ex) {
ex.printStackTrace();
} catch (MalformedObjectNameException ex) {
throw new AssertionError(ex);
}
}
@Override
public void handleNotification(Notification notification, Object handback) {
if ("perfTimer".equals(notification.getType()))
refresh();
}
@Override
public void refresh() {
collectAndResetCounters();
}
public boolean isRegistered() {
return registered;
}
private void collectAndResetCounters() {
if (isRegistered()) {
collectAndResetCounters(nanoTime() - lastCollectTime);
}
}
protected void collectAndResetCounters(long intervalNanos) {
lastCollectTime = nanoTime();
}
private long nanoTime() {
return System.nanoTime();
}
void actorStarted(ActorRef<?> actor) {
activeCount.increment();
actors.put(LocalActor.getStrand(actor).getId(), actor);
}
void actorTerminated(ActorRef<?> actor) {
activeCount.decrement();
actors.remove(LocalActor.getStrand(actor).getId());
}
@Override
public int getNumActiveActors() {
return activeCount.intValue();
}
@Override
public long[] getAllActorIds() {
int size = actors.size();
IteratorLong it = (IteratorLong) actors.keys();
long[] ids = new long[size];
int i = 0;
while (it.hasNext() && i < size) {
ids[i] = it.nextLong();
i++;
}
if (i < size)
return Arrays.copyOf(ids, i);
else
return ids; // might not include all nw fibers
}
@Override
public String getStackTrace(long actorId) {
ActorRef actor = actors.get(actorId);
if (actor == null)
return null;
return getStackTrace(actor);
}
private String getStackTrace(ActorRef actor) {
final StackTraceElement[] stackTrace = LocalActor.getStackTrace(actor);
final StringBuilder sb = new StringBuilder();
for (StackTraceElement ste : stackTrace)
sb.append(ste).append('\n');
return sb.toString();
}
@Override
public String[] getMailbox(long actorId) {
ActorRef actor = actors.get(actorId);
if (actor == null)
return null;
return getMailbox(actor);
}
private String[] getMailbox(ActorRef actor) {
List<Object> list = LocalActor.getMailboxSnapshot(actor);
String[] ms = new String[list.size()];
int i = 0;
for (Object m : list)
ms[i++] = m.toString();
return ms;
}
@Override
public void addWatch(long actorId) {
ActorRef actor = actors.get(actorId);
if (actor == null)
throw new IllegalArgumentException("No actor with id " + actorId + " found.");
SmallActorMonitor mon = new SmallActorMonitor(actorId);
LocalActor.setMonitor(actor, mon);
watchedActors.put(actorId, mon);
}
@Override
public void removeWatch(long actorId) {
SmallActorMonitor mon = watchedActors.get(actorId);
if (mon == null)
return;
LocalActor.stopMonitor(mon.actor);
}
@Override
public List<ActorInfo> getWatchedActorsInfo() {
List<ActorInfo> list = new ArrayList<>(watchedActors.size());
for (SmallActorMonitor mon : watchedActors.values()) {
ActorInfo ai = getActorInfo(mon);
if (ai != null)
list.add(ai);
}
Collections.sort(list, new Comparator<ActorInfo>() {
public int compare(ActorInfo o1, ActorInfo o2) {
return (int) (o1.getId() - o2.getId());
}
});
return list;
}
private ActorInfo getActorInfo(SmallActorMonitor mon) {
final ActorRef actor = mon.actor;
if (actor == null)
return null;
return new ActorInfo(mon.id,
actor.getName(),
LocalActor.getStrand(actor).isFiber(),
mon.messages,
LocalActor.getQueueLength(actor),
(int) mon.restartCounter.get(),
mon.deathCauses.toArray(new String[0]),
getMailbox(actor),
getStackTrace(actor));
}
private class SmallActorMonitor implements ActorMonitor {
private final long id;
private ActorRef actor;
private int messageCounter;
private int skippedMessageCounter;
private final Counter restartCounter = new Counter();
private final Queue<String> deathCauses = new ConcurrentLinkedQueue<>();
// These hold counter values for the previous window
volatile long messages;
volatile long skippedMessages;
public SmallActorMonitor(long id) {
this.id = id;
}
void refresh() {
if (registered) {
final ActorRef a1 = actor;
if (a1 != null)
collect(nanoTime() - lastCollectTime);
reset();
}
}
protected void collect(long intervalNanos) {
messages = messageCounter;
}
protected void reset() {
messageCounter = 0;
lastCollectTime = nanoTime();
}
@Override
public void setActor(ActorRef actor) {
this.actor = actor;
}
@Override
public void addDeath(Object cause) {
if (cause == null)
cause = "normal";
while (deathCauses.size() > 20)
deathCauses.poll();
deathCauses.add(cause.toString());
}
@Override
public void addRestart() {
restartCounter.inc();
}
@Override
public void addMessage() {
messageCounter++;
}
@Override
public void skippedMessage() {
skippedMessageCounter++;
}
@Override
public void resetSkippedMessages() {
skippedMessageCounter = 0;
}
@Override
public void shutdown() {
watchedActors.remove(id);
}
}
}