1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * The contents of this file are subject to the terms of either the Universal Permissive License 7 * v 1.0 as shown at http://oss.oracle.com/licenses/upl 8 * 9 * or the following license: 10 * 11 * Redistribution and use in source and binary forms, with or without modification, are permitted 12 * provided that the following conditions are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions 15 * and the following disclaimer. 16 * 17 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 18 * conditions and the following disclaimer in the documentation and/or other materials provided with 19 * the distribution. 20 * 21 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 22 * endorse or promote products derived from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 26 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 31 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 package org.openjdk.jmc.flightrecorder.ui.pages; 34 35 import static org.openjdk.jmc.common.item.Aggregators.max; 36 import static org.openjdk.jmc.common.item.Aggregators.min; 37 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.List; 41 import java.util.stream.Collectors; 42 import java.util.stream.Stream; 43 44 import org.eclipse.jface.action.IAction; 45 import org.eclipse.jface.action.Separator; 46 import org.eclipse.jface.resource.ImageDescriptor; 47 import org.eclipse.swt.SWT; 48 import org.eclipse.swt.widgets.Composite; 49 import org.eclipse.ui.forms.widgets.FormToolkit; 50 import org.openjdk.jmc.common.IState; 51 import org.openjdk.jmc.common.IWritableState; 52 import org.openjdk.jmc.common.item.Aggregators; 53 import org.openjdk.jmc.common.item.IAggregator; 54 import org.openjdk.jmc.common.item.IItemCollection; 55 import org.openjdk.jmc.common.item.IItemFilter; 56 import org.openjdk.jmc.common.item.ItemFilters; 57 import org.openjdk.jmc.common.unit.IQuantity; 58 import org.openjdk.jmc.common.unit.IRange; 59 import org.openjdk.jmc.flightrecorder.JfrAttributes; 60 import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes; 61 import org.openjdk.jmc.flightrecorder.jdk.JdkTypeIDs; 62 import org.openjdk.jmc.flightrecorder.rules.util.JfrRuleTopics; 63 import org.openjdk.jmc.flightrecorder.ui.FlightRecorderUI; 64 import org.openjdk.jmc.flightrecorder.ui.IDataPageFactory; 65 import org.openjdk.jmc.flightrecorder.ui.IDisplayablePage; 66 import org.openjdk.jmc.flightrecorder.ui.IPageContainer; 67 import org.openjdk.jmc.flightrecorder.ui.IPageDefinition; 68 import org.openjdk.jmc.flightrecorder.ui.IPageUI; 69 import org.openjdk.jmc.flightrecorder.ui.StreamModel; 70 import org.openjdk.jmc.flightrecorder.ui.common.AbstractDataPage; 71 import org.openjdk.jmc.flightrecorder.ui.common.FlavorSelector.FlavorSelectorState; 72 import org.openjdk.jmc.flightrecorder.ui.common.ImageConstants; 73 import org.openjdk.jmc.flightrecorder.ui.common.ItemHistogram; 74 import org.openjdk.jmc.flightrecorder.ui.common.ItemHistogram.HistogramSelection; 75 import org.openjdk.jmc.flightrecorder.ui.common.ItemHistogram.ItemHistogramBuilder; 76 import org.openjdk.jmc.flightrecorder.ui.common.ItemRow; 77 import org.openjdk.jmc.flightrecorder.ui.common.ThreadGraphLanes; 78 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages; 79 import org.openjdk.jmc.ui.charts.IXDataRenderer; 80 import org.openjdk.jmc.ui.charts.RendererToolkit; 81 import org.openjdk.jmc.ui.column.ColumnManager.SelectionState; 82 import org.openjdk.jmc.ui.column.TableSettings; 83 import org.openjdk.jmc.ui.handlers.ActionToolkit; 84 import org.openjdk.jmc.ui.handlers.MCContextMenuManager; 85 86 public class ThreadsPage extends AbstractDataPage { 87 88 public static class ThreadsPageFactory implements IDataPageFactory { 89 90 @Override 91 public String getName(IState state) { 92 return Messages.ThreadsPage_NAME; 93 } 94 95 @Override 96 public String[] getTopics(IState state) { 97 return new String[] {JfrRuleTopics.THREADS_TOPIC}; 98 } 99 100 @Override 101 public ImageDescriptor getImageDescriptor(IState state) { 102 return FlightRecorderUI.getDefault().getMCImageDescriptor(ImageConstants.PAGE_THREADS); 103 } 104 105 @Override 106 public IDisplayablePage createPage(IPageDefinition definition, StreamModel items, IPageContainer editor) { 107 return new ThreadsPage(definition, items, editor); 108 } 109 110 } 111 112 private static final String THREAD_START_COL = "threadStart"; //$NON-NLS-1$ 113 private static final String THREAD_END_COL = "threadEnd"; //$NON-NLS-1$ 114 private static final String THREAD_DURATION_COL = "threadDuration"; //$NON-NLS-1$ 115 private static final String THREAD_LANE = "threadLane"; //$NON-NLS-1$ 116 117 private static final IItemFilter pageFilter = ItemFilters.hasAttribute(JfrAttributes.EVENT_THREAD); 118 private static final ItemHistogramBuilder HISTOGRAM = new ItemHistogramBuilder(); 119 120 static { 121 HISTOGRAM.addColumn(JdkAttributes.EVENT_THREAD_GROUP_NAME); 122 HISTOGRAM.addColumn(JdkAttributes.EVENT_THREAD_ID); 123 HISTOGRAM.addColumn(THREAD_START_COL, 124 min(Messages.JavaApplicationPage_COLUMN_THREAD_START, 125 Messages.JavaApplicationPage_COLUMN_THREAD_START_DESC, JdkTypeIDs.JAVA_THREAD_START, 126 JfrAttributes.EVENT_TIMESTAMP)); 127 /* 128 * Will order empty cells before first end time. 129 * 130 * It should be noted that no event (empty column cell) is considered less than all values 131 * (this is common for all columns), which causes the column to sort threads without end 132 * time (indicating that the thread ended after the end of the recording) is ordered before 133 * the thread that ended first. While this is not optimal, we decided to accept it as it's 134 * not obviously better to have this particular column ordering empty cells last in contrast 135 * to all other columns. 136 */ 137 HISTOGRAM.addColumn(THREAD_END_COL, 138 max(Messages.JavaApplicationPage_COLUMN_THREAD_END, Messages.JavaApplicationPage_COLUMN_THREAD_END_DESC, 139 JdkTypeIDs.JAVA_THREAD_END, JfrAttributes.EVENT_TIMESTAMP)); 140 HISTOGRAM.addColumn(THREAD_DURATION_COL, ic -> { 141 IQuantity threadStart = ic.apply(ItemFilters.type(JdkTypeIDs.JAVA_THREAD_START)) 142 .getAggregate((IAggregator<IQuantity, ?>) Aggregators.min(JfrAttributes.EVENT_TIMESTAMP)); 143 IQuantity threadEnd = ic.apply(ItemFilters.type(JdkTypeIDs.JAVA_THREAD_END)) 144 .getAggregate((IAggregator<IQuantity, ?>) Aggregators.max(JfrAttributes.EVENT_TIMESTAMP)); 145 if (threadStart != null && threadEnd != null) { 146 return threadEnd.subtract(threadStart); 147 } 148 return null; 149 }, Messages.JavaApplicationPage_COLUMN_THREAD_DURATION, 150 Messages.JavaApplicationPage_COLUMN_THREAD_DURATION_DESC); 151 } 152 153 private class ThreadsPageUi extends ChartAndTableUI { 154 private static final String THREADS_TABLE_FILTER = "threadsTableFilter"; //$NON-NLS-1$ 155 private ThreadGraphLanes lanes; 156 private MCContextMenuManager mm; 157 158 ThreadsPageUi(Composite parent, FormToolkit toolkit, IPageContainer editor, IState state) { 159 super(pageFilter, getDataSource(), parent, toolkit, editor, state, getName(), pageFilter, getIcon(), 160 flavorSelectorState); 161 mm = (MCContextMenuManager) chartCanvas.getContextMenu(); 162 sash.setOrientation(SWT.HORIZONTAL); 163 mm.add(new Separator()); 164 // FIXME: The lanes field is initialized by initializeChartConfiguration which is called by the super constructor. This is too indirect for SpotBugs to resolve and should be simplified. 165 lanes.updateContextMenu(mm, false); 166 167 form.getToolBarManager() 168 .add(ActionToolkit.action(() -> lanes.openEditLanesDialog(mm, false), Messages.ThreadsPage_EDIT_LANES, 169 FlightRecorderUI.getDefault().getMCImageDescriptor(ImageConstants.ICON_LANES_EDIT))); 170 form.getToolBarManager().update(true); 171 chartLegend.getControl().dispose(); 172 buildChart(); 173 table.getManager().setSelectionState(histogramSelectionState); 174 tableFilterComponent.loadState(state.getChild(THREADS_TABLE_FILTER)); 175 chart.setVisibleRange(visibleRange.getStart(), visibleRange.getEnd()); 176 onFilterChange(tableFilter); 177 } 178 179 @Override 180 protected ItemHistogram buildHistogram(Composite parent, IState state) { 181 ItemHistogram build = HISTOGRAM.buildWithoutBorder(parent, JfrAttributes.EVENT_THREAD, 182 TableSettings.forState(state)); 183 return build; 184 } 185 186 @Override 187 protected IXDataRenderer getChartRenderer(IItemCollection itemsInTable, HistogramSelection tableSelection) { 188 List<IXDataRenderer> rows = new ArrayList<>(); 189 190 IItemCollection selectedItems; 191 HistogramSelection selection; 192 if (tableSelection.getRowCount() == 0) { 193 selectedItems = itemsInTable; 194 selection = table.getAllRows(); 195 } else { 196 selectedItems = tableSelection.getItems(); 197 selection = tableSelection; 198 } 199 boolean useDefaultSelection = rows.size() > 1; 200 if (lanes.getLaneDefinitions().stream().anyMatch(a -> a.isEnabled()) && selection.getRowCount() > 0) { 201 List<IXDataRenderer> threadRows = selection 202 .getSelectedRows((object, items) -> lanes.buildThreadRenderer(object, items)) 203 .collect(Collectors.toList()); 204 double threadsWeight = Math.sqrt(threadRows.size()) * 0.15; 205 double otherRowWeight = Math.max(threadsWeight * 0.1, (1 - threadsWeight) / rows.size()); 206 List<Double> weights = Stream 207 .concat(Stream.generate(() -> otherRowWeight).limit(rows.size()), Stream.of(threadsWeight)) 208 .collect(Collectors.toList()); 209 rows.add(RendererToolkit.uniformRows(threadRows)); 210 useDefaultSelection = true; 211 rows = Arrays.asList(RendererToolkit.weightedRows(rows, weights)); 212 } 213 IXDataRenderer root = rows.size() == 1 ? rows.get(0) : RendererToolkit.uniformRows(rows); 214 // We don't use the default selection when there is only one row. This is to get the correct payload. 215 return useDefaultSelection ? new ItemRow(root, selectedItems.apply(lanes.getEnabledLanesFilter())) : root; 216 } 217 218 @Override 219 protected void onFilterChange(IItemFilter filter) { 220 super.onFilterChange(filter); 221 tableFilter = filter; 222 } 223 224 @Override 225 public void saveTo(IWritableState state) { 226 super.saveTo(state); 227 tableFilterComponent.saveState(state.createChild(THREADS_TABLE_FILTER)); 228 saveToLocal(); 229 } 230 231 private void saveToLocal() { 232 flavorSelectorState = flavorSelector.getFlavorSelectorState(); 233 histogramSelectionState = table.getManager().getSelectionState(); 234 visibleRange = chart.getVisibleRange(); 235 } 236 237 @Override 238 protected List<IAction> initializeChartConfiguration(IState state) { 239 lanes = new ThreadGraphLanes(() -> getDataSource(), () -> buildChart()); 240 return lanes.initializeChartConfiguration(Stream.of(state.getChildren(THREAD_LANE))); 241 } 242 } 243 244 private FlavorSelectorState flavorSelectorState; 245 private SelectionState histogramSelectionState; 246 private IItemFilter tableFilter; 247 private IRange<IQuantity> visibleRange; 248 249 public ThreadsPage(IPageDefinition definition, StreamModel model, IPageContainer editor) { 250 super(definition, model, editor); 251 visibleRange = editor.getRecordingRange(); 252 } 253 254 @Override 255 public IPageUI display(Composite parent, FormToolkit toolkit, IPageContainer editor, IState state) { 256 return new ThreadsPageUi(parent, toolkit, editor, state); 257 } 258 259 }