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.perftrace; 17 18 import ohos.devtools.views.applicationtrace.DataProcess; 19 import ohos.devtools.views.applicationtrace.bean.AppFunc; 20 import ohos.devtools.views.applicationtrace.bean.TreeTableBean; 21 import ohos.devtools.views.applicationtrace.util.TimeUtils; 22 import ohos.devtools.views.perftrace.bean.PrefFile; 23 import ohos.devtools.views.perftrace.bean.PrefFunc; 24 import ohos.devtools.views.perftrace.bean.PrefRange; 25 import ohos.devtools.views.perftrace.bean.PrefSample; 26 import ohos.devtools.views.trace.util.Utils; 27 28 import javax.swing.tree.DefaultMutableTreeNode; 29 import javax.swing.tree.TreeNode; 30 import java.util.ArrayList; 31 import java.util.Comparator; 32 import java.util.Enumeration; 33 import java.util.HashMap; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.Objects; 37 import java.util.concurrent.TimeUnit; 38 import java.util.function.Consumer; 39 import java.util.stream.Collectors; 40 41 import static java.util.stream.Collectors.groupingBy; 42 43 /** 44 * c++ function PerfData 45 * 46 * @since 2021/04/22 12:25 47 */ 48 public class PerfData { 49 /** 50 * current Range 51 */ 52 private static PrefRange prefRange; 53 54 /** 55 * current files from db 56 */ 57 private static Map<Long, List<PrefFile>> prefFiles; 58 59 /** 60 * current func map from db 61 */ 62 private static Map<Integer, List<PrefFunc>> funcMap = new HashMap<>(); 63 64 /** 65 * current thread name map from db 66 */ 67 private static Map<Integer, String> threadNames = new HashMap<>(); 68 69 /** 70 * get the PrefFunc by PrefSample object 71 * 72 * @param sampleList sampleList 73 * @return list PrefFunc 74 */ formatSampleList(List<PrefSample> sampleList)75 public static List<PrefFunc> formatSampleList(List<PrefSample> sampleList) { 76 if (sampleList.size() == 0) { 77 return new ArrayList<>(); 78 } 79 Map<Long, List<PrefSample>> sampleMap = sampleList.stream().collect(groupingBy(PrefSample::getSampleId)); 80 List<Map.Entry<Long, List<PrefSample>>> collect = 81 sampleMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).collect(Collectors.toList()); 82 List<PrefSample> lastEntry = collect.get(0).getValue(); 83 PrefFunc threadFunc = new PrefFunc(); 84 int threadId = Long.valueOf(sampleList.get(0).getThreadId()).intValue(); 85 threadFunc.setFuncName(threadNames.get(threadId)); 86 threadFunc.setThreadName(threadNames.get(threadId)); 87 threadFunc.setBloodId(Utils.md5String(threadFunc.getFuncName())); 88 threadFunc.setDepth(-1); 89 threadFunc.setStartTs(sampleList.get(0).getTs()); 90 int collectIndex = 1; 91 List<PrefFunc> funcList = new ArrayList<>(); 92 PrefFunc currentEndNode = addFuncNodes(null, lastEntry, funcList, 0); 93 while (collectIndex < collect.size()) { 94 List<PrefSample> prefSamples = collect.get(collectIndex).getValue().stream() 95 .sorted(Comparator.comparingLong(PrefSample::getId).reversed()).collect(Collectors.toList()); 96 int splitPoint = getSplitPoint(lastEntry, prefSamples); 97 if (splitPoint < lastEntry.size()) { 98 currentEndNode = 99 updateParentEndTime(lastEntry.size() - splitPoint, prefSamples.get(0).getTs(), currentEndNode); 100 } 101 if (splitPoint < prefSamples.size()) { 102 currentEndNode = addFuncNodes(currentEndNode, prefSamples, funcList, splitPoint); 103 } 104 lastEntry = prefSamples; 105 collectIndex++; 106 } 107 if (prefRange != null) { 108 threadFunc.setEndTs(prefRange.getEndTime()); 109 threadFunc.setDur(prefRange.getEndTime() - threadFunc.getStartTs()); 110 currentEndNode.updateParentEndTime(0, prefRange.getEndTime() - prefRange.getStartTime()); 111 } 112 funcList.add(threadFunc); 113 return funcList; 114 } 115 updateParentEndTime(int index, long ts, PrefFunc node)116 private static PrefFunc updateParentEndTime(int index, long ts, PrefFunc node) { 117 PrefFunc current = node; 118 for (int pos = 0; pos < index; pos++) { 119 current.setEndTs(ts); 120 current = current.getParentNode(); 121 } 122 return current; 123 } 124 getSplitPoint(List<PrefSample> last, List<PrefSample> current)125 private static int getSplitPoint(List<PrefSample> last, List<PrefSample> current) { 126 int index = 0; 127 while (index < last.size() && index < current.size() && last.get(index).isSameStack(current.get(index))) { 128 index++; 129 } 130 return index; 131 } 132 addFuncNodes(PrefFunc parentNode, List<PrefSample> sampleList, List<PrefFunc> funcList, int startIndex)133 private static PrefFunc addFuncNodes(PrefFunc parentNode, List<PrefSample> sampleList, List<PrefFunc> funcList, 134 int startIndex) { 135 PrefFunc node = parentNode; 136 for (int sampleIndex = startIndex; sampleIndex < sampleList.size(); sampleIndex++) { 137 PrefFunc childNode = new PrefFunc(sampleList.get(sampleIndex)); 138 if (node != null) { 139 childNode.setDepth(node.getDepth() + 1); 140 childNode.setParentNode(node); 141 childNode.setParentBloodId(node.getBloodId()); 142 node.getChildrenNodes().add(childNode); 143 } else { 144 childNode.setDepth(startIndex); 145 } 146 childNode.createBloodId(); 147 funcList.add(childNode); 148 node = childNode; 149 } 150 return node; 151 } 152 153 /** 154 * get the get TopDown FuncTree by startNS and endNS 155 * 156 * @param startNS startNS 157 * @param endNS endNS 158 * @return list nodes 159 */ getFuncTreeTopDown(long startNS, long endNS)160 public static List<DefaultMutableTreeNode> getFuncTreeTopDown(long startNS, long endNS) { 161 if (Objects.isNull(funcMap)) { 162 return new ArrayList<>(); 163 } 164 return getFuncTreeTopDown(startNS, endNS, null); 165 } 166 167 /** 168 * get the get TopDown FuncTree by startNS、endNS and threadIds 169 * 170 * @param startNS startNS 171 * @param endNS endNS 172 * @param threadIds threadIds 173 * @return list nodes 174 */ getFuncTreeTopDown(long startNS, long endNS, List<Integer> threadIds)175 public static List<DefaultMutableTreeNode> getFuncTreeTopDown(long startNS, long endNS, List<Integer> threadIds) { 176 if (Objects.isNull(funcMap)) { 177 return new ArrayList<>(); 178 } 179 Map<Integer, List<AppFunc>> collect = funcMap.entrySet().stream() 180 .collect(Collectors.toMap(Map.Entry::getKey, entry -> new ArrayList<>(entry.getValue()))); 181 return DataProcess.getFuncTreeTopDown(collect, startNS, endNS, threadIds); 182 } 183 184 /** 185 * get BottomUp FuncTree data 186 * 187 * @param startNS startNS 188 * @param endNS endNS 189 * @return list TreeTableBean 190 */ getFuncTreeBottomUp(long startNS, long endNS)191 public static List<DefaultMutableTreeNode> getFuncTreeBottomUp(long startNS, long endNS) { 192 return getFuncTreeBottomUp(startNS, endNS, null); 193 } 194 195 /** 196 * get the get BottomUp FuncTree by startNS、endNS and threadIds 197 * 198 * @param startNS startNS 199 * @param endNS endNS 200 * @param threadIds threadIds 201 * @return list nodes 202 */ getFuncTreeBottomUp(long startNS, long endNS, List<Integer> threadIds)203 public static List<DefaultMutableTreeNode> getFuncTreeBottomUp(long startNS, long endNS, List<Integer> threadIds) { 204 if (Objects.isNull(funcMap)) { 205 return new ArrayList<>(); 206 } 207 Map<Integer, List<AppFunc>> collect = funcMap.entrySet().stream() 208 .collect(Collectors.toMap(Map.Entry::getKey, entry -> new ArrayList<>(entry.getValue()))); 209 return DataProcess.getFuncTreeBottomUp(collect, startNS, endNS, threadIds); 210 } 211 212 /** 213 * get the get BottomUp FuncTree by func 214 * 215 * @param func func 216 * @return list nodes 217 */ getFuncTreeByFuncTopDown(PrefFunc func)218 public static List<DefaultMutableTreeNode> getFuncTreeByFuncTopDown(PrefFunc func) { 219 List<PrefFunc> collect = funcMap.get(Long.valueOf(func.getTid()).intValue()).stream().filter( 220 item -> TimeUtils.isRangeCross(func.getStartTs(), func.getEndTs(), item.getStartTs(), item.getEndTs()) 221 && item.getDepth() > func.getDepth()).collect(Collectors.toList()); 222 DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(); 223 rootNode.setUserObject(new TreeTableBean() { 224 { 225 setName(func.getFuncName()); 226 long totalUs = TimeUnit.NANOSECONDS.toMicros(func.getDur()); 227 setThreadDur(totalUs); 228 setTotalNum(totalUs); 229 long threadFuncDuration = TimeUnit.NANOSECONDS.toMicros(collect.stream().filter( 230 item -> item.getDepth() == func.getDepth() + 1 && TimeUtils 231 .isRangeCross(func.getStartTs(), func.getEndTs(), item.getStartTs(), item.getEndTs())) 232 .mapToLong(PrefFunc::getDur).sum()); 233 setChildrenNS(func.getDur()); 234 setChildrenNum(threadFuncDuration); 235 setSelfNum(totalUs - threadFuncDuration); 236 } 237 }); 238 Map<String, TreeTableBean> longTreeTableBeanMap = funcGroupByStackId(func, collect, null); 239 List<TreeTableBean> treeTableBeans = setNumForNodes(longTreeTableBeanMap); 240 Map<String, DefaultMutableTreeNode> treeNodeMap = treeTableBeans.stream() 241 .collect(Collectors.toMap(TreeTableBean::getPrefStackId, DefaultMutableTreeNode::new)); 242 treeTableBeans.forEach(listBean -> { 243 if (listBean.getPrefParentStackId().equals(func.getBloodId())) { 244 rootNode.add(treeNodeMap.get(listBean.getPrefStackId())); 245 } else { 246 if (treeNodeMap.containsKey(listBean.getPrefParentStackId())) { 247 treeNodeMap.get(listBean.getPrefParentStackId()).add(treeNodeMap.get(listBean.getPrefStackId())); 248 } 249 } 250 }); 251 List<DefaultMutableTreeNode> objects = new ArrayList<DefaultMutableTreeNode>(); 252 objects.add(rootNode); 253 return objects; 254 } 255 256 /** 257 * get the get BottomUp FuncTree by func 258 * 259 * @param func func 260 * @return list nodes 261 */ getFuncTreeByFuncBottomUp(PrefFunc func)262 public static List<DefaultMutableTreeNode> getFuncTreeByFuncBottomUp(PrefFunc func) { 263 long totalUs = TimeUnit.NANOSECONDS.toMicros(func.getDur()); 264 ArrayList<DefaultMutableTreeNode> nodes = new ArrayList<>(); 265 List<PrefFunc> collect = funcMap.get(Long.valueOf(func.getTid()).intValue()).stream().filter( 266 item -> TimeUtils.isRangeCross(func.getStartTs(), func.getEndTs(), item.getStartTs(), item.getEndTs())) 267 .collect(Collectors.toList()); 268 Map<String, List<String>> nameToId = new HashMap<>(); 269 Map<String, TreeTableBean> treeNodeMap = funcGroupByStackId(func, collect, nameToId); 270 setNumForNodes(treeNodeMap); 271 nameToId.forEach((name, ids) -> { 272 DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(); 273 rootNode.setUserObject(new TreeTableBean(totalUs) { 274 { 275 setName(name); 276 long totalNum = 0L; 277 long childrenNum = 0L; 278 long selfNum = 0L; 279 for (String id : ids) { 280 TreeTableBean tableBean = treeNodeMap.get(id); 281 totalNum += tableBean.getTotalNum(); 282 childrenNum += tableBean.getChildrenNum(); 283 selfNum += tableBean.getSelfNum(); 284 } 285 setTotalNum(totalNum); 286 setSelfNum(selfNum); 287 setChildrenNum(childrenNum); 288 } 289 }); 290 ids.forEach(id -> recursionNode(rootNode, treeNodeMap.get(id).getPrefParentStackId(), 291 threadNames.get(Long.valueOf(func.getTid()).intValue()), treeNodeMap, id)); 292 if (ids.stream().noneMatch(id -> 293 collect.stream().filter(item -> item.getBloodId().equals(id) && item.getDepth() < func.getDepth()) 294 .toArray().length > 0)) { 295 nodes.add(rootNode); 296 } 297 }); 298 return nodes; 299 } 300 funcGroupByStackId(PrefFunc func, List<PrefFunc> collect, Map<String, List<String>> nameToId)301 private static Map<String, TreeTableBean> funcGroupByStackId(PrefFunc func, List<PrefFunc> collect, 302 Map<String, List<String>> nameToId) { 303 long totalUs = TimeUnit.NANOSECONDS.toMicros(func.getDur()); 304 return collect.stream().collect(groupingBy(PrefFunc::getBloodId)).entrySet().stream() 305 .collect(Collectors.toMap(Map.Entry::getKey, entry -> { 306 TreeTableBean uniteBean = new TreeTableBean(totalUs); 307 uniteBean.setPrefStackId(entry.getKey()); 308 if (entry.getValue().size() > 0) { 309 uniteBean.setName(entry.getValue().get(0).getFuncName()); 310 uniteBean.setPrefParentStackId(entry.getValue().get(0).getParentBloodId()); 311 if (nameToId != null) { 312 if (nameToId.containsKey(entry.getValue().get(0).getFuncName())) { 313 nameToId.get(entry.getValue().get(0).getFuncName()) 314 .add(entry.getValue().get(0).getBloodId()); 315 } else { 316 ArrayList<String> list = new ArrayList<String>(); 317 list.add(entry.getValue().get(0).getBloodId()); 318 nameToId.put(entry.getValue().get(0).getFuncName(), list); 319 } 320 } 321 } 322 long childrenTotal = entry.getValue().stream().mapToLong(child -> TimeUtils 323 .getIntersection(func.getStartTs(), func.getEndTs(), child.getStartTs(), child.getEndTs())).sum(); 324 uniteBean.setTotalNum(childrenTotal); 325 uniteBean.setChildrenNS(entry.getValue().stream().mapToLong(child -> TimeUtils 326 .getNanoIntersection(func.getStartTs(), func.getEndTs(), child.getStartTs(), child.getEndTs())) 327 .sum()); 328 return uniteBean; 329 })); 330 } 331 332 /** 333 * Set up presentation data 334 * 335 * @param map map 336 * @return list TreeTableBean 337 */ setNumForNodes(Map<String, TreeTableBean> map)338 public static List<TreeTableBean> setNumForNodes(Map<String, TreeTableBean> map) { 339 List<TreeTableBean> treeNodes = new ArrayList<>(map.values()); // Sort the array 340 for (TreeTableBean ts : treeNodes) { // Loop set children and total data 341 ts.setSelfNum(ts.getTotalNum() - ts.getChildrenNum()); 342 if (map.containsKey(ts.getPrefParentStackId())) { 343 TreeTableBean mapUserObject = map.get(ts.getPrefParentStackId()); 344 mapUserObject.setChildrenNum(mapUserObject.getChildrenNum() + ts.getTotalNum()); 345 mapUserObject.setSelfNum(mapUserObject.getTotalNum() - mapUserObject.getChildrenNum()); 346 } 347 } 348 return treeNodes; 349 } 350 recursionNode(DefaultMutableTreeNode rootNode, String parentId, String threadName, Map<String, TreeTableBean> treeNodeMap, String id)351 private static void recursionNode(DefaultMutableTreeNode rootNode, String parentId, String threadName, 352 Map<String, TreeTableBean> treeNodeMap, String id) { 353 if (!(rootNode.getUserObject() instanceof TreeTableBean)) { 354 return; 355 } 356 TreeTableBean topBean = (TreeTableBean) rootNode.getUserObject(); 357 TreeTableBean timeBean = treeNodeMap.get(id); 358 if (parentId.isEmpty()) { // Leaf node 359 if (rootNode.getChildCount() != 0) { // Merge leaf nodes 360 if (rootNode.getChildAt(rootNode.getChildCount() - 1) instanceof DefaultMutableTreeNode) { 361 DefaultMutableTreeNode leafNode = 362 (DefaultMutableTreeNode) rootNode.getChildAt(rootNode.getChildCount() - 1); 363 if (leafNode.getUserObject() instanceof TreeTableBean) { 364 TreeTableBean leafNodeUserObject = (TreeTableBean) leafNode.getUserObject(); 365 leafNodeUserObject.mergeTime(timeBean); 366 leafNode.setUserObject(leafNodeUserObject); 367 } 368 } 369 } 370 } else { // Non-leaf nodes 371 final TreeTableBean idBean = treeNodeMap.get(parentId); 372 boolean sameName = false; 373 Enumeration<TreeNode> enumeration = rootNode.children(); 374 375 // Compare whether there are node names in the current hierarchy that need to be merged 376 while (enumeration.hasMoreElements()) { 377 DefaultMutableTreeNode nextElement = (DefaultMutableTreeNode) enumeration.nextElement(); 378 if (nextElement.getUserObject() instanceof TreeTableBean) { 379 TreeTableBean nextElementUserObject = (TreeTableBean) nextElement.getUserObject(); 380 if (nextElementUserObject.getName().equals(idBean.getName())) { // The merge time difference 381 nextElementUserObject.mergeTime(timeBean); 382 recursionNode(nextElement, idBean.getPrefParentStackId(), threadName, treeNodeMap, id); 383 sameName = true; 384 } 385 } 386 } 387 if (!sameName) { // No same node needs to be merged 388 DefaultMutableTreeNode addNode = createNewNode(topBean, timeBean, idBean.getName()); 389 rootNode.add(addNode); 390 recursionNode(addNode, idBean.getPrefParentStackId(), threadName, treeNodeMap, id); 391 } 392 } 393 } 394 createNewNode(TreeTableBean topBean, TreeTableBean timeBean, String name)395 private static DefaultMutableTreeNode createNewNode(TreeTableBean topBean, TreeTableBean timeBean, String name) { 396 TreeTableBean treeTableBean = new TreeTableBean(topBean.getThreadDur()); 397 treeTableBean.setName(name); 398 treeTableBean.setTime(timeBean); 399 DefaultMutableTreeNode defaultMutableTreeNode = new DefaultMutableTreeNode(); 400 defaultMutableTreeNode.setUserObject(treeTableBean); 401 return defaultMutableTreeNode; 402 } 403 404 /** 405 * get FlameChart data 406 * 407 * @param func func 408 * @return list TreeTableBean 409 */ getFuncTreeFlameChart(PrefFunc func)410 public static List<DefaultMutableTreeNode> getFuncTreeFlameChart(PrefFunc func) { 411 return getFuncTreeByFuncTopDown(func); 412 } 413 414 /** 415 * get FlameChart data 416 * 417 * @param startNS startNS 418 * @param endNS endNS 419 * @return list nodes 420 */ getFuncTreeFlameChart(long startNS, long endNS)421 public static List<DefaultMutableTreeNode> getFuncTreeFlameChart(long startNS, long endNS) { 422 List<DefaultMutableTreeNode> funcTreeTopDown = getFuncTreeTopDown(startNS, endNS); 423 funcTreeTopDown.forEach(PerfData::resortNode); 424 sortNodeList(funcTreeTopDown); 425 return funcTreeTopDown; 426 } 427 428 /** 429 * get FlameChart data 430 * 431 * @param startNS startNS 432 * @param endNS endNS 433 * @param threadIds threadIds 434 * @return list nodes 435 */ getFuncTreeFlameChart(long startNS, long endNS, List<Integer> threadIds)436 public static List<DefaultMutableTreeNode> getFuncTreeFlameChart(long startNS, long endNS, 437 List<Integer> threadIds) { 438 List<DefaultMutableTreeNode> funcTreeTopDown = getFuncTreeTopDown(startNS, endNS, threadIds); 439 funcTreeTopDown.forEach(PerfData::resortNode); 440 sortNodeList(funcTreeTopDown); 441 return funcTreeTopDown; 442 } 443 resortNode(DefaultMutableTreeNode root)444 private static void resortNode(DefaultMutableTreeNode root) { 445 Consumer<DefaultMutableTreeNode> sort = parent -> { 446 Enumeration<TreeNode> children = parent.children(); 447 List<DefaultMutableTreeNode> childs = new ArrayList<>(); 448 while (children.hasMoreElements()) { 449 TreeNode node = children.nextElement(); 450 if (node instanceof DefaultMutableTreeNode) { 451 childs.add((DefaultMutableTreeNode) node); 452 } 453 } 454 parent.removeAllChildren(); 455 sortNodeList(childs).forEach(parent::add); 456 }; 457 Enumeration enumeration = root.depthFirstEnumeration(); 458 while (enumeration.hasMoreElements() && enumeration.nextElement() instanceof DefaultMutableTreeNode) { 459 DefaultMutableTreeNode node = (DefaultMutableTreeNode) enumeration.nextElement(); 460 if (node != null) { 461 if (!node.isLeaf() && node.getChildCount() > 1) { 462 sort.accept(node); 463 } 464 } 465 } 466 } 467 sortNodeList(List<DefaultMutableTreeNode> list)468 private static List<DefaultMutableTreeNode> sortNodeList(List<DefaultMutableTreeNode> list) { 469 return list.stream().sorted((child1, child2) -> { 470 if (child1.getUserObject() instanceof TreeTableBean && child2.getUserObject() instanceof TreeTableBean) { 471 TreeTableBean bean1 = (TreeTableBean) child1.getUserObject(); 472 TreeTableBean bean2 = (TreeTableBean) child2.getUserObject(); 473 return Long.compare(bean2.getTotalNum(), bean1.getTotalNum()); 474 } 475 return 0; 476 }).collect(Collectors.toList()); 477 } 478 479 /** 480 * clear all static data 481 */ 482 public static void clearData() { 483 if (prefRange != null) { 484 prefRange = null; 485 } 486 if (funcMap != null) { 487 if (funcMap.size() > 0) { 488 funcMap.values().forEach(List::clear); 489 } 490 funcMap.clear(); 491 } 492 if (threadNames != null) { 493 threadNames.clear(); 494 } 495 if (prefFiles != null) { 496 prefFiles.clear(); 497 } 498 } 499 500 public static PrefRange getPrefRange() { 501 return prefRange; 502 } 503 504 public static void setPrefRange(PrefRange prefRange) { 505 PerfData.prefRange = prefRange; 506 } 507 508 public static Map<Long, List<PrefFile>> getPrefFiles() { 509 return prefFiles; 510 } 511 512 public static void setPrefFiles(Map<Long, List<PrefFile>> prefFiles) { 513 PerfData.prefFiles = prefFiles; 514 } 515 516 public static Map<Integer, List<PrefFunc>> getFuncMap() { 517 return funcMap; 518 } 519 520 public static void setFuncMap(Map<Integer, List<PrefFunc>> funcMap) { 521 PerfData.funcMap = funcMap; 522 } 523 524 public static Map<Integer, String> getThreadNames() { 525 return threadNames; 526 } 527 528 public static void setThreadNames(Map<Integer, String> threadNames) { 529 PerfData.threadNames = threadNames; 530 } 531 } 532