/* * 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.base.Preconditions; import gnu.trove.map.TObjectDoubleMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.monitoring.PerformanceMonitor; import org.terasology.monitoring.ThreadActivity; import org.terasology.monitoring.ThreadMonitor; import javax.swing.*; import java.awt.*; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @SuppressWarnings("serial") public class PerformanceMonitorPanel extends JPanel { private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitorPanel.class); private final HeaderPanel header; private final JList list; public PerformanceMonitorPanel() { setLayout(new BorderLayout()); header = new HeaderPanel(); list = new JList(new PerformanceListModel()); list.setCellRenderer(new PerformanceListRenderer(header)); list.setVisible(true); add(header, BorderLayout.PAGE_START); add(list, BorderLayout.CENTER); } private static class HeaderPanel extends JPanel { private final JLabel lName = new JLabel("Title"); private final JLabel lMean = new JLabel("Running Means"); private final JLabel lSpike = new JLabel("Decaying Spikes"); HeaderPanel() { setLayout(new FlowLayout(FlowLayout.LEFT, 4, 2)); add(lName); add(lMean); add(lSpike); } public void setNameSize(Dimension d) { lName.setPreferredSize(d); doLayout(); } } private static class Entry implements Comparable<Entry> { public final String name; public boolean active; public double mean; public double spike; Entry(String name) { this.name = (name == null) ? "" : name; } @Override public int compareTo(Entry o) { return name.compareTo(o.name); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof Entry) { return Objects.equals(name, ((Entry) obj).name); } return false; } @Override public int hashCode() { return Objects.hash(name); } } private static class PerformanceListRenderer implements ListCellRenderer { private final MyRenderer renderer; PerformanceListRenderer(HeaderPanel header) { renderer = new MyRenderer(header); } @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { if (value instanceof Entry) { renderer.setEntry((Entry) value); } else { renderer.setEntry(null); } return renderer; } private static class MyRenderer extends JPanel { private final HeaderPanel header; private final DecimalFormat format = new DecimalFormat("#####0.00"); private final JLabel lName = new JLabel(); private final JLabel lMean = new JLabel(); private final JLabel lSpike = new JLabel(); private Dimension dName = new Dimension(0, 0); MyRenderer(HeaderPanel header) { this.header = Preconditions.checkNotNull(header, "The parameter 'header' must not be null"); setBackground(Color.white); setLayout(new FlowLayout(FlowLayout.LEFT, 4, 2)); lMean.setHorizontalAlignment(SwingConstants.RIGHT); lMean.setForeground(Color.gray); lMean.setPreferredSize(header.lMean.getPreferredSize()); lSpike.setHorizontalAlignment(SwingConstants.RIGHT); lSpike.setForeground(Color.gray); lSpike.setPreferredSize(header.lSpike.getPreferredSize()); add(lName); add(lMean); add(lSpike); } public void setEntry(Entry entry) { if (entry != null) { lName.setPreferredSize(null); lName.setForeground(entry.active ? Color.blue : Color.gray); lName.setText(entry.name); Dimension tmp = lName.getPreferredSize(); if (tmp.width > dName.width || tmp.height > dName.height) { dName = tmp; header.setNameSize(dName); } lName.setPreferredSize(dName); lMean.setText(" " + format.format(entry.mean) + " ms"); lSpike.setText(" " + format.format(entry.spike) + " ms"); } else { lName.setText(""); lMean.setText(""); lSpike.setText(""); } } } } private static final class PerformanceListModel extends AbstractListModel { private final List<Entry> list = new ArrayList<>(); private final Map<String, Entry> map = new HashMap<>(); private final ExecutorService executor = Executors.newSingleThreadExecutor(); private PerformanceListModel() { executor.execute(() -> { Thread.currentThread().setPriority(Thread.MIN_PRIORITY); try { while (true) { Thread.sleep(1000); try (ThreadActivity ignored = ThreadMonitor.startThreadActivity("Poll")) { updateEntries(PerformanceMonitor.getRunningMean(), PerformanceMonitor.getDecayingSpikes()); } } } catch (Exception e) { ThreadMonitor.addError(e); logger.error("Error executing performance monitor update", e); } }); } 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)); } private void updateEntries(TObjectDoubleMap<String> means, TObjectDoubleMap<String> spikes) { if (means != null) { for (final Entry entry : list) { entry.active = false; } means.forEachEntry((key, value) -> { Entry entry = map.get(key); if (entry == null) { entry = new Entry(key); list.add(entry); map.put(key, entry); invokeIntervalAdded(list.size() - 1, list.size() - 1); } entry.active = true; entry.mean = value; return true; }); spikes.forEachEntry((key, value) -> { Entry entry = map.get(key); if (entry != null) { entry.spike = value; } return true; }); Collections.sort(list); invokeContentsChanged(0, list.size() - 1); } } @Override public int getSize() { return list.size(); } @Override public Object getElementAt(int index) { return list.get(index); } } }