1 /* 2 * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. 3 * Copyright (c) 2019, Red Hat Inc. All rights reserved. 4 * 5 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 6 * 7 * The contents of this file are subject to the terms of either the Universal Permissive License 8 * v 1.0 as shown at http://oss.oracle.com/licenses/upl 9 * 10 * or the following license: 11 * 12 * Redistribution and use in source and binary forms, with or without modification, are permitted 13 * provided that the following conditions are met: 14 * 15 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions 16 * and the following disclaimer. 17 * 18 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 19 * conditions and the following disclaimer in the documentation and/or other materials provided with 20 * the distribution. 21 * 22 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 23 * endorse or promote products derived from this software without specific prior written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 26 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 27 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 31 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 32 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 */ 34 package org.openjdk.jmc.flightrecorder.ui.common; 35 36 import java.awt.Color; 37 import java.io.FileInputStream; 38 import java.io.InputStream; 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.Collection; 42 import java.util.Collections; 43 import java.util.Comparator; 44 import java.util.HashMap; 45 import java.util.HashSet; 46 import java.util.Iterator; 47 import java.util.List; 48 import java.util.Map; 49 import java.util.Objects; 50 import java.util.Optional; 51 import java.util.Set; 52 import java.util.function.Consumer; 53 import java.util.function.Predicate; 54 import java.util.function.Supplier; 55 import java.util.regex.Matcher; 56 import java.util.regex.Pattern; 57 import java.util.stream.Collectors; 58 import java.util.stream.Stream; 59 60 import org.eclipse.core.runtime.IAdaptable; 61 import org.eclipse.jface.action.Action; 62 import org.eclipse.jface.action.IAction; 63 import org.eclipse.jface.action.IMenuListener; 64 import org.eclipse.jface.action.IMenuManager; 65 import org.eclipse.jface.action.MenuManager; 66 import org.eclipse.jface.action.Separator; 67 import org.eclipse.jface.dialogs.InputDialog; 68 import org.eclipse.jface.resource.ImageDescriptor; 69 import org.eclipse.jface.resource.JFaceResources; 70 import org.eclipse.jface.viewers.ColumnViewer; 71 import org.eclipse.jface.viewers.IBaseLabelProvider; 72 import org.eclipse.jface.viewers.StyledCellLabelProvider; 73 import org.eclipse.jface.viewers.ViewerCell; 74 import org.eclipse.jface.window.ToolTip; 75 import org.eclipse.jface.window.Window; 76 import org.eclipse.jface.wizard.Wizard; 77 import org.eclipse.jface.wizard.WizardDialog; 78 import org.eclipse.jface.wizard.WizardPage; 79 import org.eclipse.swt.SWT; 80 import org.eclipse.swt.custom.CTabFolder; 81 import org.eclipse.swt.custom.CTabItem; 82 import org.eclipse.swt.events.SelectionAdapter; 83 import org.eclipse.swt.events.SelectionEvent; 84 import org.eclipse.swt.graphics.GC; 85 import org.eclipse.swt.graphics.Image; 86 import org.eclipse.swt.graphics.ImageData; 87 import org.eclipse.swt.layout.FillLayout; 88 import org.eclipse.swt.layout.GridData; 89 import org.eclipse.swt.layout.GridLayout; 90 import org.eclipse.swt.widgets.Button; 91 import org.eclipse.swt.widgets.Composite; 92 import org.eclipse.swt.widgets.Control; 93 import org.eclipse.swt.widgets.Display; 94 import org.eclipse.swt.widgets.Event; 95 import org.eclipse.swt.widgets.FileDialog; 96 import org.eclipse.swt.widgets.Label; 97 import org.eclipse.ui.forms.widgets.Form; 98 import org.eclipse.ui.forms.widgets.FormText; 99 import org.eclipse.ui.forms.widgets.FormToolkit; 100 import org.openjdk.jmc.common.item.IAggregator; 101 import org.openjdk.jmc.common.item.IAttribute; 102 import org.openjdk.jmc.common.item.ICanonicalAccessorFactory; 103 import org.openjdk.jmc.common.item.IItem; 104 import org.openjdk.jmc.common.item.IItemCollection; 105 import org.openjdk.jmc.common.item.IItemFilter; 106 import org.openjdk.jmc.common.item.IItemIterable; 107 import org.openjdk.jmc.common.item.IItemQuery; 108 import org.openjdk.jmc.common.item.IMemberAccessor; 109 import org.openjdk.jmc.common.item.IType; 110 import org.openjdk.jmc.common.item.ItemFilters; 111 import org.openjdk.jmc.common.item.ItemToolkit; 112 import org.openjdk.jmc.common.unit.IQuantity; 113 import org.openjdk.jmc.common.unit.IRange; 114 import org.openjdk.jmc.common.unit.KindOfQuantity; 115 import org.openjdk.jmc.common.unit.QuantitiesToolkit; 116 import org.openjdk.jmc.common.unit.QuantityRange; 117 import org.openjdk.jmc.common.unit.RangeContentType; 118 import org.openjdk.jmc.common.unit.UnitLookup; 119 import org.openjdk.jmc.common.util.ColorToolkit; 120 import org.openjdk.jmc.common.util.CompositeKey; 121 import org.openjdk.jmc.flightrecorder.JfrAttributes; 122 import org.openjdk.jmc.flightrecorder.jdk.JdkAggregators; 123 import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes; 124 import org.openjdk.jmc.flightrecorder.jdk.JdkFilters; 125 import org.openjdk.jmc.flightrecorder.jdk.JdkTypeIDs; 126 import org.openjdk.jmc.flightrecorder.rules.Result; 127 import org.openjdk.jmc.flightrecorder.rules.Severity; 128 import org.openjdk.jmc.flightrecorder.ui.FlightRecorderUI; 129 import org.openjdk.jmc.flightrecorder.ui.IPageContainer; 130 import org.openjdk.jmc.flightrecorder.ui.ItemCollectionToolkit; 131 import org.openjdk.jmc.flightrecorder.ui.ItemIterableToolkit; 132 import org.openjdk.jmc.flightrecorder.ui.PageManager; 133 import org.openjdk.jmc.flightrecorder.ui.common.ItemHistogram.CompositeKeyHistogramBuilder; 134 import org.openjdk.jmc.flightrecorder.ui.common.ItemList.ItemListBuilder; 135 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages; 136 import org.openjdk.jmc.flightrecorder.ui.overview.ResultOverview; 137 import org.openjdk.jmc.flightrecorder.ui.pages.itemhandler.ItemHandlerPage; 138 import org.openjdk.jmc.flightrecorder.ui.pages.itemhandler.ItemHandlerPage.ItemHandlerUiStandIn; 139 import org.openjdk.jmc.flightrecorder.ui.selection.IFilterFlavor; 140 import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStore.SelectionStoreEntry; 141 import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStoreActionToolkit; 142 import org.openjdk.jmc.ui.charts.AWTChartToolkit.IColorProvider; 143 import org.openjdk.jmc.ui.charts.ChartFilterControlBar; 144 import org.openjdk.jmc.ui.charts.IQuantitySeries; 145 import org.openjdk.jmc.ui.charts.ISpanSeries; 146 import org.openjdk.jmc.ui.charts.IXDataRenderer; 147 import org.openjdk.jmc.ui.charts.QuantitySeries; 148 import org.openjdk.jmc.ui.charts.RendererToolkit; 149 import org.openjdk.jmc.ui.charts.SpanRenderer; 150 import org.openjdk.jmc.ui.charts.SubdividedQuantityRange; 151 import org.openjdk.jmc.ui.charts.XYChart; 152 import org.openjdk.jmc.ui.charts.XYDataRenderer; 153 import org.openjdk.jmc.ui.charts.XYQuantities; 154 import org.openjdk.jmc.ui.column.ColumnMenusFactory; 155 import org.openjdk.jmc.ui.column.TableSettings; 156 import org.openjdk.jmc.ui.column.TableSettings.ColumnSettings; 157 import org.openjdk.jmc.ui.handlers.ActionToolkit; 158 import org.openjdk.jmc.ui.handlers.MCContextMenuManager; 159 import org.openjdk.jmc.ui.misc.ChartCanvas; 160 import org.openjdk.jmc.ui.misc.ChartTextCanvas; 161 import org.openjdk.jmc.ui.misc.CompositeToolkit; 162 import org.openjdk.jmc.ui.misc.DisplayToolkit; 163 import org.openjdk.jmc.ui.misc.FilterEditor; 164 import org.openjdk.jmc.ui.misc.FilterEditor.AttributeValueProvider; 165 import org.openjdk.jmc.ui.misc.OverlayImageDescriptor; 166 import org.openjdk.jmc.ui.misc.SWTColorToolkit; 167 168 public class DataPageToolkit { 169 170 public static final IColorProvider<IItem> ITEM_COLOR = item -> TypeLabelProvider 171 .getColorOrDefault(item.getType().getIdentifier()); 172 173 public static final IColorProvider<IItem> getAttributeValueColor(final IAttribute<?> attribute) { 174 return new IColorProvider<IItem>() { 175 176 @Override 177 public Color getColor(IItem item) { 178 IMemberAccessor<?, IItem> accessor = attribute.getAccessor(ItemToolkit.getItemType(item)); 179 Object attributeValue = accessor != null ? accessor.getMember(item) : null; 180 // FIXME: Should we include the type or not? 181 return attributeValue != null 182 ? TypeLabelProvider.getColorOrDefault(attribute.getIdentifier() + "=" + attributeValue + "(" //$NON-NLS-1$ //$NON-NLS-2$ 183 + item.getType().getIdentifier() + ")") //$NON-NLS-1$ 184 : ITEM_COLOR.getColor(item); 185 } 186 }; 187 } 188 189 private static final Map<String, Color> FIELD_COLOR_MAP = new HashMap<>(); 190 private static final Map<String, Integer> DEFAULT_COLUMNS_ORDER; 191 192 static { 193 194 // FIXME: Create FieldAppearance class, similar to TypeAppearence? 195 FIELD_COLOR_MAP.put(JdkAttributes.MACHINE_TOTAL.getIdentifier(), new Color(255, 128, 0)); 196 FIELD_COLOR_MAP.put(JdkAttributes.JVM_SYSTEM.getIdentifier(), new Color(128, 128, 128)); 197 FIELD_COLOR_MAP.put(JdkAttributes.JVM_USER.getIdentifier(), new Color(0, 0, 255)); 198 FIELD_COLOR_MAP.put(JdkAttributes.JVM_TOTAL.getIdentifier(), new Color(64, 64, 191)); 199 200 // FIXME: Handle ColorProvider and combined events 201 Map<String, Integer> columnsOrderMap = new HashMap<>(); 202 columnsOrderMap.put(createColumnId(JfrAttributes.START_TIME), 1); 203 columnsOrderMap.put(createColumnId(JfrAttributes.DURATION), 2); 204 columnsOrderMap.put(createColumnId(JfrAttributes.END_TIME), 3); 205 columnsOrderMap.put(createColumnId(JfrAttributes.EVENT_THREAD), 4); 206 DEFAULT_COLUMNS_ORDER = Collections.unmodifiableMap(columnsOrderMap); 207 } 208 209 public static final Color ALLOCATION_COLOR = new Color(64, 144, 230); 210 211 public static final String FORM_TOOLBAR_PAGE_RESULTS = "pageResults"; //$NON-NLS-1$ 212 public static final String FORM_TOOLBAR_PAGE_SETUP = "pageSetup"; //$NON-NLS-1$ 213 public static final String FORM_TOOLBAR_PAGE_NAV = "pageNav"; //$NON-NLS-1$ 214 215 public static final String RESULT_ACTION_ID = "resultAction"; //$NON-NLS-1$ 216 217 public static Color getFieldColor(String fieldId) { 218 return FIELD_COLOR_MAP.getOrDefault(fieldId, ColorToolkit.getDistinguishableColor(fieldId)); 219 } 220 221 public static Color getFieldColor(IAttribute<?> attribute) { 222 return getFieldColor(attribute.getIdentifier()); 223 } 224 225 public static TableSettings createTableSettingsByOrderByAndColumnsWithDefaultOrdering( 226 final String orderBy, final Collection<ColumnSettings> columns) { 227 final Stream<ColumnSettings> defaultOrderColumns = columns.stream() 228 .filter(c -> DEFAULT_COLUMNS_ORDER.containsKey(c.getId())).filter(c -> !c.isHidden()) 229 .sorted((c1, c2) -> Integer.compare(DEFAULT_COLUMNS_ORDER.get(c1.getId()), 230 DEFAULT_COLUMNS_ORDER.get(c2.getId()))); 231 final Stream<ColumnSettings> naturalOrderColumns = columns.stream() 232 .filter(c -> !DEFAULT_COLUMNS_ORDER.containsKey(c.getId())) 233 .sorted((c1, c2) -> String.CASE_INSENSITIVE_ORDER.compare(c1.getId(), c2.getId())); 234 final List<ColumnSettings> resultColumns = Stream.concat(defaultOrderColumns, naturalOrderColumns) 235 .collect(Collectors.toList()); 236 return new TableSettings(orderBy, resultColumns); 237 } 238 239 public static TableSettings createTableSettingsByAllAndVisibleColumns( 240 final Collection<String> allColumns, final Collection<String> visibleColumns) { 241 final List<ColumnSettings> defaultListCols = new ArrayList<>(); 242 for (String columnId : allColumns) { 243 defaultListCols.add(new ColumnSettings(columnId, !visibleColumns.contains(columnId), null, null)); 244 } 245 return createTableSettingsByOrderByAndColumnsWithDefaultOrdering(null, defaultListCols); 246 } 247 248 private static String createColumnId(IAttribute<?> attr) { 249 return new StringBuilder().append(attr.getIdentifier()).append(":") //$NON-NLS-1$ 250 .append(attr.getContentType().getIdentifier()).toString(); 251 } 252 253 public static IAction createAttributeCheckAction(IAttribute<?> attribute, Consumer<Boolean> onChange) { 254 return createCheckAction(attribute.getName(), attribute.getDescription(), attribute.getIdentifier(), 255 getFieldColor(attribute), onChange); 256 } 257 258 public static IAction createTypeCheckAction( 259 String actionId, String typeId, String name, String description, Consumer<Boolean> onChange) { 260 return createCheckAction(name, description, actionId, TypeLabelProvider.getColorOrDefault(typeId), onChange); 261 } 262 263 public static IAction createAggregatorCheckAction( 264 IAggregator<?, ?> aggregator, String id, Color color, Consumer<Boolean> onChange) { 265 return createCheckAction(aggregator.getName(), aggregator.getDescription(), id, color, onChange); 266 } 267 268 public static IAction createCheckAction( 269 String name, String description, String id, Color color, Consumer<Boolean> onChange) { 270 return createCheckAction(name, description, id, 271 SWTColorToolkit.getColorThumbnailDescriptor(SWTColorToolkit.asRGB(color)), onChange); 272 } 273 274 public static IAction createCheckAction( 275 String name, String description, String id, ImageDescriptor icon, Consumer<Boolean> onChange) { 276 return ActionToolkit.checkAction(onChange, name, description, icon, id); 277 } 278 279 public static Optional<IXDataRenderer> buildLinesRow( 280 String title, String description, IItemCollection items, boolean fill, IItemQuery query, 281 Predicate<IAttribute<IQuantity>> attributeFilter, IQuantity includeLow, IQuantity includeHigh) { 282 XYDataRenderer renderer = includeHigh != null 283 ? new XYDataRenderer(includeLow, includeHigh, true, title, description) 284 : new XYDataRenderer(includeLow, title, description); 285 IItemCollection filteredItemsSupplier = items.apply(query.getFilter()); 286 Stream<IAttribute<IQuantity>> attributes = getQuantityAttributes(query); 287 if (attributeFilter != null) { 288 attributes = attributes.filter(attributeFilter); 289 } 290 if (DataPageToolkit.addEndTimeLines(renderer, filteredItemsSupplier, fill, attributes)) { 291 return Optional.of(new ItemRow(title, description, renderer, filteredItemsSupplier)); 292 } 293 return Optional.empty(); 294 } 295 296 /** 297 * @param q 298 * A query containing only {@code IAttribute<IQuantity>} attributes. Queries 299 * containing non-quantity attributes are not supported and may cause 300 * ClassCastExceptions later when the attributes are used. 301 * @return a stream of the query attributes 302 */ 303 /* 304 * FIXME: JMC-5125 - This cast chain is scary and should be reworked. 305 * 306 * If the query contains any non-quantity attributes then there will be a ClassCastException 307 * later when the attributes are used to extract values. 308 */ 309 @SuppressWarnings({"unchecked", "rawtypes"}) 310 public static Stream<IAttribute<IQuantity>> getQuantityAttributes(IItemQuery q) { 311 return (Stream) q.getAttributes().stream(); 312 } 313 314 public static void setChart(ChartCanvas canvas, XYChart chart, Consumer<IItemCollection> selectionListener) { 315 setChart(canvas, chart, selectionListener, null); 316 } 317 318 public static void setChart(ChartTextCanvas canvas, XYChart chart, Consumer<IItemCollection> selectionListener) { 319 setChart(canvas, chart, selectionListener, null); 320 } 321 322 public static void setChart( 323 ChartCanvas canvas, XYChart chart, Consumer<IItemCollection> selectionListener, 324 Consumer<IRange<IQuantity>> selectRangeConsumer) { 325 IMenuManager contextMenu = canvas.getContextMenu(); 326 contextMenu.removeAll(); 327 canvas.getContextMenu().add(new Action(Messages.CHART_ZOOM_TO_SELECTED_RANGE) { 328 @Override 329 public void run() { 330 IQuantity selectionStart = chart.getSelectionStart(); 331 IQuantity selectionEnd = chart.getSelectionEnd(); 332 if (selectionStart == null || selectionEnd == null) { 333 chart.clearVisibleRange(); 334 } else { 335 chart.setVisibleRange(selectionStart, selectionEnd); 336 } 337 canvas.redrawChart(); 338 } 339 }); 340 341 canvas.setSelectionListener(() -> { 342 selectionListener.accept(ItemRow.getRangeSelection(chart, JfrAttributes.LIFETIME)); 343 IQuantity start = chart.getSelectionStart(); 344 IQuantity end = chart.getSelectionEnd(); 345 if (selectRangeConsumer != null) { 346 selectRangeConsumer 347 .accept(start != null && end != null ? QuantityRange.createWithEnd(start, end) : null); 348 } 349 }); 350 canvas.setChart(chart); 351 } 352 353 public static void setChart( 354 ChartTextCanvas canvas, XYChart chart, Consumer<IItemCollection> selectionListener, 355 Consumer<IRange<IQuantity>> selectRangeConsumer) { 356 IMenuManager contextMenu = canvas.getContextMenu(); 357 contextMenu.removeAll(); 358 canvas.getContextMenu().add(new Action(Messages.CHART_ZOOM_TO_SELECTED_RANGE) { 359 @Override 360 public void run() { 361 IQuantity selectionStart = chart.getSelectionStart(); 362 IQuantity selectionEnd = chart.getSelectionEnd(); 363 if (selectionStart == null || selectionEnd == null) { 364 chart.clearVisibleRange(); 365 } else { 366 chart.setVisibleRange(selectionStart, selectionEnd); 367 } 368 canvas.redrawChartText(); 369 } 370 }); 371 372 canvas.setSelectionListener(() -> { 373 selectionListener.accept(ItemRow.getRangeSelection(chart, JfrAttributes.LIFETIME)); 374 IQuantity start = chart.getSelectionStart(); 375 IQuantity end = chart.getSelectionEnd(); 376 if (selectRangeConsumer != null) { 377 selectRangeConsumer 378 .accept(start != null && end != null ? QuantityRange.createWithEnd(start, end) : null); 379 } 380 }); 381 canvas.setChart(chart); 382 } 383 384 public static void setChart( 385 ChartCanvas canvas, XYChart chart, IAttribute<IQuantity> selectionAttribute, 386 Consumer<IItemCollection> selectionListener) { 387 IMenuManager contextMenu = canvas.getContextMenu(); 388 contextMenu.removeAll(); 389 canvas.setSelectionListener(() -> selectionListener.accept(ItemRow.getSelection(chart, selectionAttribute))); 390 canvas.setChart(chart); 391 } 392 393 /** 394 * Only works for items that are either fully overlapping, or disjunct. Must be ensured by 395 * client code. 396 */ 397 private static class RangePayload implements IAdaptable { 398 IItem item; 399 IQuantity start; 400 IQuantity end; 401 double rangeInPixels; 402 403 RangePayload(IItem item, IQuantity start, IQuantity end, double rangeInPixels) { 404 this.item = item; 405 this.start = start; 406 this.end = end; 407 this.rangeInPixels = rangeInPixels; 408 } 409 410 void combineWith(IItem item, IQuantity start, IQuantity end, double rangeInPixels) { 411 if (this.start.compareTo(start) < 0) { 412 // Will choose the item that starts last 413 this.start = start; 414 this.end = end; 415 this.item = item; 416 extendRangeInPixels(this.end.compareTo(start) > 0, rangeInPixels); 417 } else { 418 extendRangeInPixels(end.compareTo(this.start) > 0, rangeInPixels); 419 } 420 } 421 422 void extendRangeInPixels(boolean overlapping, double rangeInPixels) { 423 this.rangeInPixels = overlapping ? Math.max(this.rangeInPixels, rangeInPixels) 424 : this.rangeInPixels + rangeInPixels; 425 } 426 427 @Override 428 public <T> T getAdapter(Class<T> adapter) { 429 return IItem.class.isAssignableFrom(adapter) ? adapter.cast(item) : null; 430 } 431 } 432 433 private static ISpanSeries<RangePayload> rangeSeries( 434 IItemCollection events, IAttribute<IQuantity> startAttribute, IAttribute<IQuantity> endAttribute) { 435 return new ISpanSeries<RangePayload>() { 436 @Override 437 public XYQuantities<RangePayload[]> getQuantities(SubdividedQuantityRange xBucketRange) { 438 SubdividedQuantityRange xRange = xBucketRange.copyWithPixelSubdividers(); 439 List<RangePayload> spanningPixels = new ArrayList<>(); 440 RangePayload[] pixelBuckets = new RangePayload[xRange.getNumSubdividers()]; 441 events.forEach(is -> { 442 IMemberAccessor<IQuantity, IItem> startAccessor = startAttribute.getAccessor(is.getType()); 443 IMemberAccessor<IQuantity, IItem> endAccessor = endAttribute.getAccessor(is.getType()); 444 is.forEach(item -> { 445 IQuantity start = startAccessor.getMember(item); 446 IQuantity end = endAccessor.getMember(item); 447 int xPos = xRange.getFloorSubdivider(start); 448 int endPos = xRange.getFloorSubdivider(end); 449 if (xPos < pixelBuckets.length && endPos >= 0) { 450 // FIXME: If we have very short events (nanosecond scale) we can sometimes get a negative range. 451 double rangeInPixels = xRange.getPixel(end) - xRange.getPixel(start); 452 if (xPos != endPos) { 453 spanningPixels.add(new RangePayload(item, start, end, rangeInPixels)); 454 } else if (pixelBuckets[xPos] == null) { 455 pixelBuckets[xPos] = new RangePayload(item, start, end, rangeInPixels); 456 } else { 457 pixelBuckets[xPos].combineWith(item, start, end, rangeInPixels); 458 } 459 } 460 }); 461 }); 462 RangePayload[] sorted = Stream 463 .concat(Stream.of(pixelBuckets).filter(Objects::nonNull), spanningPixels.stream()) 464 .sorted(Comparator.comparing(r -> r.start)).toArray(RangePayload[]::new); 465 // FIXME: Should make it possible to use the RangePayload[] directly instead 466 List<IQuantity> starts = Stream.of(sorted).map(r -> r.start).collect(Collectors.toList()); 467 List<IQuantity> ends = Stream.of(sorted).map(r -> r.end).collect(Collectors.toList()); 468 return XYQuantities.create(sorted, starts, ends, xRange); 469 } 470 471 @Override 472 public IQuantity getStartX(RangePayload payload) { 473 return payload.start; 474 } 475 }; 476 } 477 478 public final static Color GC_BASE_COLOR = TypeLabelProvider.getColor(JdkTypeIDs.GC_PAUSE); 479 private final static Color VM_OPERATIONS_BASE_COLOR = TypeLabelProvider.getColor(JdkTypeIDs.VM_OPERATIONS); 480 private final static IColorProvider<RangePayload> GC_COLOR = payload -> adjustAlpha(GC_BASE_COLOR, 481 payload.rangeInPixels); 482 private final static IColorProvider<RangePayload> APPLICATION_PAUSE_COLOR = payload -> adjustAlpha( 483 payload.item.getType().getIdentifier().equals(JdkTypeIDs.GC_PAUSE) ? GC_BASE_COLOR 484 : VM_OPERATIONS_BASE_COLOR, 485 payload.rangeInPixels); 486 public final static ImageDescriptor GC_LEGEND_ICON = new OverlayImageDescriptor( 487 SWTColorToolkit.getColorThumbnailDescriptor(SWTColorToolkit.asRGB(GC_BASE_COLOR)), false, 488 FlightRecorderUI.getDefault().getMCImageDescriptor("trash_overlay.png")); //$NON-NLS-1$ 489 490 /** 491 * Return a color with alpha calculated from a fraction. 492 * 493 * @param color 494 * a base color 495 * @param fraction 496 * A value where 0 gives the lowest alpha value and 1 gives the highest. Fractions 497 * above 1 are accepted and treated as 1. Negative fractions should not be used. 498 * @return a color with RGB from the base color and an alpha value depending on the fraction 499 */ 500 private static Color adjustAlpha(Color color, double fraction) { 501 return ColorToolkit.withAlpha(color, Math.min(200, (int) ((Math.max(0, fraction) + 0.15) * 255))); 502 } 503 504 public static ItemRow buildGcPauseRow(IItemCollection items) { 505 IItemCollection pauseEvents = items.apply(JdkFilters.GC_PAUSE); 506 ISpanSeries<RangePayload> gcBackdrop = rangeSeries(pauseEvents, JfrAttributes.START_TIME, 507 JfrAttributes.END_TIME); 508 return new ItemRow(SpanRenderer.build(gcBackdrop, GC_COLOR), pauseEvents); 509 } 510 511 public static ItemRow buildApplicationPauseRow(IItemCollection items) { 512 IItemFilter vmOperationPauseFilter = ItemFilters.and(JdkFilters.VM_OPERATIONS, 513 ItemFilters.equals(JdkAttributes.SAFEPOINT, true)); 514 IItemCollection applicationPauses = items 515 .apply(ItemFilters.or(JdkFilters.GC_PAUSE, JdkFilters.SAFE_POINTS, vmOperationPauseFilter)); 516 ISpanSeries<RangePayload> pausesSeries = rangeSeries(applicationPauses, JfrAttributes.START_TIME, 517 JfrAttributes.END_TIME); 518 return new ItemRow(SpanRenderer.build(pausesSeries, APPLICATION_PAUSE_COLOR), applicationPauses); 519 } 520 521 public static IXDataRenderer buildTimestampHistogramRenderer( 522 IItemCollection items, IAggregator<IQuantity, ?> aggregator, IAttribute<IQuantity> timestampAttribute, 523 Color color) { 524 IQuantitySeries<IQuantity[]> aggregatorSeries = BucketBuilder.aggregatorSeries(items, aggregator, 525 timestampAttribute); 526 XYDataRenderer renderer = new XYDataRenderer(getKindOfQuantity(aggregator).getDefaultUnit().quantity(0), 527 aggregator.getName(), aggregator.getDescription()); 528 renderer.addBarChart(aggregator.getName(), aggregatorSeries, color); 529 return renderer; 530 } 531 532 public static IXDataRenderer buildTimestampHistogramRenderer( 533 IItemCollection items, IAggregator<IQuantity, ?> aggregator, Color color) { 534 return buildTimestampHistogramRenderer(items, aggregator, JfrAttributes.CENTER_TIME, color); 535 } 536 537 public static ItemRow buildTimestampHistogram( 538 String title, String description, IItemCollection items, IAggregator<IQuantity, ?> aggregator, 539 IAttribute<IQuantity> timestampAttribute, Color color) { 540 return new ItemRow(title, description, 541 buildTimestampHistogramRenderer(items, aggregator, timestampAttribute, color), items); 542 } 543 544 public static ItemRow buildTimestampHistogram( 545 String title, String description, IItemCollection items, IAggregator<IQuantity, ?> aggregator, Color color) { 546 return new ItemRow(title, description, buildTimestampHistogramRenderer(items, aggregator, color), items); 547 } 548 549 public static ItemHistogram createDistinctItemsTable( 550 Composite parent, IItemCollection items, IItemQuery query, TableSettings settings) { 551 CompositeKeyHistogramBuilder histogramBuilder = new CompositeKeyHistogramBuilder(); 552 for (IAttribute<?> attribute : query.getAttributes()) { 553 histogramBuilder.addKeyColumn(attribute); 554 } 555 for (IAggregator<?, ?> aggregator : query.getAggregators()) { 556 histogramBuilder.addColumn(aggregator.getName(), aggregator); 557 } 558 ItemHistogram table = histogramBuilder.buildWithoutBorder(parent, settings); 559 return table; 560 } 561 562 public static IBaseLabelProvider createTableHighlightProvider(Pattern highlightPattern, boolean isWarning) { 563 return new StyledCellLabelProvider() { 564 @Override 565 public void update(ViewerCell cell) { 566 org.eclipse.swt.graphics.Color color = isWarning 567 ? new org.eclipse.swt.graphics.Color(Display.getCurrent(), 240, 120, 140) 568 : new org.eclipse.swt.graphics.Color(Display.getCurrent(), 255, 144, 4); 569 String text = getText(cell.getElement(), cell.getColumnIndex()); 570 Matcher matcher = highlightPattern.matcher(text); 571 if (matcher.find()) { 572 cell.getViewerRow().setBackground(0, color); 573 cell.getViewerRow().setBackground(1, color); 574 } 575 cell.setText(text); 576 super.update(cell); 577 } 578 579 private String getText(Object element, int index) { 580 Object key = AggregationGrid.getKey(element); 581 Object[] keyElements = ((CompositeKey) key).getKeyElements(); 582 return keyElements[index].toString(); 583 } 584 }; 585 } 586 587 public static void addContextMenus( 588 IPageContainer pc, ItemHistogram h, String selectionName, IAction ... extraActions) { 589 MCContextMenuManager mm = MCContextMenuManager.create(h.getManager().getViewer().getControl()); 590 ColumnMenusFactory.addDefaultMenus(h.getManager(), mm); 591 SelectionStoreActionToolkit.addSelectionStoreActions(pc.getSelectionStore(), h, selectionName, mm); 592 for (IAction action : extraActions) { 593 mm.add(action); 594 } 595 } 596 597 public static IXDataRenderer buildSizeRow( 598 String title, String description, IItemCollection items, IAggregator<IQuantity, ?> a, Color color, 599 IColorProvider<IItem> cp) { 600 return RendererToolkit.layers(buildSpanRenderer(items, cp), 601 buildTimestampHistogram(title, description, items, a, color)); 602 } 603 604 public static ItemRow buildDurationHistogram( 605 String title, String description, IItemCollection items, IAggregator<IQuantity, ?> a, Color color) { 606 IQuantitySeries<IQuantity[]> allocationSeries = BucketBuilder.aggregatorSeries(items, a, 607 JfrAttributes.DURATION); 608 XYDataRenderer renderer = new XYDataRenderer(getKindOfQuantity(a).getDefaultUnit().quantity(0), title, 609 description); 610 renderer.addBarChart(a.getName(), allocationSeries, color); 611 return new ItemRow(title, description, renderer, items); 612 } 613 614 public static ItemRow buildSizeHistogram( 615 String title, String description, IItemCollection items, IAggregator<IQuantity, ?> a, Color color, IAttribute<IQuantity> attribute) { 616 IQuantitySeries<IQuantity[]> allocationSeries = BucketBuilder.aggregatorSeries(items, a, 617 JdkAttributes.IO_SIZE); 618 XYDataRenderer renderer = new XYDataRenderer(getKindOfQuantity(a).getDefaultUnit().quantity(0), title, 619 description); 620 renderer.addBarChart(a.getName(), allocationSeries, color); 621 return new ItemRow(title, description, renderer, items); 622 } 623 624 public static IRange<IQuantity> buildSizeRange(IItemCollection items, boolean isSocket){ 625 IQuantity end = null; 626 if(isSocket) { 627 end = QuantitiesToolkit.maxPresent(items.getAggregate(JdkAggregators.SOCKET_READ_LARGEST), 628 items.getAggregate(JdkAggregators.SOCKET_WRITE_LARGEST)); 629 } else { 630 end = QuantitiesToolkit.maxPresent(items.getAggregate(JdkAggregators.FILE_READ_LARGEST), 631 items.getAggregate(JdkAggregators.FILE_WRITE_LARGEST)); 632 } 633 end = end == null ? UnitLookup.BYTE.quantity(1024) : end; 634 return QuantityRange.createWithEnd(UnitLookup.BYTE.quantity(0), end); 635 } 636 637 // FIXME: Make something that can use something other than time as x-axis? 638 public static IXDataRenderer buildSpanRenderer(IItemCollection pathItems, IColorProvider<IItem> cp) { 639 ISpanSeries<IItem> dataSeries = QuantitySeries.max(pathItems, JfrAttributes.START_TIME, JfrAttributes.END_TIME); 640 return SpanRenderer.withBoundaries(dataSeries, cp); 641 } 642 643 public static boolean addEndTimeLines( 644 XYDataRenderer renderer, IItemCollection items, boolean fill, Stream<IAttribute<IQuantity>> yAttributes) { 645 // FIXME: JMC-4520 - Handle multiple item iterables 646 Iterator<IItemIterable> ii = items.iterator(); 647 if (ii.hasNext()) { 648 IItemIterable itemStream = ii.next(); 649 IType<IItem> type = itemStream.getType(); 650 // FIXME: A better way to ensure sorting by endTime 651 return yAttributes.peek(a -> addEndTimeLine(renderer, itemStream.iterator(), type, a, fill)) 652 .mapToLong(a -> 1L).sum() > 0; 653 } 654 return false; 655 } 656 657 public static void addEndTimeLine( 658 XYDataRenderer renderer, Iterator<? extends IItem> items, IType<IItem> type, IAttribute<IQuantity> yAttribute, 659 boolean fill) { 660 IQuantitySeries<?> qs = buildQuantitySeries(items, type, JfrAttributes.END_TIME, yAttribute); 661 renderer.addLineChart(yAttribute.getName(), qs, getFieldColor(yAttribute), fill); 662 } 663 664 public static IQuantitySeries<?> buildQuantitySeries( 665 Iterator<? extends IItem> items, IType<IItem> type, IAttribute<IQuantity> xAttribute, 666 IAttribute<IQuantity> yAttribute) { 667 IMemberAccessor<IQuantity, IItem> yAccessor = yAttribute.getAccessor(type); 668 if (yAccessor == null) { 669 throw new RuntimeException(yAttribute.getIdentifier() + " is not an attribute for " + type.getIdentifier()); //$NON-NLS-1$ 670 } 671 return buildQuantitySeries(items, type, xAttribute, yAccessor); 672 } 673 674 public static IQuantitySeries<?> buildQuantitySeries( 675 Iterator<? extends IItem> items, IType<IItem> type, IAttribute<IQuantity> xAttribute, 676 IMemberAccessor<? extends IQuantity, IItem> yAccessor) { 677 IMemberAccessor<IQuantity, IItem> xAccessor = xAttribute.getAccessor(type); 678 return QuantitySeries.all(items, xAccessor, yAccessor); 679 } 680 681 public static void createChartTooltip(ChartCanvas chart) { 682 createChartTooltip(chart, ChartToolTipProvider::new); 683 } 684 685 public static void createChartTimestampTooltip(ChartCanvas chart) { 686 createChartTooltip(chart, JfrAttributes.START_TIME, JfrAttributes.END_TIME, JfrAttributes.DURATION, 687 JfrAttributes.EVENT_TYPE, JfrAttributes.EVENT_STACKTRACE); 688 } 689 690 public static void createChartTooltip(ChartCanvas chart, IAttribute<?> ... excludedAttributes) { 691 createChartTooltip(chart, new HashSet<>(Arrays.asList(excludedAttributes))); 692 } 693 694 public static void createChartTooltip(ChartCanvas chart, Set<IAttribute<?>> excludedAttributes) { 695 createChartTooltip(chart, () -> new ChartToolTipProvider() { 696 @Override 697 protected Stream<IAttribute<?>> getAttributeStream(IType<IItem> type) { 698 return type.getAttributes().stream().filter(a -> !excludedAttributes.contains(a)); 699 } 700 }); 701 } 702 703 public static void createChartTooltip(ChartCanvas chart, Supplier<ChartToolTipProvider> toolTipProviderSupplier) { 704 new ToolTip(chart) { 705 String html; 706 Map<String, Image> images; 707 708 @Override 709 protected boolean shouldCreateToolTip(Event event) { 710 ChartToolTipProvider provider = toolTipProviderSupplier.get(); 711 chart.infoAt(provider, event.x, event.y); 712 html = provider.getHTML(); 713 images = provider.getImages(); 714 return html != null; 715 } 716 717 @Override 718 protected Composite createToolTipContentArea(Event event, Composite parent) { 719 FormText formText = CompositeToolkit.createInfoFormText(parent); 720 for (Map.Entry<String, Image> imgEntry : images.entrySet()) { 721 formText.setImage(imgEntry.getKey(), imgEntry.getValue()); 722 } 723 formText.setText(html, true, false); 724 return formText; 725 } 726 727 }; 728 729 } 730 731 private static KindOfQuantity<?> getKindOfQuantity(IAggregator<IQuantity, ?> a) { 732 IType<? super IQuantity> ct = a.getValueType(); 733 // FIXME: Refactor to avoid this cast 734 return ((KindOfQuantity<?>) ct); 735 } 736 737 public static Form createForm(Composite parent, FormToolkit toolkit, String title, Image img) { 738 Form form = toolkit.createForm(parent); 739 form.setText(title.replaceAll("&", "&&")); //$NON-NLS-1$ //$NON-NLS-2$ 740 form.setImage(img); 741 toolkit.decorateFormHeading(form); 742 FillLayout fillLayout = new FillLayout(); 743 fillLayout.marginHeight = 15; 744 fillLayout.marginWidth = 8; 745 form.getBody().setLayout(fillLayout); 746 form.getToolBarManager().add(new Separator(FORM_TOOLBAR_PAGE_RESULTS)); 747 form.getToolBarManager().add(new Separator(FORM_TOOLBAR_PAGE_SETUP)); 748 form.getToolBarManager().add(new Separator(FORM_TOOLBAR_PAGE_NAV)); 749 return form; 750 } 751 752 public static class ShowResultAction extends Action { 753 754 private String[] topics; 755 private final IPageContainer pageContainer; 756 private volatile Severity maxSeverity; 757 private final List<Consumer<Result>> listeners = new ArrayList<>(); 758 759 ShowResultAction(String title, int style, ImageDescriptor icon, Supplier<String> tooltip, 760 IPageContainer pageContainer, String ... topics) { 761 super(title, style); 762 setImageDescriptor(icon); 763 setToolTipText(tooltip.get()); 764 this.topics = topics; 765 this.pageContainer = pageContainer; 766 maxSeverity = pageContainer.getRuleManager().getMaxSeverity(topics); 767 for (String topic : topics) { 768 Consumer<Result> listener = result -> { 769 Severity severity = Severity.get(result.getScore()); 770 if (severity.compareTo(maxSeverity) > 0) { 771 maxSeverity = severity; 772 setImageDescriptor(getResultIcon(maxSeverity)); 773 } else if (severity.compareTo(maxSeverity) < 0) { // severity could be less than previous max 774 maxSeverity = pageContainer.getRuleManager().getMaxSeverity(topics); 775 } 776 setToolTipText(tooltip.get()); 777 }; 778 listeners.add(listener); 779 pageContainer.getRuleManager().addResultListener(topic, listener); 780 } 781 } 782 783 private void removeListeners() { 784 listeners.forEach(l -> pageContainer.getRuleManager().removeResultListener(l)); 785 } 786 787 @Override 788 public void run() { 789 pageContainer.showResults(topics); 790 } 791 } 792 793 public static void addRuleResultAction( 794 Form form, IPageContainer pageContainer, Supplier<String> tooltip, String[] topics) { 795 if (topics == null || topics.length == 0 || !FlightRecorderUI.getDefault().isAnalysisEnabled()) { 796 return; 797 } 798 ImageDescriptor icon = getResultIcon(pageContainer.getRuleManager().getMaxSeverity(topics)); 799 ShowResultAction resultAction = new ShowResultAction(Messages.RULES_SHOW_RESULTS_ACTION, IAction.AS_PUSH_BUTTON, 800 icon, tooltip, pageContainer, topics); 801 resultAction.setId(RESULT_ACTION_ID); 802 form.getToolBarManager().appendToGroup(DataPageToolkit.FORM_TOOLBAR_PAGE_RESULTS, resultAction); 803 form.getToolBarManager().update(true); 804 form.addDisposeListener(e -> resultAction.removeListeners()); 805 } 806 807 private static ImageDescriptor getResultIcon(Severity severity) { 808 switch (severity) { 809 case OK: 810 return ResultOverview.ICON_OK; 811 case INFO: 812 return ResultOverview.ICON_INFO; 813 case WARNING: 814 return ResultOverview.ICON_WARNING; 815 case NA: 816 return ResultOverview.ICON_NA; 817 } 818 return null; 819 } 820 821 /** 822 * Return a disabled Action. 823 * 824 * @param text 825 * text to be displayed by the MenuItem, and represent it as it's id. 826 * @return an Action containing the desired text, which will be disabled in a UI component. 827 */ 828 public static IAction disabledAction(String text) { 829 IAction disabledAction = new Action(text) { 830 @Override 831 public boolean isEnabled() { 832 return false; 833 } 834 }; 835 disabledAction.setId(text); 836 return disabledAction; 837 } 838 839 public static FilterEditor buildFilterSelector( 840 Composite parent, IItemFilter filter, IItemCollection items, Supplier<Stream<SelectionStoreEntry>> selections, 841 Consumer<IItemFilter> onSelect, boolean hasBorder) { 842 Supplier<Collection<IAttribute<?>>> attributeSupplier = () -> getPersistableAttributes( 843 getAttributes(filter != null ? items.apply(filter) : items)).collect(Collectors.toList()); 844 845 AttributeValueProvider valueSupplier = new AttributeValueProvider() { 846 @Override 847 public <V> V defaultValue(ICanonicalAccessorFactory<V> attribute) { 848 return findValueForFilter(items, attribute); 849 } 850 }; 851 852 FilterEditor editor = new FilterEditor(parent, onSelect, filter, attributeSupplier, valueSupplier, 853 TypeLabelProvider::getColorOrDefault, hasBorder ? SWT.BORDER : SWT.NONE); 854 855 MenuManager addFromSelectionPredicate = new MenuManager(Messages.FILTER_ADD_FROM_SELECTION); 856 editor.getContextMenu().prependToGroup(MCContextMenuManager.GROUP_NEW, addFromSelectionPredicate); 857 addFromSelectionPredicate.setRemoveAllWhenShown(true); 858 addFromSelectionPredicate.addMenuListener(new IMenuListener() { 859 860 @Override 861 public void menuAboutToShow(IMenuManager manager) { 862 selections.get().forEach(entry -> { 863 MenuManager selectionFlavors = new MenuManager(entry.getName()); 864 entry.getSelection().getFlavors(editor.getFilter(), items, null) 865 .filter(f -> f instanceof IFilterFlavor).forEach(flavor -> { 866 selectionFlavors.add(new Action(flavor.getName()) { 867 @Override 868 public void run() { 869 editor.addRoot(((IFilterFlavor) flavor).getFilter()); 870 } 871 }); 872 }); 873 if (!selectionFlavors.isEmpty()) { 874 if (manager.find(Messages.FILTER_NO_SELECTION_AVAILABLE) != null) { 875 manager.remove(Messages.FILTER_NO_SELECTION_AVAILABLE); 876 } 877 manager.add(selectionFlavors); 878 } else { 879 manager.add(disabledAction(Messages.FILTER_NO_SELECTION_AVAILABLE)); 880 } 881 }); 882 } 883 }); 884 885 // FIXME: This could potentially move into the FilterEditor class 886 MenuManager addAttributeValuePredicate = new MenuManager(Messages.FILTER_ADD_FROM_ATTRIBUTE); 887 editor.getContextMenu().prependToGroup(MCContextMenuManager.GROUP_NEW, addAttributeValuePredicate); 888 addAttributeValuePredicate.setRemoveAllWhenShown(true); 889 addAttributeValuePredicate.addMenuListener(new IMenuListener() { 890 Collection<IAttribute<?>> attributes; 891 892 @Override 893 public void menuAboutToShow(IMenuManager manager) { 894 if (attributes == null) { 895 attributes = attributeSupplier.get(); 896 } 897 if (!attributes.isEmpty()) { 898 if (manager.find(Messages.FILTER_NO_ATTRIBUTE_AVAILABLE) != null) { 899 manager.remove(Messages.FILTER_NO_ATTRIBUTE_AVAILABLE); 900 } 901 attributes.stream().distinct().sorted((a1, a2) -> a1.getName().compareTo(a2.getName())) 902 .forEach(attr -> { 903 addAttributeValuePredicate.add(new Action(attr.getName()) { 904 @Override 905 public void run() { 906 IItemFilter filter = createDefaultFilter(items, attr); 907 editor.addRoot(filter); 908 } 909 }); 910 }); 911 } else { 912 manager.add(disabledAction(Messages.FILTER_NO_ATTRIBUTE_AVAILABLE)); 913 } 914 915 } 916 }); 917 return editor; 918 } 919 920 // FIXME: Move to some AttributeToolkit? 921 private static Stream<IAttribute<?>> getAttributes(IItemCollection items) { 922 return ItemCollectionToolkit.stream(items).filter(IItemIterable::hasItems) 923 .flatMap(is -> is.getType().getAttributes().stream()); 924 } 925 926 public static Stream<IAttribute<?>> getPersistableAttributes(Stream<IAttribute<?>> attributes) { 927 // FIXME: Would like to always be able to persist a string representation of the attribute, because this is usable by filters. 928 929 // FIXME: Should we always include event type? Does it make any sense, except on the custom pages? 930 931 // FIXME: Transform both START_TIME and END_TIME to LIFETIME? 932 // FIXME: Add derived attributes, like a conversion of any THREAD or CLASS attribute? Thread group? 933 /* 934 * Make sure to do the conversions in the right order, so for example a stack trace can be 935 * converted to a top method, which then is converted to a method string. 936 */ 937 return attributes.map(a -> a.equals(JfrAttributes.EVENT_THREAD) ? JdkAttributes.EVENT_THREAD_NAME : a) 938 .flatMap(a -> a.equals(JfrAttributes.EVENT_STACKTRACE) ? Stream.of(JdkAttributes.STACK_TRACE_STRING, 939 JdkAttributes.STACK_TRACE_TOP_METHOD_STRING, JdkAttributes.STACK_TRACE_TOP_CLASS_STRING, 940 JdkAttributes.STACK_TRACE_TOP_PACKAGE, JdkAttributes.STACK_TRACE_BOTTOM_METHOD_STRING) 941 : Stream.of(a)) 942 .map(a -> a.equals(JdkAttributes.COMPILER_METHOD) ? JdkAttributes.COMPILER_METHOD_STRING : a) 943 // FIXME: String or id? 944 .map(a -> a.equals(JdkAttributes.REC_SETTING_FOR) ? JdkAttributes.REC_SETTING_FOR_NAME : a) 945 .map(a -> a.equals(JdkAttributes.CLASS_DEFINING_CLASSLOADER) 946 ? JdkAttributes.CLASS_DEFINING_CLASSLOADER_STRING : a) 947 .map(a -> a.equals(JdkAttributes.CLASS_INITIATING_CLASSLOADER) 948 ? JdkAttributes.CLASS_INITIATING_CLASSLOADER_STRING : a) 949 .map(a -> a.equals(JdkAttributes.PARENT_CLASSLOADER) 950 ? JdkAttributes.PARENT_CLASSLOADER_STRING : a) 951 .map(a -> a.equals(JdkAttributes.CLASSLOADER) 952 ? JdkAttributes.CLASSLOADER_STRING : a) 953 .filter(a -> a.equals(JfrAttributes.EVENT_TYPE) || (a.getContentType() instanceof RangeContentType) 954 || (a.getContentType().getPersister() != null)) 955 .distinct(); 956 } 957 958 /** 959 * Returns a value for attribute, firstly by trying to find one in the items, secondly by 960 * creating a default value for some known content types. Returns null if the first two cases 961 * fail. 962 * 963 * @param items 964 * @param attribute 965 * @return a value of type V, or null 966 */ 967 @SuppressWarnings("unchecked") 968 private static <V> V findValueForFilter(IItemCollection items, ICanonicalAccessorFactory<V> attribute) { 969 IItem firstItem = ItemCollectionToolkit.stream(items).filter(is -> is.getType().hasAttribute(attribute)) 970 .flatMap(ItemIterableToolkit::stream) 971 .filter(i -> ((IMemberAccessor<V, IItem>) attribute.getAccessor(i.getType())).getMember(i) != null) 972 .findFirst().orElse(null); 973 if (firstItem != null) { 974 IMemberAccessor<V, IItem> accessor = (IMemberAccessor<V, IItem>) attribute.getAccessor(firstItem.getType()); 975 return accessor.getMember(firstItem); 976 } 977 if (UnitLookup.PLAIN_TEXT.equals(attribute.getContentType())) { 978 return (V) ""; //$NON-NLS-1$ 979 } 980 if (attribute.getContentType() instanceof KindOfQuantity<?>) { 981 return (V) ((KindOfQuantity<?>) attribute.getContentType()).getDefaultUnit().quantity(0); 982 } 983 return null; 984 } 985 986 /** 987 * Returns an default filter for attribute, which might be an equals filter, hasAttribute 988 * filter, or type filter, depending on the attribute and the contents of the items. 989 * 990 * @param items 991 * @param attribute 992 * @return a filter 993 */ 994 // FIXME: Should move to FilterEditor, or some subclass/specialization? 995 private static <V> IItemFilter createDefaultFilter(IItemCollection items, ICanonicalAccessorFactory<V> attribute) { 996 V value = findValueForFilter(items, attribute); 997 if (value == null) { 998 return ItemFilters.hasAttribute(attribute); 999 } else if (attribute.equals(JfrAttributes.EVENT_TYPE)) { 1000 return ItemFilters.type(((IType<?>) value).getIdentifier()); 1001 } 1002 return ItemFilters.equals(attribute, value); 1003 } 1004 1005 public static void addRenameAction(Form form, IPageContainer editor) { 1006 form.getMenuManager().add(new Action(Messages.PAGE_RENAME_MENU_ACTION) { 1007 @Override 1008 public void run() { 1009 InputDialog dialog = new InputDialog(form.getShell(), Messages.PAGE_RENAME_DIALOG_TITLE, 1010 Messages.PAGE_RENAME_DIALOG_MESSAGE, form.getText(), null); 1011 if (dialog.open() == Window.OK) { 1012 form.setText(dialog.getValue()); 1013 editor.currentPageRefresh(); 1014 } 1015 } 1016 }); 1017 } 1018 1019 public static void addIconChangeAction(Form form, IPageContainer editor, Consumer<Image> newIconConsumer) { 1020 form.getMenuManager().add(new Action(Messages.PAGE_CHANGE_ICON_MENU_ACTION) { 1021 @Override 1022 public void run() { 1023 WizardDialog dialog = new WizardDialog(form.getShell(), 1024 new IconChangeWizard(form.getImage(), newIconConsumer)); 1025 dialog.open(); 1026 editor.currentPageRefresh(); 1027 } 1028 }); 1029 } 1030 1031 private static class IconChangeWizard extends Wizard { 1032 1033 private final Image currentImage; 1034 private final Consumer<Image> imageConsumer; 1035 private Label imageLabel; 1036 1037 public IconChangeWizard(Image currentImage, Consumer<Image> imageConsumer) { 1038 setWindowTitle(Messages.PAGE_CHANGE_ICON_WIZARD_TITLE); 1039 this.currentImage = currentImage; 1040 this.imageConsumer = imageConsumer; 1041 } 1042 1043 @Override 1044 public void addPages() { 1045 addPage(new WizardPage(Messages.PAGE_CHANGE_ICON_WIZARD_PAGE_TITLE) { 1046 1047 @Override 1048 public String getTitle() { 1049 return Messages.PAGE_CHANGE_ICON_WIZARD_PAGE_TITLE; 1050 } 1051 1052 @Override 1053 public String getDescription() { 1054 return Messages.PAGE_CHANGE_ICON_WIZARD_PAGE_DESC; 1055 } 1056 1057 @Override 1058 public void createControl(Composite parent) { 1059 Composite container = new Composite(parent, SWT.NONE); 1060 GridLayout layout = new GridLayout(1, false); 1061 container.setLayout(layout); 1062 1063 Button button = new Button(container, SWT.NONE); 1064 button.setText(Messages.PAGE_CHANGE_ICON_CHOOSE_IMAGE_FILE); 1065 1066 button.addSelectionListener(new SelectionAdapter() { 1067 @Override 1068 public void widgetSelected(SelectionEvent e) { 1069 chooseImageFileDialog(); 1070 } 1071 }); 1072 1073 if (currentImage != null) { 1074 new Label(container, SWT.NONE).setText(Messages.PAGE_CHANGE_ICON_CURRENT_ICON); 1075 new Label(container, SWT.BORDER).setImage(currentImage); 1076 } 1077 new Label(container, SWT.NONE).setText(Messages.PAGE_CHANGE_ICON_NEW_ICON_PREVIEW); 1078 imageLabel = new Label(container, SWT.BORDER); 1079 GridData gd = new GridData(16, 16); 1080 imageLabel.setLayoutData(gd); 1081 1082 setControl(container); 1083 } 1084 1085 private void chooseImageFileDialog() { 1086 FileDialog fileDialog = new FileDialog(getShell(), SWT.OPEN); 1087 String[] filterNames = new String[] {"Image Files", "All Files (*)"}; //$NON-NLS-1$ //$NON-NLS-2$ 1088 String[] filterExtensions = new String[] {"*.gif;*.png;*.xpm;*.jpg;*.jpeg;*.tiff", "*"}; //$NON-NLS-1$ //$NON-NLS-2$ 1089 fileDialog.setFilterNames(filterNames); 1090 fileDialog.setFilterExtensions(filterExtensions); 1091 String filename = fileDialog.open(); 1092 if (filename == null) { 1093 // Dialog was cancelled. Bail out early to avoid handling that case later. Premature? 1094 return; 1095 } 1096 try (InputStream fis = new FileInputStream(filename)) { 1097 ImageData imageData = new ImageData(fis); 1098 // Validate image data 1099 if (imageData.width != 16 || imageData.height != 16) { 1100 imageData = resizeImage(imageData, 16, 16); 1101 } 1102 DisplayToolkit.dispose(imageLabel.getImage()); 1103 imageLabel.setImage(new Image(getShell().getDisplay(), imageData)); 1104 imageLabel.getParent().layout(); 1105 setPageComplete(isPageComplete()); 1106 } catch (Exception e) { 1107 // FIXME: Add proper logging 1108 e.printStackTrace(); 1109 } 1110 } 1111 1112 private ImageData resizeImage(ImageData imageData, int width, int height) { 1113 Image original = ImageDescriptor.createFromImageData(imageData).createImage(); 1114 Image scaled = new Image(Display.getDefault(), width, height); 1115 GC gc = new GC(scaled); 1116 gc.setAntialias(SWT.ON); 1117 gc.setInterpolation(SWT.HIGH); 1118 gc.drawImage(original, 0, 0, imageData.width, imageData.height, 0, 0, width, height); 1119 gc.dispose(); 1120 original.dispose(); 1121 ImageData scaledData = scaled.getImageData(); 1122 scaled.dispose(); 1123 return scaledData; 1124 } 1125 1126 @Override 1127 public boolean isPageComplete() { 1128 return imageLabel.getImage() != null; 1129 } 1130 1131 }); 1132 } 1133 1134 @Override 1135 public boolean performFinish() { 1136 imageConsumer.accept(imageLabel.getImage()); 1137 DisplayToolkit.dispose(currentImage); 1138 return true; 1139 } 1140 1141 @Override 1142 public boolean performCancel() { 1143 DisplayToolkit.dispose(imageLabel.getImage()); 1144 return true; 1145 } 1146 1147 } 1148 1149 public static ItemList createSimpleItemList( 1150 Composite parent, ItemListBuilder listBuilder, IPageContainer pageContainer, TableSettings tableSettings, 1151 String selectionName) { 1152 1153 ItemList list = listBuilder.build(parent, tableSettings); 1154 ColumnViewer viewer = list.getManager().getViewer(); 1155 MCContextMenuManager mm = MCContextMenuManager.create(viewer.getControl()); 1156 ColumnMenusFactory.addDefaultMenus(list.getManager(), mm); 1157 viewer.addSelectionChangedListener( 1158 e -> pageContainer.showSelection(ItemCollectionToolkit.build(list.getSelection().get()))); 1159 1160 if (selectionName != null) { 1161 SelectionStoreActionToolkit.addSelectionStoreActions(pageContainer.getSelectionStore(), list, selectionName, 1162 mm); 1163 } 1164 1165 return list; 1166 } 1167 1168 public static void addTabItem(CTabFolder tabFolder, Control section, String name) { 1169 CTabItem tabItem = new CTabItem(tabFolder, SWT.NONE); 1170 tabItem.setControl(section); 1171 tabItem.setText(name); 1172 } 1173 1174 public static TypeFilterBuilder buildEventTypeTree( 1175 Composite parent, FormToolkit toolkit, Runnable onChange, boolean checkbox) { 1176 // TODO: Make more accessible. 1177 // TODO: Add support for storing the expansion state in a memento. 1178 // TODO: Add input from selection store, output to selection store 1179 // TODO: Add toolbar for choosing tree or checkbox tree. 1180 Composite treeComposite = new Composite(parent, SWT.NONE); 1181 treeComposite.setLayout(new GridLayout()); 1182 toolkit.adapt(treeComposite); 1183 Label caption = toolkit.createLabel(treeComposite, Messages.EVENT_TYPE_TREE_TITLE); 1184 caption.setFont(JFaceResources.getFontRegistry().getBold(JFaceResources.DEFAULT_FONT)); 1185 caption.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); 1186 1187 TypeFilterBuilder typeFilterTree = new TypeFilterBuilder(treeComposite, onChange, checkbox); 1188 1189 typeFilterTree.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); 1190 return typeFilterTree; 1191 } 1192 1193 public static boolean isTypeWithThreadAndDuration(IType<?> type) { 1194 return JfrAttributes.EVENT_THREAD.getAccessor(type) != null 1195 && JfrAttributes.START_TIME.getAccessor(type) != JfrAttributes.END_TIME.getAccessor(type); 1196 } 1197 1198 public static void addPage(Set<IType<?>> selectedTypes) { 1199 PageManager pm = FlightRecorderUI.getDefault().getPageManager(); 1200 pm.makeRoot(pm.createPage(ItemHandlerPage.Factory.class, new ItemHandlerUiStandIn(selectedTypes))); 1201 } 1202 }