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.trace.component; 17 18 import com.intellij.ui.JBColor; 19 import com.intellij.ui.components.JBPanel; 20 import com.intellij.ui.components.JBScrollPane; 21 import ohos.devtools.views.trace.Common; 22 import ohos.devtools.views.trace.bean.CpuData; 23 import ohos.devtools.views.trace.bean.WakeupBean; 24 import ohos.devtools.views.trace.fragment.AbstractDataFragment; 25 import ohos.devtools.views.trace.fragment.CpuDataFragment; 26 import ohos.devtools.views.trace.fragment.FunctionDataFragment; 27 import ohos.devtools.views.trace.fragment.MemDataFragment; 28 import ohos.devtools.views.trace.fragment.ProcessDataFragment; 29 import ohos.devtools.views.trace.fragment.ThreadDataFragment; 30 import ohos.devtools.views.trace.fragment.ruler.AbstractNode; 31 import ohos.devtools.views.trace.util.Final; 32 import ohos.devtools.views.trace.util.TimeUtils; 33 import ohos.devtools.views.trace.util.Utils; 34 35 import javax.swing.JViewport; 36 import javax.swing.SwingUtilities; 37 import java.awt.BasicStroke; 38 import java.awt.Color; 39 import java.awt.Dimension; 40 import java.awt.Graphics; 41 import java.awt.Graphics2D; 42 import java.awt.Point; 43 import java.awt.Rectangle; 44 import java.awt.RenderingHints; 45 import java.util.ArrayList; 46 import java.util.List; 47 import java.util.Objects; 48 import java.util.Optional; 49 import java.util.stream.Collectors; 50 51 /** 52 * Rolling container 53 * 54 * @since 2021/04/20 12:24 55 */ 56 public final class ContentPanel extends JBPanel implements AbstractDataFragment.IDataFragment { 57 /** 58 * clint fragment 59 */ 60 public static AbstractDataFragment clickFragment; 61 62 /** 63 * FragmentList to be rendered 64 */ 65 public final List<AbstractDataFragment> fragmentList = new ArrayList<>(); 66 67 /** 68 * start point object 69 */ 70 protected Point startPoint; 71 72 /** 73 * end point object 74 */ 75 protected Point endPoint; 76 77 /** 78 * Analysis component 79 */ 80 protected AnalystPanel analystPanel; 81 82 /** 83 * draw range select flag 84 */ 85 protected boolean drawRangeSelect; 86 87 /** 88 * draw range select data flag 89 */ 90 protected boolean drawRangeSelectData; 91 92 /** 93 * range select x1 94 */ 95 protected int x1; 96 97 /** 98 * range select y1 99 */ 100 protected int y1; 101 102 /** 103 * range select x2 104 */ 105 protected int x2; 106 107 /** 108 * range select y2 109 */ 110 protected int y2; 111 112 /** 113 * range start time 114 */ 115 protected long rangeStartNS; 116 117 /** 118 * range end time 119 */ 120 protected long rangeEndNS; 121 122 private WakeupBean wakeupBean; 123 private long startNS; 124 private long endNS; 125 private final BasicStroke boldStoke = new BasicStroke(2); 126 private final BasicStroke normalStoke = new BasicStroke(1); 127 128 /** 129 * Constructor 130 * 131 * @param analystPanel component 132 */ ContentPanel(AnalystPanel analystPanel)133 public ContentPanel(AnalystPanel analystPanel) { 134 this.analystPanel = analystPanel; 135 this.setOpaque(false); 136 setFont(Final.NORMAL_FONT); 137 } 138 139 /** 140 * Gets the value of wakeupBean . 141 * 142 * @return the value of ohos.devtools.views.trace.bean.WakeupBean 143 */ getWakeupBean()144 public WakeupBean getWakeupBean() { 145 return wakeupBean; 146 } 147 148 /** 149 * Sets the wakeupBean . 150 * <p>You can use getWakeupBean() to get the value of wakeupBean</p> 151 * 152 * @param wakeup wakeup 153 */ setWakeupBean(WakeupBean wakeup)154 public void setWakeupBean(WakeupBean wakeup) { 155 this.wakeupBean = wakeup; 156 } 157 158 @Override paintComponent(Graphics graphics)159 protected void paintComponent(Graphics graphics) { 160 super.paintComponent(graphics); 161 if (graphics instanceof Graphics2D) { 162 Graphics2D g2 = (Graphics2D) graphics; 163 g2.setFont(Final.NORMAL_FONT); 164 g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 165 if (getParent() instanceof TimeViewPort) { 166 TimeViewPort parent = (TimeViewPort) getParent(); 167 Rectangle viewRect = parent.getViewRect(); 168 // Render the line in the viewport display area, and the line beyond the range will not be rendered 169 fragmentList.stream().filter(fragment -> { 170 // Cancel the future if the fragment is hidden 171 if (!fragment.visible) { 172 fragment.cancel(); 173 } 174 return fragment.visible; 175 }).filter(fragment -> { 176 if (fragment != null && fragment.getRect() != null && viewRect != null) { 177 boolean rs = Utils.getY(fragment.getRect()) + fragment.getRect().height 178 >= Utils.getY(viewRect) + TimeViewPort.height 179 && Utils.getY(fragment.getRect()) <= Utils.getY(viewRect) + viewRect.height; 180 if (rs) { 181 return true; 182 } else { 183 // If it is not in the display area, cancel the future 184 fragment.cancel(); 185 return false; 186 } 187 } else { 188 return false; 189 } 190 }).forEach(fragment -> { 191 if (fragment != null) { 192 fragment.draw(g2); 193 } 194 }); 195 } 196 drawRangeSelect(g2); 197 drawWakeup(g2); 198 } 199 } 200 drawRangeSelect(Graphics2D g2)201 private void drawRangeSelect(Graphics2D g2) { 202 if (Objects.nonNull(startPoint) && Objects.nonNull(endPoint)) { 203 var ref = new Object() { 204 int realY1; 205 int realY2; 206 }; 207 fragmentList.stream().filter(it -> it.visible).filter(it -> it.getDataRect().contains(x1, y1)) 208 .forEach(it -> ref.realY1 = Utils.getY(it.getRect())); 209 fragmentList.stream().filter(it -> it.visible).filter(it -> it.getDataRect().contains(x2 - 1, y2 - 1)) 210 .forEach(it -> ref.realY2 = Utils.getY(it.getRect()) + it.getRect().height); 211 int tmpWidth = Math.abs(x2 - x1); 212 int tmpHeight = Math.abs(ref.realY2 - ref.realY1); 213 Rectangle range = new Rectangle(x1, ref.realY1, tmpWidth, tmpHeight); 214 if (drawRangeSelect) { 215 g2.setStroke(new BasicStroke(2)); 216 g2.setColor(JBColor.foreground().brighter()); 217 g2.drawRect(Utils.getX(range), Utils.getY(range), range.width, range.height); 218 } else { 219 Common.setAlpha(g2, 0.5F); 220 g2.setColor(JBColor.foreground().brighter()); 221 g2.fillRect(Utils.getX(range), Utils.getY(range), range.width, range.height); 222 if (drawRangeSelectData) { 223 drawRangeSelectData = false; 224 List<AbstractDataFragment> rangeFragments = 225 fragmentList.stream().filter(it -> range.intersects(it.getDataRect())) 226 .collect(Collectors.toList()); 227 // Draw after dragging 228 rangeFragments.forEach(it -> it.drawFrame()); 229 List<AbstractDataFragment> process = new ArrayList<>(); 230 rangeFragments.stream().filter(it -> it instanceof ProcessDataFragment) 231 .map(it -> (ProcessDataFragment) it).forEach(it -> { 232 process.addAll(fragmentList.stream().filter(that -> that.parentUuid.equals(it.uuid)) 233 .collect(Collectors.toList())); 234 }); 235 rangeFragments.addAll(process); 236 List<Integer> cpu = rangeFragments.stream().filter(it -> it instanceof CpuDataFragment) 237 .map(it -> ((CpuDataFragment) it).getIndex()).collect(Collectors.toList()); 238 List<Integer> threads = rangeFragments.stream().filter(it -> it instanceof ThreadDataFragment) 239 .map(it -> ((ThreadDataFragment) it).thread.getTid()).collect(Collectors.toList()); 240 List<Integer> tracks = rangeFragments.stream().filter(it -> it instanceof MemDataFragment) 241 .map(it -> ((MemDataFragment) it).mem.getTrackId()).collect(Collectors.toList()); 242 List<Integer> functions = rangeFragments.stream().filter(it -> it instanceof FunctionDataFragment) 243 .map(it -> ((FunctionDataFragment) it).thread.getTid()).collect(Collectors.toList()); 244 AnalystPanel.LeftRightNS ns = new AnalystPanel.LeftRightNS(); 245 ns.setLeftNs(rangeStartNS); 246 ns.setRightNs(rangeEndNS); 247 this.analystPanel.boxSelection(cpu, threads, tracks, functions, ns); 248 } 249 } 250 Common.setAlpha(g2, 1.0F); 251 } 252 } 253 254 /** 255 * add data line 256 * 257 * @param fragment data fragment 258 */ addDataFragment(AbstractDataFragment fragment)259 public void addDataFragment(AbstractDataFragment fragment) { 260 fragment.setDataFragmentListener(this); 261 fragmentList.add(fragment); 262 } 263 264 /** 265 * add data line 266 * 267 * @param index line index 268 * @param fragment data fragment 269 */ addDataFragment(int index, AbstractDataFragment fragment)270 public void addDataFragment(int index, AbstractDataFragment fragment) { 271 fragment.setDataFragmentListener(this); 272 fragmentList.add(index, fragment); 273 } 274 275 /** 276 * refresh content data 277 */ refresh()278 public void refresh() { 279 List<AbstractDataFragment> fs = 280 fragmentList.stream().filter(fragment -> fragment.visible).collect(Collectors.toList()); 281 int timeViewHeight = TimeViewPort.height; 282 for (int index = 0, len = fs.size(); index < len; index++) { 283 AbstractDataFragment dataFragment = fs.get(index); 284 timeViewHeight += dataFragment.defaultHeight; 285 dataFragment.getRect().height = dataFragment.defaultHeight; 286 dataFragment.getDescRect().height = dataFragment.defaultHeight; 287 dataFragment.getDataRect().height = dataFragment.defaultHeight; 288 Utils.setY(dataFragment.getRect(), timeViewHeight - dataFragment.defaultHeight); 289 Utils.setY(dataFragment.getDescRect(), timeViewHeight - dataFragment.defaultHeight); 290 Utils.setY(dataFragment.getDataRect(), timeViewHeight - dataFragment.defaultHeight); 291 Utils.setX(dataFragment.getRect(), 0); 292 Utils.setX(dataFragment.getDescRect(), 0); 293 Utils.setX(dataFragment.getDataRect(), 200); 294 } 295 Dimension dim = new Dimension(0, timeViewHeight); 296 this.setPreferredSize(dim); 297 this.setSize(dim); 298 this.setMaximumSize(dim); 299 repaint(); 300 } 301 302 /** 303 * recycle content data 304 */ recycle()305 public void recycle() { 306 clickFragment = null; 307 if (fragmentList != null) { 308 fragmentList.forEach(AbstractDataFragment::recycle); 309 fragmentList.clear(); 310 } 311 } 312 313 /** 314 * time range change will call this 315 * 316 * @param startNS range start ns 317 * @param endNS range end ns 318 */ rangeChange(long startNS, long endNS)319 public void rangeChange(long startNS, long endNS) { 320 this.startNS = startNS; 321 this.endNS = endNS; 322 x1 = (int) getRangeX(rangeStartNS) + 200; 323 x2 = (int) getRangeX(rangeEndNS) + 200; 324 fragmentList.forEach(fragment -> fragment.range(startNS, endNS)); 325 } 326 getRangeX(long range)327 private double getRangeX(long range) { 328 double xSize = (range - startNS) * (getWidth() - 200) / (endNS - startNS); 329 if (xSize < 0) { 330 xSize = 0; 331 } 332 if (xSize > getWidth() - 200) { 333 xSize = getWidth() - 200; 334 } 335 return xSize; 336 } 337 338 @Override collect(AbstractDataFragment fgr)339 public void collect(AbstractDataFragment fgr) { 340 if (getParent() instanceof TimeViewPort) { 341 TimeViewPort viewPort = (TimeViewPort) getParent(); 342 if (fgr.visible) { 343 viewPort.favorite(fgr); 344 } else { 345 viewPort.cancel(fgr); 346 } 347 refresh(); 348 } 349 } 350 351 @Override check(AbstractDataFragment fgr)352 public void check(AbstractDataFragment fgr) { 353 } 354 drawWakeup(Graphics2D graphics)355 private void drawWakeup(Graphics2D graphics) { 356 if (clickFragment == null || CpuDataFragment.currentSelectedCpuData == null || !AnalystPanel.clicked) { 357 return; 358 } 359 Optional.ofNullable(wakeupBean).ifPresent(wakeup -> { 360 graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 361 graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 362 graphics.setColor(Color.BLACK); 363 graphics.setStroke(boldStoke); 364 Rectangle visibleRect = getVisibleRect(); 365 Rectangle dataRect = clickFragment.getDataRect(); 366 int wakeupX = clickFragment.getX(wakeup.getWakeupTime()); 367 if (wakeup.getWakeupTime() > startNS && wakeup.getWakeupTime() < endNS) { 368 /** 369 * Draw the vertical line 370 */ 371 graphics 372 .drawLine(wakeupX + Utils.getX(dataRect), Utils.getY(visibleRect), wakeupX + Utils.getX(dataRect), 373 Utils.getY(visibleRect) + visibleRect.height); 374 /** 375 * Draw a diamond First filter out which cpu fragment the wake-up thread is in, 376 * get the rect of the cpu fragment, and then calculate the coordinates of the drawing diamond 377 */ 378 for (AbstractDataFragment dataFragment : fragmentList) { 379 if (dataFragment instanceof CpuDataFragment) { 380 if (((CpuDataFragment) dataFragment).getIndex() == wakeup.getWakeupCpu()) { 381 Rectangle wakeCPURect = dataFragment.getRect(); 382 final int[] xs = {Utils.getX(dataRect) + wakeupX, Utils.getX(dataRect) + wakeupX + 6, 383 Utils.getX(dataRect) + wakeupX, Utils.getX(dataRect) + wakeupX - 6}; 384 final int[] ys = {Utils.getY(wakeCPURect) + wakeCPURect.height / 2 - 10, 385 Utils.getY(wakeCPURect) + wakeCPURect.height / 2, 386 Utils.getY(wakeCPURect) + wakeCPURect.height / 2 + 10, 387 Utils.getY(wakeCPURect) + wakeCPURect.height / 2}; 388 graphics.fillPolygon(xs, ys, xs.length); 389 break; 390 } 391 } 392 } 393 } 394 drawArrayAndText(graphics, dataRect, wakeupX, wakeup); 395 }); 396 } 397 drawArrayAndText(Graphics2D graphics, Rectangle dataRect, int wakeupX, WakeupBean wakeup)398 private void drawArrayAndText(Graphics2D graphics, Rectangle dataRect, int wakeupX, WakeupBean wakeup) { 399 /** 400 * Draw arrows and text 401 */ 402 CpuData selectCpu = CpuDataFragment.currentSelectedCpuData; 403 if (selectCpu != null) { 404 Rectangle rectangle = 405 new Rectangle(wakeupX + Utils.getX(dataRect), Utils.getY(selectCpu.rect) + selectCpu.rect.height / 2, 406 Utils.getX(selectCpu.rect) - wakeupX - Utils.getX(dataRect), 30); 407 if (selectCpu.getStartTime() > startNS && wakeup.getWakeupTime() < endNS) { 408 graphics 409 .drawLine(Utils.getX(dataRect) + wakeupX, Utils.getY(selectCpu.rect) + selectCpu.rect.height - 2, 410 Utils.getX(selectCpu.rect), Utils.getY(selectCpu.rect) + selectCpu.rect.height - 2); 411 } 412 if (rectangle.width > 10) { 413 if (wakeup.getWakeupTime() > startNS && wakeup.getWakeupTime() < endNS) { 414 drawArrow(graphics, Utils.getX(dataRect) + wakeupX, 415 Utils.getY(selectCpu.rect) + selectCpu.rect.height - 2, -1); 416 } 417 if (selectCpu.getStartTime() < endNS && selectCpu.getStartTime() > startNS) { 418 drawArrow(graphics, Utils.getX(selectCpu.rect), 419 Utils.getY(selectCpu.rect) + selectCpu.rect.height - 2, 1); 420 } 421 } 422 if (wakeup.getWakeupTime() < endNS && selectCpu.getStartTime() > startNS) { 423 long offsetTime = selectCpu.getStartTime() - wakeup.getWakeupTime(); 424 String timeString = TimeUtils.getTimeString(offsetTime); 425 Utils.setY(rectangle, Utils.getY(rectangle) - 5); 426 selectCpu.drawString(graphics, rectangle, timeString, AbstractNode.Placement.CENTER); 427 } 428 } 429 graphics.setStroke(normalStoke); 430 } 431 432 /** 433 * repaint panel 434 */ clearWakeupAndBoxSelect()435 public void clearWakeupAndBoxSelect() { 436 if (Objects.nonNull(startPoint) || Objects.nonNull(endPoint) || Objects.nonNull(wakeupBean)) { 437 drawRangeSelect = false; 438 drawRangeSelectData = false; 439 startPoint = null; 440 endPoint = null; 441 repaint(); 442 } 443 } 444 drawArrow(Graphics2D graphics, int xVal, int yVal, int align)445 private void drawArrow(Graphics2D graphics, int xVal, int yVal, int align) { 446 if (align == -1) { 447 final int[] xArray = {xVal, xVal + 5, xVal + 5}; 448 final int[] yArray = {yVal, yVal - 5, yVal + 5}; 449 graphics.fillPolygon(xArray, yArray, xArray.length); 450 } 451 if (align == 1) { 452 final int[] xArray = {xVal, xVal - 5, xVal - 5}; 453 final int[] yArray = {yVal, yVal - 5, yVal + 5}; 454 graphics.fillPolygon(xArray, yArray, xArray.length); 455 } 456 } 457 458 /** 459 * Jump to the cpu line and select the node where startTime starts 460 * 461 * @param cpu cpu 462 * @param startTime startTime 463 */ scrollToCpu(int cpu, long startTime)464 public void scrollToCpu(int cpu, long startTime) { 465 fragmentList.stream().filter(CpuDataFragment.class::isInstance).map(it -> ((CpuDataFragment) it)) 466 .filter(it -> it.getIndex() == cpu).findFirst().ifPresent(it -> { 467 if (getParent() instanceof JViewport) { 468 JViewport parent = (JViewport) getParent(); 469 if (it.getData() != null) { 470 it.getData().stream().filter(cpuData -> cpuData.getStartTime() == startTime).findFirst() 471 .ifPresent(cpuData -> it.click(null, cpuData)); 472 } else { 473 it.delayClickStartTime = startTime; 474 } 475 JBScrollPane scrollPane = (JBScrollPane) parent.getParent(); 476 scrollPane.getVerticalScrollBar().setValue(0); 477 } 478 }); 479 } 480 481 /** 482 * Jump to the specified location 483 * 484 * @param processId processId 485 * @param processName processName 486 * @param tid tid 487 * @param startTime startTime 488 * @param offsetHeight tab height 489 */ scrollToThread(int processId, String processName, int tid, long startTime, int offsetHeight)490 public void scrollToThread(int processId, String processName, int tid, long startTime, int offsetHeight) { 491 fragmentList.stream().filter(ProcessDataFragment.class::isInstance).map(it -> ((ProcessDataFragment) it)) 492 .filter(it -> it.getProcess().getPid() == processId).findFirst().ifPresent(it -> { 493 if (getParent() instanceof JViewport) { 494 JViewport parent = (JViewport) getParent(); 495 it.expandThreads(); 496 497 SwingUtilities.invokeLater(() -> { 498 fragmentList.stream().filter(ThreadDataFragment.class::isInstance) 499 .map(th -> ((ThreadDataFragment) th)) 500 .filter(th -> th.thread.getTid() == tid && th.parentUuid.equals(it.uuid)).findFirst() 501 .ifPresent(th -> { 502 // If the thread data has been loaded, data is not empty, 503 // find the node whose start time is startTime, and select it 504 if (th.getData() != null) { 505 th.getData().stream().filter(threadData -> threadData.getStartTime() == startTime) 506 .findFirst().ifPresent(threadData -> { 507 th.click(null, threadData); 508 }); 509 } else { 510 // If the thread data has not been loaded yet, 511 // save the start time of the node to be selected, 512 // the load Data() method in the component will process this field, 513 // select it directly after loading, 514 // and then set the delay Click Start Time to null 515 th.delayClickStartTime = startTime; 516 th.setDelayClickProcessName(processName); 517 } 518 parent.scrollRectToVisible( 519 new Rectangle(Utils.getX(th.getRect()), Utils.getY(th.getRect()) + offsetHeight, 520 th.getRect().width, th.getRect().height)); 521 }); 522 }); 523 } 524 }); 525 } 526 } 527