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.layout.chartview.observer; 17 18 import com.intellij.icons.AllIcons; 19 import com.intellij.openapi.util.IconLoader; 20 import ohos.devtools.datasources.utils.common.util.DateTimeUtil; 21 import ohos.devtools.datasources.utils.profilerlog.ProfilerLogManager; 22 import ohos.devtools.datasources.utils.quartzmanager.QuartzManager; 23 import ohos.devtools.datasources.utils.session.entity.SessionInfo; 24 import ohos.devtools.datasources.utils.session.service.SessionManager; 25 import ohos.devtools.views.charts.model.ChartDataRange; 26 import ohos.devtools.views.charts.model.ChartStandard; 27 import ohos.devtools.views.common.LayoutConstants; 28 import ohos.devtools.views.common.customcomp.CustomJButton; 29 import ohos.devtools.views.layout.chartview.CountingThread; 30 import ohos.devtools.views.layout.chartview.ProfilerChartsView; 31 import ohos.devtools.views.layout.chartview.event.IChartEventObserver; 32 import ohos.devtools.views.layout.chartview.event.IChartEventPublisher; 33 import org.apache.logging.log4j.LogManager; 34 import org.apache.logging.log4j.Logger; 35 36 import javax.swing.SwingWorker; 37 import java.awt.event.ActionListener; 38 import java.util.ArrayList; 39 import java.util.List; 40 import java.util.Locale; 41 import java.util.concurrent.ExecutionException; 42 import java.util.concurrent.TimeUnit; 43 44 import static ohos.devtools.views.common.LayoutConstants.INITIAL_VALUE; 45 import static ohos.devtools.views.layout.chartview.utils.ChartViewConstants.CHART_START_DELAY; 46 import static ohos.devtools.views.layout.chartview.utils.ChartViewConstants.REFRESH_FREQ; 47 48 /** 49 * 监控界面保存Chart的面板的事件发布者 50 * 51 * @since 2021/11/22 52 */ 53 public class ProfilerChartsViewPublisher implements IChartEventPublisher { 54 private static final Logger LOGGER = LogManager.getLogger(ProfilerChartsViewPublisher.class); 55 56 /** 57 * Chart监控界面的定时刷新线程的名称 58 */ 59 public static final String RUN_NAME = "ProfilerChartsViewMonitorTimer"; 60 61 /** 62 * Chart监控界面的定时刷新线程的进度条名称 63 */ 64 private static final String RUN_NAME_SCROLLBAR = "ScrollbarTimer"; 65 66 private static final int NUM_10 = 10; 67 68 /** 69 * 监听的视图 70 */ 71 private final ProfilerChartsView view; 72 73 /** 74 * 是否为Trace文件静态导入模式 75 * 76 * @see "true表示静态导入,false表示动态实时跟踪" 77 */ 78 private final boolean traceFile; 79 80 /** 81 * 监听者的集合 82 */ 83 private final List<IChartEventObserver> observers = new ArrayList<>(); 84 85 /** 86 * 绘图标准 87 */ 88 private final ChartStandard standard; 89 90 /** 91 * Chart刷新线程是否在运行 92 */ 93 private boolean refreshing = false; 94 95 /** 96 * 滚动条是否显示 97 */ 98 private boolean displayScrollbar = false; 99 100 /** 101 * 启动任务时,本机时间和数据流中的时间偏移量 102 */ 103 private long startOffset = INITIAL_VALUE; 104 105 /** 106 * 构造函数 107 * 108 * @param view 监听的视图 109 * @param traceFile 是否为Trace文件静态导入模式 110 */ ProfilerChartsViewPublisher(ProfilerChartsView view, boolean traceFile)111 public ProfilerChartsViewPublisher(ProfilerChartsView view, boolean traceFile) { 112 this.view = view; 113 this.traceFile = traceFile; 114 standard = new ChartStandard(view.getSessionId()); 115 } 116 117 /** 118 * 展示Trace文件分析结果 119 * 120 * @param firstTimestamp Trace文件中数据的开始时间 121 * @param lastTimestamp Trace文件中数据的结束时间 122 */ showTraceResult(long firstTimestamp, long lastTimestamp)123 public void showTraceResult(long firstTimestamp, long lastTimestamp) { 124 if (!traceFile) { 125 return; 126 } 127 128 standard.setFirstTimestamp(firstTimestamp); 129 // 保存trace文件导入模式下最后一个数据的时间戳 130 standard.setLastTimestamp(lastTimestamp); 131 int end = (int) (lastTimestamp - firstTimestamp); 132 int start = 0; 133 if (end > standard.getMaxDisplayMillis()) { 134 start = end - standard.getMaxDisplayMillis(); 135 // 这里需要异步初始化滚动条,否则会因为view没有渲染导致滚动条无法显示 136 new SwingWorker<>() { 137 @Override 138 protected Object doInBackground() { 139 try { 140 TimeUnit.MILLISECONDS.sleep(LayoutConstants.FIVE_HUNDRED); 141 } catch (InterruptedException exception) { 142 if (ProfilerLogManager.isErrorEnabled()) { 143 LOGGER.error("Asynchronous initialization scrollbar failed!", exception); 144 } 145 } 146 return new Object(); 147 } 148 149 @Override 150 protected void done() { 151 view.initScrollbar(); 152 view.getHorizontalBar().resizeAndReposition(); 153 displayScrollbar = true; 154 } 155 }.execute(); 156 } 157 notifyRefresh(start, end); 158 } 159 160 /** 161 * 检查Loading结果:Loading完成后启动刷新 162 */ checkLoadingResult()163 public void checkLoadingResult() { 164 new SwingWorker<SessionInfo, Object>() { 165 @Override 166 protected SessionInfo doInBackground() throws InterruptedException { 167 SessionInfo info = SessionManager.getInstance().getSessionInfo(standard.getSessionId()); 168 while (info == null || !info.isStartRefsh()) { 169 try { 170 TimeUnit.MILLISECONDS.sleep(NUM_10); 171 } catch (InterruptedException exception) { 172 if (ProfilerLogManager.isErrorEnabled()) { 173 LOGGER.error("checkLoadingResult InterruptedException", exception); 174 } 175 } 176 info = SessionManager.getInstance().getSessionInfo(standard.getSessionId()); 177 } 178 // 等待一段时间再启动刷新Chart,否则会导致查询的数据还未入库完成 179 TimeUnit.MILLISECONDS.sleep(CHART_START_DELAY); 180 return info; 181 } 182 183 @Override 184 protected void done() { 185 try { 186 SessionInfo info = get(); 187 long first = info.getStartTimestamp(); 188 view.hideLoading(); 189 startRefresh(first); 190 } catch (InterruptedException | ExecutionException exception) { 191 LOGGER.error(String.format(Locale.ENGLISH, "Error occur when loading done: %s", exception 192 .toString())); 193 } 194 } 195 }.execute(); 196 } 197 198 /** 199 * 开始刷新Chart 200 * 201 * @param firstTimestamp 本次Chart第一个数据的时间戳 202 */ startRefresh(long firstTimestamp)203 public void startRefresh(long firstTimestamp) { 204 if (traceFile) { 205 return; 206 } 207 view.setStop(false); 208 view.setPause(false); 209 standard.setFirstTimestamp(firstTimestamp); 210 startOffset = DateTimeUtil.getNowTimeLong() - standard.getFirstTimestamp(); 211 // 启动Chart绘制定时器 212 startChartTimer(); 213 refreshing = true; 214 } 215 216 /** 217 * 启动Chart绘制定时器 218 */ startChartTimer()219 private void startChartTimer() { 220 // 启动绘制Chart线程 221 QuartzManager.getInstance().addExecutor(RUN_NAME, () -> { 222 try { 223 // 保存LastTimestamp,为当前时间戳减去Chart启动延迟 224 standard.setLastTimestamp(DateTimeUtil.getNowTimeLong() - startOffset - CHART_START_DELAY); 225 int end = (int) (standard.getLastTimestamp() - standard.getFirstTimestamp()); 226 int start = end > standard.getMaxDisplayMillis() ? end - standard.getMaxDisplayMillis() : 0; 227 notifyRefresh(start, end); 228 } catch (Exception exception) { 229 if (ProfilerLogManager.isErrorEnabled()) { 230 LOGGER.error("Exception", exception); 231 } 232 } 233 }); 234 // 刷新间隔暂定30ms 235 QuartzManager.getInstance().startExecutor(RUN_NAME, 0, REFRESH_FREQ); 236 237 // 如果有线程在刷新,则不需要再另起一个轮询线程。 238 if (!refreshing) { 239 QuartzManager.getInstance().addExecutor(RUN_NAME_SCROLLBAR, () -> { 240 // 保存LastTimestamp,为当前时间戳减去Chart启动延迟 241 standard.setLastTimestamp(DateTimeUtil.getNowTimeLong() - startOffset - CHART_START_DELAY); 242 int end = (int) (standard.getLastTimestamp() - standard.getFirstTimestamp()); 243 int start = end > standard.getMaxDisplayMillis() ? end - standard.getMaxDisplayMillis() : 0; 244 // 当end大于最大展示时间时,且滚动条未显示时,初始化显示滚动条,并把isScrollbarShow置为true 245 if (end > standard.getMaxDisplayMillis() && !displayScrollbar) { 246 // isScrollbarShow判断必须保留,否则会导致Scrollbar重复初始化,频繁闪烁 247 view.initScrollbar(); 248 displayScrollbar = true; 249 } 250 notifyRefreshScrollbar(start, end); 251 }); 252 253 // 刷新间隔暂定30ms 254 QuartzManager.getInstance().startExecutor(RUN_NAME_SCROLLBAR, 0, REFRESH_FREQ); 255 } 256 } 257 258 /** 259 * 暂停刷新Chart 260 */ pauseRefresh()261 public void pauseRefresh() { 262 if (refreshing) { 263 QuartzManager.getInstance().deleteExecutor(RUN_NAME); 264 refreshing = false; 265 view.setPause(true); 266 } 267 } 268 269 /** 270 * 停止刷新Chart 271 * 272 * @param isOffline 设备是否断连 273 */ stopRefresh(boolean isOffline)274 public void stopRefresh(boolean isOffline) { 275 QuartzManager.getInstance().deleteExecutor(RUN_NAME); 276 QuartzManager.getInstance().deleteExecutor(RUN_NAME_SCROLLBAR); 277 refreshing = false; 278 view.setStop(true); 279 view.setPause(true); 280 if (isOffline) { 281 CustomJButton buttonRun = view.getTaskScenePanelChart().getjButtonRun(); 282 CustomJButton buttonStop = view.getTaskScenePanelChart().getjButtonStop(); 283 buttonRun.setIcon(IconLoader.getIcon("/images/breakpoint_grey.png", getClass())); 284 buttonRun.setEnabled(true); 285 ActionListener[] actionListenersRun = buttonRun.getActionListeners(); 286 for (ActionListener listener : actionListenersRun) { 287 buttonRun.removeActionListener(listener); 288 } 289 290 buttonStop.setIcon(AllIcons.Process.ProgressResumeHover); 291 buttonStop.setEnabled(true); 292 ActionListener[] actionListenersStop = buttonStop.getActionListeners(); 293 for (ActionListener listener : actionListenersStop) { 294 buttonStop.removeActionListener(listener); 295 } 296 CountingThread countingThread = view.getTaskScenePanelChart().getCounting(); 297 countingThread.setStopFlag(true); 298 } 299 } 300 301 /** 302 * 暂停后重新开始刷新Chart 303 */ restartRefresh()304 public void restartRefresh() { 305 if (traceFile) { 306 return; 307 } 308 309 if (view.isStop()) { 310 // 如果是已停止状态,则返回 311 return; 312 } 313 314 if (!refreshing) { 315 refreshing = true; 316 view.setPause(false); 317 startChartTimer(); 318 // 重新开始时,也要移除框选状态(暂定) 319 standard.clearAllSelectedRanges(); 320 } 321 } 322 323 /** 324 * Add observer 325 * 326 * @param observer Observer of chart refreshes event 327 */ 328 @Override attach(IChartEventObserver observer)329 public void attach(IChartEventObserver observer) { 330 observers.add(observer); 331 } 332 333 /** 334 * Remove observer 335 * 336 * @param observer Observer of chart refreshes event 337 */ 338 @Override detach(IChartEventObserver observer)339 public void detach(IChartEventObserver observer) { 340 observers.remove(observer); 341 } 342 343 /** 344 * notify to refresh 345 * 346 * @param start Start time of chart 347 * @param end End time of chart 348 */ 349 @Override notifyRefresh(int start, int end)350 public void notifyRefresh(int start, int end) { 351 standard.updateDisplayTimeRange(start, end); 352 ChartDataRange range = standard.getDisplayRange(); 353 observers.forEach(lis -> lis.refreshView(range, standard.getFirstTimestamp(), !traceFile)); 354 } 355 356 /** 357 * 通知刷新滚动条 358 * 359 * @param start 开始时间 360 * @param end 结束时间 361 */ notifyRefreshScrollbar(int start, int end)362 private void notifyRefreshScrollbar(int start, int end) { 363 // 当前时间超过最大展示时间,则调整滚动条长度和位置 364 if (end > standard.getMaxDisplayMillis()) { 365 if (view.getHorizontalBar() != null) { 366 view.getHorizontalBar().resizeAndReposition(); 367 } 368 } 369 } 370 371 /** 372 * 时间线和char缩放 373 * 374 * @param startTime 缩放后的界面开始时间 375 * @param endTime 结束时间的界面开始时间 376 * @param maxDisplayTime 窗体上可以显示的最大毫秒数 377 */ charZoom(int startTime, int endTime, int maxDisplayTime)378 public void charZoom(int startTime, int endTime, int maxDisplayTime) { 379 // 修改char展示的时间范围 380 standard.setMaxDisplayMillis(maxDisplayTime); 381 standard.updateDisplayTimeRange(startTime, endTime); 382 observers.forEach(lis -> { 383 standard.setMaxDisplayMillis(maxDisplayTime); 384 lis.refreshStandard(startTime, endTime, maxDisplayTime, standard.getMinMarkInterval()); 385 lis.refreshView(standard.getDisplayRange(), standard.getFirstTimestamp(), !traceFile); 386 }); 387 } 388 389 /** 390 * 界面毫秒数的时间刻度缩放 391 * 392 * @param maxDisplayTime 窗体上可以显示的最大毫秒数 393 * @param minMarkInterval 窗体上可以显示的时间刻度的单位 394 * @param newStartTime 新的开始时间 395 * @param newEndTime 新的结束时间 396 */ msTimeZoom(int maxDisplayTime, int minMarkInterval, int newStartTime, int newEndTime)397 public void msTimeZoom(int maxDisplayTime, int minMarkInterval, int newStartTime, int newEndTime) { 398 standard.setMaxDisplayMillis(maxDisplayTime); 399 standard.setMinMarkInterval(minMarkInterval); 400 standard.updateDisplayTimeRange(newStartTime, newEndTime); 401 observers.forEach(lis -> { 402 lis.refreshStandard(newStartTime, newEndTime, maxDisplayTime, minMarkInterval); 403 lis.refreshView(standard.getDisplayRange(), standard.getFirstTimestamp(), !traceFile); 404 }); 405 } 406 407 /** 408 * Getter 409 * 410 * @return ChartStandard 411 */ getStandard()412 public ChartStandard getStandard() { 413 return standard; 414 } 415 416 /** 417 * Getter 418 * 419 * @return traceFile 420 */ isTraceFile()421 public boolean isTraceFile() { 422 return traceFile; 423 } 424 getObservers()425 public List<IChartEventObserver> getObservers() { 426 return observers; 427 } 428 isRefreshing()429 public boolean isRefreshing() { 430 return refreshing; 431 } 432 isDisplayScrollbar()433 public boolean isDisplayScrollbar() { 434 return displayScrollbar; 435 } 436 setDisplayScrollbar(boolean displayScrollbar)437 public void setDisplayScrollbar(boolean displayScrollbar) { 438 this.displayScrollbar = displayScrollbar; 439 } 440 } 441