1 /* 2 * Copyright (c) 2021 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package ohos.devtools.views.distributed.component; 17 18 import com.intellij.ui.components.JBLabel; 19 import com.intellij.ui.components.JBPanel; 20 import com.intellij.ui.components.JBScrollPane; 21 import net.miginfocom.swing.MigLayout; 22 import ohos.devtools.views.applicationtrace.util.TimeUtils; 23 import ohos.devtools.views.distributed.DistributedDataPane; 24 import ohos.devtools.views.distributed.bean.DistributedFuncBean; 25 import ohos.devtools.views.distributed.util.DistributedCache; 26 import ohos.devtools.views.trace.AbstractRow; 27 import ohos.devtools.views.trace.EventDispatcher; 28 import ohos.devtools.views.trace.ExpandPanel; 29 import ohos.devtools.views.trace.Tip; 30 import ohos.devtools.views.trace.TraceSimpleRow; 31 import ohos.devtools.views.trace.util.Utils; 32 33 import javax.swing.SwingUtilities; 34 import java.awt.Component; 35 import java.awt.Container; 36 import java.awt.Point; 37 import java.awt.Rectangle; 38 import java.awt.event.ComponentAdapter; 39 import java.awt.event.ComponentEvent; 40 import java.awt.event.MouseAdapter; 41 import java.awt.event.MouseEvent; 42 import java.awt.event.MouseMotionAdapter; 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.List; 46 import java.util.Locale; 47 import java.util.Objects; 48 import java.util.Optional; 49 import java.util.concurrent.atomic.AtomicBoolean; 50 import java.util.stream.Collectors; 51 52 /** 53 * TracePanel 54 * 55 * @since 2021/5/13 13:06 56 */ 57 public class DistributedTracePanel extends JBPanel { 58 /** 59 * currentSelectThreadIds 60 */ 61 public static final List<Integer> CURRENT_SELECT_THREAD_IDS = new ArrayList<>(); 62 63 /** 64 * root TracePanel 65 */ 66 public static DistributedTracePanel root; 67 68 /** 69 * current start time 70 */ 71 public static long startNS; 72 73 /** 74 * current end time 75 */ 76 public static long endNS; 77 78 /** 79 * DURATION 10_000_000_000L 80 */ 81 private static long DURATION = 0L; 82 83 /** 84 * range start time 85 */ 86 private static Long rangeStartNS; 87 88 /** 89 * range end time 90 */ 91 private static Long rangeEndNS; 92 93 private boolean isDragRelease = false; 94 private DistributedTimeShaft timeShaft; 95 private JBScrollPane scrollPane; 96 private JBPanel contentPanel; 97 private List<Component> componentList; 98 private Point startPoint; 99 private Point endPoint; 100 private List<Component> allComponent; 101 private boolean isInit = false; 102 private JBLabel totalLabel = new JBLabel(); 103 private JBLabel rangeStartLabel = new JBLabel(); 104 105 /** 106 * structure function 107 */ DistributedTracePanel()108 public DistributedTracePanel() { 109 timeShaft = new DistributedTimeShaft((startNS, endNS, scale) -> { 110 EventDispatcher.dispatcherRange(startNS, endNS, scale); 111 Arrays.stream(contentPanel.getComponents()).filter(DeviceExpandPanel.class::isInstance) 112 .map(it -> ((DeviceExpandPanel) it)).forEach(it -> it.refresh(startNS, endNS)); 113 rangeStartLabel.setText(ohos.devtools.views.trace.util.TimeUtils.getSecondFromNSecond(startNS)); 114 }, keyEvent -> timeShaftComplete(), mouseEvent -> timeShaftComplete()); 115 contentPanel = new JBPanel(); 116 contentPanel.setLayout(new MigLayout("inset 0,wrap 1", "0[grow,fill]0", "0[]0")); 117 contentPanel.setFocusable(true); 118 contentPanel.setBorder(null); 119 setLayout(new MigLayout("inset 0", "0[115!]0[grow,fill]0", "0[]0")); 120 scrollPane = new JBScrollPane(contentPanel); 121 scrollPane.setBorder(null); 122 totalLabel.setText("Total: " + TimeUtils.getDistributedTotalTime(DistributedTracePanel.getDURATION())); 123 add(totalLabel, "span 1 1,align center"); 124 add(new DistributedRuler(DistributedTracePanel.getDURATION()), "pushx,growx, h 20!,wrap"); 125 add(rangeStartLabel, "span 1 1,align right,gapright 5"); 126 add(timeShaft, "pushx,growx,h 30!,wrap"); 127 add(scrollPane, "span 2,push,grow"); 128 setBorder(null); 129 scrollPane.getVerticalScrollBar().addAdjustmentListener(event -> Arrays.stream(contentPanel.getComponents()) 130 .filter(it -> scrollPane.getViewport().getViewRect().intersects(it.getBounds())) 131 .filter(DeviceExpandPanel.class::isInstance).map(it -> ((DeviceExpandPanel) it)) 132 .forEach(it -> it.refresh(startNS, endNS))); 133 root = this; 134 contentPanel.addMouseListener(new ContentMouseAdapter()); 135 contentPanel.addMouseMotionListener(new ContentMouseMotionAdapter()); 136 contentPanel.addComponentListener(new ContentComponentAdapter()); 137 // Add monitor to change the selected func color block according to funcId 138 addMonitorChange(); 139 } 140 addMonitorChange()141 private void addMonitorChange() { 142 EventDispatcher.addFuncSelectChange(funcId -> { 143 if (Objects.nonNull(DistributedFuncBean.currentSelectedFunc)) { 144 DistributedFuncBean.currentSelectedFunc.setSelected(false); 145 } 146 allComponent.stream().filter(TraceFuncRow.class::isInstance) 147 .map(it -> ((TraceFuncRow<DistributedFuncBean>) it)).forEach(row -> { 148 if (row.getData() != null) { 149 row.getData().stream().filter(it -> it.getId() == funcId).forEach(it -> { 150 it.setSelected(true); 151 // Rectangle is the bounds after row expansion 152 DistributedFuncBean.currentSelectedFunc = it; 153 int offsetHeight = 0; 154 for (Component component : scrollPane.getRootPane().getLayeredPane().getComponents()) { 155 if (component.getClass() == DistributedDataPane.class) { 156 offsetHeight = component.getBounds().height; 157 } 158 } 159 int finalOffsetHeight = offsetHeight; 160 if (row.isCollapsed()) { 161 row.setCollapsed(false, rectangle1 -> { 162 Rectangle rct = 163 SwingUtilities.convertRectangle(row.content, it.getRect(), contentPanel); 164 scrollPane.getVerticalScrollBar().setValue(0); 165 scrollPane.getViewport().scrollRectToVisible(new Rectangle(0, 166 Utils.getY(rct) + finalOffsetHeight + it.getRect().y + it.getRect().height, 167 row.getBounds().width, rct.height)); 168 long rangeX1 = it.getStartTs() - it.getDur() * 2; 169 if (rangeX1 < 0) { 170 rangeX1 = 0; 171 } 172 long rangeX2 = it.getEndTs() + it.getDur() * 2; 173 timeShaft.setRange(rangeX1, rangeX2); 174 timeShaft.notifyRangeChange(rangeX1, rangeX2); 175 }); 176 } else { 177 rowUnCollapsed(row, it, finalOffsetHeight); 178 } 179 }); 180 } 181 }); 182 }); 183 } 184 rowUnCollapsed(TraceFuncRow<DistributedFuncBean> row, DistributedFuncBean it, int finalOffsetHeight)185 private void rowUnCollapsed(TraceFuncRow<DistributedFuncBean> row, DistributedFuncBean it, 186 int finalOffsetHeight) { 187 Rectangle rct = SwingUtilities.convertRectangle(row.content, it.getRect(), contentPanel); 188 scrollPane.getVerticalScrollBar().setValue(0); 189 scrollPane.getViewport().scrollRectToVisible( 190 new Rectangle(0, Utils.getY(rct) + finalOffsetHeight + it.getRect().y + it.getRect().height, 191 row.getBounds().width, rct.height)); 192 long rangeX1 = it.getStartTs() - it.getDur() * 2; 193 if (rangeX1 < 0) { 194 rangeX1 = 0; 195 } 196 long rangeX2 = it.getEndTs() + it.getDur() * 2; 197 timeShaft.setRange(rangeX1, rangeX2); 198 timeShaft.notifyRangeChange(rangeX1, rangeX2); 199 } 200 201 /** 202 * range end time 203 * 204 * @return get current contentPanel 205 */ getContentPanel()206 public JBPanel getContentPanel() { 207 return contentPanel; 208 } 209 timeShaftComplete()210 private void timeShaftComplete() { 211 Arrays.stream(contentPanel.getComponents()).filter(it -> it instanceof ExpandPanel) 212 .map(it -> ((ExpandPanel) it)).filter(it -> !it.isCollapsed()).forEach( 213 it -> Arrays.stream(it.getContent().getComponents()).filter(row -> row instanceof TraceSimpleRow) 214 .map(row -> ((TraceSimpleRow) row)) 215 .filter(row -> row.getRowName().toLowerCase(Locale.ENGLISH).startsWith("cpu")) 216 .forEach(row -> row.reload())); 217 } 218 tip(MouseEvent event)219 private void tip(MouseEvent event) { 220 if (Objects.isNull(allComponent)) { 221 allComponent = Arrays.stream(contentPanel.getComponents()).filter(DeviceExpandPanel.class::isInstance) 222 .map(it -> ((DeviceExpandPanel) it)).flatMap(it -> Arrays.stream(it.getContent().getComponents())) 223 .filter(ExpandPanel.class::isInstance).map(it -> ((ExpandPanel) it)) 224 .flatMap(it -> Arrays.stream(it.getContent().getComponents())).collect(Collectors.toList()); 225 } 226 boolean flag = allComponent.stream().anyMatch(it -> { 227 if (it instanceof AbstractRow) { 228 AbstractRow row = (AbstractRow) it; 229 Rectangle rectangle = SwingUtilities.convertRectangle(row, row.getContentBounds(), contentPanel); 230 return rectangle.contains(event.getPoint()); 231 } 232 return false; 233 }); 234 if (flag) { 235 allComponent.forEach(component -> { 236 if (component instanceof AbstractRow) { 237 AbstractRow row = (AbstractRow) component; 238 Optional<ExpandPanel> exp = getExpandPanel(row); 239 Optional<DeviceExpandPanel> dxp = getDevicePanel(row); 240 if (exp.isPresent() && dxp.isPresent() && !exp.get().isCollapsed() && !dxp.get().isCollapsed()) { 241 Rectangle rectangle = 242 SwingUtilities.convertRectangle(row, row.getContentBounds(), contentPanel); 243 if (rectangle.contains(event.getPoint())) { 244 Point point = SwingUtilities.convertPoint(contentPanel, event.getPoint(), row.content); 245 row.mouseMoveHandler(point); 246 } 247 } 248 } 249 }); 250 } else { 251 Tip.getInstance().hidden(); 252 } 253 } 254 255 /** 256 * structure function 257 * 258 * @param startNS startNS 259 * @param endNS endNS 260 */ setRange(long startNS, long endNS)261 public void setRange(long startNS, long endNS) { 262 Optional.ofNullable(timeShaft).ifPresent(tf -> tf.setRange(startNS, endNS)); 263 } 264 notifySelectRangeChange()265 private void notifySelectRangeChange() { 266 if (Objects.isNull(DistributedTracePanel.rangeStartNS) && Objects.isNull(DistributedTracePanel.rangeEndNS)) { 267 EventDispatcher.dispatcherThreadRange(DistributedTracePanel.startNS, DistributedTracePanel.endNS, 268 CURRENT_SELECT_THREAD_IDS); 269 } else { 270 long st = 271 DistributedTracePanel.rangeStartNS < DistributedTracePanel.startNS ? DistributedTracePanel.startNS 272 : DistributedTracePanel.rangeStartNS; 273 long et = DistributedTracePanel.rangeEndNS > DistributedTracePanel.endNS ? DistributedTracePanel.endNS 274 : DistributedTracePanel.rangeEndNS; 275 EventDispatcher.dispatcherThreadRange(st, et, CURRENT_SELECT_THREAD_IDS); 276 } 277 } 278 getTimeShaft()279 public DistributedTimeShaft getTimeShaft() { 280 return timeShaft; 281 } 282 getDevicePanel(Container component)283 private Optional<DeviceExpandPanel> getDevicePanel(Container component) { 284 if (component == null) { 285 return Optional.empty(); 286 } 287 if (component instanceof DeviceExpandPanel) { 288 DeviceExpandPanel deviceExpandPanel = (DeviceExpandPanel) component; 289 return Optional.ofNullable(deviceExpandPanel); 290 } else { 291 return getDevicePanel(component.getParent()); 292 } 293 } 294 getExpandPanel(Container component)295 private Optional<ExpandPanel> getExpandPanel(Container component) { 296 if (component == null) { 297 return Optional.empty(); 298 } 299 if (component instanceof ExpandPanel) { 300 ExpandPanel expandPanel = (ExpandPanel) component; 301 return Optional.ofNullable(expandPanel); 302 } else { 303 return getExpandPanel(component.getParent()); 304 } 305 } 306 307 private class ContentMouseAdapter extends MouseAdapter { 308 @Override mousePressed(MouseEvent event)309 public void mousePressed(MouseEvent event) { 310 super.mousePressed(event); 311 if (Objects.isNull(componentList)) { 312 componentList = 313 Arrays.stream(contentPanel.getComponents()).filter(it -> it instanceof DeviceExpandPanel) 314 .map(it -> ((DeviceExpandPanel) it)) 315 .flatMap(it -> Arrays.stream(it.getContent().getComponents())) 316 .filter(it -> it instanceof ExpandPanel).map(it -> ((ExpandPanel) it)) 317 .flatMap(it -> Arrays.stream(it.getContent().getComponents())).collect(Collectors.toList()); 318 } 319 if (componentList.size() > 0) { 320 startPoint = event.getPoint(); 321 componentList.stream().filter(TraceFuncRow.class::isInstance).map(it -> ((TraceFuncRow<?>) it)) 322 .forEach(thread -> { 323 Rectangle rect = 324 SwingUtilities.convertRectangle(thread.getParent(), thread.getBounds(), contentPanel); 325 if (rect.contains(startPoint) && Utils.getX(startPoint) < Utils 326 .getX(thread.getContentBounds())) { 327 Optional<DeviceExpandPanel> devicePanel = getDevicePanel(thread); 328 if (devicePanel.isPresent()) { 329 DistributedCache.setCurrentDBFlag(devicePanel.get().getDbFlag()); 330 } 331 } 332 }); 333 } 334 } 335 336 @Override mouseClicked(MouseEvent event)337 public void mouseClicked(MouseEvent event) { 338 super.mouseClicked(event); 339 DistributedTracePanel.rangeStartNS = null; 340 DistributedTracePanel.rangeEndNS = null; 341 AtomicBoolean flag = new AtomicBoolean(false); 342 CURRENT_SELECT_THREAD_IDS.clear(); 343 componentList.stream().filter(TraceFuncRow.class::isInstance).map(it -> ((TraceFuncRow<?>) it)) 344 .forEach(thread -> { 345 Optional<ExpandPanel> exp = getExpandPanel(thread); 346 Optional<DeviceExpandPanel> dxp = getDevicePanel(thread); 347 if (exp.isPresent() && dxp.isPresent() && !exp.get().isCollapsed() && !dxp.get().isCollapsed()) { 348 // Create a rectangle 349 Rectangle rect = 350 SwingUtilities.convertRectangle(thread.getParent(), thread.getBounds(), contentPanel); 351 352 // If the click coordinates are in the thread name display area, select the thread 353 if (rect.contains(startPoint) && Utils.getX(startPoint) < Utils 354 .getX(thread.getContentBounds())) { 355 if (!CURRENT_SELECT_THREAD_IDS.contains(thread.getTid())) { 356 CURRENT_SELECT_THREAD_IDS.add(thread.getTid()); 357 } 358 thread.setSelect(true, null, null); 359 } else { 360 // If the clicked coordinates are on the color block, 361 // it means that the color block is selected instead of the selected row 362 Point point = 363 SwingUtilities.convertPoint(event.getComponent(), event.getPoint(), thread.content); 364 if ((Objects.nonNull(thread.getData()) && thread.getData().stream() 365 .anyMatch(it -> it.getRect().contains(point)))) { 366 thread.getData().stream().filter(it -> it.getRect().contains(point)) 367 .forEach(it -> it.onClick(event)); 368 flag.set(true); 369 } 370 CURRENT_SELECT_THREAD_IDS.remove(thread.getTid()); 371 thread.setSelect(false, null, null); 372 } 373 } 374 }); 375 376 // The flag value is true to indicate that the click is on the method color block 377 if (!flag.get()) { 378 notifySelectRangeChange(); 379 } 380 } 381 382 @Override mouseExited(MouseEvent event)383 public void mouseExited(MouseEvent event) { 384 super.mouseExited(event); 385 Tip.getInstance().hidden(); 386 } 387 388 @Override mouseReleased(MouseEvent event)389 public void mouseReleased(MouseEvent event) { 390 super.mouseReleased(event); 391 if (isDragRelease) { 392 notifySelectRangeChange(); 393 isDragRelease = false; 394 } 395 } 396 } 397 398 private class ContentMouseMotionAdapter extends MouseMotionAdapter { 399 @Override mouseDragged(MouseEvent event)400 public void mouseDragged(MouseEvent event) { 401 super.mouseDragged(event); 402 isDragRelease = true; 403 endPoint = event.getPoint(); 404 int xPoint = Math.min(Utils.getX(startPoint), Utils.getX(endPoint)); 405 int yPoint = Math.min(Utils.getY(startPoint), Utils.getY(endPoint)); 406 int width = Math.abs(Utils.getX(startPoint) - Utils.getX(endPoint)) == 0 ? 1 407 : Math.abs(Utils.getX(startPoint) - Utils.getX(endPoint)); 408 int height = Math.abs(Utils.getY(startPoint) - Utils.getY(endPoint)); 409 Rectangle range = new Rectangle(xPoint, yPoint, width, height); 410 CURRENT_SELECT_THREAD_IDS.clear(); 411 componentList.stream().filter(TraceFuncRow.class::isInstance).map(it -> ((TraceFuncRow<?>) it)) 412 .forEach(thread -> { 413 Optional<ExpandPanel> exp = getExpandPanel(thread); 414 Optional<DeviceExpandPanel> dxp = getDevicePanel(thread); 415 if (exp.isPresent() && dxp.isPresent() && !exp.get().isCollapsed() && !dxp.get().isCollapsed()) { 416 Rectangle rect = 417 SwingUtilities.convertRectangle(thread.getParent(), thread.getBounds(), contentPanel); 418 if (range.intersects(rect)) { 419 if (!CURRENT_SELECT_THREAD_IDS.contains(thread.getTid())) { 420 CURRENT_SELECT_THREAD_IDS.add(thread.getTid()); 421 } 422 thread.setSelect(true, xPoint - Utils.getX(thread.getContentBounds()), 423 xPoint + width - Utils.getX(thread.getContentBounds())); 424 } else { 425 if (CURRENT_SELECT_THREAD_IDS.contains(thread.getTid())) { 426 CURRENT_SELECT_THREAD_IDS.remove(thread.getTid()); 427 } 428 thread.setSelect(false, null, null); 429 } 430 } 431 }); 432 Tip.getInstance().hidden(); 433 } 434 435 @Override mouseMoved(MouseEvent event)436 public void mouseMoved(MouseEvent event) { 437 super.mouseMoved(event); 438 tip(event); 439 } 440 } 441 442 private class ContentComponentAdapter extends ComponentAdapter { 443 @Override componentResized(ComponentEvent event)444 public void componentResized(ComponentEvent event) { 445 super.componentResized(event); 446 if (!isInit) { 447 isInit = true; 448 Arrays.stream(contentPanel.getComponents()).filter(DeviceExpandPanel.class::isInstance) 449 .map(it -> ((DeviceExpandPanel) it)).forEach(it -> it.refresh(startNS, endNS)); 450 } 451 } 452 } 453 getDURATION()454 public static long getDURATION() { 455 return DURATION; 456 } 457 setDURATION(long DURATION)458 public static void setDURATION(long DURATION) { 459 DistributedTracePanel.DURATION = DURATION; 460 } 461 getRangeStartNS()462 public static Long getRangeStartNS() { 463 return rangeStartNS; 464 } 465 setRangeStartNS(Long rangeStartNS)466 public static void setRangeStartNS(Long rangeStartNS) { 467 DistributedTracePanel.rangeStartNS = rangeStartNS; 468 } 469 getRangeEndNS()470 public static Long getRangeEndNS() { 471 return rangeEndNS; 472 } 473 setRangeEndNS(Long rangeEndNS)474 public static void setRangeEndNS(Long rangeEndNS) { 475 DistributedTracePanel.rangeEndNS = rangeEndNS; 476 } 477 } 478