/*
* Copyright 2013 MovingBlocks
*
* 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.terasology.monitoring.gui;
import com.google.common.eventbus.Subscribe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.monitoring.ThreadActivity;
import org.terasology.monitoring.ThreadMonitor;
import org.terasology.monitoring.impl.SingleThreadMonitor;
import org.terasology.monitoring.impl.ThreadMonitorEvent;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@SuppressWarnings("serial")
public class ThreadMonitorPanel extends JPanel {
private static final Color BACKGROUND = Color.white;
private static final Logger logger = LoggerFactory.getLogger(ThreadMonitorPanel.class);
private final JList list;
public ThreadMonitorPanel() {
setLayout(new BorderLayout());
list = new JList(new ThreadListModel());
list.setCellRenderer(new ThreadListRenderer());
list.setVisible(true);
add(list, BorderLayout.CENTER);
}
private abstract static class Task {
private String name;
Task(String name) {
this.name = name;
}
public abstract void execute();
public String getName() {
return name;
}
}
private static class ThreadListRenderer implements ListCellRenderer {
private final MyRenderer renderer = new MyRenderer();
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
if (value instanceof SingleThreadMonitor) {
renderer.setMonitor((SingleThreadMonitor) value);
} else {
renderer.setMonitor(null);
}
return renderer;
}
private static class MyRenderer extends JPanel {
private final JPanel pHead = new JPanel();
private final JPanel pList = new JPanel();
private final JLabel lName = new JLabel();
private final JLabel lId = new JLabel();
private final JLabel lCounters = new JLabel();
private final JLabel lActive = new JLabel();
private final JPanel pError = new JPanel();
private final JLabel lErrorSpacer = new JLabel();
private final JLabel lError = new JLabel();
private Dimension dId = new Dimension(0, 0);
private Dimension dName = new Dimension(0, 0);
MyRenderer() {
setBackground(BACKGROUND);
setLayout(new BorderLayout());
pHead.setLayout(new BorderLayout());
pHead.setBackground(BACKGROUND);
pHead.add(pList, BorderLayout.LINE_START);
pHead.add(lActive, BorderLayout.LINE_END);
pHead.add(pError, BorderLayout.PAGE_END);
lId.setHorizontalAlignment(SwingConstants.RIGHT);
lName.setForeground(Color.blue);
lCounters.setForeground(Color.gray);
pList.setLayout(new FlowLayout(FlowLayout.LEFT, 4, 2));
pList.setBackground(BACKGROUND);
pList.add(lId);
pList.add(lName);
pList.add(lCounters);
pError.setVisible(false);
pError.setLayout(new FlowLayout(FlowLayout.LEFT, 4, 2));
pError.setBackground(BACKGROUND);
pError.add(lErrorSpacer);
pError.add(lError);
lError.setForeground(Color.red);
add(pHead, BorderLayout.PAGE_START);
}
public void setMonitor(SingleThreadMonitor monitor) {
if (monitor != null) {
lName.setPreferredSize(null);
lName.setText(monitor.getName());
Dimension tmp = lName.getPreferredSize();
if (tmp.width > dName.width || tmp.height > dName.height) {
dName = tmp;
}
lName.setPreferredSize(dName);
lId.setPreferredSize(null);
lId.setText("" + monitor.getThreadId());
tmp = lId.getPreferredSize();
if (tmp.width > dId.width || tmp.height > dId.height) {
dId = tmp;
}
lId.setPreferredSize(dId);
lCounters.setText(monitor.getLastTask());
if (monitor.isAlive()) {
if (monitor.isActive()) {
lActive.setForeground(Color.green);
lActive.setText("Active");
} else {
lActive.setForeground(Color.gray);
lActive.setText("Inactive");
}
} else {
lActive.setForeground(Color.red);
lActive.setText("Disposed");
}
pError.setVisible(monitor.hasErrors());
if (monitor.hasErrors()) {
lErrorSpacer.setPreferredSize(dId);
lError.setText(monitor.getNumErrors() + " Error(s), [" + monitor.getLastError().getClass().getSimpleName() + "] "
+ monitor.getLastError().getMessage());
}
} else {
lName.setText("");
lId.setText("");
lActive.setText("");
}
}
}
}
private static final class ThreadListModel extends AbstractListModel {
private final java.util.List<SingleThreadMonitor> monitors = new ArrayList<>();
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final BlockingQueue<Task> queue = new LinkedBlockingQueue<>();
private ThreadListModel() {
ThreadMonitor.registerForEvents(this);
queue.add(new Task("Sort Monitors") {
@Override
public void execute() {
ThreadMonitor.getThreadMonitors(monitors, false);
if (monitors.size() > 0) {
Collections.sort(monitors);
invokeIntervalAdded(0, monitors.size() - 1);
}
}
});
executor.execute(new Runnable() {
@Override
public void run() {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
try {
while (true) {
final Task task = queue.poll(500, TimeUnit.MILLISECONDS);
if (task != null) {
try (ThreadActivity ignored = ThreadMonitor.startThreadActivity(task.getName())) {
task.execute();
}
} else {
try (ThreadActivity ignored = ThreadMonitor.startThreadActivity("Sort Monitors")) {
Collections.sort(monitors);
invokeContentsChanged(0, monitors.size() - 1);
}
}
}
} catch (Exception e) {
ThreadMonitor.addError(e);
logger.error("Error executing thread monitor update", e);
}
invokeContentsChanged(0, monitors.size() - 1);
}
});
}
private void invokeIntervalAdded(final int a, final int b) {
final Object source = this;
SwingUtilities.invokeLater(() -> fireIntervalAdded(source, a, b));
}
private void invokeIntervalRemoved(final int a, final int b) {
final Object source = this;
SwingUtilities.invokeLater(() -> fireIntervalRemoved(source, a, b));
}
private void invokeContentsChanged(final int a, final int b) {
final Object source = this;
SwingUtilities.invokeLater(() -> fireContentsChanged(source, a, b));
}
@Subscribe
public void receiveThreadMonitorEvent(final ThreadMonitorEvent event) {
if (event != null) {
switch (event.type) {
case MonitorAdded:
queue.add(new Task("Register Monitor") {
@Override
public void execute() {
if (!monitors.contains(event.monitor)) {
monitors.add(event.monitor);
Collections.sort(monitors);
invokeContentsChanged(0, monitors.size() - 1);
}
}
});
break;
}
}
}
@Override
public int getSize() {
return monitors.size();
}
@Override
public Object getElementAt(int index) {
return monitors.get(index);
}
}
}