From 83e05e32ee9da9a055d1e348614fd411174906c2 Mon Sep 17 00:00:00 2001 From: zhaohui Date: Wed, 11 May 2022 11:24:28 +0800 Subject: [PATCH 1/4] init smartperf code commit Signed-off-by: zhaohui --- README.en.md | 36 - README.md | 39 - README_zh.md | 62 + device/device_command/BUILD.gn | 59 + device/device_command/ByTrace.cpp | 76 + device/device_command/CPU.cpp | 176 ++ device/device_command/DDR.cpp | 26 + device/device_command/FPS.cpp | 190 +++ device/device_command/GPU.cpp | 77 + device/device_command/Power.cpp | 110 ++ device/device_command/RAM.cpp | 81 + device/device_command/README_zh.md | 102 ++ device/device_command/Temperature.cpp | 108 ++ device/device_command/gp_utils.cpp | 145 ++ device/device_command/include/ByTrace.h | 51 + device/device_command/include/CPU.h | 62 + device/device_command/include/DDR.h | 40 + device/device_command/include/FPS.h | 53 + device/device_command/include/GPU.h | 46 + device/device_command/include/Power.h | 44 + device/device_command/include/RAM.h | 32 + device/device_command/include/Temperature.h | 46 + device/device_command/include/gp_constant.h | 29 + device/device_command/include/gp_data.h | 23 + device/device_command/include/gp_utils.h | 38 + device/device_command/include/profiler.h | 54 + .../include/smartperf_command.h | 74 + .../device_command/include/socket_profiler.h | 57 + device/device_command/profiler.cpp | 140 ++ device/device_command/smartperf_command.cpp | 165 ++ device/device_command/smartperf_main.cpp | 24 + device/device_command/socket_profiler.cpp | 216 +++ device/device_ui/.gitignore | 13 + device/device_ui/AppScope/app.json5 | 13 + .../resources/base/element/string.json | 8 + .../resources/base/media/app_icon.png | Bin 0 -> 6790 bytes device/device_ui/build-profile.json5 | 26 + device/device_ui/entry/build-profile.json5 | 22 + device/device_ui/entry/hvigorfile.js | 2 + device/device_ui/entry/package-lock.json | 5 + device/device_ui/entry/package.json | 14 + .../entry/src/main/cpp/CMakeLists.txt | 10 + device/device_ui/entry/src/main/cpp/FPS.cpp | 173 ++ device/device_ui/entry/src/main/cpp/FPS.h | 54 + device/device_ui/entry/src/main/cpp/RAM.cpp | 64 + device/device_ui/entry/src/main/cpp/RAM.h | 32 + .../device_ui/entry/src/main/cpp/gp_utils.cpp | 108 ++ .../device_ui/entry/src/main/cpp/gp_utils.h | 35 + .../device_ui/entry/src/main/cpp/profiler.cpp | 148 ++ .../main/cpp/types/libsmartperf/index.d.ts | 23 + .../main/cpp/types/libsmartperf/package.json | 4 + .../src/main/ets/Application/AbilityStage.ts | 23 + .../src/main/ets/MainAbility/MainAbility.ts | 80 + .../main/ets/common/FloatWindowComponent.ets | 95 ++ .../main/ets/common/constant/ConstantSQL.ts | 97 ++ .../main/ets/common/constant/ConstantsPath.ts | 19 + .../main/ets/common/database/DatabaseUtils.ts | 366 ++++ .../ets/common/database/LocalRepository.ts | 91 + .../main/ets/common/entity/DatabaseEntity.ets | 382 +++++ .../ets/common/entity/LocalConfigEntity.ts | 169 ++ .../main/ets/common/entity/SystemEntity.ets | 43 + .../src/main/ets/common/entity/UserEntity.ets | 30 + .../main/ets/common/profiler/MainWorkTask.ets | 50 + .../main/ets/common/profiler/NativeTaskFun.ts | 49 + .../main/ets/common/profiler/ProfilerTask.ets | 203 +++ .../ets/common/profiler/base/BaseProfiler.ets | 22 + .../profiler/base/BaseProfilerUtils.ets | 148 ++ .../common/profiler/base/ProfilerConstant.ts | 56 + .../common/profiler/base/ProfilerFactory.ets | 56 + .../common/profiler/base/SocketProfiler.ets | 17 + .../src/main/ets/common/profiler/item/CPU.ets | 150 ++ .../src/main/ets/common/profiler/item/DDR.ets | 54 + .../src/main/ets/common/profiler/item/FPS.ets | 71 + .../src/main/ets/common/profiler/item/GPU.ets | 64 + .../main/ets/common/profiler/item/NetWork.ets | 71 + .../main/ets/common/profiler/item/Power.ets | 68 + .../src/main/ets/common/profiler/item/RAM.ets | 58 + .../main/ets/common/profiler/item/Thermal.ets | 90 + .../ets/common/ui/StartTestTitleComponent.ets | 41 + .../src/main/ets/common/ui/detail/Summary.ets | 176 ++ .../common/ui/detail/data/DetailCommon.ets | 34 + .../common/ui/detail/utils/HandleLostFrame.ts | 97 ++ .../ui/floatwindow/FloatWindowConstant.ets | 31 + .../common/ui/floatwindow/FloatWindowFun.ts | 180 ++ .../ui/floatwindow/utils/FloatWindowUtils.ets | 137 ++ .../src/main/ets/common/ui/main/Home.ets | 153 ++ .../ets/common/ui/main/HomeBottomPage.ets | 80 + .../src/main/ets/common/ui/main/Mine.ets | 122 ++ .../src/main/ets/common/ui/main/Report.ets | 97 ++ .../main/ets/common/ui/main/TopComponent.ets | 35 + .../src/main/ets/common/utils/AbilityUtils.ts | 42 + .../ets/common/utils/BundleMangerUtils.ts | 106 ++ .../src/main/ets/common/utils/CSVUtils.ts | 58 + .../main/ets/common/utils/CalculationUtils.ts | 198 +++ .../main/ets/common/utils/CheckEmptyUtils.ts | 47 + .../src/main/ets/common/utils/GameUtils.ets | 253 +++ .../src/main/ets/common/utils/IOUtils.ts | 46 + .../src/main/ets/common/utils/JsonUtils.ets | 31 + .../src/main/ets/common/utils/SPLogger.ts | 56 + .../src/main/ets/common/utils/StringUtils.ts | 33 + .../src/main/ets/common/utils/SystemUtils.ets | 57 + .../src/main/ets/common/utils/TimeUtils.ts | 85 + .../src/main/ets/pages/AppSelectPage.ets | 73 + .../src/main/ets/pages/CPU0LineChartPage.ets | 77 + .../src/main/ets/pages/CPU1LineChartPage.ets | 78 + .../src/main/ets/pages/CPU2LineChartPage.ets | 77 + .../ets/pages/CurrentNowLineChartPage.ets | 77 + .../src/main/ets/pages/DDRLineChartPage.ets | 78 + .../entry/src/main/ets/pages/FloatBall.ets | 317 ++++ .../src/main/ets/pages/FpsLineChartPage.ets | 119 ++ .../src/main/ets/pages/GPULineChartPage.ets | 78 + .../entry/src/main/ets/pages/LightAdjust.ets | 78 + .../entry/src/main/ets/pages/LoginPage.ets | 58 + .../entry/src/main/ets/pages/MainPage.ets | 53 + .../entry/src/main/ets/pages/Question.ets | 62 + .../src/main/ets/pages/RAMLineChartPage.ets | 80 + .../entry/src/main/ets/pages/ReportDetail.ets | 97 ++ .../entry/src/main/ets/pages/SettingsPage.ets | 57 + .../ets/pages/ShellBackTempLineChartPage.ets | 82 + .../src/main/ets/pages/StartTestPage.ets | 425 +++++ .../src/main/ets/pages/TitleWindowPage.ets | 218 +++ .../entry/src/main/ets/workers/worker.js | 212 +++ device/device_ui/entry/src/main/module.json5 | 69 + .../main/resources/base/element/color.json | 37 + .../main/resources/base/element/string.json | 56 + .../src/main/resources/base/media/icon.png | Bin 0 -> 6790 bytes .../resources/base/media/icon_about_we.png | Bin 0 -> 4213 bytes .../base/media/icon_average_frame_b.png | Bin 0 -> 1644 bytes .../main/resources/base/media/icon_back.png | Bin 0 -> 1246 bytes .../base/media/icon_brightness_plus.png | Bin 0 -> 2890 bytes .../main/resources/base/media/icon_camera.png | Bin 0 -> 2126 bytes .../resources/base/media/icon_close_small.png | Bin 0 -> 2799 bytes .../resources/base/media/icon_counter.png | Bin 0 -> 1619 bytes .../main/resources/base/media/icon_enter.png | Bin 0 -> 1312 bytes .../resources/base/media/icon_frame_score.png | Bin 0 -> 1631 bytes .../base/media/icon_home_selected.png | Bin 0 -> 3691 bytes .../base/media/icon_home_unselected.png | Bin 0 -> 3792 bytes .../base/media/icon_jank_each_hour.png | Bin 0 -> 1615 bytes .../resources/base/media/icon_jank_score.png | Bin 0 -> 4173 bytes .../resources/base/media/icon_language.png | Bin 0 -> 3343 bytes .../base/media/icon_max_temperature.png | Bin 0 -> 2252 bytes .../base/media/icon_mine_selected.png | Bin 0 -> 2345 bytes .../base/media/icon_mine_unselected.png | Bin 0 -> 3588 bytes .../main/resources/base/media/icon_net.png | Bin 0 -> 3035 bytes .../base/media/icon_normalized_current.png | Bin 0 -> 1815 bytes .../base/media/icon_report_selected.png | Bin 0 -> 1713 bytes .../base/media/icon_report_unselected.png | Bin 0 -> 2306 bytes .../resources/base/media/icon_screencap.png | Bin 0 -> 2028 bytes .../resources/base/media/icon_test_index.png | Bin 0 -> 2780 bytes .../resources/base/media/icon_test_name.png | Bin 0 -> 2524 bytes .../main/resources/base/media/icon_upload.png | Bin 0 -> 3100 bytes .../main/resources/base/media/icon_video.png | Bin 0 -> 2115 bytes .../src/main/resources/base/media/logo.png | Bin 0 -> 5257 bytes .../src/main/resources/base/media/person.png | Bin 0 -> 3163 bytes .../main/resources/base/media/question.png | Bin 0 -> 3581 bytes .../resources/base/media/report_upload.png | Bin 0 -> 3100 bytes .../main/resources/base/media/settings.png | Bin 0 -> 2904 bytes .../resources/base/media/test_apps_count.png | Bin 0 -> 1107 bytes .../base/media/test_session_count.png | Bin 0 -> 1469 bytes .../resources/base/media/test_times_count.png | Bin 0 -> 2542 bytes .../resources/base/profile/main_pages.json | 23 + device/device_ui/hvigorfile.js | 2 + device/device_ui/package.json | 14 + host/doc/compile_smartperf.md | 93 + host/doc/compile_trace_streamer.md | 133 ++ host/doc/des_stat.md | 413 +++++ host/doc/des_support_event.md | 224 +++ host/doc/des_tables.md | 306 ++++ host/doc/quickstart_hiprofiler_cmd.md | 39 + host/doc/quickstart_smartperf.md | 66 + host/doc/quickstart_trace_streamer.md | 309 ++++ host/figures/Scheduling.jpg | Bin 0 -> 71861 bytes host/figures/callstackclick.jpg | Bin 0 -> 19084 bytes host/figures/callstackselect.jpg | Bin 0 -> 127570 bytes host/figures/command.jpg | Bin 0 -> 63489 bytes host/figures/cpu.jpg | Bin 0 -> 113908 bytes host/figures/cpu_frequency.png | Bin 0 -> 36246 bytes host/figures/cpubyprocess.jpg | Bin 0 -> 167455 bytes host/figures/cpubythread.jpg | Bin 0 -> 186540 bytes host/figures/cpuclick.jpg | Bin 0 -> 100490 bytes host/figures/cpusage.jpg | Bin 0 -> 156691 bytes host/figures/dump_and_mem.png | Bin 0 -> 5805 bytes host/figures/excutecommand.jpg | Bin 0 -> 101387 bytes host/figures/filters.png | Bin 0 -> 49833 bytes host/figures/fps.jpg | Bin 0 -> 7002 bytes host/figures/fpsselect.jpg | Bin 0 -> 92018 bytes host/figures/gray.jpg | Bin 0 -> 31408 bytes host/figures/highlit.jpg | Bin 0 -> 19856 bytes host/figures/htrace.jpg | Bin 0 -> 97938 bytes host/figures/log.png | Bin 0 -> 4037 bytes host/figures/main.jpg | Bin 0 -> 7630 bytes host/figures/mem_usage.png | Bin 0 -> 30381 bytes host/figures/opentrace.jpg | Bin 0 -> 21461 bytes host/figures/process.jpg | Bin 0 -> 134828 bytes host/figures/process_thread.png | Bin 0 -> 1841 bytes host/figures/smartperf_framework.png | Bin 0 -> 19831 bytes host/figures/systraceconfig.jpg | Bin 0 -> 36103 bytes host/figures/thread_state.png | Bin 0 -> 1906 bytes host/figures/threadclick.jpg | Bin 0 -> 57375 bytes host/figures/threadinfo.jpg | Bin 0 -> 169830 bytes host/figures/threadselect.jpg | Bin 0 -> 138639 bytes host/figures/time.jpg | Bin 0 -> 25236 bytes host/figures/trace.jpg | Bin 0 -> 120151 bytes host/figures/trace_streamer_stream.png | Bin 0 -> 22417 bytes host/ide/LICENSE | 178 ++ host/ide/README_zh.md | 93 + host/ide/build.js | 142 ++ host/ide/package.json | 50 + host/ide/server/go.mod | 21 + host/ide/server/main.go | 350 ++++ host/ide/server/version.txt | 1 + host/ide/src/base-ui/BaseElement.ts | 50 + host/ide/src/base-ui/button/LitButton.ts | 26 + host/ide/src/base-ui/checkbox/LitCheckBox.ts | 164 ++ .../base-ui/checkbox/LitCheckBoxWithText.ts | 109 ++ .../ide/src/base-ui/checkbox/LitCheckGroup.ts | 62 + host/ide/src/base-ui/icon.svg | 1 + host/ide/src/base-ui/icon/LitIcon.ts | 118 ++ host/ide/src/base-ui/menu/LitMainMenu.ts | 176 ++ host/ide/src/base-ui/menu/LitMainMenuGroup.ts | 141 ++ host/ide/src/base-ui/menu/LitMainMenuItem.ts | 166 ++ host/ide/src/base-ui/popover/LitPopContent.ts | 92 + host/ide/src/base-ui/popover/LitPopover.ts | 458 +++++ .../src/base-ui/popover/LitPopoverTitle.ts | 58 + host/ide/src/base-ui/popover/LitPopoverV.ts | 494 ++++++ .../base-ui/progress-bar/LitProgressBar.ts | 89 + host/ide/src/base-ui/radiobox/LitRadioBox.ts | 222 +++ .../ide/src/base-ui/radiobox/LitRadioGroup.ts | 66 + host/ide/src/base-ui/select/LitSelect.ts | 602 +++++++ .../ide/src/base-ui/select/LitSelectOption.ts | 116 ++ host/ide/src/base-ui/slider/LitSlider.ts | 429 +++++ host/ide/src/base-ui/switch/lit-switch.ts | 179 ++ host/ide/src/base-ui/table/TableRowObject.ts | 25 + .../ide/src/base-ui/table/lit-table-column.ts | 63 + host/ide/src/base-ui/table/lit-table-group.ts | 56 + host/ide/src/base-ui/table/lit-table.ts | 1161 +++++++++++++ host/ide/src/base-ui/tabs/lit-tabpane.ts | 120 ++ host/ide/src/base-ui/tabs/lit-tabs.ts | 646 +++++++ host/ide/src/base-ui/utils/Template.ts | 95 ++ host/ide/src/icon.svg | 1 + host/ide/src/img/copy.png | Bin 0 -> 869 bytes host/ide/src/img/dark_pic.png | Bin 0 -> 96014 bytes host/ide/src/img/function.png | Bin 0 -> 398 bytes host/ide/src/img/library.png | Bin 0 -> 422 bytes host/ide/src/img/logo.png | Bin 0 -> 3118 bytes host/ide/src/img/pic.png | Bin 0 -> 89971 bytes host/ide/src/index.html | 47 + host/ide/src/log/Log.ts | 81 + host/ide/src/trace/SpApplication.ts | 619 +++++++ host/ide/src/trace/bean/BaseStruct.ts | 21 + host/ide/src/trace/bean/BinderArgBean.ts | 22 + host/ide/src/trace/bean/BoxSelection.ts | 71 + host/ide/src/trace/bean/CpuFreqStruct.ts | 64 + host/ide/src/trace/bean/CpuStruct.ts | 100 ++ host/ide/src/trace/bean/CpuUsage.ts | 36 + host/ide/src/trace/bean/FpsStruct.ts | 80 + host/ide/src/trace/bean/FuncStruct.ts | 93 + host/ide/src/trace/bean/HeapBean.ts | 27 + host/ide/src/trace/bean/HeapStruct.ts | 78 + host/ide/src/trace/bean/HeapTreeDataBean.ts | 25 + host/ide/src/trace/bean/NativeHook.ts | 135 ++ host/ide/src/trace/bean/ProcessMemStruct.ts | 46 + host/ide/src/trace/bean/ProcessStruct.ts | 44 + host/ide/src/trace/bean/StateProcessThread.ts | 64 + host/ide/src/trace/bean/ThreadStruct.ts | 142 ++ host/ide/src/trace/bean/WakeUpTimeBean.ts | 20 + host/ide/src/trace/bean/WakeupBean.ts | 25 + host/ide/src/trace/component/SpInfoAndStas.ts | 28 + host/ide/src/trace/component/SpMetrics.ts | 55 + host/ide/src/trace/component/SpQuerySQL.ts | 68 + host/ide/src/trace/component/SpRecordTrace.ts | 574 +++++++ .../trace/component/SpRecyclerSystemTrace.ts | 805 +++++++++ host/ide/src/trace/component/SpSystemTrace.ts | 1517 +++++++++++++++++ host/ide/src/trace/component/SpWelcomePage.ts | 44 + host/ide/src/trace/component/Sptext.ts | 59 + host/ide/src/trace/component/StackBar.ts | 127 ++ .../trace/component/setting/SpAllocations.ts | 208 +++ .../trace/component/setting/SpCheckDesBox.ts | 110 ++ .../trace/component/setting/SpProbesConfig.ts | 261 +++ .../component/setting/SpRecordSetting.ts | 123 ++ .../trace/component/setting/SpTraceCommand.ts | 146 ++ .../setting/bean/ProfilerServiceTypes.ts | 850 +++++++++ .../setting/utils/PluginConvertUtils.ts | 132 ++ .../component/trace/TimerShaftElement.ts | 521 ++++++ .../trace/component/trace/base/ColorUtils.ts | 86 + .../trace/component/trace/base/RangeSelect.ts | 205 +++ .../trace/component/trace/base/TraceRow.ts | 823 +++++++++ .../component/trace/base/TraceRowObject.ts | 42 + .../trace/base/TraceRowRecyclerView.ts | 190 +++ .../trace/component/trace/base/TraceSheet.ts | 586 +++++++ .../src/trace/component/trace/base/Utils.ts | 170 ++ .../trace/component/trace/search/Search.ts | 216 +++ .../component/trace/sheet/TabPaneBoxChild.ts | 157 ++ .../trace/sheet/TabPaneContextSwitch.ts | 262 +++ .../component/trace/sheet/TabPaneCounter.ts | 172 ++ .../trace/component/trace/sheet/TabPaneCpu.ts | 58 + .../trace/sheet/TabPaneCpuByProcess.ts | 116 ++ .../trace/sheet/TabPaneCpuByThread.ts | 120 ++ .../component/trace/sheet/TabPaneCpuUsage.ts | 175 ++ .../trace/sheet/TabPaneCurrentSelection.ts | 450 +++++ .../component/trace/sheet/TabPaneFilter.ts | 364 ++++ .../trace/component/trace/sheet/TabPaneFps.ts | 69 + .../component/trace/sheet/TabPaneHeap.ts | 171 ++ .../trace/sheet/TabPaneNMCallInfo.ts | 344 ++++ .../trace/sheet/TabPaneNMSampleList.ts | 456 +++++ .../trace/sheet/TabPaneNMStatstics.ts | 226 +++ .../component/trace/sheet/TabPaneNMemory.ts | 312 ++++ .../trace/component/trace/sheet/TabPanePTS.ts | 267 +++ .../trace/component/trace/sheet/TabPaneSPT.ts | 265 +++ .../component/trace/sheet/TabPaneSlices.ts | 115 ++ .../trace/sheet/TabPaneThreadStates.ts | 128 ++ .../trace/sheet/TabPaneThreadSwitch.ts | 258 +++ .../component/trace/sheet/TabProgressBar.ts | 56 + .../trace/component/trace/timer-shaft/Flag.ts | 38 + .../component/trace/timer-shaft/Graph.ts | 30 + .../component/trace/timer-shaft/RangeRuler.ts | 469 +++++ .../trace/component/trace/timer-shaft/Rect.ts | 102 ++ .../component/trace/timer-shaft/SportRuler.ts | 288 ++++ .../trace/timer-shaft/TabPaneFlag.ts | 104 ++ .../component/trace/timer-shaft/TimeRuler.ts | 60 + host/ide/src/trace/database/Procedure.ts | 146 ++ .../ide/src/trace/database/ProcedureWorker.ts | 457 +++++ .../src/trace/database/ProcedureWorkerCPU.ts | 169 ++ .../trace/database/ProcedureWorkerCommon.ts | 259 +++ .../src/trace/database/ProcedureWorkerFPS.ts | 102 ++ .../src/trace/database/ProcedureWorkerFreq.ts | 107 ++ .../src/trace/database/ProcedureWorkerFunc.ts | 133 ++ .../src/trace/database/ProcedureWorkerHeap.ts | 97 ++ .../src/trace/database/ProcedureWorkerMem.ts | 86 + .../trace/database/ProcedureWorkerProcess.ts | 99 ++ .../trace/database/ProcedureWorkerThread.ts | 190 +++ .../trace/database/ProcedureWorkerTimeline.ts | 728 ++++++++ host/ide/src/trace/database/SqlLite.ts | 874 ++++++++++ host/ide/src/trace/database/SqlLiteWorker.ts | 106 ++ host/ide/src/trace/database/TempSql.ts | 282 +++ host/ide/src/trace/database/TraceWorker.ts | 118 ++ host/ide/src/trace/grpc/HiProfilerClient.ts | 56 + host/ide/src/trace/grpc/ProfilerClient.ts | 119 ++ host/ide/src/trace/grpc/ProfilerController.ts | 92 + host/ide/src/trace/proto/common_types.proto | 58 + .../src/trace/proto/profiler_service.proto | 47 + .../trace/proto/profiler_service_types.proto | 129 ++ .../ide/test/base-ui/button/LitButton.test.ts | 25 + .../test/base-ui/checkbox/LitCheckBox.test.ts | 60 + .../checkbox/LitCheckBoxWithText.test.ts | 72 + .../base-ui/checkbox/LitCheckGroup.test.ts | 39 + host/ide/test/base-ui/icon/LitIcon.test.ts | 59 + .../ide/test/base-ui/menu/LitMainMenu.test.ts | 59 + .../base-ui/menu/LitMainMenuGroup.test.ts | 44 + .../test/base-ui/menu/LitMainMenuItem.test.ts | 54 + .../base-ui/popover/LitPopContent.test.ts | 52 + .../test/base-ui/popover/LitPopover.test.ts | 96 ++ .../progress-bar/LitProgressBar.test.ts | 27 + .../test/base-ui/radiobox/LitRadioBox.test.ts | 44 + .../ide/test/base-ui/slider/LitSlider.test.ts | 76 + .../ide/test/base-ui/switch/LitSwitch.test.ts | 56 + host/ide/test/base-ui/table/LitTable.test.ts | 85 + .../test/base-ui/table/LitTableColumn.test.ts | 30 + .../test/base-ui/table/LitTableGroup.test.ts | 30 + host/ide/test/base-ui/tabs/LitTabpane.test.ts | 53 + host/ide/test/base-ui/tabs/LitTabs.test.ts | 116 ++ host/ide/test/trace/SpApplication.test.ts | 113 ++ host/ide/test/trace/bean/BoxSelection.test.ts | 40 + .../ide/test/trace/bean/CpuFreqStruct.test.ts | 39 + host/ide/test/trace/bean/CpuStruct.test.ts | 42 + host/ide/test/trace/bean/CpuUsage.test.ts | 30 + host/ide/test/trace/bean/FpsStruct.test.ts | 38 + host/ide/test/trace/bean/FuncStruct.test.ts | 70 + host/ide/test/trace/bean/HeapStruct.test.ts | 78 + .../test/trace/bean/ProcessMemStruct.test.ts | 38 + .../ide/test/trace/bean/ProcessStruct.test.ts | 38 + .../trace/bean/StateProcessThread.test.ts | 25 + host/ide/test/trace/bean/ThreadStruct.test.ts | 88 + .../test/trace/component/FrameChart.test.ts | 77 + .../trace/component/SpRecordTrace.test.ts | 44 + .../component/SpRecyclerSystemTrace.test.ts | 94 + .../trace/component/SpSystemTrace.test.ts | 90 + .../ide/test/trace/component/StackBar.test.ts | 43 + .../component/setting/SpAllocations.test.ts | 81 + .../component/setting/SpProbesConfig.test.ts | 35 + .../component/setting/SpRecordSetting.test.ts | 36 + .../component/setting/SpTraceCommand.test.ts | 41 + .../setting/utils/PluginConvertUtils.test.ts | 115 ++ .../component/trace/TimerShaftElement.test.ts | 130 ++ .../component/trace/base/ColorUtils.test.ts | 53 + .../component/trace/base/RangeSelect.test.ts | 179 ++ .../component/trace/base/TraceRow.test.ts | 320 ++++ .../trace/base/TraceRowObject.test.ts | 28 + .../trace/base/TraceRowRecyclerView.test.ts | 70 + .../component/trace/base/TraceSheet.test.ts | 94 + .../trace/component/trace/base/Utils.test.ts | 90 + .../trace/sheet/TabPaneBoxChild.test.ts | 65 + .../trace/sheet/TabPaneContextSwitch.test.ts | 58 + .../trace/sheet/TabPaneCounter.test.ts | 76 + .../component/trace/sheet/TabPaneCpu.test.ts | 29 + .../trace/sheet/TabPaneCpuByProcess.test.ts | 53 + .../trace/sheet/TabPaneCpuByThread.test.ts | 29 + .../trace/sheet/TabPaneCpuUsage.test.ts | 73 + .../sheet/TabPaneCurrentSelection.test.ts | 186 ++ .../component/trace/sheet/TabPanePTS.test.ts | 59 + .../component/trace/sheet/TabPaneSPT.test.ts | 59 + .../trace/sheet/TabPaneSlices.test.ts | 54 + .../trace/sheet/TabPaneThreadStates.test.ts | 64 + .../trace/sheet/TabPaneThreadSwitch.test.ts | 73 + .../trace/timer-shaft/RangeRuler.test.ts | 199 +++ .../component/trace/timer-shaft/Rect.test.ts | 50 + .../trace/timer-shaft/SportRuler.test.ts | 104 ++ .../trace/timer-shaft/TabPaneFlag.test.ts | 52 + .../trace/timer-shaft/TimeRuler.test.ts | 35 + .../ide/test/trace/database/Procedure.test.ts | 24 + .../test/trace/grpc/HiProfilerClient.test.ts | 43 + .../test/trace/grpc/ProfilerClient.test.ts | 30 + host/ide/tsconfig.json | 71 + host/trace_streamer/.gn | 14 + host/trace_streamer/BUILD.gn | 23 + host/trace_streamer/README.md | 309 ++++ host/trace_streamer/build.sh | 119 ++ host/trace_streamer/build/ohos.gni | 118 ++ host/trace_streamer/build/protoc.sh | 22 + host/trace_streamer/build/test.gni | 25 + host/trace_streamer/doc/README.md | 309 ++++ .../doc/des_compile_trace_streamer.md | 133 ++ host/trace_streamer/doc/des_stat.md | 413 +++++ .../doc/des_support_eventlist.md | 224 +++ host/trace_streamer/doc/des_tables.md | 306 ++++ host/trace_streamer/figures/cpu_frequency.png | Bin 0 -> 36246 bytes host/trace_streamer/figures/dump_and_mem.png | Bin 0 -> 5805 bytes host/trace_streamer/figures/filters.png | Bin 0 -> 49833 bytes host/trace_streamer/figures/log.png | Bin 0 -> 4037 bytes host/trace_streamer/figures/mem_usage.png | Bin 0 -> 30381 bytes .../trace_streamer/figures/process_thread.png | Bin 0 -> 1841 bytes host/trace_streamer/figures/thread_state.png | Bin 0 -> 1906 bytes .../figures/trace_streamer_stream.png | Bin 0 -> 22417 bytes host/trace_streamer/gn/.emscripten | 32 + host/trace_streamer/gn/BUILD.gn | 126 ++ host/trace_streamer/gn/CONFIG.gn | 72 + host/trace_streamer/gn/toolchain/BUILD.gn | 178 ++ host/trace_streamer/gn/wasm.gni | 78 + host/trace_streamer/gn/wasm_vars.gni | 15 + .../libprotobuf_lite_la_SOURCES.pri | 64 + .../buildprotobuf/libprotoc_la_SOURCES.pri | 173 ++ .../prebuilts/buildprotobuf/protobuf.pri | 69 + .../prebuilts/buildprotobuf/protobuf.pro | 35 + .../prebuilts/buildsqlite/sqlite.pro | 31 + host/trace_streamer/prebuilts/protos/BUILD.gn | 100 ++ host/trace_streamer/src/BUILD.gn | 228 +++ host/trace_streamer/src/base/BUILD.gn | 37 + host/trace_streamer/src/base/args_set.h | 44 + host/trace_streamer/src/base/base.pri | 23 + host/trace_streamer/src/base/codec_cov.cpp | 120 ++ host/trace_streamer/src/base/double_map.h | 86 + host/trace_streamer/src/base/file.cpp | 80 + host/trace_streamer/src/base/log.cpp | 22 + .../src/base/parting_string.cpp | 57 + host/trace_streamer/src/base/triple_map.h | 79 + host/trace_streamer/src/base/ts_common.h | 122 ++ host/trace_streamer/src/cfg/cfg.pri | 19 + .../src/cfg/trace_streamer_config.cpp | 989 +++++++++++ .../src/cfg/trace_streamer_config.h | 423 +++++ host/trace_streamer/src/ext/BUILD.gn | 25 + host/trace_streamer/src/ext/sqlite_ext.pri | 16 + .../src/ext/sqlite_ext_funcs.cpp | 54 + .../trace_streamer/src/ext/sqlite_ext_funcs.h | 27 + .../trace_streamer/src/filter/args_filter.cpp | 54 + host/trace_streamer/src/filter/args_filter.h | 43 + .../src/filter/binder_filter.cpp | 294 ++++ .../trace_streamer/src/filter/binder_filter.h | 199 +++ .../src/filter/clock_filter.cpp | 94 + host/trace_streamer/src/filter/clock_filter.h | 77 + host/trace_streamer/src/filter/cpu_filter.cpp | 219 +++ host/trace_streamer/src/filter/cpu_filter.h | 147 ++ host/trace_streamer/src/filter/filter.pri | 43 + .../trace_streamer/src/filter/filter_base.cpp | 27 + host/trace_streamer/src/filter/filter_base.h | 42 + .../src/filter/filter_filter.cpp | 36 + .../trace_streamer/src/filter/filter_filter.h | 36 + host/trace_streamer/src/filter/irq_filter.cpp | 65 + host/trace_streamer/src/filter/irq_filter.h | 58 + .../src/filter/measure_filter.cpp | 89 + .../src/filter/measure_filter.h | 79 + .../src/filter/process_filter.cpp | 219 +++ .../src/filter/process_filter.h | 55 + .../src/filter/slice_filter.cpp | 349 ++++ host/trace_streamer/src/filter/slice_filter.h | 95 ++ .../trace_streamer/src/filter/stat_filter.cpp | 32 + host/trace_streamer/src/filter/stat_filter.h | 42 + .../src/filter/symbols_filter.cpp | 57 + .../src/filter/symbols_filter.h | 40 + .../filter/system_event_measure_filter.cpp | 63 + .../src/filter/system_event_measure_filter.h | 54 + host/trace_streamer/src/include/BUILD.gn | 26 + host/trace_streamer/src/include/codec_cov.h | 39 + host/trace_streamer/src/include/file.h | 42 + host/trace_streamer/src/include/log.h | 66 + .../src/include/parting_string.h | 47 + .../src/include/string_to_numerical.h | 91 + host/trace_streamer/src/main.cpp | 287 ++++ .../src/multi_platform/BUILD.gn | 305 ++++ .../src/multi_platform/global.pri | 75 + .../src/multi_platform/protogen.pri | 107 ++ .../bytrace_parser/bytrace_event_parser.cpp | 509 ++++++ .../bytrace_parser/bytrace_event_parser.h | 79 + .../parser/bytrace_parser/bytrace_parser.cpp | 316 ++++ .../parser/bytrace_parser/bytrace_parser.h | 103 ++ host/trace_streamer/src/parser/common_types.h | 113 ++ .../src/parser/event_parser_base.cpp | 24 + .../src/parser/event_parser_base.h | 38 + .../src/parser/htrace_parser/BUILD.gn | 78 + .../htrace_clock_detail_parser.cpp | 52 + .../htrace_clock_detail_parser.h | 44 + .../htrace_cpu_detail_parser.cpp | 38 + .../htrace_cpu_detail_parser.h | 44 + .../htrace_event_parser.cpp | 671 ++++++++ .../htrace_event_parser/htrace_event_parser.h | 98 ++ .../htrace_parser/htrace_hidump_parser.cpp | 74 + .../htrace_parser/htrace_hidump_parser.h | 44 + .../htrace_parser/htrace_hilog_parser.cpp | 94 + .../htrace_parser/htrace_hilog_parser.h | 51 + .../htrace_parser/htrace_mem_parser.cpp | 863 ++++++++++ .../parser/htrace_parser/htrace_mem_parser.h | 53 + .../htrace_native_hook_parser.cpp | 132 ++ .../htrace_parser/htrace_native_hook_parser.h | 47 + .../parser/htrace_parser/htrace_parser.cpp | 377 ++++ .../src/parser/htrace_parser/htrace_parser.h | 92 + .../htrace_symbols_detail_parser.cpp | 49 + .../htrace_symbols_detail_parser.h | 41 + host/trace_streamer/src/parser/parser.pri | 51 + .../trace_streamer/src/parser/parser_base.cpp | 24 + host/trace_streamer/src/parser/parser_base.h | 37 + .../src/parser/print_event_parser.cpp | 288 ++++ .../src/parser/print_event_parser.h | 49 + .../src/parser/thread_state.cpp | 60 + host/trace_streamer/src/parser/thread_state.h | 71 + host/trace_streamer/src/protos/README_zh.md | 10 + host/trace_streamer/src/protos/protogen.sh | 78 + host/trace_streamer/src/protos/protos.gni | 24 + .../src/protos/services/BUILD.gn | 225 +++ .../src/protos/services/common_types.proto | 58 + .../src/protos/services/plugin_service.proto | 31 + .../services/plugin_service_types.proto | 103 ++ .../protos/services/profiler_service.proto | 44 + .../services/profiler_service_types.proto | 129 ++ .../protos/types/plugins/agent_data/BUILD.gn | 69 + .../agent_data/agent_plugin_app_data.proto | 68 + .../agent_data/agent_plugin_config.proto | 23 + .../agent_data/agent_plugin_energy_data.proto | 201 +++ .../agent_data/agent_plugin_java_heap.proto | 62 + .../agent_plugin_network_data.proto | 64 + .../agent_data/agent_plugin_result.proto | 31 + .../types/plugins/bytrace_plugin/BUILD.gn | 63 + .../bytrace_plugin_config.proto | 24 + .../protos/types/plugins/cpu_data/BUILD.gn | 65 + .../plugins/cpu_data/cpu_plugin_config.proto | 23 + .../plugins/cpu_data/cpu_plugin_result.proto | 71 + .../protos/types/plugins/diskio_data/BUILD.gn | 65 + .../diskio_data/diskio_plugin_config.proto | 23 + .../diskio_data/diskio_plugin_result.proto | 31 + .../protos/types/plugins/ftrace_data/BUILD.gn | 67 + .../plugins/ftrace_data/autogenerated.gni | 48 + .../types/plugins/ftrace_data/binder.proto | 216 +++ .../types/plugins/ftrace_data/block.proto | 179 ++ .../types/plugins/ftrace_data/cgroup.proto | 93 + .../types/plugins/ftrace_data/clk.proto | 96 ++ .../plugins/ftrace_data/compaction.proto | 114 ++ .../types/plugins/ftrace_data/cpuhp.proto | 44 + .../types/plugins/ftrace_data/dma_fence.proto | 76 + .../types/plugins/ftrace_data/ext4.proto | 872 ++++++++++ .../types/plugins/ftrace_data/filelock.proto | 97 ++ .../types/plugins/ftrace_data/filemap.proto | 36 + .../types/plugins/ftrace_data/ftrace.proto | 115 ++ .../plugins/ftrace_data/ftrace_event.proto | 452 +++++ .../types/plugins/ftrace_data/gpio.proto | 34 + .../types/plugins/ftrace_data/i2c.proto | 56 + .../types/plugins/ftrace_data/ipi.proto | 36 + .../types/plugins/ftrace_data/irq.proto | 47 + .../types/plugins/ftrace_data/kmem.proto | 113 ++ .../types/plugins/ftrace_data/net.proto | 185 ++ .../types/plugins/ftrace_data/oom.proto | 27 + .../types/plugins/ftrace_data/pagemap.proto | 34 + .../types/plugins/ftrace_data/power.proto | 175 ++ .../types/plugins/ftrace_data/printk.proto | 25 + .../plugins/ftrace_data/raw_syscalls.proto | 32 + .../types/plugins/ftrace_data/rcu.proto | 25 + .../types/plugins/ftrace_data/sched.proto | 206 +++ .../types/plugins/ftrace_data/signal.proto | 40 + .../types/plugins/ftrace_data/sunrpc.proto | 247 +++ .../types/plugins/ftrace_data/task.proto | 36 + .../types/plugins/ftrace_data/timer.proto | 101 ++ .../ftrace_data/trace_plugin_config.proto | 28 + .../ftrace_data/trace_plugin_result.proto | 80 + .../types/plugins/ftrace_data/v4l2.proto | 140 ++ .../types/plugins/ftrace_data/vmscan.proto | 111 ++ .../types/plugins/ftrace_data/workqueue.proto | 45 + .../types/plugins/ftrace_data/writeback.proto | 288 ++++ .../protos/types/plugins/hidump_data/BUILD.gn | 65 + .../hidump_data/hidump_plugin_config.proto | 20 + .../hidump_data/hidump_plugin_result.proto | 39 + .../protos/types/plugins/hilog_data/BUILD.gn | 65 + .../hilog_data/hilog_plugin_config.proto | 38 + .../hilog_data/hilog_plugin_result.proto | 36 + .../types/plugins/hiperf_call_plugin/BUILD.gn | 63 + .../hiperf_call_plugin_config.proto | 26 + .../protos/types/plugins/memory_data/BUILD.gn | 66 + .../memory_data/memory_plugin_common.proto | 187 ++ .../memory_data/memory_plugin_config.proto | 42 + .../memory_data/memory_plugin_result.proto | 73 + .../protos/types/plugins/native_hook/BUILD.gn | 65 + .../native_hook/native_hook_config.proto | 27 + .../native_hook/native_hook_result.proto | 54 + .../types/plugins/network_data/BUILD.gn | 65 + .../network_data/network_plugin_config.proto | 21 + .../network_data/network_plugin_result.proto | 37 + .../types/plugins/process_data/BUILD.gn | 65 + .../process_data/process_plugin_config.proto | 22 + .../process_data/process_plugin_result.proto | 26 + .../protos/types/plugins/sample_data/BUILD.gn | 65 + .../sample_data/sample_plugin_config.proto | 19 + .../sample_data/sample_plugin_result.proto | 19 + .../protos/types/plugins/stream_data/BUILD.gn | 65 + .../stream_data/stream_plugin_config.proto | 19 + .../stream_data/stream_plugin_result.proto | 19 + host/trace_streamer/src/rpc/http_server.cpp | 450 +++++ host/trace_streamer/src/rpc/http_server.h | 80 + host/trace_streamer/src/rpc/http_socket.cpp | 173 ++ host/trace_streamer/src/rpc/http_socket.h | 51 + host/trace_streamer/src/rpc/rpc.pri | 17 + host/trace_streamer/src/rpc/rpc_server.cpp | 114 ++ host/trace_streamer/src/rpc/rpc_server.h | 42 + host/trace_streamer/src/rpc/wasm_func.cpp | 58 + host/trace_streamer/src/rpc/wasm_func.h | 19 + host/trace_streamer/src/table/args_table.cpp | 73 + host/trace_streamer/src/table/args_table.h | 45 + .../src/table/callstack_table.cpp | 148 ++ .../src/table/callstack_table.h | 44 + .../src/table/clk_event_filter_table.cpp | 75 + .../src/table/clk_event_filter_table.h | 42 + .../src/table/clock_event_filter_table.cpp | 74 + .../src/table/clock_event_filter_table.h | 42 + .../src/table/cpu_measure_filter_table.cpp | 73 + .../src/table/cpu_measure_filter_table.h | 43 + .../src/table/data_dict_table.cpp | 62 + .../src/table/data_dict_table.h | 40 + .../src/table/data_type_table.cpp | 67 + .../src/table/data_type_table.h | 42 + .../trace_streamer/src/table/filter_table.cpp | 69 + host/trace_streamer/src/table/filter_table.h | 45 + .../src/table/heap_frame_table.cpp | 97 ++ .../src/table/heap_frame_table.h | 43 + host/trace_streamer/src/table/heap_table.cpp | 122 ++ host/trace_streamer/src/table/heap_table.h | 43 + .../trace_streamer/src/table/hidump_table.cpp | 62 + host/trace_streamer/src/table/hidump_table.h | 43 + .../src/table/instants_table.cpp | 74 + .../trace_streamer/src/table/instants_table.h | 43 + host/trace_streamer/src/table/irq_table.cpp | 154 ++ host/trace_streamer/src/table/irq_table.h | 44 + host/trace_streamer/src/table/log_table.cpp | 102 ++ host/trace_streamer/src/table/log_table.h | 43 + .../src/table/measure_filter_table.cpp | 74 + .../src/table/measure_filter_table.h | 40 + .../src/table/measure_table.cpp | 70 + host/trace_streamer/src/table/measure_table.h | 44 + host/trace_streamer/src/table/meta_table.cpp | 59 + host/trace_streamer/src/table/meta_table.h | 39 + .../src/table/process_filter_table.cpp | 72 + .../src/table/process_filter_table.h | 43 + .../table/process_measure_filter_table.cpp | 74 + .../src/table/process_measure_filter_table.h | 42 + .../src/table/process_table.cpp | 78 + host/trace_streamer/src/table/process_table.h | 40 + host/trace_streamer/src/table/range_table.cpp | 57 + host/trace_streamer/src/table/range_table.h | 39 + host/trace_streamer/src/table/raw_table.cpp | 84 + host/trace_streamer/src/table/raw_table.h | 44 + .../src/table/sched_slice_table.cpp | 87 + .../src/table/sched_slice_table.h | 44 + host/trace_streamer/src/table/stat_table.cpp | 80 + host/trace_streamer/src/table/stat_table.h | 39 + .../src/table/symbols_table.cpp | 69 + host/trace_streamer/src/table/symbols_table.h | 40 + .../src/table/system_call_table.cpp | 74 + .../src/table/system_call_table.h | 42 + .../src/table/system_event_filter_table.cpp | 67 + .../src/table/system_event_filter_table.h | 42 + host/trace_streamer/src/table/table.pri | 78 + host/trace_streamer/src/table/table_base.cpp | 153 ++ host/trace_streamer/src/table/table_base.h | 85 + .../src/table/thread_filter_table.cpp | 73 + .../src/table/thread_filter_table.h | 41 + .../src/table/thread_state_table.cpp | 88 + .../src/table/thread_state_table.h | 43 + .../trace_streamer/src/table/thread_table.cpp | 110 ++ host/trace_streamer/src/table/thread_table.h | 41 + .../src/trace_data/trace_data.pri | 28 + .../src/trace_data/trace_data_cache.cpp | 95 ++ .../src/trace_data/trace_data_cache.h | 40 + .../src/trace_data/trace_data_cache_base.cpp | 33 + .../src/trace_data/trace_data_cache_base.h | 113 ++ .../trace_data/trace_data_cache_reader.cpp | 165 ++ .../src/trace_data/trace_data_cache_reader.h | 69 + .../trace_data/trace_data_cache_writer.cpp | 230 +++ .../src/trace_data/trace_data_cache_writer.h | 68 + .../src/trace_data/trace_data_db.cpp | 349 ++++ .../src/trace_data/trace_data_db.h | 59 + .../src/trace_data/trace_stdtype.cpp | 695 ++++++++ .../src/trace_data/trace_stdtype.h | 824 +++++++++ host/trace_streamer/src/trace_streamer.pro | 79 + .../src/trace_streamer/trace_streamer.pri | 21 + .../trace_streamer/trace_streamer_filters.cpp | 35 + .../trace_streamer/trace_streamer_filters.h | 66 + .../trace_streamer_selector.cpp | 206 +++ .../trace_streamer/trace_streamer_selector.h | 64 + host/trace_streamer/src/ts.gni | 56 + host/trace_streamer/test/BUILD.gn | 130 ++ host/trace_streamer/test/test_ts.gni | 48 + host/trace_streamer/test/unittest/README.md | 53 + .../test/unittest/binder_filter_test.cpp | 305 ++++ .../test/unittest/bytrace_parser_test.cpp | 242 +++ .../test/unittest/clock_filter_test.cpp | 507 ++++++ .../test/unittest/cpu_filter_test.cpp | 370 ++++ .../test/unittest/event_parser_test.cpp | 1214 +++++++++++++ .../test/unittest/filter_filter_test.cpp | 85 + .../test/unittest/hilog_parser_test.cpp | 353 ++++ .../unittest/htrace_binder_event_test.cpp | 513 ++++++ .../unittest/htrace_event_parser_test.cpp | 609 +++++++ .../test/unittest/htrace_irq_event_test.cpp | 483 ++++++ .../test/unittest/htrace_mem_parser_test.cpp | 255 +++ .../unittest/htrace_sys_mem_parser_test.cpp | 269 +++ .../unittest/htrace_sys_vmem_parser_test.cpp | 275 +++ .../test/unittest/irq_filter_test.cpp | 342 ++++ .../test/unittest/measure_filter_test.cpp | 333 ++++ .../test/unittest/parser_test.cpp | 179 ++ .../test/unittest/process_filter_test.cpp | 389 +++++ .../test/unittest/slice_filter_test.cpp | 414 +++++ 734 files changed, 80732 insertions(+), 75 deletions(-) delete mode 100644 README.en.md delete mode 100644 README.md create mode 100644 README_zh.md create mode 100644 device/device_command/BUILD.gn create mode 100644 device/device_command/ByTrace.cpp create mode 100644 device/device_command/CPU.cpp create mode 100644 device/device_command/DDR.cpp create mode 100644 device/device_command/FPS.cpp create mode 100644 device/device_command/GPU.cpp create mode 100644 device/device_command/Power.cpp create mode 100644 device/device_command/RAM.cpp create mode 100644 device/device_command/README_zh.md create mode 100644 device/device_command/Temperature.cpp create mode 100644 device/device_command/gp_utils.cpp create mode 100644 device/device_command/include/ByTrace.h create mode 100644 device/device_command/include/CPU.h create mode 100644 device/device_command/include/DDR.h create mode 100644 device/device_command/include/FPS.h create mode 100644 device/device_command/include/GPU.h create mode 100644 device/device_command/include/Power.h create mode 100644 device/device_command/include/RAM.h create mode 100644 device/device_command/include/Temperature.h create mode 100644 device/device_command/include/gp_constant.h create mode 100644 device/device_command/include/gp_data.h create mode 100644 device/device_command/include/gp_utils.h create mode 100644 device/device_command/include/profiler.h create mode 100644 device/device_command/include/smartperf_command.h create mode 100644 device/device_command/include/socket_profiler.h create mode 100644 device/device_command/profiler.cpp create mode 100644 device/device_command/smartperf_command.cpp create mode 100644 device/device_command/smartperf_main.cpp create mode 100644 device/device_command/socket_profiler.cpp create mode 100644 device/device_ui/.gitignore create mode 100644 device/device_ui/AppScope/app.json5 create mode 100644 device/device_ui/AppScope/resources/base/element/string.json create mode 100644 device/device_ui/AppScope/resources/base/media/app_icon.png create mode 100644 device/device_ui/build-profile.json5 create mode 100644 device/device_ui/entry/build-profile.json5 create mode 100644 device/device_ui/entry/hvigorfile.js create mode 100644 device/device_ui/entry/package-lock.json create mode 100644 device/device_ui/entry/package.json create mode 100644 device/device_ui/entry/src/main/cpp/CMakeLists.txt create mode 100644 device/device_ui/entry/src/main/cpp/FPS.cpp create mode 100644 device/device_ui/entry/src/main/cpp/FPS.h create mode 100644 device/device_ui/entry/src/main/cpp/RAM.cpp create mode 100644 device/device_ui/entry/src/main/cpp/RAM.h create mode 100644 device/device_ui/entry/src/main/cpp/gp_utils.cpp create mode 100644 device/device_ui/entry/src/main/cpp/gp_utils.h create mode 100644 device/device_ui/entry/src/main/cpp/profiler.cpp create mode 100644 device/device_ui/entry/src/main/cpp/types/libsmartperf/index.d.ts create mode 100644 device/device_ui/entry/src/main/cpp/types/libsmartperf/package.json create mode 100644 device/device_ui/entry/src/main/ets/Application/AbilityStage.ts create mode 100644 device/device_ui/entry/src/main/ets/MainAbility/MainAbility.ts create mode 100644 device/device_ui/entry/src/main/ets/common/FloatWindowComponent.ets create mode 100644 device/device_ui/entry/src/main/ets/common/constant/ConstantSQL.ts create mode 100644 device/device_ui/entry/src/main/ets/common/constant/ConstantsPath.ts create mode 100644 device/device_ui/entry/src/main/ets/common/database/DatabaseUtils.ts create mode 100644 device/device_ui/entry/src/main/ets/common/database/LocalRepository.ts create mode 100644 device/device_ui/entry/src/main/ets/common/entity/DatabaseEntity.ets create mode 100644 device/device_ui/entry/src/main/ets/common/entity/LocalConfigEntity.ts create mode 100644 device/device_ui/entry/src/main/ets/common/entity/SystemEntity.ets create mode 100644 device/device_ui/entry/src/main/ets/common/entity/UserEntity.ets create mode 100644 device/device_ui/entry/src/main/ets/common/profiler/MainWorkTask.ets create mode 100644 device/device_ui/entry/src/main/ets/common/profiler/NativeTaskFun.ts create mode 100644 device/device_ui/entry/src/main/ets/common/profiler/ProfilerTask.ets create mode 100644 device/device_ui/entry/src/main/ets/common/profiler/base/BaseProfiler.ets create mode 100644 device/device_ui/entry/src/main/ets/common/profiler/base/BaseProfilerUtils.ets create mode 100644 device/device_ui/entry/src/main/ets/common/profiler/base/ProfilerConstant.ts create mode 100644 device/device_ui/entry/src/main/ets/common/profiler/base/ProfilerFactory.ets create mode 100644 device/device_ui/entry/src/main/ets/common/profiler/base/SocketProfiler.ets create mode 100644 device/device_ui/entry/src/main/ets/common/profiler/item/CPU.ets create mode 100644 device/device_ui/entry/src/main/ets/common/profiler/item/DDR.ets create mode 100644 device/device_ui/entry/src/main/ets/common/profiler/item/FPS.ets create mode 100644 device/device_ui/entry/src/main/ets/common/profiler/item/GPU.ets create mode 100644 device/device_ui/entry/src/main/ets/common/profiler/item/NetWork.ets create mode 100644 device/device_ui/entry/src/main/ets/common/profiler/item/Power.ets create mode 100644 device/device_ui/entry/src/main/ets/common/profiler/item/RAM.ets create mode 100644 device/device_ui/entry/src/main/ets/common/profiler/item/Thermal.ets create mode 100644 device/device_ui/entry/src/main/ets/common/ui/StartTestTitleComponent.ets create mode 100644 device/device_ui/entry/src/main/ets/common/ui/detail/Summary.ets create mode 100644 device/device_ui/entry/src/main/ets/common/ui/detail/data/DetailCommon.ets create mode 100644 device/device_ui/entry/src/main/ets/common/ui/detail/utils/HandleLostFrame.ts create mode 100644 device/device_ui/entry/src/main/ets/common/ui/floatwindow/FloatWindowConstant.ets create mode 100644 device/device_ui/entry/src/main/ets/common/ui/floatwindow/FloatWindowFun.ts create mode 100644 device/device_ui/entry/src/main/ets/common/ui/floatwindow/utils/FloatWindowUtils.ets create mode 100644 device/device_ui/entry/src/main/ets/common/ui/main/Home.ets create mode 100644 device/device_ui/entry/src/main/ets/common/ui/main/HomeBottomPage.ets create mode 100644 device/device_ui/entry/src/main/ets/common/ui/main/Mine.ets create mode 100644 device/device_ui/entry/src/main/ets/common/ui/main/Report.ets create mode 100644 device/device_ui/entry/src/main/ets/common/ui/main/TopComponent.ets create mode 100644 device/device_ui/entry/src/main/ets/common/utils/AbilityUtils.ts create mode 100644 device/device_ui/entry/src/main/ets/common/utils/BundleMangerUtils.ts create mode 100644 device/device_ui/entry/src/main/ets/common/utils/CSVUtils.ts create mode 100644 device/device_ui/entry/src/main/ets/common/utils/CalculationUtils.ts create mode 100644 device/device_ui/entry/src/main/ets/common/utils/CheckEmptyUtils.ts create mode 100644 device/device_ui/entry/src/main/ets/common/utils/GameUtils.ets create mode 100644 device/device_ui/entry/src/main/ets/common/utils/IOUtils.ts create mode 100644 device/device_ui/entry/src/main/ets/common/utils/JsonUtils.ets create mode 100644 device/device_ui/entry/src/main/ets/common/utils/SPLogger.ts create mode 100644 device/device_ui/entry/src/main/ets/common/utils/StringUtils.ts create mode 100644 device/device_ui/entry/src/main/ets/common/utils/SystemUtils.ets create mode 100644 device/device_ui/entry/src/main/ets/common/utils/TimeUtils.ts create mode 100644 device/device_ui/entry/src/main/ets/pages/AppSelectPage.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/CPU0LineChartPage.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/CPU1LineChartPage.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/CPU2LineChartPage.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/CurrentNowLineChartPage.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/DDRLineChartPage.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/FloatBall.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/FpsLineChartPage.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/GPULineChartPage.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/LightAdjust.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/LoginPage.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/MainPage.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/Question.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/RAMLineChartPage.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/ReportDetail.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/SettingsPage.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/ShellBackTempLineChartPage.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/StartTestPage.ets create mode 100644 device/device_ui/entry/src/main/ets/pages/TitleWindowPage.ets create mode 100644 device/device_ui/entry/src/main/ets/workers/worker.js create mode 100644 device/device_ui/entry/src/main/module.json5 create mode 100644 device/device_ui/entry/src/main/resources/base/element/color.json create mode 100644 device/device_ui/entry/src/main/resources/base/element/string.json create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_about_we.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_average_frame_b.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_back.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_brightness_plus.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_camera.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_close_small.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_counter.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_enter.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_frame_score.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_home_selected.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_home_unselected.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_jank_each_hour.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_jank_score.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_language.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_max_temperature.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_mine_selected.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_mine_unselected.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_net.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_normalized_current.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_report_selected.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_report_unselected.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_screencap.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_test_index.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_test_name.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_upload.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/icon_video.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/logo.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/person.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/question.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/report_upload.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/settings.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/test_apps_count.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/test_session_count.png create mode 100644 device/device_ui/entry/src/main/resources/base/media/test_times_count.png create mode 100644 device/device_ui/entry/src/main/resources/base/profile/main_pages.json create mode 100644 device/device_ui/hvigorfile.js create mode 100644 device/device_ui/package.json create mode 100644 host/doc/compile_smartperf.md create mode 100644 host/doc/compile_trace_streamer.md create mode 100644 host/doc/des_stat.md create mode 100644 host/doc/des_support_event.md create mode 100644 host/doc/des_tables.md create mode 100644 host/doc/quickstart_hiprofiler_cmd.md create mode 100644 host/doc/quickstart_smartperf.md create mode 100644 host/doc/quickstart_trace_streamer.md create mode 100644 host/figures/Scheduling.jpg create mode 100644 host/figures/callstackclick.jpg create mode 100644 host/figures/callstackselect.jpg create mode 100644 host/figures/command.jpg create mode 100644 host/figures/cpu.jpg create mode 100644 host/figures/cpu_frequency.png create mode 100644 host/figures/cpubyprocess.jpg create mode 100644 host/figures/cpubythread.jpg create mode 100644 host/figures/cpuclick.jpg create mode 100644 host/figures/cpusage.jpg create mode 100644 host/figures/dump_and_mem.png create mode 100644 host/figures/excutecommand.jpg create mode 100644 host/figures/filters.png create mode 100644 host/figures/fps.jpg create mode 100644 host/figures/fpsselect.jpg create mode 100644 host/figures/gray.jpg create mode 100644 host/figures/highlit.jpg create mode 100644 host/figures/htrace.jpg create mode 100644 host/figures/log.png create mode 100644 host/figures/main.jpg create mode 100644 host/figures/mem_usage.png create mode 100644 host/figures/opentrace.jpg create mode 100644 host/figures/process.jpg create mode 100644 host/figures/process_thread.png create mode 100644 host/figures/smartperf_framework.png create mode 100644 host/figures/systraceconfig.jpg create mode 100644 host/figures/thread_state.png create mode 100644 host/figures/threadclick.jpg create mode 100644 host/figures/threadinfo.jpg create mode 100644 host/figures/threadselect.jpg create mode 100644 host/figures/time.jpg create mode 100644 host/figures/trace.jpg create mode 100644 host/figures/trace_streamer_stream.png create mode 100644 host/ide/LICENSE create mode 100644 host/ide/README_zh.md create mode 100644 host/ide/build.js create mode 100644 host/ide/package.json create mode 100644 host/ide/server/go.mod create mode 100644 host/ide/server/main.go create mode 100644 host/ide/server/version.txt create mode 100644 host/ide/src/base-ui/BaseElement.ts create mode 100644 host/ide/src/base-ui/button/LitButton.ts create mode 100644 host/ide/src/base-ui/checkbox/LitCheckBox.ts create mode 100644 host/ide/src/base-ui/checkbox/LitCheckBoxWithText.ts create mode 100644 host/ide/src/base-ui/checkbox/LitCheckGroup.ts create mode 100644 host/ide/src/base-ui/icon.svg create mode 100644 host/ide/src/base-ui/icon/LitIcon.ts create mode 100644 host/ide/src/base-ui/menu/LitMainMenu.ts create mode 100644 host/ide/src/base-ui/menu/LitMainMenuGroup.ts create mode 100644 host/ide/src/base-ui/menu/LitMainMenuItem.ts create mode 100644 host/ide/src/base-ui/popover/LitPopContent.ts create mode 100644 host/ide/src/base-ui/popover/LitPopover.ts create mode 100644 host/ide/src/base-ui/popover/LitPopoverTitle.ts create mode 100644 host/ide/src/base-ui/popover/LitPopoverV.ts create mode 100644 host/ide/src/base-ui/progress-bar/LitProgressBar.ts create mode 100644 host/ide/src/base-ui/radiobox/LitRadioBox.ts create mode 100644 host/ide/src/base-ui/radiobox/LitRadioGroup.ts create mode 100644 host/ide/src/base-ui/select/LitSelect.ts create mode 100644 host/ide/src/base-ui/select/LitSelectOption.ts create mode 100644 host/ide/src/base-ui/slider/LitSlider.ts create mode 100644 host/ide/src/base-ui/switch/lit-switch.ts create mode 100644 host/ide/src/base-ui/table/TableRowObject.ts create mode 100644 host/ide/src/base-ui/table/lit-table-column.ts create mode 100644 host/ide/src/base-ui/table/lit-table-group.ts create mode 100644 host/ide/src/base-ui/table/lit-table.ts create mode 100644 host/ide/src/base-ui/tabs/lit-tabpane.ts create mode 100644 host/ide/src/base-ui/tabs/lit-tabs.ts create mode 100644 host/ide/src/base-ui/utils/Template.ts create mode 100644 host/ide/src/icon.svg create mode 100644 host/ide/src/img/copy.png create mode 100644 host/ide/src/img/dark_pic.png create mode 100644 host/ide/src/img/function.png create mode 100644 host/ide/src/img/library.png create mode 100644 host/ide/src/img/logo.png create mode 100644 host/ide/src/img/pic.png create mode 100644 host/ide/src/index.html create mode 100644 host/ide/src/log/Log.ts create mode 100644 host/ide/src/trace/SpApplication.ts create mode 100644 host/ide/src/trace/bean/BaseStruct.ts create mode 100644 host/ide/src/trace/bean/BinderArgBean.ts create mode 100644 host/ide/src/trace/bean/BoxSelection.ts create mode 100644 host/ide/src/trace/bean/CpuFreqStruct.ts create mode 100644 host/ide/src/trace/bean/CpuStruct.ts create mode 100644 host/ide/src/trace/bean/CpuUsage.ts create mode 100644 host/ide/src/trace/bean/FpsStruct.ts create mode 100644 host/ide/src/trace/bean/FuncStruct.ts create mode 100644 host/ide/src/trace/bean/HeapBean.ts create mode 100644 host/ide/src/trace/bean/HeapStruct.ts create mode 100644 host/ide/src/trace/bean/HeapTreeDataBean.ts create mode 100644 host/ide/src/trace/bean/NativeHook.ts create mode 100644 host/ide/src/trace/bean/ProcessMemStruct.ts create mode 100644 host/ide/src/trace/bean/ProcessStruct.ts create mode 100644 host/ide/src/trace/bean/StateProcessThread.ts create mode 100644 host/ide/src/trace/bean/ThreadStruct.ts create mode 100644 host/ide/src/trace/bean/WakeUpTimeBean.ts create mode 100644 host/ide/src/trace/bean/WakeupBean.ts create mode 100644 host/ide/src/trace/component/SpInfoAndStas.ts create mode 100644 host/ide/src/trace/component/SpMetrics.ts create mode 100644 host/ide/src/trace/component/SpQuerySQL.ts create mode 100644 host/ide/src/trace/component/SpRecordTrace.ts create mode 100644 host/ide/src/trace/component/SpRecyclerSystemTrace.ts create mode 100644 host/ide/src/trace/component/SpSystemTrace.ts create mode 100644 host/ide/src/trace/component/SpWelcomePage.ts create mode 100644 host/ide/src/trace/component/Sptext.ts create mode 100644 host/ide/src/trace/component/StackBar.ts create mode 100644 host/ide/src/trace/component/setting/SpAllocations.ts create mode 100644 host/ide/src/trace/component/setting/SpCheckDesBox.ts create mode 100644 host/ide/src/trace/component/setting/SpProbesConfig.ts create mode 100644 host/ide/src/trace/component/setting/SpRecordSetting.ts create mode 100644 host/ide/src/trace/component/setting/SpTraceCommand.ts create mode 100644 host/ide/src/trace/component/setting/bean/ProfilerServiceTypes.ts create mode 100644 host/ide/src/trace/component/setting/utils/PluginConvertUtils.ts create mode 100644 host/ide/src/trace/component/trace/TimerShaftElement.ts create mode 100644 host/ide/src/trace/component/trace/base/ColorUtils.ts create mode 100644 host/ide/src/trace/component/trace/base/RangeSelect.ts create mode 100644 host/ide/src/trace/component/trace/base/TraceRow.ts create mode 100644 host/ide/src/trace/component/trace/base/TraceRowObject.ts create mode 100644 host/ide/src/trace/component/trace/base/TraceRowRecyclerView.ts create mode 100644 host/ide/src/trace/component/trace/base/TraceSheet.ts create mode 100644 host/ide/src/trace/component/trace/base/Utils.ts create mode 100644 host/ide/src/trace/component/trace/search/Search.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneBoxChild.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneContextSwitch.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneCounter.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneCpu.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneCpuByProcess.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneCpuByThread.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneCpuUsage.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneFilter.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneFps.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneHeap.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneNMCallInfo.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneNMSampleList.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneNMStatstics.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneNMemory.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPanePTS.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneSPT.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneSlices.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneThreadStates.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabPaneThreadSwitch.ts create mode 100644 host/ide/src/trace/component/trace/sheet/TabProgressBar.ts create mode 100644 host/ide/src/trace/component/trace/timer-shaft/Flag.ts create mode 100644 host/ide/src/trace/component/trace/timer-shaft/Graph.ts create mode 100644 host/ide/src/trace/component/trace/timer-shaft/RangeRuler.ts create mode 100644 host/ide/src/trace/component/trace/timer-shaft/Rect.ts create mode 100644 host/ide/src/trace/component/trace/timer-shaft/SportRuler.ts create mode 100644 host/ide/src/trace/component/trace/timer-shaft/TabPaneFlag.ts create mode 100644 host/ide/src/trace/component/trace/timer-shaft/TimeRuler.ts create mode 100644 host/ide/src/trace/database/Procedure.ts create mode 100644 host/ide/src/trace/database/ProcedureWorker.ts create mode 100644 host/ide/src/trace/database/ProcedureWorkerCPU.ts create mode 100644 host/ide/src/trace/database/ProcedureWorkerCommon.ts create mode 100644 host/ide/src/trace/database/ProcedureWorkerFPS.ts create mode 100644 host/ide/src/trace/database/ProcedureWorkerFreq.ts create mode 100644 host/ide/src/trace/database/ProcedureWorkerFunc.ts create mode 100644 host/ide/src/trace/database/ProcedureWorkerHeap.ts create mode 100644 host/ide/src/trace/database/ProcedureWorkerMem.ts create mode 100644 host/ide/src/trace/database/ProcedureWorkerProcess.ts create mode 100644 host/ide/src/trace/database/ProcedureWorkerThread.ts create mode 100644 host/ide/src/trace/database/ProcedureWorkerTimeline.ts create mode 100644 host/ide/src/trace/database/SqlLite.ts create mode 100644 host/ide/src/trace/database/SqlLiteWorker.ts create mode 100644 host/ide/src/trace/database/TempSql.ts create mode 100644 host/ide/src/trace/database/TraceWorker.ts create mode 100644 host/ide/src/trace/grpc/HiProfilerClient.ts create mode 100644 host/ide/src/trace/grpc/ProfilerClient.ts create mode 100644 host/ide/src/trace/grpc/ProfilerController.ts create mode 100644 host/ide/src/trace/proto/common_types.proto create mode 100644 host/ide/src/trace/proto/profiler_service.proto create mode 100644 host/ide/src/trace/proto/profiler_service_types.proto create mode 100644 host/ide/test/base-ui/button/LitButton.test.ts create mode 100644 host/ide/test/base-ui/checkbox/LitCheckBox.test.ts create mode 100644 host/ide/test/base-ui/checkbox/LitCheckBoxWithText.test.ts create mode 100644 host/ide/test/base-ui/checkbox/LitCheckGroup.test.ts create mode 100644 host/ide/test/base-ui/icon/LitIcon.test.ts create mode 100644 host/ide/test/base-ui/menu/LitMainMenu.test.ts create mode 100644 host/ide/test/base-ui/menu/LitMainMenuGroup.test.ts create mode 100644 host/ide/test/base-ui/menu/LitMainMenuItem.test.ts create mode 100644 host/ide/test/base-ui/popover/LitPopContent.test.ts create mode 100644 host/ide/test/base-ui/popover/LitPopover.test.ts create mode 100644 host/ide/test/base-ui/progress-bar/LitProgressBar.test.ts create mode 100644 host/ide/test/base-ui/radiobox/LitRadioBox.test.ts create mode 100644 host/ide/test/base-ui/slider/LitSlider.test.ts create mode 100644 host/ide/test/base-ui/switch/LitSwitch.test.ts create mode 100644 host/ide/test/base-ui/table/LitTable.test.ts create mode 100644 host/ide/test/base-ui/table/LitTableColumn.test.ts create mode 100644 host/ide/test/base-ui/table/LitTableGroup.test.ts create mode 100644 host/ide/test/base-ui/tabs/LitTabpane.test.ts create mode 100644 host/ide/test/base-ui/tabs/LitTabs.test.ts create mode 100644 host/ide/test/trace/SpApplication.test.ts create mode 100644 host/ide/test/trace/bean/BoxSelection.test.ts create mode 100644 host/ide/test/trace/bean/CpuFreqStruct.test.ts create mode 100644 host/ide/test/trace/bean/CpuStruct.test.ts create mode 100644 host/ide/test/trace/bean/CpuUsage.test.ts create mode 100644 host/ide/test/trace/bean/FpsStruct.test.ts create mode 100644 host/ide/test/trace/bean/FuncStruct.test.ts create mode 100644 host/ide/test/trace/bean/HeapStruct.test.ts create mode 100644 host/ide/test/trace/bean/ProcessMemStruct.test.ts create mode 100644 host/ide/test/trace/bean/ProcessStruct.test.ts create mode 100644 host/ide/test/trace/bean/StateProcessThread.test.ts create mode 100644 host/ide/test/trace/bean/ThreadStruct.test.ts create mode 100644 host/ide/test/trace/component/FrameChart.test.ts create mode 100644 host/ide/test/trace/component/SpRecordTrace.test.ts create mode 100644 host/ide/test/trace/component/SpRecyclerSystemTrace.test.ts create mode 100644 host/ide/test/trace/component/SpSystemTrace.test.ts create mode 100644 host/ide/test/trace/component/StackBar.test.ts create mode 100644 host/ide/test/trace/component/setting/SpAllocations.test.ts create mode 100644 host/ide/test/trace/component/setting/SpProbesConfig.test.ts create mode 100644 host/ide/test/trace/component/setting/SpRecordSetting.test.ts create mode 100644 host/ide/test/trace/component/setting/SpTraceCommand.test.ts create mode 100644 host/ide/test/trace/component/setting/utils/PluginConvertUtils.test.ts create mode 100644 host/ide/test/trace/component/trace/TimerShaftElement.test.ts create mode 100644 host/ide/test/trace/component/trace/base/ColorUtils.test.ts create mode 100644 host/ide/test/trace/component/trace/base/RangeSelect.test.ts create mode 100644 host/ide/test/trace/component/trace/base/TraceRow.test.ts create mode 100644 host/ide/test/trace/component/trace/base/TraceRowObject.test.ts create mode 100644 host/ide/test/trace/component/trace/base/TraceRowRecyclerView.test.ts create mode 100644 host/ide/test/trace/component/trace/base/TraceSheet.test.ts create mode 100644 host/ide/test/trace/component/trace/base/Utils.test.ts create mode 100644 host/ide/test/trace/component/trace/sheet/TabPaneBoxChild.test.ts create mode 100644 host/ide/test/trace/component/trace/sheet/TabPaneContextSwitch.test.ts create mode 100644 host/ide/test/trace/component/trace/sheet/TabPaneCounter.test.ts create mode 100644 host/ide/test/trace/component/trace/sheet/TabPaneCpu.test.ts create mode 100644 host/ide/test/trace/component/trace/sheet/TabPaneCpuByProcess.test.ts create mode 100644 host/ide/test/trace/component/trace/sheet/TabPaneCpuByThread.test.ts create mode 100644 host/ide/test/trace/component/trace/sheet/TabPaneCpuUsage.test.ts create mode 100644 host/ide/test/trace/component/trace/sheet/TabPaneCurrentSelection.test.ts create mode 100644 host/ide/test/trace/component/trace/sheet/TabPanePTS.test.ts create mode 100644 host/ide/test/trace/component/trace/sheet/TabPaneSPT.test.ts create mode 100644 host/ide/test/trace/component/trace/sheet/TabPaneSlices.test.ts create mode 100644 host/ide/test/trace/component/trace/sheet/TabPaneThreadStates.test.ts create mode 100644 host/ide/test/trace/component/trace/sheet/TabPaneThreadSwitch.test.ts create mode 100644 host/ide/test/trace/component/trace/timer-shaft/RangeRuler.test.ts create mode 100644 host/ide/test/trace/component/trace/timer-shaft/Rect.test.ts create mode 100644 host/ide/test/trace/component/trace/timer-shaft/SportRuler.test.ts create mode 100644 host/ide/test/trace/component/trace/timer-shaft/TabPaneFlag.test.ts create mode 100644 host/ide/test/trace/component/trace/timer-shaft/TimeRuler.test.ts create mode 100644 host/ide/test/trace/database/Procedure.test.ts create mode 100644 host/ide/test/trace/grpc/HiProfilerClient.test.ts create mode 100644 host/ide/test/trace/grpc/ProfilerClient.test.ts create mode 100644 host/ide/tsconfig.json create mode 100644 host/trace_streamer/.gn create mode 100644 host/trace_streamer/BUILD.gn create mode 100644 host/trace_streamer/README.md create mode 100644 host/trace_streamer/build.sh create mode 100644 host/trace_streamer/build/ohos.gni create mode 100644 host/trace_streamer/build/protoc.sh create mode 100644 host/trace_streamer/build/test.gni create mode 100644 host/trace_streamer/doc/README.md create mode 100644 host/trace_streamer/doc/des_compile_trace_streamer.md create mode 100644 host/trace_streamer/doc/des_stat.md create mode 100644 host/trace_streamer/doc/des_support_eventlist.md create mode 100644 host/trace_streamer/doc/des_tables.md create mode 100644 host/trace_streamer/figures/cpu_frequency.png create mode 100644 host/trace_streamer/figures/dump_and_mem.png create mode 100644 host/trace_streamer/figures/filters.png create mode 100644 host/trace_streamer/figures/log.png create mode 100644 host/trace_streamer/figures/mem_usage.png create mode 100644 host/trace_streamer/figures/process_thread.png create mode 100644 host/trace_streamer/figures/thread_state.png create mode 100644 host/trace_streamer/figures/trace_streamer_stream.png create mode 100644 host/trace_streamer/gn/.emscripten create mode 100644 host/trace_streamer/gn/BUILD.gn create mode 100644 host/trace_streamer/gn/CONFIG.gn create mode 100644 host/trace_streamer/gn/toolchain/BUILD.gn create mode 100644 host/trace_streamer/gn/wasm.gni create mode 100644 host/trace_streamer/gn/wasm_vars.gni create mode 100644 host/trace_streamer/prebuilts/buildprotobuf/libprotobuf_lite_la_SOURCES.pri create mode 100644 host/trace_streamer/prebuilts/buildprotobuf/libprotoc_la_SOURCES.pri create mode 100644 host/trace_streamer/prebuilts/buildprotobuf/protobuf.pri create mode 100644 host/trace_streamer/prebuilts/buildprotobuf/protobuf.pro create mode 100644 host/trace_streamer/prebuilts/buildsqlite/sqlite.pro create mode 100644 host/trace_streamer/prebuilts/protos/BUILD.gn create mode 100644 host/trace_streamer/src/BUILD.gn create mode 100644 host/trace_streamer/src/base/BUILD.gn create mode 100644 host/trace_streamer/src/base/args_set.h create mode 100644 host/trace_streamer/src/base/base.pri create mode 100644 host/trace_streamer/src/base/codec_cov.cpp create mode 100644 host/trace_streamer/src/base/double_map.h create mode 100644 host/trace_streamer/src/base/file.cpp create mode 100644 host/trace_streamer/src/base/log.cpp create mode 100644 host/trace_streamer/src/base/parting_string.cpp create mode 100644 host/trace_streamer/src/base/triple_map.h create mode 100644 host/trace_streamer/src/base/ts_common.h create mode 100644 host/trace_streamer/src/cfg/cfg.pri create mode 100644 host/trace_streamer/src/cfg/trace_streamer_config.cpp create mode 100644 host/trace_streamer/src/cfg/trace_streamer_config.h create mode 100644 host/trace_streamer/src/ext/BUILD.gn create mode 100644 host/trace_streamer/src/ext/sqlite_ext.pri create mode 100644 host/trace_streamer/src/ext/sqlite_ext_funcs.cpp create mode 100644 host/trace_streamer/src/ext/sqlite_ext_funcs.h create mode 100644 host/trace_streamer/src/filter/args_filter.cpp create mode 100644 host/trace_streamer/src/filter/args_filter.h create mode 100644 host/trace_streamer/src/filter/binder_filter.cpp create mode 100644 host/trace_streamer/src/filter/binder_filter.h create mode 100644 host/trace_streamer/src/filter/clock_filter.cpp create mode 100644 host/trace_streamer/src/filter/clock_filter.h create mode 100644 host/trace_streamer/src/filter/cpu_filter.cpp create mode 100644 host/trace_streamer/src/filter/cpu_filter.h create mode 100644 host/trace_streamer/src/filter/filter.pri create mode 100644 host/trace_streamer/src/filter/filter_base.cpp create mode 100644 host/trace_streamer/src/filter/filter_base.h create mode 100644 host/trace_streamer/src/filter/filter_filter.cpp create mode 100644 host/trace_streamer/src/filter/filter_filter.h create mode 100644 host/trace_streamer/src/filter/irq_filter.cpp create mode 100644 host/trace_streamer/src/filter/irq_filter.h create mode 100644 host/trace_streamer/src/filter/measure_filter.cpp create mode 100644 host/trace_streamer/src/filter/measure_filter.h create mode 100644 host/trace_streamer/src/filter/process_filter.cpp create mode 100644 host/trace_streamer/src/filter/process_filter.h create mode 100644 host/trace_streamer/src/filter/slice_filter.cpp create mode 100644 host/trace_streamer/src/filter/slice_filter.h create mode 100644 host/trace_streamer/src/filter/stat_filter.cpp create mode 100644 host/trace_streamer/src/filter/stat_filter.h create mode 100644 host/trace_streamer/src/filter/symbols_filter.cpp create mode 100644 host/trace_streamer/src/filter/symbols_filter.h create mode 100644 host/trace_streamer/src/filter/system_event_measure_filter.cpp create mode 100644 host/trace_streamer/src/filter/system_event_measure_filter.h create mode 100644 host/trace_streamer/src/include/BUILD.gn create mode 100644 host/trace_streamer/src/include/codec_cov.h create mode 100644 host/trace_streamer/src/include/file.h create mode 100644 host/trace_streamer/src/include/log.h create mode 100644 host/trace_streamer/src/include/parting_string.h create mode 100644 host/trace_streamer/src/include/string_to_numerical.h create mode 100644 host/trace_streamer/src/main.cpp create mode 100644 host/trace_streamer/src/multi_platform/BUILD.gn create mode 100644 host/trace_streamer/src/multi_platform/global.pri create mode 100644 host/trace_streamer/src/multi_platform/protogen.pri create mode 100644 host/trace_streamer/src/parser/bytrace_parser/bytrace_event_parser.cpp create mode 100644 host/trace_streamer/src/parser/bytrace_parser/bytrace_event_parser.h create mode 100644 host/trace_streamer/src/parser/bytrace_parser/bytrace_parser.cpp create mode 100644 host/trace_streamer/src/parser/bytrace_parser/bytrace_parser.h create mode 100644 host/trace_streamer/src/parser/common_types.h create mode 100644 host/trace_streamer/src/parser/event_parser_base.cpp create mode 100644 host/trace_streamer/src/parser/event_parser_base.h create mode 100644 host/trace_streamer/src/parser/htrace_parser/BUILD.gn create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_clock_detail_parser.cpp create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_clock_detail_parser.h create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_cpu_parser/htrace_cpu_detail_parser.cpp create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_cpu_parser/htrace_cpu_detail_parser.h create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_event_parser/htrace_event_parser.cpp create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_event_parser/htrace_event_parser.h create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_hidump_parser.cpp create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_hidump_parser.h create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_hilog_parser.cpp create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_hilog_parser.h create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_mem_parser.cpp create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_mem_parser.h create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_native_hook_parser.cpp create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_native_hook_parser.h create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_parser.cpp create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_parser.h create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_symbols_detail_parser.cpp create mode 100644 host/trace_streamer/src/parser/htrace_parser/htrace_symbols_detail_parser.h create mode 100644 host/trace_streamer/src/parser/parser.pri create mode 100644 host/trace_streamer/src/parser/parser_base.cpp create mode 100644 host/trace_streamer/src/parser/parser_base.h create mode 100644 host/trace_streamer/src/parser/print_event_parser.cpp create mode 100644 host/trace_streamer/src/parser/print_event_parser.h create mode 100644 host/trace_streamer/src/parser/thread_state.cpp create mode 100644 host/trace_streamer/src/parser/thread_state.h create mode 100644 host/trace_streamer/src/protos/README_zh.md create mode 100644 host/trace_streamer/src/protos/protogen.sh create mode 100644 host/trace_streamer/src/protos/protos.gni create mode 100644 host/trace_streamer/src/protos/services/BUILD.gn create mode 100644 host/trace_streamer/src/protos/services/common_types.proto create mode 100644 host/trace_streamer/src/protos/services/plugin_service.proto create mode 100644 host/trace_streamer/src/protos/services/plugin_service_types.proto create mode 100644 host/trace_streamer/src/protos/services/profiler_service.proto create mode 100644 host/trace_streamer/src/protos/services/profiler_service_types.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/agent_data/BUILD.gn create mode 100644 host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_app_data.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_config.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_energy_data.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_java_heap.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_network_data.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_result.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/bytrace_plugin/BUILD.gn create mode 100644 host/trace_streamer/src/protos/types/plugins/bytrace_plugin/bytrace_plugin_config.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/cpu_data/BUILD.gn create mode 100644 host/trace_streamer/src/protos/types/plugins/cpu_data/cpu_plugin_config.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/cpu_data/cpu_plugin_result.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/diskio_data/BUILD.gn create mode 100644 host/trace_streamer/src/protos/types/plugins/diskio_data/diskio_plugin_config.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/diskio_data/diskio_plugin_result.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/BUILD.gn create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/autogenerated.gni create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/binder.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/block.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/cgroup.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/clk.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/compaction.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/cpuhp.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/dma_fence.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/ext4.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/filelock.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/filemap.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/ftrace.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/ftrace_event.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/gpio.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/i2c.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/ipi.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/irq.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/kmem.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/net.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/oom.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/pagemap.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/power.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/printk.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/raw_syscalls.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/rcu.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/sched.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/signal.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/sunrpc.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/task.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/timer.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/trace_plugin_config.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/trace_plugin_result.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/v4l2.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/vmscan.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/workqueue.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/ftrace_data/writeback.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/hidump_data/BUILD.gn create mode 100644 host/trace_streamer/src/protos/types/plugins/hidump_data/hidump_plugin_config.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/hidump_data/hidump_plugin_result.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/hilog_data/BUILD.gn create mode 100644 host/trace_streamer/src/protos/types/plugins/hilog_data/hilog_plugin_config.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/hilog_data/hilog_plugin_result.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/hiperf_call_plugin/BUILD.gn create mode 100644 host/trace_streamer/src/protos/types/plugins/hiperf_call_plugin/hiperf_call_plugin_config.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/memory_data/BUILD.gn create mode 100644 host/trace_streamer/src/protos/types/plugins/memory_data/memory_plugin_common.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/memory_data/memory_plugin_config.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/memory_data/memory_plugin_result.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/native_hook/BUILD.gn create mode 100644 host/trace_streamer/src/protos/types/plugins/native_hook/native_hook_config.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/native_hook/native_hook_result.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/network_data/BUILD.gn create mode 100644 host/trace_streamer/src/protos/types/plugins/network_data/network_plugin_config.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/network_data/network_plugin_result.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/process_data/BUILD.gn create mode 100644 host/trace_streamer/src/protos/types/plugins/process_data/process_plugin_config.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/process_data/process_plugin_result.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/sample_data/BUILD.gn create mode 100644 host/trace_streamer/src/protos/types/plugins/sample_data/sample_plugin_config.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/sample_data/sample_plugin_result.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/stream_data/BUILD.gn create mode 100644 host/trace_streamer/src/protos/types/plugins/stream_data/stream_plugin_config.proto create mode 100644 host/trace_streamer/src/protos/types/plugins/stream_data/stream_plugin_result.proto create mode 100644 host/trace_streamer/src/rpc/http_server.cpp create mode 100644 host/trace_streamer/src/rpc/http_server.h create mode 100644 host/trace_streamer/src/rpc/http_socket.cpp create mode 100644 host/trace_streamer/src/rpc/http_socket.h create mode 100644 host/trace_streamer/src/rpc/rpc.pri create mode 100644 host/trace_streamer/src/rpc/rpc_server.cpp create mode 100644 host/trace_streamer/src/rpc/rpc_server.h create mode 100644 host/trace_streamer/src/rpc/wasm_func.cpp create mode 100644 host/trace_streamer/src/rpc/wasm_func.h create mode 100644 host/trace_streamer/src/table/args_table.cpp create mode 100644 host/trace_streamer/src/table/args_table.h create mode 100644 host/trace_streamer/src/table/callstack_table.cpp create mode 100644 host/trace_streamer/src/table/callstack_table.h create mode 100644 host/trace_streamer/src/table/clk_event_filter_table.cpp create mode 100644 host/trace_streamer/src/table/clk_event_filter_table.h create mode 100644 host/trace_streamer/src/table/clock_event_filter_table.cpp create mode 100644 host/trace_streamer/src/table/clock_event_filter_table.h create mode 100644 host/trace_streamer/src/table/cpu_measure_filter_table.cpp create mode 100644 host/trace_streamer/src/table/cpu_measure_filter_table.h create mode 100644 host/trace_streamer/src/table/data_dict_table.cpp create mode 100644 host/trace_streamer/src/table/data_dict_table.h create mode 100644 host/trace_streamer/src/table/data_type_table.cpp create mode 100644 host/trace_streamer/src/table/data_type_table.h create mode 100644 host/trace_streamer/src/table/filter_table.cpp create mode 100644 host/trace_streamer/src/table/filter_table.h create mode 100644 host/trace_streamer/src/table/heap_frame_table.cpp create mode 100644 host/trace_streamer/src/table/heap_frame_table.h create mode 100644 host/trace_streamer/src/table/heap_table.cpp create mode 100644 host/trace_streamer/src/table/heap_table.h create mode 100644 host/trace_streamer/src/table/hidump_table.cpp create mode 100644 host/trace_streamer/src/table/hidump_table.h create mode 100644 host/trace_streamer/src/table/instants_table.cpp create mode 100644 host/trace_streamer/src/table/instants_table.h create mode 100644 host/trace_streamer/src/table/irq_table.cpp create mode 100644 host/trace_streamer/src/table/irq_table.h create mode 100644 host/trace_streamer/src/table/log_table.cpp create mode 100644 host/trace_streamer/src/table/log_table.h create mode 100644 host/trace_streamer/src/table/measure_filter_table.cpp create mode 100644 host/trace_streamer/src/table/measure_filter_table.h create mode 100644 host/trace_streamer/src/table/measure_table.cpp create mode 100644 host/trace_streamer/src/table/measure_table.h create mode 100644 host/trace_streamer/src/table/meta_table.cpp create mode 100644 host/trace_streamer/src/table/meta_table.h create mode 100644 host/trace_streamer/src/table/process_filter_table.cpp create mode 100644 host/trace_streamer/src/table/process_filter_table.h create mode 100644 host/trace_streamer/src/table/process_measure_filter_table.cpp create mode 100644 host/trace_streamer/src/table/process_measure_filter_table.h create mode 100644 host/trace_streamer/src/table/process_table.cpp create mode 100644 host/trace_streamer/src/table/process_table.h create mode 100644 host/trace_streamer/src/table/range_table.cpp create mode 100644 host/trace_streamer/src/table/range_table.h create mode 100644 host/trace_streamer/src/table/raw_table.cpp create mode 100644 host/trace_streamer/src/table/raw_table.h create mode 100644 host/trace_streamer/src/table/sched_slice_table.cpp create mode 100644 host/trace_streamer/src/table/sched_slice_table.h create mode 100644 host/trace_streamer/src/table/stat_table.cpp create mode 100644 host/trace_streamer/src/table/stat_table.h create mode 100644 host/trace_streamer/src/table/symbols_table.cpp create mode 100644 host/trace_streamer/src/table/symbols_table.h create mode 100644 host/trace_streamer/src/table/system_call_table.cpp create mode 100644 host/trace_streamer/src/table/system_call_table.h create mode 100644 host/trace_streamer/src/table/system_event_filter_table.cpp create mode 100644 host/trace_streamer/src/table/system_event_filter_table.h create mode 100644 host/trace_streamer/src/table/table.pri create mode 100644 host/trace_streamer/src/table/table_base.cpp create mode 100644 host/trace_streamer/src/table/table_base.h create mode 100644 host/trace_streamer/src/table/thread_filter_table.cpp create mode 100644 host/trace_streamer/src/table/thread_filter_table.h create mode 100644 host/trace_streamer/src/table/thread_state_table.cpp create mode 100644 host/trace_streamer/src/table/thread_state_table.h create mode 100644 host/trace_streamer/src/table/thread_table.cpp create mode 100644 host/trace_streamer/src/table/thread_table.h create mode 100644 host/trace_streamer/src/trace_data/trace_data.pri create mode 100644 host/trace_streamer/src/trace_data/trace_data_cache.cpp create mode 100644 host/trace_streamer/src/trace_data/trace_data_cache.h create mode 100644 host/trace_streamer/src/trace_data/trace_data_cache_base.cpp create mode 100644 host/trace_streamer/src/trace_data/trace_data_cache_base.h create mode 100644 host/trace_streamer/src/trace_data/trace_data_cache_reader.cpp create mode 100644 host/trace_streamer/src/trace_data/trace_data_cache_reader.h create mode 100644 host/trace_streamer/src/trace_data/trace_data_cache_writer.cpp create mode 100644 host/trace_streamer/src/trace_data/trace_data_cache_writer.h create mode 100644 host/trace_streamer/src/trace_data/trace_data_db.cpp create mode 100644 host/trace_streamer/src/trace_data/trace_data_db.h create mode 100644 host/trace_streamer/src/trace_data/trace_stdtype.cpp create mode 100644 host/trace_streamer/src/trace_data/trace_stdtype.h create mode 100644 host/trace_streamer/src/trace_streamer.pro create mode 100644 host/trace_streamer/src/trace_streamer/trace_streamer.pri create mode 100644 host/trace_streamer/src/trace_streamer/trace_streamer_filters.cpp create mode 100644 host/trace_streamer/src/trace_streamer/trace_streamer_filters.h create mode 100644 host/trace_streamer/src/trace_streamer/trace_streamer_selector.cpp create mode 100644 host/trace_streamer/src/trace_streamer/trace_streamer_selector.h create mode 100644 host/trace_streamer/src/ts.gni create mode 100644 host/trace_streamer/test/BUILD.gn create mode 100644 host/trace_streamer/test/test_ts.gni create mode 100644 host/trace_streamer/test/unittest/README.md create mode 100644 host/trace_streamer/test/unittest/binder_filter_test.cpp create mode 100644 host/trace_streamer/test/unittest/bytrace_parser_test.cpp create mode 100644 host/trace_streamer/test/unittest/clock_filter_test.cpp create mode 100644 host/trace_streamer/test/unittest/cpu_filter_test.cpp create mode 100644 host/trace_streamer/test/unittest/event_parser_test.cpp create mode 100644 host/trace_streamer/test/unittest/filter_filter_test.cpp create mode 100644 host/trace_streamer/test/unittest/hilog_parser_test.cpp create mode 100644 host/trace_streamer/test/unittest/htrace_binder_event_test.cpp create mode 100644 host/trace_streamer/test/unittest/htrace_event_parser_test.cpp create mode 100644 host/trace_streamer/test/unittest/htrace_irq_event_test.cpp create mode 100644 host/trace_streamer/test/unittest/htrace_mem_parser_test.cpp create mode 100644 host/trace_streamer/test/unittest/htrace_sys_mem_parser_test.cpp create mode 100644 host/trace_streamer/test/unittest/htrace_sys_vmem_parser_test.cpp create mode 100644 host/trace_streamer/test/unittest/irq_filter_test.cpp create mode 100644 host/trace_streamer/test/unittest/measure_filter_test.cpp create mode 100644 host/trace_streamer/test/unittest/parser_test.cpp create mode 100644 host/trace_streamer/test/unittest/process_filter_test.cpp create mode 100644 host/trace_streamer/test/unittest/slice_filter_test.cpp diff --git a/README.en.md b/README.en.md deleted file mode 100644 index 5ceb97d..0000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# smartperf - -#### Description -{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**} - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md deleted file mode 100644 index 2782415..0000000 --- a/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# smartperf - -#### 介绍 -{**以下是 Gitee 平台说明,您可以替换此简介** -Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 -无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} - -#### 软件架构 -软件架构说明 - - -#### 安装教程 - -1. xxxx -2. xxxx -3. xxxx - -#### 使用说明 - -1. xxxx -2. xxxx -3. xxxx - -#### 参与贡献 - -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request - - -#### 特技 - -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 0000000..5c4be42 --- /dev/null +++ b/README_zh.md @@ -0,0 +1,62 @@ +# SmartPerf + +- [简介](#简介) +- [目录](#目录) +- [约束](#约束) +- [相关文档](#相关文档) + +## 简介 + SmartPerf是一款基于OpenHarmony系统开发的性能功耗测试分析工具,支持两个组成部分: SP-Device端和SP-Host端。 + SP-Device端:是一款初级的,粗粒度的数据采集分析的性能功耗测试工具。支持FPS、功耗、热、Soc信息的实时采集、实时展示、数据报告生成。 + SP-Host端: 是一款深入挖掘数据、细粒度的展示数据的性能功耗测试工具。支持CPU调度、频点、进程线程时间片、堆内存、FPS数据采集和展示。支持在泳道图中展示非实时的采集数据,支持GUI操作数据分析。 + + +其主要的结构如下图所示: + +![系统架构图](http://image.huawei.com/tiny-lts/v1/images/83a570e4c58496abb6b1e6bbe36da9d8_660x541.png) + + +## 目录 +``` +/developtools/smartperf +├── device # device端 相关代码 +│   ├── device_command # device端 命令行方式采集代码 +│ ├── device_ui # device端 GUI方式采集代码 +├── host # host端 相关代码 +│ ├── doc # host端 相关使用文档 +│ ├── ide # host端 IDE 模块目录 +│   │ └── src # 主机测调优模块代码 +│ │ │ ├── base-ui # 基础组件目录 +│ │ │ └── Trace # 业务逻辑目录 +│ ├── trace_streamer # 解析模块代码目录 +│   │ ├── base # 基础功能 +│ │ ├── cfg # 配置目录 +│ │ ├── filter # Filter功能 +│ │ ├── include # Include头文件 +│ │ ├── multi_platform # 平台适配 +│ │ ├── parser # 解析业务逻辑 +│ │ │ ├── bytrace_parser # byTrace解析业务逻辑 +│ │ │ └── htrace_parser # hTrace 解析业务逻辑 +│ │ ├── table # 表结构 +│ │ ├── trace_data # trace 结构 +│ │ ├── trace_streamer # traceStreamer 结构 +│ │ │   └── kits # js/napi接口存放目录 +``` + + + +## 约束 +- 语言版本 + - C++11或以上 + - TypeScript 4.2.3 + + +## 相关文档 +- [quickstart_trace_streamer](https://gitee.com/openharmony/developtools_profiler/blob/master/host/smartperf/doc/quickstart_trace_streamer.md) +- [quickstart_smartperf](https://gitee.com/openharmony/developtools_profiler/blob/master/host/smartperf/doc/quickstart_smartperf.md) +- [quickstart_hiprofiler_cmd](https://gitee.com/openharmony/developtools_profiler/blob/master/host/smartperf/doc/quickstart_hiprofiler_cmd.md) +- [compile_smartperf](https://gitee.com/openharmony/developtools_profiler/blob/master/host/smartperf/doc/compile_smartperf.md) +- [compile_trace_streamer](https://gitee.com/openharmony/developtools_profiler/blob/master/host/smartperf/doc/compile_trace_streamer.md) +- [des_stat](https://gitee.com/openharmony/developtools_profiler/blob/master/host/smartperf/doc/des_stat.md) +- [des_support_event](https://gitee.com/openharmony/developtools_profiler/blob/master/host/smartperf/doc/des_support_event.md) +- [des_tables](https://gitee.com/openharmony/developtools_profiler/blob/master/host/smartperf/doc/des_tables.md) \ No newline at end of file diff --git a/device/device_command/BUILD.gn b/device/device_command/BUILD.gn new file mode 100644 index 0000000..2975184 --- /dev/null +++ b/device/device_command/BUILD.gn @@ -0,0 +1,59 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../../build/config.gni") + +## Build so {{{ +config("config") { + visibility = [ ":*" ] + + cflags = [ + "-Wall", + "-Werror", + "-g3", + "-Wunused-variable", + ] +} + +config("public_config") { +} + +ohos_executable("SP_daemon") { + sources = [ + "ByTrace.cpp", + "CPU.cpp", + "DDR.cpp", + "FPS.cpp", + "GPU.cpp", + "Power.cpp", + "RAM.cpp", + "Temperature.cpp", + "gp_utils.cpp", + "profiler.cpp", + "smartperf_command.cpp", + "smartperf_main.cpp", + "socket_profiler.cpp", + ] + include_dirs = [ + "//developtools/profiler/host/smartperf/client/client_command/include", + "//utils/native/base/include", + ] + configs = [ ":config" ] + + deps = [ "//utils/native/base:utils" ] + + subsystem_name = "${OHOS_PROFILER_SUBSYS_NAME}" + part_name = "${OHOS_PROFILER_PART_NAME}" +} +## Build so }}} diff --git a/device/device_command/ByTrace.cpp b/device/device_command/ByTrace.cpp new file mode 100644 index 0000000..f790429 --- /dev/null +++ b/device/device_command/ByTrace.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include "include/ByTrace.h" + +namespace OHOS { +namespace SmartPerf { +void ByTrace::thread_get_trace() +{ + std::stringstream sstream; + sstream << "bytrace --trace_begin --overwrite"; + std::string cmd_trace = sstream.str(); + GPUtils::readFile(cmd_trace); +} +void ByTrace::thread_finish_trace(std::string &pathName) +{ + std::stringstream sstream; + sstream << "bytrace --trace_finish"; + const std::string &cmd_trace_finish = sstream.str(); + GPUtils::readFile(cmd_trace_finish); + sstream.str(""); + sstream << "bytrace --overwrite sched ace app disk ohos graphic sync workq ability > /data/mynewtrace"; + sstream << pathName; + sstream << "s.ftrace"; + const std::string &cmd_trace_overwrite = sstream.str(); + GPUtils::readFile(cmd_trace_overwrite); +} + +TraceStatus ByTrace::init_trace(bool isStart) +{ + if (isStart) { + return TRACE_START; + } else { + return TRACE_NO; + } +} + +TraceStatus ByTrace::check_fps_jitters(std::vector jitters, int curProfilerNum) +{ + if (curNum <= sum) { + for (size_t i = 0; i < jitters.size(); i++) { + long long normalJitter = jitters[i] / 1e6; + if (normalJitter > threshold) { + if ((flagProfilerNum != -1) && curProfilerNum < (flagProfilerNum + interval)) { + // 如果不是第一次抓取 并且 小于抓取周期间隔 则放弃抓取 + return TRACE_NO; + } + curNum++; + flagProfilerNum = curProfilerNum; + std::cout << "***************************************************************************" << std::endl; + std::cout << "***************************************************************************" << std::endl; + std::cout << "************* ByTrace::getTrace:curJitter:" << normalJitter << "*******************" << + std::endl; + std::cout << "***************************************************************************" << std::endl; + std::cout << "***************************************************************************" << std::endl; + return TRACE_FINISH; + } + } + } + return TRACE_NO; +} +} +} diff --git a/device/device_command/CPU.cpp b/device/device_command/CPU.cpp new file mode 100644 index 0000000..0f8d556 --- /dev/null +++ b/device/device_command/CPU.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include "securec.h" +#include "include/CPU.h" +namespace OHOS { +namespace SmartPerf { +int CPU::get_cpu_num() +{ + char cpu_node[128]; + int cpu_num = 0; + while (true) { + if (snprintf_s(cpu_node, sizeof(cpu_node), sizeof(cpu_node), "%s/cpu%d", CPU_BASE_PATH.c_str(), cpu_num) > 0) { + if (access(cpu_node, F_OK) == -1) { + break; + } + } + ++cpu_num; + } + return m_cpu_num = cpu_num; +} +int CPU::get_cpu_freq(int cpu_id) +{ + char buffer[128]; + if (access(CPU_SCALING_CUR_FREQ(cpu_id).c_str(), F_OK) == -1) { + return -1; + } + FILE *fp = fopen(CPU_SCALING_CUR_FREQ(cpu_id).c_str(), "r"); + if (fp == nullptr) { + return -1; + } + buffer[0] = '\0'; + while (fgets(buffer, sizeof(buffer), fp) == nullptr) { + std::cout << "fgets fail"; + } + if (fclose(fp) == EOF) { + return EOF; + } + return atoi(buffer); +} +std::vector CPU::get_cpu_load() +{ + if (m_cpu_num <= 0) { + std::vector workload; + return workload; + } + std::vector workload; + + static char pre_buffer[10][256] = { + "\0", + "\0", + "\0", + "\0", + "\0", + "\0", + "\0", + "\0", + "\0", + "\0", + }; + if (access(PROC_STAT.c_str(), F_OK) == -1) { + return workload; + } + FILE *fp = fopen(PROC_STAT.c_str(), "r"); + if (fp == nullptr) { + for (int i = 0; i <= m_cpu_num; ++i) { + workload.push_back(-1.0f); + } + return workload; + } + char buffer[1024]; + buffer[0] = '\0'; + int line = 0; + while (fgets(buffer, sizeof(buffer), fp) != nullptr) { + const int zeroPos = 0; + const int firstPos = 1; + const int secondPos = 2; + const int length = 3; + if (strlen(buffer) >= length && buffer[zeroPos] == 'c' && buffer[firstPos] == 'p' && buffer[secondPos] == 'u') { + float b = cac_workload(buffer, pre_buffer[line]); + workload.push_back(b); + if (snprintf_s(pre_buffer[line], sizeof(pre_buffer[line]), sizeof(pre_buffer[line]), "%s", buffer) < 0) { + std::cout << "snprintf_s fail"; + } + } + ++line; + + if (line >= m_cpu_num + 1) { + break; + } + } + if (fclose(fp) == EOF) { + return workload; + } + + return workload; +} + +float CPU::cac_workload(const char *buffer, const char *pre_buffer) +{ + const size_t default_index = 4; + const size_t default_shift = 10; + const char default_start = '0'; + const char default_end = '9'; + + size_t pre_len = strlen(pre_buffer); + size_t len = strlen(buffer); + if (pre_len == 0 || len == 0) { + return -1.0f; + } + size_t time[10]; + size_t pre_time[10]; + size_t cnt = 0; + + for (size_t i = default_index; i < len; ++i) { + size_t tmp = 0; + if (buffer[i] < default_start || buffer[i] > default_end) { + continue; + } + while (buffer[i] >= default_start && buffer[i] <= default_end) { + tmp = tmp * default_shift + (buffer[i] - default_start); + i++; + } + time[cnt++] = tmp; + } + + size_t pre_cnt = 0; + for (size_t i = default_index; i < pre_len; ++i) { + size_t tmp = 0; + if (pre_buffer[i] < default_start || pre_buffer[i] > default_end) { + continue; + } + while (pre_buffer[i] >= default_start && pre_buffer[i] <= default_end) { + tmp = tmp * default_shift + (pre_buffer[i] - default_start); + i++; + } + pre_time[pre_cnt++] = tmp; + } + + size_t user = time[0] + time[1] - pre_time[0] - pre_time[1]; + size_t sys = time[2] - pre_time[2]; + size_t idle = time[3] - pre_time[3]; + size_t iowait = time[4] - pre_time[4]; + size_t irq = time[5] + time[6] - pre_time[5] - pre_time[6]; + size_t total = user + sys + idle + iowait + irq; + + if (user < 0 || sys < 0 || idle < 0 || iowait < 0 || irq < 0) { + return 0.0f; + } + + double per_user = std::atof(std::to_string(user * 100.0 / total).c_str()); + double per_sys = std::atof(std::to_string(sys * 100.0 / total).c_str()); + double per_iowait = std::atof(std::to_string(iowait * 100.0 / total).c_str()); + double per_irq = std::atof(std::to_string(irq * 100.0 / total).c_str()); + + double workload = per_user + per_sys + per_iowait + per_irq; + + return static_cast(workload); +} +} +} diff --git a/device/device_command/DDR.cpp b/device/device_command/DDR.cpp new file mode 100644 index 0000000..80997f9 --- /dev/null +++ b/device/device_command/DDR.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "include/DDR.h" +namespace OHOS { +namespace SmartPerf { +long long DDR::get_ddr_freq() +{ + long long curFreq; + std::string ddr_freq = GPUtils::freadFile(std::string(ddr_cur_freq_path.c_str())); + curFreq = std::atoll(ddr_freq.c_str()); + return curFreq; +} +} +} diff --git a/device/device_command/FPS.cpp b/device/device_command/FPS.cpp new file mode 100644 index 0000000..93f1236 --- /dev/null +++ b/device/device_command/FPS.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include "securec.h" +#include "include/gp_utils.h" +#include "include/FPS.h" +namespace OHOS { + namespace SmartPerf { + void FPS::setPackageName(std::string pkgName) + { + pkg_name = std::move(pkgName); + std::vector sps; + GPUtils::mSplit(pkg_name, ".", sps); + if (sps.size() > 0) { + cur_layer_name = std::string(""); + std::string cur_layer = sps[2]; + char cmd[100]; + if (snprintf_s(cmd, sizeof(cmd), sizeof(cmd), + "hidumper -s 10 |grep surface |grep %s", cur_layer.c_str()) < 0) { + std::cout << "snprintf fail"; + } + std::string layer_line = GPUtils::readFile(cmd); + int flag = 0; + for (size_t i = 0; i < layer_line.size(); i++) { + if (layer_line[i] == ']') { + flag = 0; + break; + } + if (flag) { + cur_layer_name += layer_line[i]; + } + if (layer_line[i] == '[') { + flag = 1; + } + } + } + } + FpsInfo FPS::getFpsInfo(int is_video, int is_camera) + { + FpsInfo fpsInfoMax; + fpsInfoMax.fps = -1; + + std::string layerName; + if (is_video) { + layerName = std::string("RosenRenderTexture"); + } else if (is_camera) { + layerName = std::string("RosenRenderXComponent"); + } else { + std::vector sps; + GPUtils::mSplit(this->pkg_name, ".", sps); + std::string addEndChar = "0"; + const int pNameLastPos = 2; + layerName = std::string(sps[pNameLastPos].c_str() + addEndChar); + } + FpsInfo fpsInfo = GetSurfaceFrame(layerName); + if (fpsInfo.fps > fpsInfoMax.fps) { + fpsInfoMax = fpsInfo; + } + return fpsInfoMax; + } + FpsInfo FPS::GetSurfaceFrame(std::string name) + { + if (name == "") { + return FpsInfo(); + } + static std::map fps_map; + if (fps_map.count(name) == 0) { + FpsInfo tmp; + tmp.fps = 0; + tmp.pre_fps = 0; + fps_map[name] = tmp; + } + FpsInfo &fpsInfo = fps_map[name]; + fpsInfo.fps = 0; + FILE *fp; + static char tmp[1024]; + std::string cmd = "hidumper -s 10 -a \"fps " + name + "\""; + fp = popen(cmd.c_str(), "r"); + if (fp == nullptr) { + return fpsInfo; + } + long long MOD = 1e9; + long long lastReadyTime = -1; + int fps_gb = 0; + if (!(fpsInfo.time_stamp_q).empty()) { + lastReadyTime = (fpsInfo.time_stamp_q).back(); + } + bool jump = false; + bool refresh = false; + + int cnt = 0; + int zeroNum = 0; + while (fgets(tmp, sizeof(tmp), fp) != nullptr) { + long long frameReadyTime = 0; + std::stringstream sstream; + sstream << tmp; + sstream >> frameReadyTime; + cnt++; + if (frameReadyTime == 0) { + zeroNum++; + continue; + } + if (lastReadyTime >= frameReadyTime) { + lastReadyTime = -1; + continue; + } + refresh = true; + long long t_frameReadyTime = frameReadyTime / MOD; + long long t_lastReadyTime = lastReadyTime / MOD; + long long lastFrame = -1; + if (t_frameReadyTime == t_lastReadyTime) { + (fpsInfo.time_stamp_q).push(frameReadyTime); + } else if (t_frameReadyTime == t_lastReadyTime + 1) { + jump = true; + lastFrame = fpsInfo.last_frame_ready_time; + lastReadyTime = frameReadyTime; + int fps_tmp = 0; + fpsInfo.jitters.clear(); + while (!(fpsInfo.time_stamp_q).empty()) { + fps_tmp++; + long long currFrame = (fpsInfo.time_stamp_q.front()); + if (lastFrame != -1) { + long long jitter = currFrame - lastFrame; + fpsInfo.jitters.push_back(jitter); + } + lastFrame = currFrame; + (fpsInfo.time_stamp_q).pop(); + } + + fps_gb = fps_tmp; + + (fpsInfo.time_stamp_q).push(frameReadyTime); + + fpsInfo.last_frame_ready_time = lastFrame; + } else if (t_frameReadyTime > t_lastReadyTime + 1) { + jump = true; + lastReadyTime = frameReadyTime; + + while (!(fpsInfo.time_stamp_q).empty()) { + (fpsInfo.time_stamp_q).pop(); + } + + (fpsInfo.time_stamp_q).push(frameReadyTime); + } + } + + pclose(fp); + const int maxZeroNum = 120; + if (zeroNum >= maxZeroNum) { + while (!(fpsInfo.time_stamp_q.empty())) { + fpsInfo.time_stamp_q.pop(); + } + fpsInfo.fps = 0; + return fpsInfo; + } + const int minPrintLine = 5; + if (cnt < minPrintLine) { + fpsInfo.fps = fpsInfo.pre_fps; + return fpsInfo; + } + + if (fps_gb > 0) { + fpsInfo.fps = fps_gb; + fpsInfo.pre_fps = fps_gb; + return fpsInfo; + } else if (refresh && !jump) { + fpsInfo.fps = fpsInfo.pre_fps; + return fpsInfo; + } else { + fpsInfo.fps = 0; + return fpsInfo; + } + } + } +} diff --git a/device/device_command/GPU.cpp b/device/device_command/GPU.cpp new file mode 100644 index 0000000..4c13a7b --- /dev/null +++ b/device/device_command/GPU.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "include/GPU.h" + +namespace OHOS { + namespace SmartPerf { + void GPU::init_gpu_node() + { + for (int i = 0; i < sizeof(GPU_CUR_FREQ_PATH) / sizeof(const char *); ++i) { + if (GPUtils::canOpen(GPU_CUR_FREQ_PATH[i])) { + gpu_cur_freq_path = std::string(GPU_CUR_FREQ_PATH[i]); + } + } + + for (int i = 0; i < sizeof(GPU_CUR_WORKLOAD_PATH) / sizeof(const char *); ++i) { + if (GPUtils::canOpen(GPU_CUR_WORKLOAD_PATH[i])) { + gpu_cur_load_path = std::string(GPU_CUR_WORKLOAD_PATH[i]); + } + } + } + int GPU::get_gpu_freq() + { + std::string gpu_freq = GPUtils::readFile(std::string(gpu_cur_freq_path.c_str())); + return atoi(gpu_freq.c_str()); + } + float GPU::calc_workload(const char *buffer) const + { + std::vector sps; + std::string buffer_line = buffer; + GPUtils::mSplit(buffer, "@", sps); + if (sps.size() > 0) { + // rk3568 + float loadRk = std::stof(sps[0]); + return loadRk; + } else { + // wgr + float loadWgr = std::stof(buffer); + return loadWgr; + } + return -1.0; + } + float GPU::get_gpu_load() + { + static char buffer[128]; + if (access(gpu_cur_load_path.c_str(), F_OK) == -1) { + return -1.0; + } + FILE *fp = fopen(gpu_cur_load_path.c_str(), "r"); + if (fp == nullptr) { + return EOF; + } + buffer[0] = '\0'; + while (fgets(buffer, sizeof(buffer), fp) == nullptr) { + std::cout << "fgets fail"; + } + if (fclose(fp) == EOF) { + return -1.0; + } + return calc_workload(buffer); + } + } +} diff --git a/device/device_command/Power.cpp b/device/device_command/Power.cpp new file mode 100644 index 0000000..6c10b6c --- /dev/null +++ b/device/device_command/Power.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include "securec.h" +#include "include/gp_utils.h" +#include "include/Power.h" + +namespace OHOS { +namespace SmartPerf { +void Power::init_power() +{ + for (int i = 0; i < sizeof(power_path) / sizeof(const char *); ++i) { + if (GPUtils::canOpen(std::string(power_path[i]))) { + power_base_path = std::string(power_path[i]); + } + } + + char powerNode[256]; + for (int j = 0; j < sizeof(default_collect_power_info) / sizeof(const char *); ++j) { + if (snprintf_s(powerNode, sizeof(powerNode), sizeof(powerNode), "%s/%s", power_base_path.c_str(), + default_collect_power_info[j]) < 0) { + std::cout << "snprintf fail"; + } + // file exists + std::string type = std::string(default_collect_power_info[j]); + if (power_node_path_map.count(type) > 0) { + continue; + } + power_node_path_map[type] = std::string(powerNode); + } +} + +std::map Power::getPowerMap() +{ + std::map power_map; + FILE *fp = nullptr; + char buffer[256]; + std::map::iterator iter; + int charging = 1; + for (iter = power_node_path_map.begin(); iter != power_node_path_map.end(); ++iter) { + std::string type = iter->first; + std::string powerNode = power_node_path_map[type]; + if (access(powerNode.c_str(), F_OK) == -1) { + continue; + } + fp = fopen(powerNode.c_str(), "r"); + if (fp == nullptr) { + power_map[type] = "-1.0"; + continue; + } + buffer[0] = '\0'; + while (fgets(buffer, sizeof(buffer), fp) == nullptr) { + std::cout << "fgets fail"; + } + if (fclose(fp) == EOF) { + std::cout << "fclose fail"; + } + std::string power_value = std::string(buffer); + if (iter->first == "status") { + if (power_value.find("Charging") == std::string::npos && power_value.find("Full") == std::string::npos) { + charging = 0; + } + if (power_value.find("Discharging") != std::string::npos) { + charging = 0; + } + } else if (iter->first == "enable_hiz") { + if (strcmp(buffer, "1") == 0) { + charging = 0; + } + if (std::stoi(power_value) == 1) { + charging = 0; + } + } else if (iter->first == "current_now") { + // 若current now 大于 100000 单位归一化为 1000,大于10000 单位归一化为100,大于3000 单位归一化为10 + double tmp = fabs(std::stof(power_value)); + power_value = std::to_string(tmp); + } else if (iter->first == "voltage_now") { + // 若voltage now 大于100 单位归一化为100 + double tmp = std::stof(power_value); + power_value = std::to_string(tmp); + } + power_map[type] = power_value; + } + if (power_map.count("voltage_now") == 0 && power_map.count("bat_id_0") > 0 && power_map.count("bat_id_1") > 0) { + power_map["voltage_now"] = std::to_string(std::stof(power_map["bat_id_0"]) + std::stof(power_map["bat_id_1"])); + } + if (power_map.count("current_now") > 0 && charging) { + power_map["current_now"] = "-" + power_map["current_now"]; + } + if (power_map.count("status") > 0 && charging == 0) { + power_map["status"] = "Discharging"; + } + return power_map; +} +} +} \ No newline at end of file diff --git a/device/device_command/RAM.cpp b/device/device_command/RAM.cpp new file mode 100644 index 0000000..653182a --- /dev/null +++ b/device/device_command/RAM.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include "securec.h" +#include "include/gp_utils.h" +#include "include/RAM.h" + +namespace OHOS { +namespace SmartPerf { +void RAM::setPkgName(std::string ss) +{ + pkgName = std::move(ss); +} + +std::map RAM::getRamInfo(std::string pkg_name, int pid) +{ + std::map ramInfo; + ramInfo["pss"] = "-1"; + std::string pid_value = ""; + if (pid > 0) { + pid_value = std::to_string(pid); + } else { + char pidStr[100]; + if (snprintf_s(pidStr, sizeof(pidStr), sizeof(pidStr), + "ps -ef |grep -w %s", pkg_name.c_str()) < 0) { + std::cout << "snprintf_s fail"; + } + std::string pidLine = GPUtils::readFile(pidStr); + std::vector sps; + GPUtils::mSplit(pidLine, " ", sps); + if (sps.size() > 0) { + pid_value = sps[1]; + } + } + + std::string tmp = "-1"; + if (atoi(pid_value.c_str()) > 0) { + char ram[50]; + if (snprintf_s(ram, sizeof(ram), sizeof(ram), "/proc/%s/smaps_rollup", pid_value.c_str()) < 0) { + std::cout << "snprintf fail"; + } + std::string path = ram; + FILE *fp; + if (access(path.c_str(), F_OK) == -1) { + return ramInfo; + } + if ((fp = fopen(path.c_str(), "r")) != nullptr) { + char s[1024]; + s[0] = '\0'; + while (fgets(s, sizeof(s), fp) != nullptr) { + const int zeroPos = 0; + const int firstPos = 1; + const int secondPos = 2; + const int thirdPos = 3; + if (s[zeroPos] == 'P' && s[firstPos] == 's' && s[secondPos] == 's' && s[thirdPos] == ':') { + tmp += std::string(s); + } + } + } + if (fp != nullptr) { + pclose(fp); + } + } + ramInfo["pss"] = GPUtils::getNumber(tmp); + return ramInfo; +} +} +} diff --git a/device/device_command/README_zh.md b/device/device_command/README_zh.md new file mode 100644 index 0000000..c9e0b9c --- /dev/null +++ b/device/device_command/README_zh.md @@ -0,0 +1,102 @@ +## 工具介绍 + +> OpenHarmony性能测试工具,通过采集设备性能指标,对采集数据进行实时展示、导出csv。 + +## 支持功能 + +> 当前版本支持如下功能 + +- 支持RK3568、Hi3516; +- 支持Shell启动; +- 支持采集整机CPU、GPU、DDR、POWER、TEMPERATURE、应用的FPS、RAM; + +## 使用方式 +>1、首先检查系统是否默认预制了SP_daemon,如打印如下日志,系统已内置SP_daemon + +```bash +C:\>hdc_std shell +SP_daemon --help +usage: SP_daemon +-------------------------------------------------------------------- +These are common commands list: + -N set num of profiler + -PKG set pkgname of profiler + -PID set process id of profiler + -OUT set output path of CSV + -c get cpuFreq and cpuLoad + -g get gpuFreq and gpuLoad + -f get fps and fps jitters + -t get soc-temp gpu-temp .. + -p get current_now and voltage_now + -r get ram(pss) + -snapshot get screen capture +-------------------------------------------------------------------- +Example: SP_daemon -N 20 -PKG ohos.samples.ecg -c -g -t -p -f +-------------------------------------------------------------------- +command exec finished! +``` +>2、执行示例命令:SP_daemon -N 20 -PKG ohos.samples.ecg -c -g -t -p -f +``` +----------------------------------Print START------------------------------------ +order:0 cpu0freq=1992000 +order:1 cpu0load=23.469387 +order:2 cpu1freq=1992000 +order:3 cpu1load=26.262627 +order:4 cpu2freq=1992000 +order:5 cpu2load=19.000000 +order:6 cpu3freq=1992000 +order:7 cpu3load=74.747475 +order:8 current_now=-1000.000000 +order:9 gpu-thermal=48333.000000 +order:10 gpufreq=200000000 +order:11 gpuload=0.000000 +order:12 soc-thermal=48888.000000 +order:13 timestamp=1501925596847 +order:14 voltage_now=4123456.000000 +----------------------------------Print END-------------------------------------- +----------------------------------Print START------------------------------------ +order:0 cpu0freq=1992000 +order:1 cpu0load=33.673470 +order:2 cpu1freq=1992000 +order:3 cpu1load=19.801979 +order:4 cpu2freq=1992000 +order:5 cpu2load=37.755100 +order:6 cpu3freq=1992000 +order:7 cpu3load=55.555553 +order:8 current_now=-1000.000000 +order:9 gpu-thermal=48333.000000 +order:10 gpufreq=200000000 +order:11 gpuload=0.000000 +order:12 soc-thermal=48888.000000 +order:13 timestamp=1501925597848 +order:14 voltage_now=4123456.000000 +----------------------------------Print END-------------------------------------- +``` +>3、执行完毕后会在data/local/tmp生成data.csv文件,每次执行命令覆盖写入 +```bash +# cat /data/local/tmp/data.csv +cpu0freq,cpu0load,cpu1freq,cpu1load,cpu2freq,cpu2load,cpu3freq,cpu3load,current_now,gpu-thermal,gpufreq,gpuload,soc-thermal,timestamp,voltage_now +1992000,-1.000000,1992000,-1.000000,1992000,-1.000000,1992000,-1.000000,-1000.000000,48333.000000,200000000,0.000000,49444.000000,1501925677010,4123456.000000 +1992000,16.326530,1992000,22.680412,1992000,62.626263,1992000,41.836735,-1000.000000,48333.000000,200000000,0.000000,48888.000000,1501925678011,4123456.000000 +1992000,16.326530,1992000,35.353535,1992000,50.505051,1992000,42.857143,-1000.000000,48333.000000,200000000,0.000000,49444.000000,1501925679013,4123456.000000 +``` +--- + +## 参数说明 + +| 命令 | 功能 |是否必选| +| :-----| :--------------------- |:-----| +| -N | 设置采集次数 |是| +| -PKG | 设置包名 |否| +| -PID | 设置进程pid(对于ram适用) |否| +| -c | 是否采集cpu |否| +| -g | 是否采集gpu |否| +| -f | 是否采集fps |否| +| -t | 是否采集温度 |否| +| -p | 是否采集电流 |否| +| -r | 是否采集内存(需指定进程pid) |否| +| -snapshot | 是否截图 |否| + +--- +## 构建方式 +>1、在OpenHarmony系统根目录执行全量编译命令(RK3568为例): ./build.sh --product-name rk3568 --ccache
diff --git a/device/device_command/Temperature.cpp b/device/device_command/Temperature.cpp new file mode 100644 index 0000000..f7149b6 --- /dev/null +++ b/device/device_command/Temperature.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include "securec.h" +#include "include/Temperature.h" +namespace OHOS { +namespace SmartPerf { +void Temperature::init_temperature() +{ + int cnt = sizeof(thermal_path) / sizeof(const char *); + for (int i = 0; i < cnt; ++i) { + if (GPUtils::canOpen(std::string(thermal_path[i]) + "/thermal_zone1/temp")) { + thermal_base_path = std::string(thermal_path[i]); + init_thermal_node(); + } + } +} + +void Temperature::init_thermal_node() +{ + char typeNode[256]; + char tempNode[256]; + char buffer[256]; + FILE *fp = nullptr; + const int zoneTravelNum = 100; + for (int zone = 0; zone < zoneTravelNum; ++zone) { + if (snprintf_s(typeNode, sizeof(typeNode), sizeof(typeNode), "%s/thermal_zone%d/type", + thermal_base_path.c_str(), zone) < 0) { + std::cout << "snprintf_s fail"; + } + if (access(typeNode, F_OK) == -1) { + continue; + } + fp = fopen(typeNode, "r"); + if (fp == nullptr) { + continue; + } + buffer[0] = '\0'; + while (fgets(buffer, sizeof(buffer), fp) == nullptr) { + std::cout << "fgets fail"; + } + if (fclose(fp) == EOF) { + std::cout << "fclose fail"; + } + + if (strlen(buffer) == 0) { + continue; + } + + std::string type = std::string(buffer); + if (collect_nodes.count(type) == 0) { + continue; + } + if (snprintf_s(tempNode, sizeof(tempNode), sizeof(tempNode), "%s/thermal_zone%d/temp", + thermal_base_path.c_str(), zone) < 0) { + std::cout << "snprintf_s fail"; + } + thermal_node_path_map[type] = std::string(tempNode); + } +} + +std::map Temperature::getThermalMap() +{ + std::map thermal_map; + + FILE *fp = nullptr; + char buffer[256]; + std::map::iterator iter; + for (iter = thermal_node_path_map.begin(); iter != thermal_node_path_map.end(); ++iter) { + std::string type = iter->first; + std::string tempNode = thermal_node_path_map[type]; + fp = fopen(tempNode.c_str(), "r"); + if (fp == nullptr) { + thermal_map[type] = -1.0f; + continue; + } + buffer[0] = '\0'; + while (fgets(buffer, sizeof(buffer), fp) == nullptr) { + std::cout << "fgets fail"; + } + float temp = std::fabs(atof(buffer)); + if (fclose(fp) == EOF) { + std::cout << "fclose fail"; + } + if (strlen(buffer) == 0) { + thermal_map[type] = -1.0f; + continue; + } + thermal_map[type] = temp; + } + return thermal_map; +} +} +} diff --git a/device/device_command/gp_utils.cpp b/device/device_command/gp_utils.cpp new file mode 100644 index 0000000..e2954ee --- /dev/null +++ b/device/device_command/gp_utils.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include "securec.h" +#include "include/gp_utils.h" + +namespace OHOS { + namespace SmartPerf { + void GPUtils::mSplit(const std::string &content, const std::string &sp, std::vector &out) + { + size_t index = 0; + while (index != std::string::npos) { + size_t t_end = content.find_first_of(sp, index); + std::string tmp = content.substr(index, t_end - index); + if (tmp != "" && tmp != " ") { + out.push_back(tmp); + } + if (t_end == std::string::npos) { + break; + } + index = t_end + 1; + } + } + + bool GPUtils::canOpen(const std::string &path) + { + if (access(path.c_str(), F_OK) == -1) { + return false; + } + FILE *fp = fopen(path.c_str(), "r"); + if (fp == nullptr) { + return false; + } + if (fclose(fp) == EOF) { + return false; + } + return true; + } + + // popen + std::string GPUtils::readFile(const std::string &cmd) + { + const int buffLengh = 1024; + std::string res = "NA"; + FILE *fp = popen(cmd.c_str(), "r"); + char line[buffLengh]; + line[0] = '\0'; + while (fgets(line, buffLengh, fp) != nullptr) { + res = std::string(line); + } + + if (pclose(fp) == EOF) { + return ""; + } + return res; + } + + // fopen + std::string GPUtils::freadFile(const std::string &path) + { + std::string res = "NA"; + const int buffLengh = 1024; + if (access(path.c_str(), F_OK) == -1) { + return res; + } + FILE *fp; + if ((fp = fopen(path.c_str(), "r")) != nullptr) { + char s[buffLengh]; + s[0] = '\0'; + while (fgets(s, sizeof(s), fp) != nullptr) { + res += std::string(s); + } + } + if (fp != nullptr) { + fclose(fp); + } + return res; + } + + // get number_str from str + std::string GPUtils::getNumber(const std::string &str) + { + int cntInt = 0; + const int shift = 10; + + for (int i = 0; str[i] != '\0'; ++i) { + if (str[i] >= '0' && str[i] <= '9') { + cntInt *= shift; + cntInt += str[i] - '0'; + } + } + return std::to_string(cntInt); + } + + // wirte to csv by path + void GPUtils::writeCsv(const std::string &path, std::vector &vmap) + { + std::ofstream outFile; + char realPath[PATH_MAX + 1] = {0x00}; + if (strlen(path.c_str()) > PATH_MAX || realpath(path.c_str(), realPath) == NULL) { + return; + } + + outFile.open(path.c_str(), std::ios::out); + int i = 0; + std::string title = ""; + for (GPData gpdata : vmap) { + std::map::iterator iter; + std::string line_content = ""; + for (iter = gpdata.values.begin(); iter != gpdata.values.end(); ++iter) { + if (i == 0) { + title += iter->first + ","; + } + line_content += iter->second + ","; + } + if (i == 0) { + title.pop_back(); + outFile << title << std::endl; + } + line_content.pop_back(); + outFile << line_content << std::endl; + ++i; + } + outFile.close(); + } + } +} diff --git a/device/device_command/include/ByTrace.h b/device/device_command/include/ByTrace.h new file mode 100644 index 0000000..3c5c749 --- /dev/null +++ b/device/device_command/include/ByTrace.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef BY_TRACE_H +#define BY_TRACE_H +#include "gp_utils.h" +#include "singleton.h" +namespace OHOS { +namespace SmartPerf { +enum TraceStatus { + TRACE_START, + TRACE_FINISH, + TRACE_NO +}; +class ByTrace : public DelayedSingleton { +public: + // 开始抓trace线程 + void thread_get_trace(); + // 结束抓trace线程 + void thread_finish_trace(std::string &pathName); + // 初始化抓取 + TraceStatus init_trace(bool isStart); + // 校验fps-jitters + TraceStatus check_fps_jitters(std::vector jitters, int curProfilerNum); + // 抓trace总次数 默认2次 + int sum = 2; + // 当前触发的次数 + int curNum = 0; + +private: + // 抓trace间隔 默认60s + int interval = 60; + // 当前触发标记次数 + int flagProfilerNum = -1; + // 抓trace触发条件:默认 某一帧的某个jitter>100 ms触发 + long long threshold = 100; +}; +} +} +#endif \ No newline at end of file diff --git a/device/device_command/include/CPU.h b/device/device_command/include/CPU.h new file mode 100644 index 0000000..e6172a3 --- /dev/null +++ b/device/device_command/include/CPU.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CPU_H +#define CPU_H +#include +#include +#include +#include "gp_utils.h" +#include "singleton.h" + + +namespace OHOS { +namespace SmartPerf { +class CPU : public DelayedSingleton { +public: + int get_cpu_num(); + int get_cpu_freq(int cpu_id); + std::vector get_cpu_load(); + +private: + + const std::string CPU_BASE_PATH = "/sys/devices/system/cpu"; + const std::string PROC_STAT = "/proc/stat"; + inline const std::string CPU_SCALING_CUR_FREQ(int CPUID) + { + return CPU_BASE_PATH + "/cpu" + std::to_string(CPUID) + "/cpufreq/scaling_cur_freq"; + } + inline const std::string CPU_SCALING_MAX_FREQ(int CPUID) + { + return CPU_BASE_PATH + "/cpu" + std::to_string(CPUID) + "/cpufreq/scaling_max_freq"; + } + inline const std::string CPU_SCALING_MIN_FREQ(int CPUID) + { + return CPU_BASE_PATH + "/cpu" + std::to_string(CPUID) + "/cpufreq/scaling_min_freq"; + } + inline const std::string CPUINFO_MAX_FREQ(int CPUID) + { + return CPU_BASE_PATH + "/cpu" + std::to_string(CPUID) + "/cpufreq/cpuinfo_max_freq"; + } + inline const std::string CPUINFO_MIN_FREQ(int CPUID) + { + return CPU_BASE_PATH + "/cpu" + std::to_string(CPUID) + "/cpufreq/cpuinfo_min_freq"; + } + + int m_cpu_num; + float cac_workload(const char *buffer, const char *pre_buffer); +}; +} +} +#endif \ No newline at end of file diff --git a/device/device_command/include/DDR.h b/device/device_command/include/DDR.h new file mode 100644 index 0000000..434b67b --- /dev/null +++ b/device/device_command/include/DDR.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef DDR_H +#define DDR_H + +#include +#include +#include +#include "gp_utils.h" +#include "singleton.h" +namespace OHOS { +namespace SmartPerf { +class DDR : public DelayedSingleton { +public: + static constexpr const char *DDR_CUR_FREQ_PATH[] = { + "/sys/class/devfreq/ddrfreq/cur_freq", + }; + static constexpr const char *DDR_AVAILABLE_FREQ_PATH[] = { + "/sys/class/devfreq/ddrfreq/available_frequencies", + }; + long long get_ddr_freq(); + +private: + std::string ddr_cur_freq_path; +}; +}; +} +#endif diff --git a/device/device_command/include/FPS.h b/device/device_command/include/FPS.h new file mode 100644 index 0000000..38cdd08 --- /dev/null +++ b/device/device_command/include/FPS.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef FPS_H +#define FPS_H + +#include +#include +#include +#include +#include "gp_utils.h" +#include "singleton.h" +namespace OHOS { +namespace SmartPerf { +struct FpsInfo { + int fps; + int pre_fps; + std::vector jitters; + std::queue time_stamp_q; + long long last_frame_ready_time; + long long current_fps_time; + FpsInfo() + { + fps = 0; + pre_fps = 0; + last_frame_ready_time = 0; + current_fps_time = 0; + } +}; +class FPS : public DelayedSingleton { +public: + void setPackageName(std::string pkgName); + FpsInfo getFpsInfo(int is_video, int is_camera); + FpsInfo m_fpsInfo; +private: + std::string pkg_name; + std::string cur_layer_name; + FpsInfo GetSurfaceFrame(std::string name); +}; +} +} +#endif diff --git a/device/device_command/include/GPU.h b/device/device_command/include/GPU.h new file mode 100644 index 0000000..aedcb97 --- /dev/null +++ b/device/device_command/include/GPU.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GPU_H +#define GPU_H +#include +#include +#include +#include "gp_utils.h" +#include "singleton.h" + +namespace OHOS { +namespace SmartPerf { +class GPU : public DelayedSingleton { +public: + static constexpr const char *GPU_CUR_FREQ_PATH[] = { + "/sys/class/devfreq/fde60000.gpu/cur_freq", // rk3568 + "/sys/class/devfreq/gpufreq/cur_freq", // wgr + }; + static constexpr const char *GPU_CUR_WORKLOAD_PATH[] = { + "/sys/class/devfreq/gpufreq/gpu_scene_aware/utilisation", // wgr + "/sys/class/devfreq/fde60000.gpu/load", // rk3568 + }; + int get_gpu_freq(); + float get_gpu_load(); + void init_gpu_node(); +private: + std::string gpu_cur_freq_path; + std::string gpu_cur_load_path; + float calc_workload(const char *buffer) const; +}; +} +} +#endif // GPU_H diff --git a/device/device_command/include/Power.h b/device/device_command/include/Power.h new file mode 100644 index 0000000..286f4ba --- /dev/null +++ b/device/device_command/include/Power.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef POWER_H +#define POWER_H + +#include +#include +#include +#include "singleton.h" +namespace OHOS { +namespace SmartPerf { +class Power : public DelayedSingleton { +public: + static constexpr const char *power_path[] = { + "/sys/class/power_supply/battery", + "/sys/class/power_supply/Battery", + "/data/local/tmp/battery" + }; + + static constexpr const char *default_collect_power_info[] = { + "current_now", + "voltage_now", + }; + std::map getPowerMap(); + void init_power(); +private: + std::string power_base_path; + std::map power_node_path_map; +}; +} +} +#endif diff --git a/device/device_command/include/RAM.h b/device/device_command/include/RAM.h new file mode 100644 index 0000000..f880185 --- /dev/null +++ b/device/device_command/include/RAM.h @@ -0,0 +1,32 @@ +/* +* Copyright (C) 2021 Huawei Device Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +#ifndef RAM_H +#define RAM_H +#include +#include +#include "singleton.h" + +namespace OHOS { + namespace SmartPerf { + class RAM : public DelayedSingleton { + public: + void setPkgName(std::string ss); + std::map getRamInfo(std::string pkg_name, int pid); + private: + std::string pkgName; + }; + } +} +#endif diff --git a/device/device_command/include/Temperature.h b/device/device_command/include/Temperature.h new file mode 100644 index 0000000..df5935a --- /dev/null +++ b/device/device_command/include/Temperature.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TEMPERATURE_H +#define TEMPERATURE_H + +#include +#include +#include +#include +#include "gp_utils.h" +#include "singleton.h" +namespace OHOS { +namespace SmartPerf { +class Temperature : public DelayedSingleton { +public: + static constexpr const char *thermal_path[] = { + "/sys/devices/virtual/thermal", + "/sys/class/thermal" + }; + const std::map collect_nodes = { + { "soc_thermal", "soc_thermal" }, { "system_h", "system_h" }, { "soc-thermal", "soc-thermal" }, + { "gpu-thermal", "gpu-thermal" }, { "shell_frame", "shell_frame" }, { "shell_front", "shell_front" }, + { "shell_back", "shell_back" } + }; + std::map getThermalMap(); + void init_thermal_node(); + void init_temperature(); +private: + std::string thermal_base_path; + std::map thermal_node_path_map; +}; +} +} +#endif \ No newline at end of file diff --git a/device/device_command/include/gp_constant.h b/device/device_command/include/gp_constant.h new file mode 100644 index 0000000..ca08557 --- /dev/null +++ b/device/device_command/include/gp_constant.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef GP_CONSTANT_H +#define GP_CONSTANT_H +enum NumberConstant { + ZERO = 0X00, + ONE = 0X01, + TWO = 0X02, + THREE = 0X03 +}; +enum FunConstant { + SUCCESS_ONE = 0X01, + SUCCESS_ZERO = 0X00, + ERROR_ZERO = 0X00, + ERROR_MINUX = 0xFFFFFFFF +}; +#endif \ No newline at end of file diff --git a/device/device_command/include/gp_data.h b/device/device_command/include/gp_data.h new file mode 100644 index 0000000..05c939b --- /dev/null +++ b/device/device_command/include/gp_data.h @@ -0,0 +1,23 @@ +/* +* Copyright (C) 2021 Huawei Device Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +#ifndef GPDATA_H +#define GPDATA_H +#include + +class GPData { +public: + std::map values; +}; +#endif \ No newline at end of file diff --git a/device/device_command/include/gp_utils.h b/device/device_command/include/gp_utils.h new file mode 100644 index 0000000..37b0650 --- /dev/null +++ b/device/device_command/include/gp_utils.h @@ -0,0 +1,38 @@ +/* +* Copyright (C) 2021 Huawei Device Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +#ifndef GP_UTILS_H +#define GP_UTILS_H +#include +#include +#include "gp_data.h" +namespace OHOS { + namespace SmartPerf { + namespace GPUtils { + void mSplit(const std::string &content, const std::string &sp, std::vector &out); + + bool canOpen(const std::string &path); + + std::string readFile(const std::string &cmd); + + std::string freadFile(const std::string &path); + + std::string getNumber(const std::string &str); + + void writeCsv(const std::string &path, std::vector &vmap); + }; + } +} + +#endif // GP_UTILS_H diff --git a/device/device_command/include/profiler.h b/device/device_command/include/profiler.h new file mode 100644 index 0000000..c5db299 --- /dev/null +++ b/device/device_command/include/profiler.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef PROFILER_H +#define PROFILER_H +#include +#include +#include "CPU.h" +#include "GPU.h" +#include "DDR.h" +#include "FPS.h" +#include "RAM.h" +#include "Temperature.h" +#include "Power.h" +#include "ByTrace.h" +#include "gp_data.h" + +namespace OHOS { +namespace SmartPerf { +class Profiler : public DelayedSingleton{ +public: + void initProfiler(); + void createCpu(std::map &gpMap); + void createGpu(std::map &gpMap); + void createDdr(std::map &gpMap); + void createFps(int isVideo, int isCamera, int isCatchTrace, int curProfilerNum, + std::map &gpMap); + void createTemp(std::map &gpMap); + void createPower(std::map &gpMap); + void createRam(const std::string &pkg_name, std::map &gpMap, int pid); + void createSnapshot(std::map &gpMap, long long timestamp); + std::shared_ptr mCpu = nullptr; + std::shared_ptr mGpu = nullptr; + std::shared_ptr mDdr = nullptr; + std::shared_ptr mFps = nullptr; + std::shared_ptr mRam = nullptr; + std::shared_ptr mTemperature = nullptr; + std::shared_ptr mPower = nullptr; + std::shared_ptr mByTrace = nullptr; +}; +} +} +#endif \ No newline at end of file diff --git a/device/device_command/include/smartperf_command.h b/device/device_command/include/smartperf_command.h new file mode 100644 index 0000000..f8453ff --- /dev/null +++ b/device/device_command/include/smartperf_command.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SMARTPERF_COMMAND_H +#define SMARTPERF_COMMAND_H + +#include +#include +#include "profiler.h" +#include "socket_profiler.h" +namespace OHOS { + namespace SmartPerf { + class SmartPerfCommand { + public: + const std::string SmartPerf_EXE_NAME = "SP_daemon"; + const std::string SmartPerf_VERSION = "1.0.1\n"; + const std::string SmartPerf_MSG_ERR = "error input!\n use command '--help' get more information\n"; + const std::string SmartPerf_MSG = "usage: SP_daemon \n" + "--------------------------------------------------------------------\n" + "These are common commands list:\n" + " -N set num of profiler \n" + " -PKG set pkgname of profiler \n" + " -PID set process id of profiler \n" + " -OUT set output path of CSV\n" + " -c get cpuFreq and cpuLoad \n" + " -g get gpuFreq and gpuLoad \n" + " -f get fps and fps jitters \n" + " -t get soc-temp gpu-temp .. \n" + " -p get current_now and voltage_now \n" + " -r get ram(pss) \n" + " -snapshot get screen capture\n" + "--------------------------------------------------------------------\n" + "Example: SP_daemon -N 20 -PKG ohos.samples.ecg -c -g -t -p -f \n" + "--------------------------------------------------------------------\n"; + const int ONE_PARAM = 1; + const int TWO_PARAM = 2; + const int THREE_PARAM_MORE = 3; + SmartPerfCommand(int argc, char *argv[]); + ~SmartPerfCommand() {}; + void initSomething(); + std::string ExecCommand(); + // 采集次数 + int num = 0; + // 包名 + std::string pkgName = ""; + // 是否开启trace 抓取 + int trace = 0; + // csv输出路径 + std::string outPath = "/data/local/tmp/data.csv"; + std::string outPathParam = ""; + // 指定进程pid + int pid = 0; + // 采集配置项 + std::vector configs; + // 命令式采集 + std::shared_ptr profiler = nullptr; + // Socket采集 + std::shared_ptr socketProfiler = nullptr; + }; + } +} +#endif // SMARTPERF_COMMAND_H \ No newline at end of file diff --git a/device/device_command/include/socket_profiler.h b/device/device_command/include/socket_profiler.h new file mode 100644 index 0000000..eefd123 --- /dev/null +++ b/device/device_command/include/socket_profiler.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef SOCKET_PROFILER_H +#define SOCKET_PROFILER_H +#include +#include +#include "CPU.h" +#include "GPU.h" +#include "DDR.h" +#include "FPS.h" +#include "RAM.h" +#include "Temperature.h" +#include "Power.h" +#include "ByTrace.h" +namespace OHOS { +namespace SmartPerf { +enum SockConstant { + SOCK_PORT = 8283, + BUFF_SIZE_RECV = 256, + BUFF_SIZE_SEND = 2048 +}; +class SocketProfiler : public DelayedSingleton { +public: + void initSocketProfiler(); + int bufsendto(int sockLocal, const char *bufsend, int length, struct sockaddr *clientLocal, socklen_t len); + void callSend(std::stringstream &sstream, std::string &str1, std::string &str2); + void thread_udp_server(); + void initSocket(); + + std::shared_ptr mCpu = nullptr; + std::shared_ptr mGpu = nullptr; + std::shared_ptr mDdr = nullptr; + std::shared_ptr mFps = nullptr; + std::shared_ptr mRam = nullptr; + std::shared_ptr mTemperature = nullptr; + std::shared_ptr mPower = nullptr; + std::shared_ptr mByTrace = nullptr; + + int sock; + struct sockaddr_in local; + struct sockaddr_in client; +}; +} +} +#endif \ No newline at end of file diff --git a/device/device_command/profiler.cpp b/device/device_command/profiler.cpp new file mode 100644 index 0000000..f6d598e --- /dev/null +++ b/device/device_command/profiler.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include "securec.h" +#include "include/profiler.h" +namespace OHOS { +namespace SmartPerf { +void Profiler::initProfiler() +{ + // get singleton instance + mCpu = CPU::GetInstance(); + mGpu = GPU::GetInstance(); + mDdr = DDR::GetInstance(); + mFps = FPS::GetInstance(); + mRam = RAM::GetInstance(); + mTemperature = Temperature::GetInstance(); + mPower = Power::GetInstance(); + mByTrace = ByTrace::GetInstance(); + + // some init methods + mTemperature->init_temperature(); + mGpu->init_gpu_node(); + mPower->init_power(); + if (mByTrace->init_trace(true) == TRACE_START) { + std::thread pInitTrace(&ByTrace::thread_get_trace, mByTrace); + } +} + +void Profiler::createCpu(std::map &gpMap) +{ + int cpuCoreNum = mCpu->get_cpu_num(); + for (int i = 0; i < cpuCoreNum; i++) { + int curFreq = mCpu->get_cpu_freq(i); + char desc[10]; + if (snprintf_s(desc, sizeof(desc), sizeof(desc), "cpu%dfreq", i) > 0) { + gpMap.insert(std::pair(std::string(desc), std::to_string(curFreq))); + } + } + + std::vector workloads = mCpu->get_cpu_load(); + + for (size_t i = 1; i < workloads.size(); ++i) { + char desc[10]; + if (snprintf_s(desc, sizeof(desc), sizeof(desc), "cpu%dload", i - 1) > 0) { + gpMap.insert(std::pair(std::string(desc), std::to_string(workloads[i]))); + } + } +} +void Profiler::createGpu(std::map &gpMap) +{ + int ret = mGpu->get_gpu_freq(); + float workload = mGpu->get_gpu_load(); + char desc[10]; + if (snprintf_s(desc, sizeof(desc), sizeof(desc), "gpufreq") > 0) { + gpMap.insert(std::pair(std::string(desc), std::to_string(ret))); + } + if (snprintf_s(desc, sizeof(desc), sizeof(desc), "gpuload") > 0) { + gpMap.insert(std::pair(std::string(desc), std::to_string(workload))); + } +} +void Profiler::createDdr(std::map &gpMap) +{ + long long ret = mDdr->get_ddr_freq(); + char desc[10]; + if (snprintf_s(desc, sizeof(desc), sizeof(desc), "ddrfreq") > 0) { + gpMap.insert(std::pair(std::string(desc), std::to_string(ret))); + } +} +void Profiler::createFps(int isVideo, int isCamera, int isCatchTrace, int curProfilerNum, + std::map &gpMap) +{ + FpsInfo gfpsInfo = mFps->getFpsInfo(isVideo, isCamera); + char desc[10]; + if (snprintf_s(desc, sizeof(desc), sizeof(desc), "fps") > 0) { + gpMap.insert(std::pair(std::string(desc), std::to_string(gfpsInfo.fps))); + } + if (isCatchTrace > 0) { + if (mByTrace->check_fps_jitters(gfpsInfo.jitters, curProfilerNum) == TRACE_FINISH) { + std::string profilerNum = std::to_string(curProfilerNum); + std::thread pFinishTrace(&ByTrace::thread_finish_trace, mByTrace, std::ref(profilerNum)); + } + } +} +void Profiler::createTemp(std::map &gpMap) +{ + std::map tempInfo = mTemperature->getThermalMap(); + std::map::iterator iter; + for (iter = tempInfo.begin(); iter != tempInfo.end(); ++iter) { + float value = iter->second; + gpMap.insert(std::pair(iter->first, std::to_string(value))); + } +} +void Profiler::createPower(std::map &gpMap) +{ + std::map powerInfo; + powerInfo = mPower->getPowerMap(); + std::map::iterator iter; + for (iter = powerInfo.begin(); iter != powerInfo.end(); ++iter) { + gpMap.insert(std::pair(iter->first, iter->second)); + } +} +void Profiler::createRam(const std::string &pkg_name, std::map &gpMap, int pid) +{ + std::map gramInfo; + gramInfo = mRam->getRamInfo(pkg_name, pid); + std::map::iterator iter; + for (iter = gramInfo.begin(); iter != gramInfo.end(); ++iter) { + gpMap.insert(std::pair(iter->first, iter->second)); + } +} +void Profiler::createSnapshot(std::map &gpMap, long long timestamp) +{ + char pathstr[50]; + std::string path; + if (snprintf_s(pathstr, sizeof(pathstr), sizeof(pathstr), "/data/local/tmp/capture/%lld", timestamp) > 0) { + path = pathstr; + } + char cmdCapture[100]; + if (snprintf_s(cmdCapture, sizeof(cmdCapture), sizeof(cmdCapture), + "snapshot_display -f /data/local/tmp/capture/%lld.png", timestamp) > 0) { + GPUtils::readFile(cmdCapture); + } + gpMap.insert(std::pair(std::string("snapshotPath"), path)); +} +} +} \ No newline at end of file diff --git a/device/device_command/smartperf_command.cpp b/device/device_command/smartperf_command.cpp new file mode 100644 index 0000000..20cc479 --- /dev/null +++ b/device/device_command/smartperf_command.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include "sys/time.h" +#include "unistd.h" +#include "include/gp_data.h" +#include "include/gp_utils.h" +#include "include/smartperf_command.h" + +namespace OHOS { +namespace SmartPerf { +SmartPerfCommand::SmartPerfCommand(int argc, char *argv[]) +{ + if (argc == ONE_PARAM) { + socketProfiler = SocketProfiler::GetInstance(); + socketProfiler->initSocketProfiler(); + daemon(0, 0); + std::thread t_udp(&SocketProfiler::thread_udp_server, socketProfiler); + t_udp.join(); + } + if (argc == TWO_PARAM) { + char *cmd = argv[1]; + if (strcmp(cmd, "--help") == 0 || strcmp(cmd, "-h") == 0) { + std::cout << SmartPerf_MSG; + } else if (strcmp(cmd, "--version") == 0) { + std::cout << SmartPerf_VERSION; + } else { + std::cout << SmartPerf_MSG_ERR; + } + } + if (argc >= THREE_PARAM_MORE) { + profiler = Profiler::GetInstance(); + profiler->initProfiler(); + for (int i = 1; i <= argc - 1; i++) { + if ((strcmp(argv[i], "-N") == 0) || (strcmp(argv[i], "--num") == 0)) { + num = atoi(argv[i + 1]); + if (num > 0) { + std::cout << "set num:" << num << std::endl; + } else { + std::cout << "error input args: -N" << std::endl; + } + } + if ((strcmp(argv[i], "-PKG") == 0) || (strcmp(argv[i], "--pkgname") == 0)) { + pkgName = argv[i + 1]; + if (strcmp(pkgName.c_str(), "") != 0) { + profiler->mFps->setPackageName(pkgName); + std::cout << "set pkg name:" << pkgName << std::endl; + } else { + std::cout << "empty input args: -PKG" << std::endl; + } + } + if ((strcmp(argv[i], "-PID") == 0) || (strcmp(argv[i], "--processid") == 0)) { + pid = atoi(argv[i + 1]); + if (pid > 0) { + std::cout << "set test pid:" << pid << std::endl; + } else { + std::cout << "error input args: -PID " << std::endl; + } + } + + if ((strcmp(argv[i], "-OUT") == 0) || (strcmp(argv[i], "--output") == 0)) { + outPathParam = argv[i + 1]; + if (strcmp(outPathParam.c_str(), "") != 0) { + outPath = outPathParam + std::string(".csv"); + } + } + + if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "-g") == 0 || strcmp(argv[i], "-d") == 0 || + strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "-f1") == 0 || strcmp(argv[i], "-f2") == 0 || + strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "-r") == 0 || + strcmp(argv[i], "-trace") == 0 || strcmp(argv[i], "-snapshot") == 0) { + configs.push_back(argv[i]); + } + } + } +} +std::string SmartPerfCommand::ExecCommand() +{ + int index = 0; + std::vector vmap; + while (index < num) { + std::map gpMap; + struct timeval tv; + gettimeofday(&tv, nullptr); + long long timestamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; + gpMap.insert(std::pair(std::string("timestamp"), std::to_string(timestamp))); + + for (size_t j = 0; j < configs.size(); j++) { + std::string curParam = configs[j]; + if (strcmp(curParam.c_str(), "-trace") == 0) { + trace = 1; + } + if (strcmp(curParam.c_str(), "-c") == 0) { + profiler->createCpu(gpMap); + } + if (strcmp(curParam.c_str(), "-g") == 0) { + profiler->createGpu(gpMap); + } + if (strcmp(curParam.c_str(), "-d") == 0) { + profiler->createDdr(gpMap); + } + if (strcmp(curParam.c_str(), "-f") == 0) { + profiler->createFps(0, 0, trace, index, gpMap); + } + if (strcmp(curParam.c_str(), "-f1") == 0) { + profiler->createFps(1, 0, trace, index, gpMap); + } + if (strcmp(curParam.c_str(), "-f2") == 0) { + profiler->createFps(0, 1, trace, index, gpMap); + } + if (strcmp(curParam.c_str(), "-t") == 0) { + profiler->createTemp(gpMap); + } + if (strcmp(curParam.c_str(), "-p") == 0) { + profiler->createPower(gpMap); + } + if (strcmp(curParam.c_str(), "-r") == 0) { + if (strcmp(pkgName.c_str(), "") != 0 || pid > 0) { + profiler->createRam(pkgName, gpMap, pid); + } + } + if (strcmp(curParam.c_str(), "-snapshot") == 0) { + profiler->createSnapshot(gpMap, timestamp); + } + } + + printf("----------------------------------Print START------------------------------------\n"); + std::map::iterator iter; + int i = 0; + for (iter = gpMap.begin(); iter != gpMap.end(); ++iter) { + printf("order:%d %s=%s\n", i, iter->first.c_str(), iter->second.c_str()); + i++; + } + printf("----------------------------------Print END--------------------------------------\n"); + + GPData gpdata; + gpdata.values = gpMap; + vmap.push_back(gpdata); + sleep(1); + index++; + } + + GPUtils::writeCsv(std::string(outPath.c_str()), vmap); + + return std::string("command exec finished!"); +} +void SmartPerfCommand::initSomething() +{ + GPUtils::readFile("chmod a+w /proc/stat"); +} +} +} diff --git a/device/device_command/smartperf_main.cpp b/device/device_command/smartperf_main.cpp new file mode 100644 index 0000000..fcca632 --- /dev/null +++ b/device/device_command/smartperf_main.cpp @@ -0,0 +1,24 @@ +/* +* Copyright (C) 2021 Huawei Device Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "include/smartperf_command.h" + +int main(int argc, char *argv[]) +{ + OHOS::SmartPerf::SmartPerfCommand cmd(argc, argv); + cmd.initSomething(); + std::cout << cmd.ExecCommand() << std::endl; + return 0; +} diff --git a/device/device_command/socket_profiler.cpp b/device/device_command/socket_profiler.cpp new file mode 100644 index 0000000..ecd6694 --- /dev/null +++ b/device/device_command/socket_profiler.cpp @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include "include/gp_utils.h" +#include "include/gp_constant.h" +#include "include/socket_profiler.h" +namespace OHOS { +namespace SmartPerf { +void SocketProfiler::initSocketProfiler() +{ + mCpu = CPU::GetInstance(); + mGpu = GPU::GetInstance(); + mDdr = DDR::GetInstance(); + mFps = FPS::GetInstance(); + mRam = RAM::GetInstance(); + mTemperature = Temperature::GetInstance(); + mPower = Power::GetInstance(); + mByTrace = ByTrace::GetInstance(); + + mTemperature->init_temperature(); + mGpu->init_gpu_node(); + mPower->init_power(); +} + +int SocketProfiler::bufsendto(int sockLocal, const char *bufsend, int length, + struct sockaddr *clientLocal, socklen_t len) +{ + ssize_t echo_size = sendto(sockLocal, bufsend, length, ZERO, clientLocal, len); + if (echo_size < ZERO) { + printf("sendto error, buf is %s\n", bufsend); + return ERROR_MINUX; + } + return SUCCESS_ZERO; +} + +void SocketProfiler::callSend(std::stringstream &sstream, std::string &str1, std::string &str2) +{ + sstream.str(""); + sstream.clear(); + sstream << str1 << "::" << str2; + std::string streamSend = sstream.str(); + bufsendto(sock, streamSend.c_str(), streamSend.size(), + reinterpret_cast(&client), sizeof(sockaddr_in)); +} + +void SocketProfiler::initSocket() +{ + sock = socket(AF_INET, SOCK_DGRAM, ZERO); + if (sock < ZERO) { + perror("socket error"); + } + local.sin_family = AF_INET; + local.sin_port = htons(SOCK_PORT); + local.sin_addr.s_addr = htonl(INADDR_ANY); + if (::bind(sock, reinterpret_cast(&local), sizeof(local)) < ZERO) { + perror("bind error"); + } +} + +void SocketProfiler::thread_udp_server() +{ + std::shared_ptr SP = SocketProfiler::GetInstance(); + SP->initSocket(); + socklen_t len = sizeof(sockaddr_in); + std::stringstream sstream; + const int loopforever = 1; + printf("enter while loop forever\n"); + while (loopforever) { + char recvbuf[BUFF_SIZE_RECV]; + recvbuf[0] = '\0'; + ssize_t _size = recvfrom(sock, recvbuf, sizeof(recvbuf) - 1, 0, + reinterpret_cast(&client), &len); + if (_size > 0) { + recvbuf[_size] = '\0'; + printf("server recvbuf:%s\n", recvbuf); + } + sstream.str(""); + sstream.clear(); + std::string recv = std::string(recvbuf); + if (recv.find("get_cpu_num") != std::string::npos) { + std::string recvStr = "get_cpu_num"; + int ret = SP->mCpu->get_cpu_num(); + std::string str2 = std::to_string(ret); + SP->callSend(sstream, recvStr, str2); + } else if (recv.find("get_cpu_freq") != std::string::npos) { + std::vector sps; + GPUtils::mSplit(recv, "_", sps); + int cpu_id = std::stoi(sps[sps.size() - 1]); + int ret = SP->mCpu->get_cpu_freq(cpu_id); + std::string str2 = std::to_string(ret); + SP->callSend(sstream, recv, str2); + } else if (recv.find("get_cpu_load") != std::string::npos) { + std::vector workloads = SP->mCpu->get_cpu_load(); + std::string res = ""; + for (size_t i = 0; i < workloads.size(); ++i) { + if (i != 0) { + res += "=="; + } + res += std::to_string(workloads[i]); + } + SP->callSend(sstream, recv, res); + } else if (recv.find("set_pkgName") != std::string::npos) { + std::vector sps; + GPUtils::mSplit(recv, "::", sps); + if (sps.size() > 1) { + SP->mFps->setPackageName(sps[1]); + SP->mRam->setPkgName(sps[1]); + } + } else if (recv.find("get_fps_and_jitters") != std::string::npos) { + std::vector sps; + GPUtils::mSplit(recv, "::", sps); + if (sps.size() > TWO) { + int is_video = atoi(sps[1].c_str()); + int is_camera = atoi(sps[2].c_str()); + + FpsInfo gfpsInfo = SP->mFps->getFpsInfo(is_video, is_camera); + std::string res = ""; + res += "timestamp|"; + res += std::to_string(gfpsInfo.current_fps_time); + res += ";"; + res += "fps|"; + res += std::to_string(gfpsInfo.fps); + res += ";"; + res += "jitter|"; + for (size_t i = 0; i < gfpsInfo.jitters.size(); ++i) { + res += std::to_string(gfpsInfo.jitters[i]); + res += "=="; + } + std::string recvStr = "get_fps_and_jitters"; + SP->callSend(sstream, recvStr, res); + } + } else if (recv.find("get_gpu_freq") != std::string::npos) { + int ret = SP->mGpu->get_gpu_freq(); + std::string str2 = std::to_string(ret); + SP->callSend(sstream, recv, str2); + } else if (recv.find("get_gpu_load") != std::string::npos) { + float workload = SP->mGpu->get_gpu_load(); + std::string str2 = std::to_string(workload); + SP->callSend(sstream, recv, str2); + } else if (recv.find("get_ddr_freq") != std::string::npos) { + long long ret = SP->mDdr->get_ddr_freq(); + std::string str2 = std::to_string(ret); + SP->callSend(sstream, recv, str2); + } else if (recv.find("get_ram_info") != std::string::npos) { + std::vector sps; + GPUtils::mSplit(recv, "::", sps); + if (sps.size() > 1) { + std::map gramInfo = SP->mRam->getRamInfo(sps[1], 0); + std::string res = "Pss"; + std::map::iterator iter; + int i = 0; + for (iter = gramInfo.begin(); iter != gramInfo.end(); ++iter) { + if (i != 0) + res += "=="; + res += iter->second; + ++i; + } + SP->callSend(sstream, sps[0], res); + } + } else if (recv.find("get_temperature") != std::string::npos) { + std::map tempInfo = SP->mTemperature->getThermalMap(); + std::string res = ""; + std::map::iterator iter; + int i = 0; + for (iter = tempInfo.begin(); iter != tempInfo.end(); ++iter) { + if (i != 0) { + res += "=="; + } + res += (iter->first + ",," + std::to_string(iter->second)); + ++i; + } + SP->callSend(sstream, recv, res); + } else if (recv.find("get_power") != std::string::npos) { + std::map powerInfo; + powerInfo = SP->mPower->getPowerMap(); + std::string res = ""; + std::map::iterator iter; + int i = 0; + for (iter = powerInfo.begin(); iter != powerInfo.end(); ++iter) { + if (i != 0) { + res += "=="; + } + res += (iter->first + ",," + iter->second); + ++i; + } + SP->callSend(sstream, recv, res); + } else if (recv.find("get_capture") != std::string::npos) { + sstream << "snapshot_display"; + std::string cmd_capture = sstream.str(); + GPUtils::readFile(cmd_capture); + } else if (recv.find("catch_trace_start") != std::string::npos) { + std::thread tStart(&ByTrace::thread_get_trace, SP->mByTrace); + } else if (recv.find("catch_trace_finish") != std::string::npos) { + std::vector traces; + GPUtils::mSplit(recv, "::", traces); + std::thread tFinish(&ByTrace::thread_finish_trace, SP->mByTrace, std::ref(traces[1])); + } + } +} +} +} diff --git a/device/device_ui/.gitignore b/device/device_ui/.gitignore new file mode 100644 index 0000000..ce9e170 --- /dev/null +++ b/device/device_ui/.gitignore @@ -0,0 +1,13 @@ +*/build +/build/* +/entry/build/* +/gp_daemon_for_hap/libs/* +/gp_daemon_for_hap/obj/* +/gp_daemon_for_hap/push_x64.bat +/.idea +/local.properties +/node_modules/* +/.deveco/* +/entry/.preview +/entry/.preview/* + diff --git a/device/device_ui/AppScope/app.json5 b/device/device_ui/AppScope/app.json5 new file mode 100644 index 0000000..f1c18a3 --- /dev/null +++ b/device/device_ui/AppScope/app.json5 @@ -0,0 +1,13 @@ +{ + "app": { + "bundleName": "com.ohos.gameperceptio", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name", + "distributedNotificationEnabled": true, + "minAPIVersion": 8, + "targetAPIVersion": 9 + } +} diff --git a/device/device_ui/AppScope/resources/base/element/string.json b/device/device_ui/AppScope/resources/base/element/string.json new file mode 100644 index 0000000..a14439a --- /dev/null +++ b/device/device_ui/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "OpenHarmony_SP" + } + ] +} diff --git a/device/device_ui/AppScope/resources/base/media/app_icon.png b/device/device_ui/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 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 +#include +#include +#include +#include +#include "FPS.h" +#include "gp_utils.h" + +pthread_mutex_t FPS::mutex; +FPS *FPS::instance = nullptr; +FPS *FPS::getInstance() +{ + if (instance == nullptr) { + pthread_mutex_lock(&mutex); + if (instance == nullptr) { + instance = new FPS(); + } + pthread_mutex_unlock(&mutex); + } + return instance; +} +FPS::FPS() +{ + pthread_mutex_init(&mutex, nullptr); +} +void FPS::setPackageName(std::string pkgName) +{ + pkg_name = std::move(pkgName); +} +FpsInfo FPS::getFpsInfo() +{ + FpsInfo fpsInfoMax; + fpsInfoMax.fps = -1; + + std::string layerName; + + std::vector sps; + gpUtils::mSplit(this->pkg_name, ".", sps); + std::string addEndChar = "0"; + const int pNameLastPos = 2; + std::string pkgSuffix = sps[pNameLastPos]; + layerName = std::string( pkgSuffix.c_str()+ addEndChar); + if (pkgSuffix.find("camera")!=std::string::npos) { + layerName = std::string("RosenRenderXComponent"); + } + + FpsInfo fpsInfo = GetSurfaceFrameDataGB(layerName); + if (fpsInfo.fps > fpsInfoMax.fps) { + fpsInfoMax = fpsInfo; + } + return fpsInfoMax; +} +FpsInfo FPS::GetSurfaceFrameDataGB(std::string name) +{ + if (name == "") { + return FpsInfo(); + } + static std::map fps_map; + if (fps_map.count(name) == 0) { + FpsInfo tmp; + tmp.fps = 0; + tmp.pre_fps = 0; + fps_map[name] = tmp; + } + FpsInfo &fpsInfo = fps_map[name]; + fpsInfo.fps = 0; + FILE *fp; + char tmp[1024]; + tmp[0] = '\0'; + std::string cmd = "hidumper -s 10 -a \"fps " + name + "\""; + fp = popen(cmd.c_str(), "r"); + if (fp == nullptr) { + return fpsInfo; + } + long long MOD = 1e9; + long long lastReadyTime = -1; + int fps_gb = 0; + if (!(fpsInfo.time_stamp_q).empty()) { + lastReadyTime = (fpsInfo.time_stamp_q).back(); + } + bool jump = false; + bool refresh = false; + int cnt = 0; + int zeroNum = 0; + while (fgets(tmp, sizeof(tmp), fp) != nullptr) { + long long frameReadyTime = 0; + sscanf(tmp, "%lld", &frameReadyTime); + cnt++; + if (frameReadyTime == 0) { + zeroNum++; + continue; + } + if (lastReadyTime >= frameReadyTime) { + lastReadyTime = -1; + continue; + } + refresh = true; + long long t_frameReadyTime = frameReadyTime / MOD; + long long t_lastReadyTime = lastReadyTime / MOD; + long long lastFrame = -1; + if (t_frameReadyTime == t_lastReadyTime) { + (fpsInfo.time_stamp_q).push(frameReadyTime); + } else if (t_frameReadyTime == t_lastReadyTime + 1) { + jump = true; + lastFrame = fpsInfo.last_frame_ready_time; + lastReadyTime = frameReadyTime; + int fps_tmp = 0; + fpsInfo.jitters.clear(); + while (!(fpsInfo.time_stamp_q).empty()) { + fps_tmp++; + long long currFrame = (fpsInfo.time_stamp_q.front()); + if (lastFrame != -1) { + long long jitter = currFrame - lastFrame; + fpsInfo.jitters.push_back(jitter); + } + lastFrame = currFrame; + (fpsInfo.time_stamp_q).pop(); + } + fps_gb = fps_tmp; + (fpsInfo.time_stamp_q).push(frameReadyTime); + fpsInfo.last_frame_ready_time = lastFrame; + } else if (t_frameReadyTime > t_lastReadyTime + 1) { + jump = true; + lastReadyTime = frameReadyTime; + while (!(fpsInfo.time_stamp_q).empty()) { + (fpsInfo.time_stamp_q).pop(); + } + (fpsInfo.time_stamp_q).push(frameReadyTime); + } + } + pclose(fp); + const int maxZeroNum = 120; + if (zeroNum >= maxZeroNum) { + while (!(fpsInfo.time_stamp_q.empty())) { + fpsInfo.time_stamp_q.pop(); + } + fpsInfo.fps = 0; + return fpsInfo; + } + const int minPrintLine = 5; + if (cnt < minPrintLine) { + fpsInfo.fps = fpsInfo.pre_fps; + return fpsInfo; + } + if (fps_gb > 0) { + fpsInfo.fps = fps_gb; + fpsInfo.pre_fps = fps_gb; + return fpsInfo; + } else if (refresh && !jump) { + fpsInfo.fps = fpsInfo.pre_fps; + return fpsInfo; + } else { + fpsInfo.fps = 0; + return fpsInfo; + } +} + + diff --git a/device/device_ui/entry/src/main/cpp/FPS.h b/device/device_ui/entry/src/main/cpp/FPS.h new file mode 100644 index 0000000..d0e3fb0 --- /dev/null +++ b/device/device_ui/entry/src/main/cpp/FPS.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +#ifndef FPS_H +#define FPS_H + +#include +#include +#include +#include +#include "pthread.h" +#include "gp_utils.h" + +struct FpsInfo { + int fps; + int pre_fps; + std::vector jitters; + std::queue time_stamp_q; + long long last_frame_ready_time; + long long current_fps_time; + FpsInfo() + { + fps = 0; + pre_fps = 0; + last_frame_ready_time = 0; + current_fps_time = 0; + } +}; +class FPS{ +public: + static FPS* getInstance(); + FpsInfo getFpsInfo(); + FpsInfo GetSurfaceFrameDataGB(std::string name); + void setPackageName(std::string pkgName); + std::string pkg_name; + static pthread_mutex_t mutex; +private: + FPS(); + ~FPS(){}; + static FPS* instance; +}; + +#endif //FPS_H diff --git a/device/device_ui/entry/src/main/cpp/RAM.cpp b/device/device_ui/entry/src/main/cpp/RAM.cpp new file mode 100644 index 0000000..e05bde5 --- /dev/null +++ b/device/device_ui/entry/src/main/cpp/RAM.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include "gp_utils.h" +#include "RAM.h" + +pthread_mutex_t RAM::mutex; +RAM *RAM::instance = nullptr; +RAM *RAM::getInstance() +{ + if (instance == nullptr) + { + pthread_mutex_lock(&mutex); + if (instance == nullptr) + { + instance = new RAM(); + } + pthread_mutex_unlock(&mutex); + } + return instance; +} +RAM::RAM() +{ + pthread_mutex_init(&mutex, nullptr); +} + +std::map RAM::getRamInfo(std::string pid) +{ + std::map ramInfo; + std::string pss_value = ""; + ramInfo["pss"] = "-1"; + if (pid.size() > 0) { + std::ostringstream cmd_grep; + cmd_grep << "hidumper --mem "; + cmd_grep << pid; + std::string cmd = cmd_grep.str(); + std::string pidLine = gpUtils::readCmd(cmd); + std::vector sps; + gpUtils::mSplit(pidLine, " ", sps); + if (sps.size() > 0) { + pss_value = sps[1]; + } + } + if (atoi(pss_value.c_str()) > 0) { + ramInfo["pss"] = gpUtils::extractNumber(pss_value.c_str()); + } + return ramInfo; +} diff --git a/device/device_ui/entry/src/main/cpp/RAM.h b/device/device_ui/entry/src/main/cpp/RAM.h new file mode 100644 index 0000000..d0a94c2 --- /dev/null +++ b/device/device_ui/entry/src/main/cpp/RAM.h @@ -0,0 +1,32 @@ +/* +* Copyright (C) 2021 Huawei Device Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +#ifndef RAM_H +#define RAM_H +#include +#include +#include "pthread.h" + +class RAM { +public: + static pthread_mutex_t mutex; + static RAM* getInstance(); + std::map getRamInfo(std::string pid); +private: + RAM(); + ~RAM(){}; + static RAM* instance; +}; + +#endif diff --git a/device/device_ui/entry/src/main/cpp/gp_utils.cpp b/device/device_ui/entry/src/main/cpp/gp_utils.cpp new file mode 100644 index 0000000..6846b93 --- /dev/null +++ b/device/device_ui/entry/src/main/cpp/gp_utils.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +#include +#include +#include +#include +#include "gp_utils.h" + +#define LOG_DOMAIN 0x200 // 标识业务领域,范围0x0~0xFFFFF +#define LOG_TAG "gpUtils" + +void gpUtils::mSplit(const std::string &content, const std::string &sp, std::vector &out) { + int index = 0; + while (index != std::string::npos) { + int t_end = content.find_first_of(sp, index); + std::string tmp = content.substr(index, t_end - index); + if (tmp != "" && tmp != " ") + out.push_back(tmp); + if (t_end == std::string::npos) + break; + index = t_end + 1; + } +} + +bool gpUtils::canOpen(const std::string &path) { + FILE* fp; + fp = fopen(path.c_str(), "r"); + if(fp == nullptr){ + return false; + } + fclose(fp); + return true; +} + +bool gpUtils::canCmd(const std::string &cmd) { + FILE* pp; + pp = popen(cmd.c_str(), "r"); + if(pp == nullptr){ + return false; + } + pclose(pp); + return true; +} + + +// popen +std::string gpUtils::readCmd(const std::string &cmd) +{ + const int buffLength = 1024; + std::string res = "NA"; + FILE *pp = popen(cmd.c_str(), "r"); + if (pp == nullptr) { + return res; + } else { + char line[buffLength]; + line[0] = '\0'; + while (fgets(line, buffLength, pp) != nullptr) { + res = std::string(line); + } + } + pclose(pp); + return res; +} + +// fopen +std::string gpUtils::readFile(const std::string &path) +{ + std::string res = "NA"; + const int buffLengh = 1024; + FILE *fp; + if ((fp = fopen(path.c_str(), "r")) != nullptr) { + char s[buffLengh]; + s[0] = '\0'; + while (fgets(s, sizeof(s), fp) != nullptr) { + res += std::string(s); + } + } + if (fp != nullptr) { + fclose(fp); + } + return res; +} +// get number_str from str +std::string gpUtils::extractNumber(const std::string &str) +{ + int cntInt = 0; + const int shift = 10; + + for (int i = 0; str[i] != '\0'; ++i) { + if (str[i] >= '0' && str[i] <= '9') { + cntInt *= shift; + cntInt += str[i] - '0'; + } + } + return std::to_string(cntInt); +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/cpp/gp_utils.h b/device/device_ui/entry/src/main/cpp/gp_utils.h new file mode 100644 index 0000000..6e44d46 --- /dev/null +++ b/device/device_ui/entry/src/main/cpp/gp_utils.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +#ifndef GP_UTILS_H +#define GP_UTILS_H +#include +#include + +namespace gpUtils { + + void mSplit(const std::string &content, const std::string &sp, std::vector& out); + + bool canOpen(const std::string &path); + + bool canCmd(const std::string &cmd); + + std::string extractNumber(const std::string &str); + + std::string readFile(const std::string &path); + + std::string readCmd(const std::string &cmd); +}; + +#endif //GP_UTILS_H diff --git a/device/device_ui/entry/src/main/cpp/profiler.cpp b/device/device_ui/entry/src/main/cpp/profiler.cpp new file mode 100644 index 0000000..50b95c1 --- /dev/null +++ b/device/device_ui/entry/src/main/cpp/profiler.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "napi/native_api.h" +#include +#include +#include +#include +#include "gp_utils.h" + +#define LOG_DOMAIN 0x200 // 标识业务领域,范围0x0~0xFFFFF +#define LOG_TAG "PROFILER" + +void collectFpsThread(std::promise &promiseObj){ + FpsInfo fpsInfo = FPS::getInstance()->getFpsInfo(); + promiseObj.set_value(fpsInfo); +} + +static napi_value getFpsData(napi_env env, napi_callback_info info) +{ + size_t argc = 1; + napi_value args[1] = { nullptr }; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + napi_valuetype valuetype0; + napi_typeof(env, args[0], &valuetype0); + char pkgName[64] = {0}; + size_t typeLen = 0; + napi_get_value_string_utf8(env, args[0], pkgName, 63, &typeLen); + FPS::getInstance()->setPackageName(pkgName); + + std::promise promiseObj; + std::future futureObj = promiseObj.get_future(); + std::thread tFps(collectFpsThread, ref(promiseObj)); + tFps.join(); + + FpsInfo fpsInfo = futureObj.get(); + std::string fps = std::to_string(fpsInfo.fps); + std::vector fpsJitters = fpsInfo.jitters; + std::string fps_str = fps + "|"; + for (int i = 0; i < fpsJitters.size(); ++i) { + fps_str += std::to_string(fpsJitters[i]); + fps_str += "=="; + } + napi_value fps_result; + napi_create_string_utf8(env, fps_str.c_str(), fps_str.size(), &fps_result); + return fps_result; +} + +static napi_value getRamData(napi_env env, napi_callback_info info) +{ + size_t argc = 1; + napi_value args[1] = { nullptr }; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + napi_valuetype valuetype0; + napi_typeof(env, args[0], &valuetype0); + char pidNumber[64] = {0}; + size_t typeLen = 0; + napi_get_value_string_utf8(env, args[0], pidNumber, 63, &typeLen); + std::map gramInfo = RAM::getInstance()->getRamInfo(pidNumber); + std::string ram_pss = gramInfo["pss"]; + napi_value ram_result; + napi_create_string_utf8(env, ram_pss.c_str(), ram_pss.size(), &ram_result); + return ram_result; +} + +static napi_value checkDaemon(napi_env env, napi_callback_info info) +{ + std::string status = "Dead"; + std::string spRunning = gpUtils::readCmd(std::string("ps -ef |grep SP_daemon |grep -v grep")); + if (spRunning.find("NA")!= std::string::npos) { + gpUtils::canCmd(std::string("SP_daemon")); + } else { + status = "Running"; + } + napi_value result; + napi_create_string_utf8(env, status.c_str(), status.size(), &result); + return result; +} + +static napi_value checkAccess(napi_env env, napi_callback_info info) +{ + size_t argc = 1; + napi_value args[1] = { nullptr }; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + napi_valuetype valuetype0; + napi_typeof(env, args[0], &valuetype0); + char pathName[64] = {0}; + size_t typeLen = 0; + napi_get_value_string_utf8(env, args[0], pathName, 63, &typeLen); + std::string pathNameStr = pathName; + std::string status = "PermissionDenied"; + bool isAccess = gpUtils::canOpen(pathNameStr); + if (isAccess) { + status = "PermissionAccessed"; + } + napi_value result; + napi_create_string_utf8(env, status.c_str(), status.size(), &result); + return result; +} + +EXTERN_C_START +static napi_value Init(napi_env env, napi_value exports) +{ + napi_property_descriptor desc[] = { + { "getFpsData", nullptr, getFpsData, nullptr, nullptr, nullptr, napi_default, nullptr }, + { "getRamData", nullptr, getRamData, nullptr, nullptr, nullptr, napi_default, nullptr }, + { "checkDaemon", nullptr, checkDaemon, nullptr, nullptr, nullptr, napi_default, nullptr }, + { "checkAccess", nullptr, checkAccess, nullptr, nullptr, nullptr, napi_default, nullptr }, + }; + napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); + return exports; +} +EXTERN_C_END + +static napi_module demoModule = { +.nm_version = 1, +.nm_flags = 0, +.nm_filename = nullptr, +.nm_register_func = Init, +.nm_modname = "libsmartperf", +.nm_priv = ((void *)0), +.reserved = { 0 }, +}; + +extern "C" __attribute__((constructor)) void RegisterModule(void) +{ +napi_module_register(&demoModule); +} diff --git a/device/device_ui/entry/src/main/cpp/types/libsmartperf/index.d.ts b/device/device_ui/entry/src/main/cpp/types/libsmartperf/index.d.ts new file mode 100644 index 0000000..c678fb8 --- /dev/null +++ b/device/device_ui/entry/src/main/cpp/types/libsmartperf/index.d.ts @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const getFpsData: (pkg: string) => string + +export const getRamData: (pid: string) => string + +export const checkDaemon: () => string + +export const checkAccess: (path: string) => string + diff --git a/device/device_ui/entry/src/main/cpp/types/libsmartperf/package.json b/device/device_ui/entry/src/main/cpp/types/libsmartperf/package.json new file mode 100644 index 0000000..3af17c2 --- /dev/null +++ b/device/device_ui/entry/src/main/cpp/types/libsmartperf/package.json @@ -0,0 +1,4 @@ +{ + "name": "libsmartperf.so", + "types": "./index.d.ts" +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/Application/AbilityStage.ts b/device/device_ui/entry/src/main/ets/Application/AbilityStage.ts new file mode 100644 index 0000000..d0a6115 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/Application/AbilityStage.ts @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import AbilityStage from "@ohos.application.AbilityStage" + +export default class MyAbilityStage extends AbilityStage { + onCreate() { + console.log("MyApplication MyAbilityStage onCreate") + globalThis.hapModuleName = "entry" + } +} diff --git a/device/device_ui/entry/src/main/ets/MainAbility/MainAbility.ts b/device/device_ui/entry/src/main/ets/MainAbility/MainAbility.ts new file mode 100644 index 0000000..b085e60 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/MainAbility/MainAbility.ts @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Ability from '@ohos.application.Ability' +import { initDb } from '../common/database/LocalRepository' +import { FloatWindowFun } from '../common/ui/floatwindow/FloatWindowFun' +import { NativeTaskFun } from "../common/profiler/NativeTaskFun" +import { NetWork } from '../common/profiler/item/NetWork'; +import BundleManager from '../common/utils/BundleMangerUtils'; +import SPLogger from '../common/utils/SPLogger' + + +var abilityWindowStage +const TAG = "MainAbility" + +export default class MainAbility extends Ability { + + + onCreate(want, launchParam) { + globalThis.showFloatingWindow=false + // Ability is creating, initialize resources for this ability + BundleManager.getAppList().then(appList => { + globalThis.appList = appList + + }) + SPLogger.DEBUG(TAG,"getReportListDb" + "--> MainAbility createDd 1") + } + + onDestroy() { + // Ability is destroying, release resources for this ability + SPLogger.DEBUG(TAG,"[MyApplication] MainAbility onDestroy") + } + + onWindowStageCreate(windowStage) { + globalThis.abilityContext = this.context + + // Main window is created, set main page for this ability + SPLogger.DEBUG(TAG,"[MyApplication] MainAbility onWindowStageCreate") + abilityWindowStage = windowStage; + abilityWindowStage.setUIContent(this.context, "pages/LoginPage", null) + } + + onWindowStageDestroy() { + // Main window is destroyed, release UI related resources + SPLogger.DEBUG(TAG,"[MyApplication] MainAbility onWindowStageDestroy") + } + + onForeground() { + initDb() + // Ability has brought to foreground + FloatWindowFun.initAllFun() + NativeTaskFun.initAllFun() + //check netWork + NetWork.getInstance().init() + } + + onBackground() { + // Ability has back to background + SPLogger.DEBUG(TAG,"[MyApplication] MainAbility onBackground") + } +}; + +export function initTest() { + +} + + + diff --git a/device/device_ui/entry/src/main/ets/common/FloatWindowComponent.ets b/device/device_ui/entry/src/main/ets/common/FloatWindowComponent.ets new file mode 100644 index 0000000..09e24e6 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/FloatWindowComponent.ets @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {moveFloatWindow,setFloatWindow} from './ui/floatwindow/utils/floatwindowutils' + + + + + +@Component +export struct FloatWindowComponent { + private settings: RenderingContextSettings = new RenderingContextSettings(true) + private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings) + @State title: string = "SmartPerf" + private XPoint: number = 5 //x起始坐标 + private YPoint: number = 73 //Y起始坐标 + private XScale: number = 5 //刻度 + private YScale: number = 15 //刻度 + private XLength: number = 110 //X轴长度 + private YLength: number = 70 //Y轴长度 + @State data: number[]= [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] //数据集合 + private MaxDataSize: number= this.XLength / this.XScale //数据集合的最大长度 + @State NumericalValue: number= 0 //数值 + offsetX: number = -1 //悬浮框移动触摸点 X + offsetY: number = -1 //悬浮框移动触摸点 X + + MoveWindow(offsetX: number, offsetY: number) { + moveFloatWindow(this.title,offsetX, offsetY) + } + + SetWindowPosition(offsetX: number, offsetY: number) { + setFloatWindow(offsetX, offsetY) + } + build() { + Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start, justifyContent: FlexAlign.Center }) { + Canvas(this.context) + .width('100%') + .height('100%') + .onReady(() => { + //Y轴 + this.context.clearRect(this.XPoint + 0.5, this.YPoint - this.YLength, this.XLength, this.YLength) + this.context.beginPath() + this.context.moveTo(this.XPoint, this.YPoint - this.YLength) + this.context.lineTo(this.XPoint, this.YPoint) + this.context.stroke() + //X轴 + this.context.beginPath() + this.context.moveTo(this.XPoint, this.YPoint) + this.context.lineTo(this.XPoint + this.XLength, this.YPoint) + this.context.stroke() + //K线绘制 + if (this.data.length > 1) { + for (let i = 1; i < this.data.length; i++) { + this.context.beginPath() + console.log("GestureEvent--------------beginPath:" + this.data[i-1]); + this.context.moveTo(this.XPoint + (i - 1) * this.XScale, this.YPoint - this.data[i-1]) + this.context.lineTo(this.XPoint + i * this.XScale, this.YPoint - this.data[i]) + this.context.stroke() + } + } + }) + } + .width('100%') + .height('100%').margin({ top: 14 }).gesture( + GestureGroup(GestureMode.Exclusive, + PanGesture({}) + .onActionStart((event: GestureEvent) => { + + }) + .onActionUpdate((event: GestureEvent) => { + this.offsetX = event.offsetX + this.offsetY = event.offsetY + + }) + .onActionEnd(() => { + this.MoveWindow(this.offsetX, this.offsetY) + this.SetWindowPosition(this.offsetX, this.offsetY) + + + }) + )) + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/constant/ConstantSQL.ts b/device/device_ui/entry/src/main/ets/common/constant/ConstantSQL.ts new file mode 100644 index 0000000..913f285 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/constant/ConstantSQL.ts @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const dbName = "gp.db" + +export const dbVersion = 1 + +export const sql_t_general_info = + "CREATE TABLE IF NOT EXISTS " + "t_general_info" + "(" + + "sessionId TEXT PRIMARY KEY, " + + "taskId TEXT NOT NULL," + + "appName TEXT," + + "appVersion TEXT," + + "packageName TEXT," + + "startTime TEXT," + + "endTime TEXT," + + "testDuration TEXT," + + "taskName TEXT," + + "testCase TEXT," + + "testType TEXT," + + "user TEXT," + + "userId TEXT," + + "projectId TEXT," + + "dataSource TEXT," + + "spVersion TEXT," + + "deviceTypeName TEXT," + + "brand TEXT," + + "deviceName TEXT," + + "board TEXT," + + "version TEXT," + + "plat TEXT," + + "cpuCluster TEXT," + + "sn TEXT," + + "resolution TEXT," + + "screenBrightness TEXT," + + "volume TEXT," + + "batteryVolume TEXT," + + "isOnline TEXT," + + "deviceNum TEXT," + + "roomNumber TEXT," + + "taskType TEXT" + + ")" + +export const sql_t_index_info = "CREATE TABLE IF NOT EXISTS " + "t_index_info" + "(" + +"timestamp TEXT PRIMARY KEY, " + +"taskId TEXT NOT NULL, " + +"flag TEXT, " + +"shellBackTemp TEXT," + +"shellFrameTemp TEXT," + +"shellFrontTemp TEXT," + +"socThermalTemp TEXT," + +"systemHTemp TEXT," + +"gpuTemp TEXT," + +"ambientTemp TEXT," + +"batteryTemp TEXT," + +"ddrFrequency TEXT," + +"fps TEXT," + +"fpsJitters TEXT," + +"cpu0Frequency TEXT," + +"cpu0Load TEXT," + +"cpu1Frequency TEXT," + +"cpu1Load TEXT," + +"cpu2Frequency TEXT," + +"cpu2Load TEXT," + +"cpu3Frequency TEXT," + +"cpu3Load TEXT," + +"cpu4Frequency TEXT," + +"cpu4Load TEXT," + +"cpu5Frequency TEXT," + +"cpu5Load TEXT," + +"cpu6Frequency TEXT," + +"cpu6Load TEXT," + +"cpu7Frequency TEXT," + +"cpu7Load TEXT," + +"cpuLoad TEXT," + +"gpuFrequency TEXT," + +"gpuLoad TEXT," + +"currentNow TEXT," + +"capacity TEXT," + +"enableHiz TEXT," + +"status TEXT," + +"voltageNow TEXT," + +"pss TEXT," + +"networkUpSpeed TEXT," + +"networkDownSpeed TEXT" + +")" diff --git a/device/device_ui/entry/src/main/ets/common/constant/ConstantsPath.ts b/device/device_ui/entry/src/main/ets/common/constant/ConstantsPath.ts new file mode 100644 index 0000000..11d246d --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/constant/ConstantsPath.ts @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//实际存储路径 对应 沙箱路径 globalThis.abilityContext.getApplicationContext().filesDir 即/data/storage/base/files +export const AppFileRealDir = "/data/app/el2/100/base/com.ohos.gameperceptio/files/" + + diff --git a/device/device_ui/entry/src/main/ets/common/database/DatabaseUtils.ts b/device/device_ui/entry/src/main/ets/common/database/DatabaseUtils.ts new file mode 100644 index 0000000..1881f30 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/database/DatabaseUtils.ts @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import dataRdb from '@ohos.data.rdb' +import { GPData,TIndexInfo,TGeneralInfo } from '../entity/DatabaseEntity'; +import { sql_t_index_info, dbVersion, dbName } from '../constant/ConstantSQL' +import SPLogger from '../utils/SPLogger' + +const TAG = "DatabaseUtils" + +export default { + + //创建表(T_INDEX_INFO T_GENERAL_INFO) + async createTable(pathSuffix: number): Promise { + const STORE_CONFIG_Index = { + name: pathSuffix + ".db" + } + dataRdb.getRdbStore(globalThis.abilityContext, STORE_CONFIG_Index, dbVersion) + .then(rdbStore => { + rdbStore.executeSql(sql_t_index_info, null).catch(err => { + SPLogger.DEBUG(TAG,"--> createTable t_index_info err:" + err) + }) + SPLogger.DEBUG(TAG,"--> createTable start execute sql_t_index_info:" + sql_t_index_info) + return rdbStore + }) + }, + + //插入表( T_GENERAL_INFO) + insertGeneraData(tableName: string, tGeneralInfo: TGeneralInfo) { + var strMap = new Map; + for (let k of Object.keys(tGeneralInfo)) { + strMap.set(k, tGeneralInfo[k]) + } + const valueInsert = { + "sessionId": strMap.get("sessionId"), + "taskId": strMap.get("taskId"), + "appName": strMap.get("appName"), + "appVersion": strMap.get("appVersion"), + "packageName": strMap.get("packageName"), + "startTime": strMap.get("startTime"), + "endTime": strMap.get("endTime"), + "testDuration": strMap.get("testDuration"), + "taskName": strMap.get("taskName") + // "testCase":strMap.get("testCase"), + // "testType":strMap.get("testType"), + // "user":strMap.get("user"), + // "userId":strMap.get("userId"), + // "projectId":strMap.get("projectId"), + // "dataSource":strMap.get("dataSource"), + // "spVersion":strMap.get("spVersion"), + // "deviceTypeName":strMap.get("deviceTypeName"), + // "brand":strMap.get("brand"), + // "deviceName":strMap.get("deviceName"), + // "board":strMap.get("board"), + // "version":strMap.get("version"), + // "plat":strMap.get("plat"), + // "cpuCluster":strMap.get("cpuCluster"), + // "sn":strMap.get("sn"), + // "resolution":strMap.get("resolution"), + // "screenBrightness":strMap.get("screenBrightness"), + // "volume":strMap.get("volume"), + // "batteryVolume":strMap.get("batteryVolume") + } + const STORE_CONFIG = { + name: dbName + } + dataRdb.getRdbStore(globalThis.abilityContext, STORE_CONFIG, dbVersion, (err, rdbStore) => { + SPLogger.DEBUG(TAG,"--> insert into insertGeneraData :tableName:" + tableName + "| valueInsert:" + JSON.stringify(valueInsert)) + rdbStore.insert(tableName, valueInsert).catch(err => { + SPLogger.DEBUG(TAG,"--> insert into insertGeneraData err:" + err) + }).then(() => { + SPLogger.DEBUG(TAG,"--> insert into insertGeneraData success:") + }) + }) + }, + + //插入表(T_INDEX_INFO) + insertData(tableName: string, pathSuffix: number, tIndexInfo: TIndexInfo) { + + var strMap = new Map; + for (let k of Object.keys(tIndexInfo)) { + strMap.set(k, tIndexInfo[k]); + } + const valueInsert = { + "timestamp": strMap.get("timestamp"), + "taskId": strMap.get("taskId"), + "ddrFrequency": strMap.get("ddrFrequency"), + "cpu0Frequency": strMap.get("cpu0Frequency"), + "cpu1Frequency": strMap.get("cpu1Frequency"), + "cpu2Frequency": strMap.get("cpu2Frequency"), + "cpu3Frequency": strMap.get("cpu3Frequency"), + "cpu4Frequency": strMap.get("cpu4Frequency"), + "cpu5Frequency": strMap.get("cpu5Frequency"), + "cpu6Frequency": strMap.get("cpu6Frequency"), + "cpu7Frequency": strMap.get("cpu7Frequency"), + "cpu0Load": strMap.get("cpu0Load"), + "cpu1Load": strMap.get("cpu1Load"), + "cpu2Load": strMap.get("cpu2Load"), + "cpu3Load": strMap.get("cpu3Load"), + "cpu4Load": strMap.get("cpu4Load"), + "cpu5Load": strMap.get("cpu5Load"), + "cpu6Load": strMap.get("cpu6Load"), + "cpu7Load": strMap.get("cpu7Load"), + "gpuFrequency": strMap.get("gpuFrequency"), + "gpuLoad": strMap.get("gpuLoad"), + "currentNow": strMap.get("currentNow"), + "voltageNow": strMap.get("voltageNow"), + "shellFrameTemp": strMap.get("shellFrameTemp"), + "shellFrontTemp": strMap.get("shellFrontTemp"), + "shellBackTemp": strMap.get("shellBackTemp"), + "socThermalTemp": strMap.get("socThermalTemp"), + "systemHTemp": strMap.get("systemHTemp"), + "gpuTemp": strMap.get("gpuTemp"), + "ambientTemp": strMap.get("ambientTemp"), + "batteryTemp": strMap.get("batteryTemp"), + "pss": strMap.get("pss"), + "fps": strMap.get("fps"), + "fpsJitters": strMap.get("fpsJitters"), + "networkUpSpeed": strMap.get("networkUpSpeed"), + "networkDownSpeed": strMap.get("networkDownSpeed") + } + const STORE_CONFIG = { + name: pathSuffix + ".db" + } + dataRdb.getRdbStore(globalThis.abilityContext, STORE_CONFIG, dbVersion, (err, rdbStore) => { + rdbStore.insert(tableName, valueInsert) + }) + }, + + //查询表( T_GENERAL_INFO) + async queryGeneralData(): Promise> { + const STORE_CONFIG = { + name: dbName + } + let results = Array() + try { + return await dataRdb.getRdbStore(globalThis.abilityContext, STORE_CONFIG, 1) + .then(rdbStore => { + let predicates = new dataRdb.RdbPredicates('t_general_info') + predicates.orderByDesc("startTime") + return rdbStore.query(predicates, ["sessionId", + "startTime", "taskId", "appName", "appVersion", "packageName", "endTime", "testDuration", "taskName" + ]) + }) + .then(resultSet => { + while (resultSet.goToNextRow()) { + let sessionId = resultSet.getString(resultSet.getColumnIndex("sessionId")) + let taskId = resultSet.getString(resultSet.getColumnIndex("taskId")) + let appName = resultSet.getString(resultSet.getColumnIndex("appName")) + let appVersion = resultSet.getString(resultSet.getColumnIndex("appVersion")) + let packageName = resultSet.getString(resultSet.getColumnIndex("packageName")) + let startTime = resultSet.getString(resultSet.getColumnIndex("startTime")) + let endTime = resultSet.getString(resultSet.getColumnIndex("endTime")) + let testDuration = resultSet.getLong(resultSet.getColumnIndex("testDuration")) + let taskName = resultSet.getString(resultSet.getColumnIndex("taskName")) + let board = resultSet.getString(resultSet.getColumnIndex("board")) + let deviceTypeName = resultSet.getString(resultSet.getColumnIndex("deviceTypeName")) + let brand = resultSet.getString(resultSet.getColumnIndex("brand")) + let version = resultSet.getString(resultSet.getColumnIndex("version")) + let sn = resultSet.getString(resultSet.getColumnIndex("sn")) + + let tGeneralInfo = new TGeneralInfo( + sessionId, taskId, appName, appVersion, packageName, Number(startTime).valueOf(), Number(endTime).valueOf(), testDuration, taskName + ) + tGeneralInfo.board = board + tGeneralInfo.deviceTypeName = deviceTypeName + tGeneralInfo.brand = brand + tGeneralInfo.version = version + tGeneralInfo.sn = sn + results.push(tGeneralInfo) + } + return results + }) + + } catch (err) { + SPLogger.ERROR(TAG,"resultSet queryGeneralData:err" + err) + } + }, + + //查询表( T_INDEX_INFO) 2022-02-23 改为传时间戳 + async queryData(dbPath: string): Promise> { + const STORE_CONFIG = { + name: dbPath + } + let results = Array() + try { + return await dataRdb.getRdbStore(globalThis.abilityContext, STORE_CONFIG, 1) + .then(rdbStore => { + let predicates = new dataRdb.RdbPredicates('t_index_info') + predicates.orderByDesc("timestamp") + return rdbStore.query(predicates, [ + "timestamp", + "taskId", + "shellBackTemp", "shellFrameTemp", "shellFrontTemp", "socThermalTemp", "systemHTemp", "gpuTemp", "ambientTemp", "batteryTemp", + "ddrFrequency", + "cpu0Frequency", "cpu1Frequency", "cpu2Frequency", "cpu3Frequency", "cpu4Frequency", "cpu5Frequency", "cpu6Frequency", "cpu7Frequency", + "cpu0Load", "cpu1Load", "cpu2Load", "cpu3Load", "cpu4Load", "cpu5Load", "cpu6Load", "cpu7Load", + "gpuLoad", "gpuFrequency", + "currentNow", "voltageNow", + "pss", + "fps", "fpsJitters", + "networkUpSpeed","networkDownSpeed" + ]) + }) + .then(resultSet => { + while (resultSet.goToNextRow()) { + let timestamp = resultSet.getString(resultSet.getColumnIndex("timestamp")) + let shellBackTemp = resultSet.getString(resultSet.getColumnIndex("shellBackTemp")) + let shellFrameTemp = resultSet.getString(resultSet.getColumnIndex("shellFrameTemp")) + let shellFrontTemp = resultSet.getString(resultSet.getColumnIndex("shellFrontTemp")) + let socThermalTemp = resultSet.getString(resultSet.getColumnIndex("socThermalTemp")) + let systemHTemp = resultSet.getString(resultSet.getColumnIndex("systemHTemp")) + let gpuTemp = resultSet.getString(resultSet.getColumnIndex("gpuTemp")) + let ambientTemp = resultSet.getString(resultSet.getColumnIndex("ambientTemp")) + let batteryTemp = resultSet.getString(resultSet.getColumnIndex("batteryTemp")) + let ddrFrequency = resultSet.getString(resultSet.getColumnIndex("ddrFrequency")) + let cpu0Frequency = resultSet.getString(resultSet.getColumnIndex("cpu0Frequency")) + let cpu1Frequency = resultSet.getString(resultSet.getColumnIndex("cpu1Frequency")) + let cpu2Frequency = resultSet.getString(resultSet.getColumnIndex("cpu2Frequency")) + let cpu3Frequency = resultSet.getString(resultSet.getColumnIndex("cpu3Frequency")) + let cpu4Frequency = resultSet.getString(resultSet.getColumnIndex("cpu4Frequency")) + let cpu5Frequency = resultSet.getString(resultSet.getColumnIndex("cpu5Frequency")) + let cpu6Frequency = resultSet.getString(resultSet.getColumnIndex("cpu6Frequency")) + let cpu7Frequency = resultSet.getString(resultSet.getColumnIndex("cpu7Frequency")) + + let cpu0Load = resultSet.getString(resultSet.getColumnIndex("cpu0Load")) + let cpu1Load = resultSet.getString(resultSet.getColumnIndex("cpu1Load")) + let cpu2Load = resultSet.getString(resultSet.getColumnIndex("cpu2Load")) + let cpu3Load = resultSet.getString(resultSet.getColumnIndex("cpu3Load")) + let cpu4Load = resultSet.getString(resultSet.getColumnIndex("cpu4Load")) + let cpu5Load = resultSet.getString(resultSet.getColumnIndex("cpu5Load")) + let cpu6Load = resultSet.getString(resultSet.getColumnIndex("cpu6Load")) + let cpu7Load = resultSet.getString(resultSet.getColumnIndex("cpu7Load")) + + let gpuLoad = resultSet.getString(resultSet.getColumnIndex("gpuLoad")) + let gpuFrequency = resultSet.getString(resultSet.getColumnIndex("gpuFrequency")) + let currentNow = resultSet.getString(resultSet.getColumnIndex("currentNow")) + let voltageNow = resultSet.getString(resultSet.getColumnIndex("voltageNow")) + + let pss = resultSet.getString(resultSet.getColumnIndex("pss")) + let fps = resultSet.getString(resultSet.getColumnIndex("fps")) + let fpsJitters = resultSet.getString(resultSet.getColumnIndex("fpsJitters")) + + let networkUpSpeed = resultSet.getString(resultSet.getColumnIndex("networkUpSpeed")) + let networkDownSpeed = resultSet.getString(resultSet.getColumnIndex("networkDownSpeed")) + + results.push(new TIndexInfo( + timestamp, + "18", + cpu0Frequency, cpu1Frequency, cpu2Frequency, cpu3Frequency, cpu4Frequency, cpu5Frequency, cpu6Frequency, cpu7Frequency, + cpu0Load, cpu1Load, cpu2Load, cpu3Load, cpu4Load, cpu5Load, cpu6Load, cpu7Load, + gpuFrequency, gpuLoad, + ddrFrequency, + shellFrameTemp, shellFrontTemp, shellBackTemp, socThermalTemp, systemHTemp, gpuTemp, ambientTemp, batteryTemp, + currentNow, voltageNow, + pss, + fps,fpsJitters, + networkUpSpeed,networkDownSpeed + )) + console.log("resultSet column names:results:index:" + resultSet.rowIndex) + } + console.log("resultSet column names:results length:" + results.length) + console.log("resultSet column names:results:" + JSON.stringify(results)) + return results + }) + } catch (err) { + SPLogger.ERROR(TAG,"resultSet queryIndexInfo Data:err" + err) + } + }, + /** + * Array 封装为TIndexInfo + * @param gpDatas + */ + gpArray2Index(gpDatas: Array): TIndexInfo{ + var tIndexInfo: TIndexInfo = new TIndexInfo() + tIndexInfo.setTaskId("18") + tIndexInfo.setTimeStamp(new Date().getTime().toString()) + if (gpDatas != null) { + for (var index = 0; index < gpDatas.length; index++) { + let curGPData: GPData = gpDatas[index] + let map = curGPData.values + switch (curGPData.moduleType) { + case "CPU": + tIndexInfo.setCPUData( + map.get("cpu0Freq"), + map.get("cpu1Freq"), + map.get("cpu2Freq"), + map.get("cpu3Freq"), + map.get("cpu4Freq"), + map.get("cpu5Freq"), + map.get("cpu6Freq"), + map.get("cpu7Freq") + ) + break; + case "CPULoad": + tIndexInfo.setCPULoadData( + map.get("cpu0Load"), + map.get("cpu1Load"), + map.get("cpu2Load"), + map.get("cpu3Load"), + map.get("cpu4Load"), + map.get("cpu5Load"), + map.get("cpu6Load"), + map.get("cpu7Load") + ) + break; + case "GPU": + tIndexInfo.setGPUData( + map.get("gpuFreq"), + map.get("gpuLoad") + ) + break; + case "DDR": + tIndexInfo.setDDRData( + map.get("ddrFreq") + ) + break; + case "Temp": + tIndexInfo.setTempData( + map.get("shell_frame"), + map.get("shell_front"), + map.get("shell_back"), + map.get("soc-thermal"), + map.get("system_h"), + map.get("gpu-thermal"), + map.get("ambient"), + map.get("Battery") + ) + break; + case "Power": + tIndexInfo.setPowerData( + map.get("current_now"), + map.get("voltage_now") + ) + break; + case "RAM": + tIndexInfo.setRamData( + map.get("pss") + ) + break; + case "FPS": + tIndexInfo.setFpsData( + map.get("fps"), map.get("fpsJitters")) + break; + case "NetWork": + tIndexInfo.setNetWorkData( + map.get("netSpeedUp"), map.get("netSpeedDown")) + break; + } + } + } + return tIndexInfo + } +} diff --git a/device/device_ui/entry/src/main/ets/common/database/LocalRepository.ts b/device/device_ui/entry/src/main/ets/common/database/LocalRepository.ts new file mode 100644 index 0000000..c349f94 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/database/LocalRepository.ts @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import dataRdb from '@ohos.data.rdb' +import database from './DatabaseUtils' +import BundleManager from '../utils/BundleMangerUtils'; +import { dateFormat } from '../utils/TimeUtils' +import { ReportItem } from '../entity/LocalConfigEntity' +import { sql_t_general_info, dbVersion, dbName } from '../constant/ConstantSQL'; +import { AppFileRealDir } from '../constant/ConstantsPath' +import SPLogger from '../utils/SPLogger' + + +const TAG = "LocalRepository" + + +export function initDb(): void { + + const STORE_CONFIG = { + name: dbName + } + + dataRdb.getRdbStore(globalThis.abilityContext, STORE_CONFIG, dbVersion) + .then(rdbStore => { + rdbStore.executeSql(sql_t_general_info, null).catch(err => { + SPLogger.DEBUG(TAG, "--> createTable t_genneral_info err:" + err) + }) + SPLogger.DEBUG(TAG, "--> createTable start execute sql_t_genneral_info:" + sql_t_general_info) + return rdbStore + }) + + getReportListDb().then(res => { + globalThis.reportList = res + let bundleNameArr = [] + for (let reportItemKey in globalThis.reportList) { + bundleNameArr.push(globalThis.reportList[reportItemKey].packageName) + } + + BundleManager.getIconByBundleName(bundleNameArr).then(map => { + globalThis.iconMap = map + }) + + let resReport: Array = res + + globalThis.sumTest = resReport.length + globalThis.sumTestTime = 0 + + let sumTestAppMap = new Map + for (let resReportKey in resReport) { + sumTestAppMap.set(resReport[resReportKey].appName, "") + globalThis.sumTestTime += Number(resReport[resReportKey].testDuration).valueOf() + } + globalThis.sumTestApp = sumTestAppMap.size + }).catch(err => { + SPLogger.DEBUG(TAG, "getReportListDb ERR:" + err); + }) +} + + +export async function getReportListDb(): Promise> { + var result = Array() + await database.queryGeneralData().then(generals => { + for (var i = 0; i < generals.length; i++) { + var curGeneralInfo = generals[i] + result.push( + new ReportItem( + curGeneralInfo.taskId.toString(), + AppFileRealDir + "/" + curGeneralInfo.sessionId.toString(), + curGeneralInfo.packageName, + "", + curGeneralInfo.taskName, + curGeneralInfo.appName, + dateFormat(curGeneralInfo.startTime), + curGeneralInfo.testDuration.toString(), + false + )) + } + }) + return result +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/entity/DatabaseEntity.ets b/device/device_ui/entry/src/main/ets/common/entity/DatabaseEntity.ets new file mode 100644 index 0000000..8ccc496 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/entity/DatabaseEntity.ets @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * GPData实体类 + */ +export class GPData { + public moduleType: String + public timeStamp: number + public values: Map + + constructor(moduleType: String, timeStamp: number, values: Map) { + this.moduleType = moduleType; + this.timeStamp = timeStamp; + this.values = values; + } + + toString() { + var obj = Object.create(null); + var iterator = this.values.keys(); + for (var i = 0; i < this.values.size; i++) { + var key = iterator.next().value; + obj[key] = this.values.get(key); + } + return "GPData{" + + "module='" + this.moduleType + '\'' + + ", timeStamp=" + this.timeStamp + + ", values=" + JSON.stringify(obj) + + '}'; + } +} + +/** + * 任务列表页 任务概览页实体类 + */ +export class TGeneralInfo { + //目录生成的UUID 作为主键 + public sessionId: String + //任务Id + public taskId: String + //测试游戏名称 + public appName: String + //测试游戏版本 + public appVersion: String + //游戏包名 + public packageName: String + //开始时间 + public startTime: number + //结束时间 + public endTime: number + //测试时长 + public testDuration: number + //任务名称 + public taskName: String + //测试方式 middle + public testCase: String + //测试类型 smartPerf beta + public testType: String + //用户名 + public user: String + //用户id + public userId: String + //项目Id + public projectId: String + //报告来源 + public dataSource: String + //sp版本 + public spVersion: String + //设备类型 + public deviceTypeName: String + //设备品牌 + public brand: String + //设备名称 XX-XXX(****00018) + public deviceName: String + //设备型号 + public board: String + //手机版本 + public version: String + //芯片平台 + public plat: String + //大中小核心 + public cpuCluster: String + //设备sn号 + public sn: String + //分辨类 + public resolution: String + //当前亮度 + public screenBrightness: String + //设备当前音量 + public volume: String + //当前电池电量 + public batteryVolume: String + //是否是多路测试 + public isOnline: boolean + //设备号 + public deviceNum: number + //房间号 + public roomNumber: String + //任务类型 主控、被控 + public taskType: number + + constructor(sessionId ?: String, taskId ?: String, appName ?: String, appVersion ?: String, packageName ?: String, startTime ?: number, endTime ?: number, testDuration ?: number, taskName ?: String) { + this.sessionId = sessionId + this.taskId = taskId + this.appName = appName + this.appVersion = appVersion + this.packageName = packageName + this.startTime = startTime + this.endTime = endTime + this.testDuration = testDuration + this.taskName = taskName + } +} + +/** + * 任务详情实体类 + */ +export class TIndexInfo { + //时间戳 主键 + public timestamp: String; + //任务ID + public taskId: String; + //场景标识 + public flag: String; + //温度 + public shellBackTemp: String; + public shellFrameTemp: String; + public shellFrontTemp: String; + public socThermalTemp: String; + public systemHTemp: String; + public gpuTemp: String; + public ambientTemp: String; + public batteryTemp: String; + + //ddr + public ddrFrequency: String; + //fps + public fps: String; + public fpsJitters: String; + //cpu + public cpu0Frequency: String; + public cpu0Load: String; + public cpu1Frequency: String; + public cpu1Load: String; + public cpu2Frequency: String; + public cpu2Load: String; + public cpu3Frequency: String; + public cpu3Load: String; + public cpu4Frequency: String; + public cpu4Load: String; + public cpu5Frequency: String; + public cpu5Load: String; + public cpu6Frequency: String; + public cpu6Load: String; + public cpu7Frequency: String; + public cpu7Load: String; + public cpuLoad: String; + //gpu + public gpuFrequency: String; + public gpuLoad: String; + //power + public currentNow: String; + public capacity: String; + public enableHiz: String; + public status: String; + public voltageNow: String; + //ram + public pss: String; + //HWCPipeGPU + public cacheMisses: String; + public instructions: String; + //HWCPipeGPU + public gpuCycles: String; + public vertexComputeCycles: String; + public fragmentCycles: String; + public tilerCycles: String; + public vertexComputeJobs: String; + public fragmentJobs: String; + public pixels: String; + public earlyZTests: String; + public earlyZKilled: String; + public externalMemoryReadAccesses: String; + public externalMemoryWriteAccesses: String; + public externalMemoryReadBytes: String; + public externalMemoryWriteBytes: String; + public cacheWriteLookups: String; + public cacheReadLookups: String; + public externalMemoryWriteStalls: String; + public externalMemoryReadStalls: String; + public shaderCycles: String; + public shaderArithmeticCycles: String; + public shaderLoadStoreCycles: String; + public shaderTextureCycles: String; + + //QPCounters + public clocksSecond: String; + public gpuUtilization: String; + public gpuBusBusy: String; + public verticesShadedSecond: String; + public fragmentsShadedSecond: String; + public texturesVertex: String; + public texturesFragment: String; + public aluVertex: String; + public aluFragment: String; + public timeShadingFragments: String; + public timeShadingVertices: String; + public timeCompute: String; + public readTotal: String; + public writeTotal: String; + public textureMemoryReadBW: String; + public vertexMemoryRead: String; + public spMemoryRead: String; + public qpGPUFrequency: String; + //network + public currNetworkType: String; + public networkUpSpeed: String; + public networkDownSpeed: String; + public wlanSingleIntensity: String; + public radioSingleIntensity: String; + public networkDelay_SDK: String; + constructor( + timestamp?: String, + taskId?: String, + cpu0Freq?: String, cpu1Freq?: String, cpu2Freq?: String, cpu3Freq?: String, cpu4Freq?: String, cpu5Freq?: String, cpu6Freq?: String, cpu7Freq?: String, + cpu0Load?: string, cpu1Load?: string, cpu2Load?: string, cpu3Load?: string, cpu4Load?: string, cpu5Load?: string, cpu6Load?: string, cpu7Load?: string, + gpuFreq?: String, gpuLoad?: String, + ddrFreq?: String, + shellFrame?: String, shellFront?: String, shellBack?: String, socThermal?: String, systemH?: String, gpu?: String, ambient?: String, battery?: String, + currentNow?: String, voltageNow?: String, + pss?: String, + fps?: String, + fpsJitters?: String, + networkUpSpeed?: String, networkDownSpeed?: String + ) { + this.timestamp = timestamp + this.taskId = taskId + this.cpu0Frequency = cpu0Freq + this.cpu1Frequency = cpu1Freq + this.cpu2Frequency = cpu2Freq + this.cpu3Frequency = cpu3Freq + this.cpu4Frequency = cpu4Freq + this.cpu5Frequency = cpu5Freq + this.cpu6Frequency = cpu6Freq + this.cpu7Frequency = cpu7Freq + this.cpu0Load = cpu0Load + this.cpu1Load = cpu1Load + this.cpu2Load = cpu2Load + this.cpu3Load = cpu3Load + this.cpu4Load = cpu4Load + this.cpu5Load = cpu5Load + this.cpu6Load = cpu6Load + this.cpu7Load = cpu7Load + this.gpuFrequency = gpuFreq + this.gpuLoad = gpuLoad + this.ddrFrequency = ddrFreq + this.shellFrameTemp = shellFrame + this.shellFrontTemp = shellFront + this.shellBackTemp = shellBack + this.socThermalTemp = socThermal + this.systemHTemp = systemH + this.gpuTemp = gpu + this.ambientTemp = ambient + this.batteryTemp = battery + this.currentNow = currentNow + this.voltageNow = voltageNow + this.pss = pss + this.fps = fps + this.fpsJitters = fpsJitters + this.networkUpSpeed = networkUpSpeed + this.networkDownSpeed = networkDownSpeed + } + + setTimeStamp(timestamp: String) { + this.timestamp = timestamp + } + + setTaskId(taskId: String) { + this.taskId = taskId + } + + setCPUData(cpu0Freq: String, cpu1Freq: String, cpu2Freq: String, cpu3Freq: String, cpu4Freq: String, cpu5Freq: String, cpu6Freq: String, cpu7Freq: String) { + this.cpu0Frequency = cpu0Freq + this.cpu1Frequency = cpu1Freq + this.cpu2Frequency = cpu2Freq + this.cpu3Frequency = cpu3Freq + this.cpu4Frequency = cpu4Freq + this.cpu5Frequency = cpu5Freq + this.cpu6Frequency = cpu6Freq + this.cpu7Frequency = cpu7Freq + } + + setCPULoadData(cpu0Load: String, cpu1Load: String, cpu2Load: String, cpu3Load: String, cpu4Load: String, cpu5Load: String, cpu6Load: String, cpu7Load: String) { + this.cpu0Load = cpu0Load + this.cpu1Load = cpu1Load + this.cpu2Load = cpu2Load + this.cpu3Load = cpu3Load + this.cpu4Load = cpu4Load + this.cpu5Load = cpu5Load + this.cpu6Load = cpu6Load + this.cpu7Load = cpu7Load + } + + setGPUData(gpuFreq: String, gpuLoad: String) { + this.gpuFrequency = gpuFreq + this.gpuLoad = gpuLoad + } + + setDDRData(ddrFreq: String) { + this.ddrFrequency = ddrFreq + } + + setTempData(shellFrame: String, shellFront: String, shellBack: String, soc_thermal: String, system_h: String, gpu: String, ambient: String, battery: String) { + this.shellFrameTemp = shellFrame + this.shellFrontTemp = shellFront + this.shellBackTemp = shellBack + this.socThermalTemp = soc_thermal + this.systemHTemp = system_h + this.gpuTemp = gpu + this.ambientTemp = ambient + this.batteryTemp = battery + } + + setPowerData(currentNow: String, voltageNow: String) { + this.currentNow = currentNow + this.voltageNow = voltageNow + } + + setFpsData(fps: String, fpsJitter: String) { + this.fps = fps + this.fpsJitters = fpsJitter + } + + setRamData(pss: String) { + this.pss = pss + } + + setNetWorkData(networkUpSpeed:String, networkDownSpeed:String){ + this.networkDownSpeed = networkDownSpeed + this.networkUpSpeed = networkUpSpeed + } + + setDefaultValue() { + let properties: any[] = Object.keys(this) + properties.forEach(property => { + this[property] = "-1" + }) + } +} + +//dubai app功耗 +export class TPowerAppInfo { + //主键 自增 + public id: String; + public taskId: String; + public application: String; + public power: String; + public current: String; + public percent: String; +} + +//dubai 器件功耗 +export class TPowerSensorInfo { + //主键 自增 + public id: String; + public taskId: String; + public sensor: String; + public power: String; + public current: String; + public percent: String; +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/entity/LocalConfigEntity.ts b/device/device_ui/entry/src/main/ets/common/entity/LocalConfigEntity.ts new file mode 100644 index 0000000..55bb4bb --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/entity/LocalConfigEntity.ts @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export enum TestMode { + ONLINE, + BRIGHTNESS, + STARTUP +} + +export class OtherSupport { + public testName: string + public testSrc: string + public testMode: TestMode + public resource: Resource + + constructor(testName: string, testSrc: string, testMode: TestMode, resource: Resource) { + this.testName = testName + this.testSrc = testSrc + this.testMode = testMode + this.resource = resource + } +} + +export class SwitchItem { + public id: string + public switchName: string + public switchSrc: Resource + public isOpen: boolean + public enable: boolean + + constructor(id: string, switchName: string, switchSrc: Resource, isOpen: boolean, enable: boolean) { + this.id = id + this.switchName = switchName + this.switchSrc = switchSrc + this.isOpen = isOpen + this.enable = enable + } +} + +export class CollectItem { + public name: string + public isSupport: boolean + public isSelect: boolean + + constructor(name: string, isSupport: boolean, isSelect: boolean) { + this.name = name + this.isSupport = isSupport + this.isSelect = isSelect + } +} + +export class TaskInfoConfig { + public testName: string + public collectItem: Array + public switchItem: Array + + constructor(testName?: string, collectItem?: Array, switchItem?: Array) { + this.testName = testName + this.collectItem = collectItem + this.switchItem = switchItem + } +} + +export class AppInfoItem { + public id: number + public packageName: string + public appName: string + public appVersion: String + public appIcon: string + public abilityName: string + + constructor(packageName: string, appName: string, appVersion: String, appIcon: string, abilityName: string) { + this.packageName = packageName + this.appName = appName + this.appVersion = appVersion + this.appIcon = appIcon + this.abilityName = abilityName + } +} + +export class ReportItem { + public sessionId: String; + public dbPath: String; + public packageName: String; + public iconId: String; + public name: String; + public appName: String; + public startTime: String; + public testDuration: String; + public upStatus: boolean; + + constructor(sessionId: String, dbPath: String, packageName: String, iconId: String, name: String, appName: String, startTime: String, testDuration: String, upStatus: boolean) { + this.sessionId = sessionId + this.dbPath = dbPath + this.packageName = packageName + this.iconId = iconId + this.name = name + this.appName = appName + this.startTime = startTime + this.testDuration = testDuration + this.upStatus = upStatus + } + + public getStartTime(): string{ + return this.startTime.valueOf() + } + + public getTestDuration(): string{ + return this.testDuration.valueOf() + } + + public getDbPath(): string{ + return this.dbPath.valueOf() + } +} + +export const otherSupportList = new Array( + new OtherSupport('亮度调节', '调节屏幕亮度', TestMode.BRIGHTNESS, $r("app.media.icon_brightness_plus")), +) + +export let switchList = new Array( + new SwitchItem("trace", '是否抓取trace', $r("app.media.icon_average_frame_b"), false, true), + new SwitchItem("is_camera", '是否为相机应用', $r("app.media.icon_camera"), false, true), + new SwitchItem("is_video", '是否为视频应用', $r("app.media.icon_video"), false, true), + new SwitchItem("screen_capture", '是否开启截图', $r("app.media.icon_screencap"), false, true) +) + +export class QuestionItem { + public question: string + public answer: string + + constructor(question: string, answer: string) { + this.answer = answer + this.question = question + } +} + +export const questionList = new Array( + new QuestionItem('1.SP工具支持FPS采集吗?', '可以,fps依赖Hidumper能力..'), + new QuestionItem('2.SP工具支持RAM采集吗?', 'ram采集目前是 读取进程节点内存信息中的PSS值...'), + new QuestionItem('3.FPS采集不到?', '可能是视频应用,需要联系开发添加对应的图层,做采集适配'), + new QuestionItem('4.SP采集原理?', '目前除fps外,其他采集均是通过cat 系统节点获取'), + new QuestionItem('5.报告页的值是怎么算的?', '最终以一场测试结果的平均值为准'), + new QuestionItem('6.SP后续规划?', '集成更多采集能力,如trace采集,counter采集,网络采集等等;优化数据展示方式,报告上传网站端,在线分析性能功耗问题') +) + +export class SummaryItem { + public icon: Resource + public content: string + public value: string + + constructor(icon: Resource, content: string, value: string) { + this.icon = icon + this.content = content + this.value = value + } +} + diff --git a/device/device_ui/entry/src/main/ets/common/entity/SystemEntity.ets b/device/device_ui/entry/src/main/ets/common/entity/SystemEntity.ets new file mode 100644 index 0000000..94f0dc2 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/entity/SystemEntity.ets @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export interface ProcessRunningInfo { + /** + * @default process id + * @since 8 + * @syscap SystemCapability.Ability.AbilityRuntime.Mission + */ + pid: number; + + /** + * @default user id + * @since 8 + * @syscap SystemCapability.Ability.AbilityRuntime.Mission + */ + uid: number; + + /** + * @default the name of the process + * @since 8 + * @syscap SystemCapability.Ability.AbilityRuntime.Mission + */ + processName: string; + + /** + * @default an array of the bundleNames running in the process + * @since 8 + * @syscap SystemCapability.Ability.AbilityRuntime.Mission + */ + bundleNames: Array; +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/entity/UserEntity.ets b/device/device_ui/entry/src/main/ets/common/entity/UserEntity.ets new file mode 100644 index 0000000..1b38d3f --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/entity/UserEntity.ets @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class User { + public token: String; + public expires_in: String; //过期时间 + public refresh_token: String; //刷新token + public expires_at: String; //时间 + public token_type: String; //token类型 + + constructor(token?: String, expires_in?: String, refresh_token?: String, expires_at?: String, token_type?: String) { + this.token = token + this.expires_in = expires_in + this.refresh_token = refresh_token + this.expires_at = expires_at + this.token_type = token_type + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/profiler/MainWorkTask.ets b/device/device_ui/entry/src/main/ets/common/profiler/MainWorkTask.ets new file mode 100644 index 0000000..94ab554 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/profiler/MainWorkTask.ets @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import worker from '@ohos.worker'; // 导入worker模块 +import { CatchTraceStatus } from '../profiler/base/ProfilerConstant'; +import SPLogger from '../utils/SPLogger' +export let MainWorker = new worker.Worker("workers/worker.js") + +MainWorker.onmessage = function (e) { + SPLogger.DEBUG("MainWorker","MainWorker recv:" + e.data); + let arr = e.data.split("$") + if (arr[0] == "RAM ") { + globalThis.ramArr.push(arr[1]) + } + if (arr[0] == "FPS") { + globalThis.fpsArr.push(arr[1]) + globalThis.fpsJitterArr.push(arr[2]) + globalThis.timerFps=arr[1] + if (globalThis.catchTraceState == CatchTraceStatus.catch_trace_start + || globalThis.catchTraceState == CatchTraceStatus.catch_trace_finish + || globalThis.catchTraceState == CatchTraceStatus.catch_trace_first_running + ) { + if (globalThis.fpsJitterArr != undefined && globalThis.fpsJitterArr != null && globalThis.fpsJitterArr != "") { + let tempQueue: Array = globalThis.fpsJitterArr + let curJitter = tempQueue.pop() + let tempJitterArr = curJitter.split("==") + for (var i = 0; i < tempJitterArr.length; i++) { + let tmp = tempJitterArr[i] + let jitter = parseInt(tmp) / 1e6 + if (jitter > 100) { + globalThis.jitterTrace = true + return + } + } + } + } + } +} + diff --git a/device/device_ui/entry/src/main/ets/common/profiler/NativeTaskFun.ts b/device/device_ui/entry/src/main/ets/common/profiler/NativeTaskFun.ts new file mode 100644 index 0000000..6ee272f --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/profiler/NativeTaskFun.ts @@ -0,0 +1,49 @@ + +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { getPidOfAbility } from '../utils/SystemUtils'; +import SPLogger from '../utils/SPLogger' +import nativeProfiler from "libsmartperf.so" + + +const TAG = "NativeTaskFun" + +export class NativeTaskFun { + static initAllFun() { + globalThis.CreateNativeFps = ((pkgName: string) => { + let fpsStr: string = nativeProfiler.getFpsData(pkgName) + SPLogger.DEBUG(TAG, "nativeProfiler" + "--> fpsStr:" + fpsStr) + return fpsStr + }) + globalThis.CreateNativeRam = (() => { + let ramStr: string = nativeProfiler.getRamData(globalThis.processPid) + SPLogger.DEBUG(TAG, "nativeProfiler" + "--> ramStr :" + ramStr) + globalThis.ramArr.push(ramStr) + return ramStr + }) + + globalThis.CheckDaemon = (() => { + let status: string = nativeProfiler.checkDaemon() + SPLogger.DEBUG(TAG, "nativeProfiler" + "--> daemon status :" + status) + return status + }) + + globalThis.checkAccess = ((path: string) => { + let status: string = nativeProfiler.checkAccess(path) + SPLogger.DEBUG(TAG, "nativeProfiler --> "+ path + " status :" + status) + return status + }) + } +} diff --git a/device/device_ui/entry/src/main/ets/common/profiler/ProfilerTask.ets b/device/device_ui/entry/src/main/ets/common/profiler/ProfilerTask.ets new file mode 100644 index 0000000..6aaa9a7 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/profiler/ProfilerTask.ets @@ -0,0 +1,203 @@ + +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import deviceInfo from '@ohos.deviceInfo' +import CommonEvent from '@ohos.commonevent' +import database from '../database/databaseutils' +import { getPidOfAbility } from '../utils/SystemUtils'; +import { CPU } from './item/CPU'; +import { csvGeneralInfo, csvTIndexInfo } from '../utils/csvutils' +import { createFilePath } from '../utils/ioutils' +import { GPData, TIndexInfo, TGeneralInfo } from '../entity/databaseentity' +import { ProfilerFactory } from './base/ProfilerFactory' +import SPLogger from '../../common/utils/SPLogger' + +export class ProfilerTask { + private collectItems:Array = [] + private static instance: ProfilerTask + + public static getInstance(): ProfilerTask { + if (this.instance == null) { + this.instance = new ProfilerTask + } + return ProfilerTask.instance + } + + initModule() { + SPLogger.DEBUG(ProfilerTask.name,'ProfilerTask initModule configs: ' + JSON.stringify(globalThis.collectConfigs)) + const keys: any[] = Object.keys(globalThis.collectConfigs) + for (var key of keys) { + if (globalThis.collectConfigs[key]) { + let typeCollect = ProfilerFactory.getProfilerByConfig(key.toString()) + SPLogger.DEBUG(ProfilerTask.name,'ProfilerTask initModule: ' + typeCollect) + if (typeCollect == null) { + continue + }else{ + this.collectItems.push(typeCollect.init()) + } + } + } + //默认不使用daemon + globalThis.useDaemon = false + let status = globalThis.CheckDaemon() + if (status == "Running") { + globalThis.useDaemon = true + } + SPLogger.DEBUG(ProfilerTask.name,'ProfilerTask initModule:Daemon status ' + status) + } + + getSupports(keys:string[]){ + let supports = new Map + for (var key of keys) { + let typeCollect = ProfilerFactory.getProfilerByConfig(key) + supports.set(key.toString(),typeCollect.isSupport()) + } + return supports + } + + taskInit() { + SPLogger.DEBUG(ProfilerTask.name,'ProfilerUtils taskInit call'); + var now = new Date() + //数据库时间戳标记 + globalThis.dbTime = now.getTime() + //开始时间 + globalThis.startTime = now.getTime() + //结束时间 + globalThis.endTime = -1 + //时间计数器 + globalThis.collectIntervalNum = -1 + //入库数据 + globalThis.tTndexs = new Array() + //实时数据 + globalThis.tTndex = new TIndexInfo() + // ram队列 + globalThis.ramArr = new Array() + // fps队列 + globalThis.fpsArr = new Array() + // fpsJitter队列 + globalThis.fpsJitterArr = new Array() + //初始化采集目标进程pid + getPidOfAbility(globalThis.collectPkg).then(pid=>{ + globalThis.processPid = pid + }) + //初始化数据库 2022-02-23 dbName改为时间戳 + database.createTable(globalThis.dbTime) + SPLogger.DEBUG(ProfilerTask.name,'ProfilerUtils taskInit called'); + } + + taskStart() { + SPLogger.DEBUG(ProfilerTask.name,'ProfilerTask taskStart call'); + var gpDataArr: Array = new Array() + this.collectItems.forEach( + (moduleName)=>{ + let typeCollect = ProfilerFactory.getProfilerByConfig(moduleName.toString()) + if (typeCollect != null) { + let gpData:GPData =typeCollect.readData() + if(typeCollect instanceof CPU){ + typeCollect.readCPULoad() + } + gpDataArr.push(gpData) + SPLogger.DEBUG(ProfilerTask.name,"profiler ProfilerTask:curData:"+gpData); + } + } + ) + + //防止cpuLoad -1 + if (globalThis.collectIntervalNum > 0) { + let tTndex = database.gpArray2Index(gpDataArr) + globalThis.tTndexs.push(tTndex) + globalThis.tTndex = tTndex; + CommonEvent.publish("event", { code: 0, data: tTndex.fps + "", }, (err)=>{}); + } + globalThis.collectIntervalNum++ + SPLogger.DEBUG(ProfilerTask.name,"profiler globalThis.collectIntervalNum " + globalThis.collectIntervalNum) + + SPLogger.DEBUG(ProfilerTask.name,'ProfilerTask taskStart called'); + } + + taskStop() { + SPLogger.DEBUG(ProfilerTask.name,'ProfilerTask taskStop call'); + // t_index_info 入库 + if (globalThis.tTndexs.length > 2) { + for (var index = 2; index < globalThis.tTndexs.length; index++) { + const tTndex = globalThis.tTndexs[index]; + database.insertData("t_index_info", globalThis.dbTime, tTndex) + } + } + createFilePath(globalThis.abilityContext.getApplicationContext().filesDir + "/" + globalThis.dbTime + "/t_index_info.csv", csvTIndexInfo(globalThis.tTndexs)) + // t_general_info 入库 + globalThis.endTime = new Date().getTime() + let tGeneralInfo = new TGeneralInfo( + globalThis.dbTime.toString(), + "NA", + globalThis.appName, + globalThis.appVersion, + globalThis.packageName, + globalThis.startTime, + globalThis.endTime, + globalThis.collectIntervalNum, + globalThis.testTaskName + ) + tGeneralInfo.board = deviceInfo.brand + tGeneralInfo.deviceTypeName = deviceInfo.deviceType + tGeneralInfo.brand = deviceInfo.brand + tGeneralInfo.version = deviceInfo.displayVersion + tGeneralInfo.sn = deviceInfo.serial + database.insertGeneraData("t_general_info", tGeneralInfo) + createFilePath(globalThis.abilityContext.getApplicationContext().filesDir + "/" + globalThis.dbTime + "/t_general_info.csv", csvGeneralInfo(tGeneralInfo)) + + SPLogger.DEBUG(ProfilerTask.name,'ProfilerTask taskStop called'); + } + + taskDestroy() { + //数据库时间戳标记 + globalThis.dbTime = -1 + //开始时间 + globalThis.startTime = -1 + //结束时间 + globalThis.endTime = -1 + //入库数据 + globalThis.tTndexs = new Array() + + //采集配置恢复fileOpen + globalThis.collectConfigs = -1 + globalThis.collectPkg = -1 + + //采集定时器 配置 + globalThis.collectInterval = -1 + globalThis.collectIntervalCollect = -1 + globalThis.collectIntervalNum = -1 + + //socket采集队列 fps ram + globalThis.fpsArr = new Array() + globalThis.fpsJitterArr = new Array() + globalThis.ramArr = new Array() + } +} + + + + + + + + + + + + + + + diff --git a/device/device_ui/entry/src/main/ets/common/profiler/base/BaseProfiler.ets b/device/device_ui/entry/src/main/ets/common/profiler/base/BaseProfiler.ets new file mode 100644 index 0000000..78fcd09 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/profiler/base/BaseProfiler.ets @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { GPData } from '../../entity/databaseentity'; +import { CollectorType } from './ProfilerConstant' + +export abstract class BaseProfiler { + abstract init(): CollectorType + abstract isSupport(): Boolean + abstract readData(): GPData +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/profiler/base/BaseProfilerUtils.ets b/device/device_ui/entry/src/main/ets/common/profiler/base/BaseProfilerUtils.ets new file mode 100644 index 0000000..8077764 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/profiler/base/BaseProfilerUtils.ets @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import fileio from '@ohos.fileio'; +import util from '@ohos.util'; +import { GPData } from '../../entity/databaseentity'; +import SPLogger from '../../../common/utils/SPLogger' + +const BUFF_SIZE = 1024 +const TAG="BaseProfilerUtils" +/** + * 构造GPData + */ +export function createGPData(moduleType: string, values: Map): GPData { + var now = new Date(); + return new GPData(moduleType, now.getTime(), values) +} + + +export function isAccess(path: string):boolean{ + var isAccess = false + try { + fileio.accessSync(path); + isAccess = true + } catch(err) { + SPLogger.DEBUG(TAG,"accessSync failed with error:"+ err + "path:" + path); + } + return isAccess +} + +/** + * 通用文件节点打开 + * @param path + */ +export function fileOpen(path: string): String { + + if (!isAccess(path)) { + return "null" + } + + try { + var fd = -1 + fd = fileio.openSync(path, 0o0) + let buf = new ArrayBuffer(BUFF_SIZE); + fileio.readSync(fd, buf); + var result: String = String.fromCharCode.apply(null, new Uint8Array(buf)) + SPLogger.DEBUG(TAG,"fileOpen path data:"+result); + return util.printf("%s", result.substring(0, lastIndex(result))).toString() + } catch (err) { + SPLogger.ERROR(TAG,"fileOpen path:" + path + " error:" + err); + } finally { + fileio.closeSync(fd) + } + return "null" +} + +/** + * 通用遍历目录节点 + * @param path + * @param regexp + */ +export function travelFile(path: string, regexp: string): Array { + let dir + let fileNames = [] + + if (!isAccess(path)) { + return [] + } + + try { + dir = fileio.opendirSync(path); + do { + var dirent = dir.readSync() + if (dirent == null) { + break + } + let name: String = dirent.name + if (name.match(regexp)) { + SPLogger.DEBUG(TAG, "travelFile get name:" + dirent.name) + fileNames.push(name) + } + if (regexp == "") { + fileNames.push(name) + } + } while (dirent != null) + } catch (err) { + SPLogger.ERROR(TAG, "travelFile get err:" + err); + SPLogger.ERROR(TAG, "travelFile err path:" + path); + } finally { + dir.closeSync(); + } + return fileNames +} + + + +/** + * 返回字符结尾符坐标 + * @param str + */ +function lastIndex(str) { + var index = -1; + for (var i = 0; i < str.length; i++) { + var temp = str.charCodeAt(i).toString(16); + if (temp == 'a') { + return i; + } + } + return index; +} + +/** + * 睡眠函数 + * @param numberMillis + */ +export function sleep(numberMillis) { + var now = new Date(); + var exitTime = now.getTime() + numberMillis; + while (true) { + now = new Date(); + if (now.getTime() > exitTime) + return; + } +} +/** + * 提取字符串数字 + */ +export function extractNumber(originStr) { + let result = "" + for (var index = 0; index < originStr.length; index++) { + const element: string = originStr[index]; + if (element.match("^[0-9]*$")) { + result += element + } + } + return result +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/profiler/base/ProfilerConstant.ts b/device/device_ui/entry/src/main/ets/common/profiler/base/ProfilerConstant.ts new file mode 100644 index 0000000..142b14e --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/profiler/base/ProfilerConstant.ts @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//trace 状态 +export enum CatchTraceStatus { + catch_trace_start = 1, + // trace采集结束 + catch_trace_finish = 2, + // trace采集第一次运行中 + catch_trace_first_running = 3, + // trace采集运行中 + catch_trace_running = 4, + // trace 采集 停止后间隔60次 + catch_trace_times = 60 +} + +//采集状态 +export enum TaskStatus { + //采集初始化 + task_init = 1, + //采集运行中 + task_running = 2, + //采集暂停 + task_pause = 3, + //采集结束 + task_stop = 4 +} + +//采集项 +export enum CollectorType { + TYPE_CPU = "CPU", + TYPE_GPU = "GPU", + TYPE_DDR = "DDR", + TYPE_FPS = "FPS", + TYPE_HW_COUNTER = "HW_COUNTER", + TYPE_POWER = "POWER", + TYPE_TEMPERATURE = "TEMP", + TYPE_RAM = "RAM", + TYPE_NET = "NETWORK", + TYPE_UNKNOWN = "UNKNOWN" +}; + + + diff --git a/device/device_ui/entry/src/main/ets/common/profiler/base/ProfilerFactory.ets b/device/device_ui/entry/src/main/ets/common/profiler/base/ProfilerFactory.ets new file mode 100644 index 0000000..dbe7d78 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/profiler/base/ProfilerFactory.ets @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { CPU } from '../item/CPU' +import { GPU } from '../item/GPU' +import { FPS } from '../item/FPS' +import { Power } from '../item/Power' +import { RAM } from '../item/RAM' +import { Thermal } from '../item/Thermal' +import { DDR } from '../item/DDR' +import { NetWork } from '../item/NetWork'; +import { CollectorType } from './ProfilerConstant' +import { BaseProfiler } from './BaseProfiler' +import SPLogger from '../../../common/utils/SPLogger' + +export class ProfilerFactory { + static getProfilerByConfig(moduleName: string): BaseProfiler{ + if (moduleName == CollectorType.TYPE_FPS) { + SPLogger.DEBUG(ProfilerFactory.name,"getProfilerByConfig:TYPE_FPS" ); + return FPS.getInstance() + } else if (moduleName == CollectorType.TYPE_CPU) { + SPLogger.DEBUG(ProfilerFactory.name,"getProfilerByConfig:TYPE_CPU"); + return CPU.getInstance() + } else if (moduleName == CollectorType.TYPE_GPU) { + SPLogger.DEBUG(ProfilerFactory.name,"getProfilerByConfig:TYPE_GPU" ); + return GPU.getInstance() + } else if (moduleName == CollectorType.TYPE_POWER) { + SPLogger.DEBUG(ProfilerFactory.name,"getProfilerByConfig:TYPE_POWER" ); + return Power.getInstance() + } else if (moduleName == CollectorType.TYPE_RAM) { + SPLogger.DEBUG(ProfilerFactory.name,"getProfilerByConfig:TYPE_RAM" ); + return RAM.getInstance() + } else if (moduleName == CollectorType.TYPE_TEMPERATURE) { + SPLogger.DEBUG(ProfilerFactory.name,"getProfilerByConfig:TYPE_TEMPERATURE" ); + return Thermal.getInstance() + } else if (moduleName == CollectorType.TYPE_DDR) { + SPLogger.DEBUG(ProfilerFactory.name,"getProfilerByConfig:TYPE_DDR"); + return DDR.getInstance() + }else if (moduleName == CollectorType.TYPE_NET) { + SPLogger.DEBUG(ProfilerFactory.name,"getProfilerByConfig:TYPE_NET"); + return NetWork.getInstance() + } + return null + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/profiler/base/SocketProfiler.ets b/device/device_ui/entry/src/main/ets/common/profiler/base/SocketProfiler.ets new file mode 100644 index 0000000..98200ab --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/profiler/base/SocketProfiler.ets @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export interface SocketProfiler{ + readMessageQueue():void +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/profiler/item/CPU.ets b/device/device_ui/entry/src/main/ets/common/profiler/item/CPU.ets new file mode 100644 index 0000000..e475360 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/profiler/item/CPU.ets @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import fileio from '@ohos.fileio'; +import { fileOpen, travelFile, createGPData, isAccess} from '../base/BaseProfilerUtils'; +import { BaseProfiler } from '../base/BaseProfiler' +import { CollectorType } from '../base/ProfilerConstant' +import { GPData } from '../../entity/databaseentity'; +import SPLogger from '../../../common/utils/SPLogger' +enum CPU_CONFIG { + CPU_BASE = "/sys/devices/system/cpu", + CPU_CUR_FREQ = "/cpufreq/scaling_cur_freq", + CPU_LOAD = "/proc/stat" +} + +export class CPU extends BaseProfiler { + private cpuMap: Map = new Map + private cpuCoreNum: number + private prebufferArr = ["", "", "", "", "", "", "", "", ""] + + public static instance: CPU = null + public static getInstance() { + if (this.instance == null) { + this.instance = new CPU() + } + return this.instance + } + + init() { + //初始化CPU 核心数 节点路径 + this.cpuCoreNum = travelFile(CPU_CONFIG.CPU_BASE,"cpu[0-9]").length + return CollectorType.TYPE_CPU + } + + isSupport(){ + if(isAccess(CPU_CONFIG.CPU_BASE)&&isAccess(CPU_CONFIG.CPU_LOAD)){ + if (globalThis.checkAccess(CPU_CONFIG.CPU_LOAD) == "PermissionAccessed") { + return true + } + } + return false + } + + readData() { + for (var i = 0;i < this.cpuCoreNum; i++) { + const path = CPU_CONFIG.CPU_BASE + "/cpu" + i + CPU_CONFIG.CPU_CUR_FREQ + var cpu = fileOpen(path) + this.cpuMap.set("cpu" + i + "Freq", cpu) + SPLogger.DEBUG(CPU.name,"profile_readCpu cpu" + i + "freq:" + cpu); + } + return createGPData("CPU", this.cpuMap) + } + + readCPULoad(): GPData { + const path = CPU_CONFIG.CPU_LOAD + var workLoadArr = [] + try { + var fd = -1 + fd = fileio.openSync(path, 0o0) + SPLogger.DEBUG(CPU.name,"readCPULoad fd:" + fd); + let buf = new ArrayBuffer(2048); + fileio.readSync(fd, buf); + let cpuStr: String = String.fromCharCode.apply(null, new Uint8Array(buf)) + SPLogger.DEBUG(CPU.name,"readCPULoad cpuStr:" + cpuStr); + let cpuStrArr = [] + cpuStr = cpuStr.substring(0, cpuStr.indexOf("intr")) + let nextj = 0; + let j + for (var i = 1;i < cpuStr.length; i++) { + if (cpuStr.charAt(i) == 'c') { + j = nextj + nextj = i + cpuStrArr.push(cpuStr.substring(j, nextj)) + } + } + cpuStrArr.push(cpuStr.substring(nextj, nextj + 50)) + let buffer = "" + for (let index = 1;index < cpuStrArr.length; index++) { + buffer = cpuStrArr[index] + let load = this.calCPULoad(buffer, this.prebufferArr[index]) + workLoadArr.push(load) + this.prebufferArr[index] = buffer + } + } catch (err) { + SPLogger.ERROR(CPU.name,"readCPULoad path:" + path + " error:" + err); + } finally { + fileio.closeSync(fd) + } + + let map = new Map + for (let index = 0; index < workLoadArr.length; index++) { + const element = workLoadArr[index]; + map.set("cpu" + index + "Load", element) + } + return createGPData("CPULoad", map) + } + + calCPULoad(buffer: string, pre_buffer: string): number { + + if (pre_buffer.length == 0) { + return -1 + } + let timeArr: string[] = buffer.split(" "); + let pre_timeArr: string[] = pre_buffer.split(" "); + + timeArr.reverse().pop() + pre_timeArr.reverse().pop() + timeArr.reverse() + pre_timeArr.reverse() + + let time = this.ArrStr2Number(timeArr) + let pre_time = this.ArrStr2Number(pre_timeArr) + + let user = time[0] + time[1] - pre_time[0] - pre_time[1]; + let sys = time[2] - pre_time[2]; + let idle = time[3] - pre_time[3]; + let iowait = time[4] - pre_time[4]; + let irq = time[5] + time[6] - pre_time[5] - pre_time[6]; + let total = user + sys + idle + iowait + irq; + if (user < 0 || sys < 0 || idle < 0 || iowait < 0 || irq < 0) { + return 0; + } + let per_user = user * 100.0 / total; + let per_sys = sys * 100.0 / total; + let per_iowait = iowait * 100.0 / total; + let per_irq = irq * 100.0 / total; + let workload = per_user + per_sys + per_iowait + per_irq; + return Number(workload.toFixed(2)).valueOf() + } + + ArrStr2Number(arr: Array): Array { + let result = new Array() + for (var index = 0; index < arr.length; index++) { + const element = arr[index].replace("\n", ""); + result.push(new Number(element).valueOf()) + } + return result + } +} diff --git a/device/device_ui/entry/src/main/ets/common/profiler/item/DDR.ets b/device/device_ui/entry/src/main/ets/common/profiler/item/DDR.ets new file mode 100644 index 0000000..d118bad --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/profiler/item/DDR.ets @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { fileOpen, createGPData, isAccess} from '../base/BaseProfilerUtils'; +import { BaseProfiler } from '../base/BaseProfiler' +import { CollectorType } from '../base/ProfilerConstant' +import SPLogger from '../../../common/utils/SPLogger' + +enum DDR_CONFIG { + DDR_BASE = "/sys/class/devfreq/ddrfreq/cur_freq" +} + +export class DDR extends BaseProfiler { + private ddrMap: Map = new Map + + public static instance: DDR = null + public static getInstance() { + if (this.instance == null) { + this.instance = new DDR() + } + return this.instance + } + + init() { + //初始化DDR 节点 + return CollectorType.TYPE_DDR + } + + isSupport(){ + if(isAccess(DDR_CONFIG.DDR_BASE)){ + return true + } + return false + } + + readData() { + const path = DDR_CONFIG.DDR_BASE + let ddr = fileOpen(path) + this.ddrMap.set("ddrFreq", ddr) + SPLogger.DEBUG(DDR.name, "profile_readDDR" + ddr); + return createGPData("DDR", this.ddrMap) + } +} diff --git a/device/device_ui/entry/src/main/ets/common/profiler/item/FPS.ets b/device/device_ui/entry/src/main/ets/common/profiler/item/FPS.ets new file mode 100644 index 0000000..1fa4443 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/profiler/item/FPS.ets @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createGPData, extractNumber , isAccess} from '../base/BaseProfilerUtils'; +import { BaseProfiler } from '../base/BaseProfiler' +import { CollectorType } from '../base/ProfilerConstant' +import { SocketProfiler } from '../base/SocketProfiler' +import SPLogger from '../../../common/utils/SPLogger' + +export class FPS extends BaseProfiler implements SocketProfiler { + private fpsMap: Map = new Map + + public static instance: FPS = null + public static getInstance() { + if (this.instance == null) { + this.instance = new FPS() + } + return this.instance + } + + init() { + //初始化FPS + return CollectorType.TYPE_FPS + } + + isSupport(){ + return true + } + + readData() { + if (globalThis.useDaemon) { + this.readMessageQueue() + } else { + let fpsStr: string = globalThis.CreateNativeFps(globalThis.collectPkg) + if (fpsStr.length > 0) { + let fpsArr = fpsStr.split("|") + this.fpsMap.set("fps", extractNumber(fpsArr[0])) + let fpsJitters = "\"" + fpsArr[1].split("==").join(",") + "\"" + this.fpsMap.set("fpsJitters", fpsJitters) + globalThis.fpsArr.push(fpsArr[0]) + globalThis.fpsJitterArr.push(fpsArr[1]) + globalThis.timerFps = fpsArr[0] + } + } + return createGPData("FPS", this.fpsMap) + } + + readMessageQueue() { + SPLogger.DEBUG(FPS.name, "messageQueue for fps" + globalThis.fpsArr.length) + if (globalThis.fpsArr.length > 0) { + let fpsQueue: Array = globalThis.fpsArr + let fpsJitterQueue: Array = globalThis.fpsJitterArr + let curFPS = fpsQueue.pop() + let curFPSJitter = fpsJitterQueue.pop() + let fpsJitters = "\"" + curFPSJitter.split("==").join(",") + "\"" + this.fpsMap.set("fpsJitters", fpsJitters) + this.fpsMap.set("fps", extractNumber(curFPS)) + } + } +} diff --git a/device/device_ui/entry/src/main/ets/common/profiler/item/GPU.ets b/device/device_ui/entry/src/main/ets/common/profiler/item/GPU.ets new file mode 100644 index 0000000..1ebd21f --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/profiler/item/GPU.ets @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { fileOpen, createGPData, isAccess} from '../base/BaseProfilerUtils'; +import { BaseProfiler } from '../base/BaseProfiler' +import { CollectorType } from '../base/ProfilerConstant' + +enum GPU_CONFIG { + GPU_FREQ_PATH = "/sys/class/devfreq/fde60000.gpu/cur_freq", //RK + GPU_LOAD_PATH = "/sys/class/devfreq/fde60000.gpu/load", //RK +// GPU_FREQ_PATH = "/sys/class/devfreq/gpufreq/cur_freq", // wgr +// GPU_LOAD_PATH = "/sys/class/devfreq/gpufreq/gpu_scene_aware/utilisation", // wgr +} + +export class GPU extends BaseProfiler { + private gpuMap: Map = new Map + + public static instance: GPU = null + public static getInstance() { + if (this.instance == null) { + this.instance = new GPU() + } + return this.instance + } + + init() { + //初始化GPU + return CollectorType.TYPE_GPU + } + isSupport(){ + if(isAccess(GPU_CONFIG.GPU_FREQ_PATH)&&isAccess(GPU_CONFIG.GPU_LOAD_PATH)){ + return true + } + return false + } + readData() { + console.log("GPU:BaseProfiler called") + let gpuFreq = fileOpen(GPU_CONFIG.GPU_FREQ_PATH) + let gpuLoad = fileOpen(GPU_CONFIG.GPU_LOAD_PATH) + console.log("GPU:BaseProfiler called1111") + let loadStr: string[] = gpuLoad.split("@") + let load = "-1" + if (loadStr.length > 0) { + load = loadStr[0].toString() + }else{ + load = gpuLoad.toString() + } + this.gpuMap.set("gpuFreq", gpuFreq) + this.gpuMap.set("gpuLoad", load) + console.log("GPU:BaseProfiler called2222") + return createGPData("GPU", this.gpuMap) + } +} diff --git a/device/device_ui/entry/src/main/ets/common/profiler/item/NetWork.ets b/device/device_ui/entry/src/main/ets/common/profiler/item/NetWork.ets new file mode 100644 index 0000000..33d1917 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/profiler/item/NetWork.ets @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createGPData } from '../base/BaseProfilerUtils'; +import { BaseProfiler } from '../base/BaseProfiler' +import { CollectorType } from '../base/ProfilerConstant' +import connection from '@ohos.net.connection' +import SPLogger from '../../../common/utils/SPLogger' + +export class NetWork extends BaseProfiler{ + private netMap: Map = new Map + private catchNetUp = "-1" + private catchNetDown = "-1" + public static instance: NetWork = null + public static getInstance() { + if (this.instance == null) { + this.instance = new NetWork() + } + return this.instance + } + + init() { + //初始化NET + connection.getDefaultNet().then(function (netHandle) { + connection.getNetCapabilities(netHandle).then(function (info) { + if(info != undefined){ + globalThis.isNetAlive = true + }else{ + globalThis.isNetAlive = false + } + }) + }) + return CollectorType.TYPE_NET + } + + isSupport() { + SPLogger.DEBUG(NetWork.name, "networkInfo support:" + globalThis.isNetAlive) + return globalThis.isNetAlive + } + + readData() { + this.handleMessage() + this.netMap.set("netSpeedUp",this.catchNetUp) + this.netMap.set("netSpeedDown",this.catchNetDown) + return createGPData("NetWork", this.netMap) + } + + handleMessage(){ + var that = this + connection.getDefaultNet().then(function (netHandle) { + connection.getNetCapabilities(netHandle).then(function (info) { + SPLogger.DEBUG(NetWork.name,"networkInfo up:" + info["linkUpBandwidthKbps"] ) + SPLogger.DEBUG(NetWork.name,"networkInfo down:" + info["linkDownBandwidthKbps"]) + that.catchNetDown = info["linkDownBandwidthKbps"].toString() + that.catchNetUp = info["linkUpBandwidthKbps"].toString() + }) + }) + } + +} diff --git a/device/device_ui/entry/src/main/ets/common/profiler/item/Power.ets b/device/device_ui/entry/src/main/ets/common/profiler/item/Power.ets new file mode 100644 index 0000000..35d69f7 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/profiler/item/Power.ets @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { fileOpen, travelFile, createGPData, isAccess} from '../base/BaseProfilerUtils'; +import { BaseProfiler } from '../base/BaseProfiler' +import { CollectorType } from '../base/ProfilerConstant' +import SPLogger from '../../../common/utils/SPLogger' +enum POWER_CONFIG { + //此目录 fileio 无法读取 需自行适配 +// POWER_PATH = "/data/local/tmp/battery" + POWER_PATH = "/sys/class/power_supply/Battery" // wgr +} + +export class Power extends BaseProfiler { + private powerMap: Map = new Map + private enableSupportItem = ["current_now", "voltage_now", "charge_now", "temp", "status"] + private supportItemKey = [] + + public static instance: Power = null + public static getInstance() { + if (this.instance == null) { + this.instance = new Power() + } + return this.instance + } + + init() { + //初始化Power节点 + let pathNodes = travelFile(POWER_CONFIG.POWER_PATH,"") + pathNodes.forEach(path=>{ + this.enableSupportItem.forEach(item=>{ + if(path == item ){ + this.supportItemKey.push(item) + } + }) + }) + SPLogger.DEBUG(Power.name,"pathNodes init length:"+ pathNodes.length + "support items:" + JSON.stringify(this.supportItemKey)) + return CollectorType.TYPE_POWER + } + + isSupport(){ + if(isAccess(POWER_CONFIG.POWER_PATH)){ + return true + } + return false + } + + readData() { + if (this.supportItemKey.length > 0) { + for (let powerKey of this.supportItemKey) { + let powerValue = fileOpen(POWER_CONFIG.POWER_PATH + "/" + powerKey) + this.powerMap.set(powerKey, powerValue) + } + } + return createGPData("Power", this.powerMap) + } +} diff --git a/device/device_ui/entry/src/main/ets/common/profiler/item/RAM.ets b/device/device_ui/entry/src/main/ets/common/profiler/item/RAM.ets new file mode 100644 index 0000000..a7f9a31 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/profiler/item/RAM.ets @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createGPData, extractNumber } from '../base/BaseProfilerUtils'; +import { BaseProfiler } from '../base/BaseProfiler' +import { CollectorType } from '../base/ProfilerConstant' +import { SocketProfiler} from '../base/SocketProfiler' + +export class RAM extends BaseProfiler implements SocketProfiler{ + private ramMap: Map = new Map + + public static instance: RAM = null + public static getInstance() { + if (this.instance == null) { + this.instance = new RAM() + } + return this.instance + } + + init() { + //初始化RAM节点 + return CollectorType.TYPE_RAM + } + + isSupport(){ + return true + } + + readData() { + if (globalThis.useDaemon) { + this.readMessageQueue() + }else{ + let ramStr:string = globalThis.CreateNativeRam(globalThis.collectPkg) + if (ramStr.length > 0) { + this.ramMap.set("pss", ramStr) + } + } + return createGPData("RAM", this.ramMap) + } + readMessageQueue(){ + if (globalThis.ramArr.length > 0) { + let ramQueue: Array = globalThis.ramArr + let curRAM = ramQueue.pop() + this.ramMap.set("pss", extractNumber(curRAM)) + } + } +} diff --git a/device/device_ui/entry/src/main/ets/common/profiler/item/Thermal.ets b/device/device_ui/entry/src/main/ets/common/profiler/item/Thermal.ets new file mode 100644 index 0000000..4c85ebe --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/profiler/item/Thermal.ets @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import fileio from '@ohos.fileio'; +import { fileOpen, createGPData, isAccess} from '../base/BaseProfilerUtils'; +import { BaseProfiler } from '../base/BaseProfiler' +import { CollectorType } from '../base/ProfilerConstant' +import SPLogger from '../../../common/utils/SPLogger' +enum THERMAL_CONFIG{ + THERMAL_BASE = "/sys/devices/virtual/thermal" +} + +export class Thermal extends BaseProfiler { + private thermalMap: Map = new Map + private enableSupportItem = [ + "gpu-thermal", + "soc-thermal", + "shell_back", + "shell_front", + "shell_frame", + ] + private supportItemMap = new Map + + public static instance: Thermal = null + public static getInstance() { + if (this.instance == null) { + this.instance = new Thermal() + } + return this.instance + } + + init() { + //初始化Thermal node + var dir + dir = fileio.opendirSync(THERMAL_CONFIG.THERMAL_BASE); + do { + var dirent = dir.readSync() + if (dirent == null ) { + break + } + let name: String = dirent.name + SPLogger.DEBUG(Thermal.name,"thermal_zone name:" + name) + if (name.match("thermal_zone")) { + let typeName = fileOpen(THERMAL_CONFIG.THERMAL_BASE + "/" + dirent.name + "/type") + if (this.enableSupportItem.length > 0) { + this.enableSupportItem.find((item: String) => { + if (typeName.match(item.toString())) { + SPLogger.DEBUG(Thermal.name,"thermal_zone match name:" + typeName) + this.supportItemMap.set(name, item) + SPLogger.DEBUG(Thermal.name,"thermal_zone match name this.supportItemMap:" + this.supportItemMap.size) + } + }) + } + } + } while (dirent != null) + return CollectorType.TYPE_TEMPERATURE + } + + isSupport(){ + if(isAccess(THERMAL_CONFIG.THERMAL_BASE)){ + return true + } + return false + } + + readData() { + if (this.supportItemMap.size > 0) { + let timeZones = this.supportItemMap.keys() + for (let timeZone of timeZones) { + let tempValue = fileOpen(THERMAL_CONFIG.THERMAL_BASE + "/" + timeZone + "/temp") + this.thermalMap.set(this.supportItemMap.get(timeZone),tempValue) + } + } + return createGPData("Temp", this.thermalMap) + } +} + + + diff --git a/device/device_ui/entry/src/main/ets/common/ui/StartTestTitleComponent.ets b/device/device_ui/entry/src/main/ets/common/ui/StartTestTitleComponent.ets new file mode 100644 index 0000000..8d83a75 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/ui/StartTestTitleComponent.ets @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import router from '@system.router'; + +@Component +export struct StartTestTitleComponent { + private title: string = "开始测试" + + build() { + //开始测试title + Row({ space: '15vp' }) { + Image($r('app.media.icon_back')) + .width('25vp') + .height('15%') + .margin({ left: '5%', top: '30vp' }) + .alignSelf(ItemAlign.Center) + .onClick(() => { + router.back() + }) + + Text(this.title) + .fontSize('20fp') + .fontWeight(FontWeight.Bold) + .fontColor($r("app.color.color_fff")) + .margin({ top: '30vp' }) + }.backgroundColor($r("app.color.colorPrimary")).width('100%').height('12%') + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/ui/detail/Summary.ets b/device/device_ui/entry/src/main/ets/common/ui/detail/Summary.ets new file mode 100644 index 0000000..fee3ba3 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/ui/detail/Summary.ets @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TIndexInfo } from '../../entity/DatabaseEntity'; +import { SummaryItem } from '../../entity/LocalConfigEntity' +import { getCpuCoreInfo, } from '../../utils/SystemUtils'; +import CalculationUtils from '../../utils/CalculationUtils'; +import SPLogger from "../../utils/SPLogger" + +const TAG = "SummaryTAG" +/* + * 报告详情概览页 + */ +@Component +@Preview +export struct Summary { + @State private gpData: Array = new Array() + @State private summaryItems: Array = new Array() + + aboutToAppear() { + SPLogger.DEBUG(TAG,"Summary: aboutToAppear"); + var cpuCoreArr = getCpuCoreInfo() + SPLogger.DEBUG(TAG,"Summary:aboutToAppear: cpuCoreArr:" + cpuCoreArr); + + var cpu0FreqSum: number = 0 + var cpu1FreqSum: number = 0 + var cpu2FreqSum: number = 0 + var cpu3FreqSum: number = 0 + var cpu4FreqSum: number = 0 + var cpu5FreqSum: number = 0 + var cpu6FreqSum: number = 0 + var cpu7FreqSum: number = 0 + var cpuFreqMap = new Map + + var cpuMin: number = 0 + var cpuMid: number = 0 + var cpuMax: number = 0 + + var currentNowSum: number = 0 + var curVoltageSum: number = 0 + var normalCurrentNow: number = 0 + + + var socThermalTemp: number = 0 + var gpuLoadSum: number = 0 + var ddrFreqSum: number = 0 + + // fps和ram 为空时 过滤掉脏数据 0和空 + var fpsNullSum = 0 + var ramNullSum = 0 + + var pssSum: number = 0 + var fpsSum: number = 0 + let fpsMax: number = 0 + let fpsList = [] + let fpsJitters = [] + + for (var index = 0; index < this.gpData.length; index++) { + const gpDataCur = this.gpData[index]; + SPLogger.DEBUG(TAG,"Summary s:" + JSON.stringify(gpDataCur)); + let currentNow = Number(gpDataCur.currentNow).valueOf() + currentNowSum += Math.abs(currentNow) + + let curVoltage = Number(gpDataCur.voltageNow).valueOf() / 1e6 + curVoltageSum += Math.abs(curVoltage) + normalCurrentNow += Math.abs(currentNow) * Math.abs(curVoltage) / 3.8 + + socThermalTemp += Number(gpDataCur.socThermalTemp).valueOf() + gpuLoadSum += Number(gpDataCur.gpuLoad).valueOf() + ddrFreqSum += Number(gpDataCur.ddrFrequency).valueOf() + cpu0FreqSum += Number(gpDataCur.cpu0Frequency).valueOf() + cpu1FreqSum += Number(gpDataCur.cpu1Frequency).valueOf() + cpu2FreqSum += Number(gpDataCur.cpu2Frequency).valueOf() + cpu3FreqSum += Number(gpDataCur.cpu3Frequency).valueOf() + cpu4FreqSum += Number(gpDataCur.cpu4Frequency).valueOf() + cpu5FreqSum += Number(gpDataCur.cpu5Frequency).valueOf() + cpu6FreqSum += Number(gpDataCur.cpu6Frequency).valueOf() + cpu7FreqSum += Number(gpDataCur.cpu7Frequency).valueOf() + + if (gpDataCur.pss == "") { + ramNullSum++ + } + if (gpDataCur.fps == "" || gpDataCur.fps == "0") { + fpsNullSum++ + } + + pssSum += Number(gpDataCur.pss).valueOf() + let fpxCur = Number(gpDataCur.fps).valueOf() + fpsSum += fpxCur + if (fpsMax < fpxCur) { + fpsMax = fpxCur + } + fpsList.push(Number(gpDataCur.fps).valueOf()) + fpsJitters.push(gpDataCur.fpsJitters.toString().replace("\"", "")) + } + + cpuFreqMap.set("cpu0FreqSum", cpu0FreqSum) + cpuFreqMap.set("cpu1FreqSum", cpu1FreqSum) + cpuFreqMap.set("cpu2FreqSum", cpu2FreqSum) + cpuFreqMap.set("cpu3FreqSum", cpu3FreqSum) + cpuFreqMap.set("cpu4FreqSum", cpu4FreqSum) + cpuFreqMap.set("cpu5FreqSum", cpu5FreqSum) + cpuFreqMap.set("cpu6FreqSum", cpu6FreqSum) + cpuFreqMap.set("cpu7FreqSum", cpu7FreqSum) + + SPLogger.DEBUG(TAG,"Summary cpu0FreqSum" + cpu0FreqSum); + SPLogger.DEBUG(TAG,"Summary cpu1FreqSum" + cpu1FreqSum); + SPLogger.DEBUG(TAG,"Summary cpu2FreqSum" + cpu2FreqSum); + SPLogger.DEBUG(TAG,"Summary cpu3FreqSum" + cpu3FreqSum); + SPLogger.DEBUG(TAG,"Summary cpu4FreqSum" + cpu4FreqSum); + SPLogger.DEBUG(TAG,"Summary cpu5FreqSum" + cpu5FreqSum); + SPLogger.DEBUG(TAG,"Summary cpu6FreqSum" + cpu6FreqSum); + SPLogger.DEBUG(TAG,"Summary cpu7FreqSum" + cpu7FreqSum); + SPLogger.DEBUG(TAG,"Summary FpsJitters" + JSON.stringify(fpsJitters)); + + SPLogger.DEBUG(TAG,"Summary:aboutToAppear currentNowSum:" + currentNowSum); + SPLogger.DEBUG(TAG,"Summary:aboutToAppear socThermalTemp:" + socThermalTemp); + SPLogger.DEBUG(TAG,"Summary:aboutToAppear gpuLoadSum:" + gpuLoadSum); + SPLogger.DEBUG(TAG,"Summary:aboutToAppear ddrFreqSum:" + ddrFreqSum); + SPLogger.DEBUG(TAG,"Summary:aboutToAppear cpuFreqMap:" + JSON.stringify(cpuFreqMap)); + + + cpuMin = cpuFreqMap.get("cpu" + 0 + "FreqSum") / 1e3 + cpuMid = cpuFreqMap.get("cpu" +1 + "FreqSum") / 1e3 + cpuMax = cpuFreqMap.get("cpu" + 2 + "FreqSum") / 1e3 + + let calculationTest = new CalculationUtils(fpsList, CalculationUtils.calculateFPSNew(fpsList)) + this.summaryItems.push( + new SummaryItem($r("app.media.icon_normalized_current"), "归一化电流", (normalCurrentNow / this.gpData.length).toFixed(0) + "mA"), + new SummaryItem($r("app.media.icon_max_temperature"), "soc温度", (socThermalTemp / this.gpData.length / 1000 ).toFixed(0) + "℃"), + new SummaryItem($r("app.media.icon_jank_score"), "平均帧率/最高帧率", (fpsSum / (this.gpData.length - fpsNullSum)).toFixed(0) + "Hz" + "/" + (fpsMax).toFixed(0) + "HZ"), + new SummaryItem($r("app.media.icon_jank_score"), "低帧率", (calculationTest.Low_Frame_Rate()).toFixed(2) + "%"), + new SummaryItem($r("app.media.icon_jank_score"), "抖动率", (calculationTest.Jitter_rate()).toFixed(2) + "%"), + new SummaryItem($r("app.media.icon_jank_score"), "卡顿次数", (calculationTest.calculateCaton(fpsJitters)).toFixed(0) + "次"), + new SummaryItem($r("app.media.icon_frame_score"), "GPU负载", (gpuLoadSum / this.gpData.length).toFixed(0) + "%"), + new SummaryItem($r("app.media.icon_frame_score"), "DDR频率", (ddrFreqSum / this.gpData.length / 1e6).toFixed(0) + "MHZ"), + new SummaryItem($r("app.media.icon_average_frame_b"), "CPU0频率", (cpuMin / this.gpData.length).toFixed(0) + "MHZ"), + new SummaryItem($r("app.media.icon_average_frame_b"), "CPU1频率", (cpuMid / this.gpData.length).toFixed(0) + "MHZ"), + new SummaryItem($r("app.media.icon_average_frame_b"), "CPU2频率", (cpuMax / this.gpData.length).toFixed(0) + "MHZ"), + new SummaryItem($r("app.media.icon_jank_each_hour"), "RAM", (pssSum / (this.gpData.length - ramNullSum)).toFixed(0) + "KB") + ) + } + + build() { + Scroll() { + Column() { + Grid() { + ForEach(this.summaryItems, item => { + GridItem() { + Flex({ alignItems: ItemAlign.Center, alignContent: FlexAlign.Start }) { + Row({ space: "8vp" }) { + Image(item.icon).width('50vp').height('50vp') + Text(item.content).fontSize('14fp').textAlign(TextAlign.Start) + Text(item.value).fontSize('11fp').textAlign(TextAlign.Start) + }.alignItems(VerticalAlign.Center) + }.margin({ left: '10' }).borderStyle(BorderStyle.Solid).borderStyle(1).borderColor(Color.Gray) + }.margin({ left: "6%" }) + }, item => item.content) + }.margin({ bottom: "20%" }) + .columnsTemplate('1fr 1fr') + .rowsGap(20) + } + }.height('100%').width('100%') + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/ui/detail/data/DetailCommon.ets b/device/device_ui/entry/src/main/ets/common/ui/detail/data/DetailCommon.ets new file mode 100644 index 0000000..c715500 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/ui/detail/data/DetailCommon.ets @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * FPS丢帧占比类 + * @param key + * @param value + * @param percent + */ +export class FpsLostFrame { + public key: string + public value: string + public percent: string + public color: Resource + + constructor(key: string, value: string, percent: string, color: Resource) { + this.key = key + this.value = value + this.percent = percent + this.color = color + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/ui/detail/utils/HandleLostFrame.ts b/device/device_ui/entry/src/main/ets/common/ui/detail/utils/HandleLostFrame.ts new file mode 100644 index 0000000..606a779 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/ui/detail/utils/HandleLostFrame.ts @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import StringUtils from '../../../utils/StringUtils' +/** + * 处理丢帧 + * @param fullFps + */ +let TAG = "HandleLostFrame" + +export default class HandleLostFrame { + private mFullFps: number; + private mJankArray: Array = Array(21); + + constructor(fullFps: number) { + this.mFullFps = fullFps + let frameTime = 1000 / this.mFullFps + + for (let i = 1; i < 22; i++) { + this.mJankArray[i - 1] = (frameTime * i + frameTime * (i + 1)) / 2 + } + } + + getJankMap(jankSrcStr: String): Map { + if (jankSrcStr == "" || jankSrcStr === undefined) { + return new Map + } + let allDrawFrame: string[] = jankSrcStr.split(",") + let jitters = Array(allDrawFrame.length) + + for (let i = 0; i < allDrawFrame.length; i++) { + console.log(TAG + "allDrawFrame[i]" + i+ allDrawFrame[i]) + if (!isNaN(StringUtils.s2L(allDrawFrame[i]))) { + jitters[i] = StringUtils.s2L(allDrawFrame[i]) + } + } + let jankCountMap: Map = new Map + + for (let i of jitters.keys()) { + + let doubleJank = jitters[i] / 1e6 + console.log(TAG + "for jitters[" + i + "]" + doubleJank) + var jankRange = 0 + jankRange = this.getJankRange(2, doubleJank) + console.log(TAG + "for jankRange" + jankRange) + if (jankRange != 0) { + console.log(TAG + "for jitters[" + jankRange + "]" + jankCountMap.get(jankRange)) + if (jankCountMap.get(jankRange) == null) { + jankCountMap.set(jankRange, 1) + } else { + jankCountMap.set(jankRange, jankCountMap.get(jankRange) + 1) + } + } + } + for (let i = 2; i < 22; i++) { + if (!jankCountMap.has(i)) { + jankCountMap.set(i, 0) + } + } + return jankCountMap + } + + private getJankRange(currRange: number, jank: number): number { + if (currRange > 22) { + return 0 + } + if (currRange == 2) { + if (jank < this.mJankArray[currRange - 2]) { + return 0 + } + } + if (currRange == 22) { + if (jank >= this.mJankArray[currRange - 2]) { + return currRange + } else { + return 0 + } + } + if (jank >= this.mJankArray[currRange - 2] && jank < this.mJankArray[currRange - 1] + ) { + return currRange + } else { + return this.getJankRange(currRange + 1, jank) + } + } +} diff --git a/device/device_ui/entry/src/main/ets/common/ui/floatwindow/FloatWindowConstant.ets b/device/device_ui/entry/src/main/ets/common/ui/floatwindow/FloatWindowConstant.ets new file mode 100644 index 0000000..fa3741d --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/ui/floatwindow/FloatWindowConstant.ets @@ -0,0 +1,31 @@ + /* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const CURRENTNOW = 0 + +export const SHELLBACKTEMP = 1 + +export const DDRFREQUENCY = 2 + +export const GPUFREQUENCY = 3 + +export const CPU0FREQUENCY = 4 + +export const CPU1FREQUENCY = 5 + +export const CPU2FREQUENCY = 6 + +export const RAM = 6 + +export const FPS = 7 \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/ui/floatwindow/FloatWindowFun.ts b/device/device_ui/entry/src/main/ets/common/ui/floatwindow/FloatWindowFun.ts new file mode 100644 index 0000000..b5f2298 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/ui/floatwindow/FloatWindowFun.ts @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import wm from '@ohos.window' +import SPLogger from '../../utils/SPLogger' + +const TAG = "FloatWindowFun" + +export class FloatWindowFun { + static floatingWindowOffsetX: number = 50 + static floatingWindowOffsetY: number = 200 + static titleWindowOffsetX: number = 300 + static titleWindowOffsetY: number = 200 + static lineChartWindowOffsetX: number= 700 + static lineChartWindowOffsetY: number= 200 + static windowWidth: number = 2560 + static windowHeight: number = 1600 + + + static initAllFun() { + globalThis.CreateFloatingWindow = (() => { + //5.5SP2 2106 改成 8 + wm.create(globalThis.abilityContext, 'floatingWindow', 2106).then((floatWin) => { + floatWin.setWindowMode(102).then(() => { + floatWin.moveTo(this.floatingWindowOffsetX, this.floatingWindowOffsetY).then(() => { + floatWin.resetSize(95, 95).then(() => { + floatWin.getProperties().then((property) => { + property.isTransparent = false + }) + floatWin.loadContent('pages/FloatBall').then(() => { + floatWin.setTransparent(true).then(() => { //透明 + floatWin.show().then(() => { + globalThis.showFloatingWindow = true + }) + + }) + }) + }) + }) + }) + }) + }) + globalThis.MoveFloatingWindow = ((offsetX: number, offsetY: number) => { + var xx = (this.floatingWindowOffsetX + offsetX * 2) < 0 ? 0 : ((this.floatingWindowOffsetX + offsetX * 2) > (this.windowWidth - 200) ? (this.windowWidth - 200) : (this.floatingWindowOffsetX + offsetX * 2)) + var yy = (this.floatingWindowOffsetY + offsetY * 2) < 0 ? 0 : ((this.floatingWindowOffsetY + offsetY * 2) > (this.windowHeight - 200) ? (this.windowHeight - 200) : (this.floatingWindowOffsetY + offsetY * 2)) + + wm.find("floatingWindow").then((fltWin) => { + fltWin.moveTo(xx, yy) + }) + }) + + globalThis.SetFloatingWindowPosition = ((offsetX: number, offsetY: number) => { + this.floatingWindowOffsetX = (this.floatingWindowOffsetX + offsetX * 2) < 0 ? 0 : ((this.floatingWindowOffsetX + offsetX * 2) > (this.windowWidth - 200) ? (this.windowWidth - 200) : (this.floatingWindowOffsetX + offsetX * 2)) + this.floatingWindowOffsetY = (this.floatingWindowOffsetY + offsetY * 2) < 0 ? 0 : ((this.floatingWindowOffsetY + offsetY * 2) > (this.windowHeight - 200) ? (this.windowHeight - 200) : (this.floatingWindowOffsetY + offsetY * 2)) + }) + + globalThis.DestroyFloatingWindow = (() => { + wm.find("floatingWindow").then((fltWin) => { + fltWin.destroy().then(() => { + globalThis.showFloatingWindow = false + }) + }) + }) + + globalThis.CreateTitleWindow = (() => { + wm.create(globalThis.abilityContext, 'TitleWindow', 2106).then((floatWin) => { + floatWin.setWindowMode(102).then(() => { + floatWin.moveTo(this.titleWindowOffsetX, this.titleWindowOffsetY).then(() => { + floatWin.resetSize(350, 260).then(() => { + floatWin.getProperties().then((property) => { + property.isTransparent = false + }) + floatWin.loadContent('pages/TitleWindowPage').then(() => { + floatWin.setTransparent(true) + floatWin.hide() + SPLogger.DEBUG(TAG, 'CreateTitleWindow Done') + }) + }) + }) + }) + }) + }) + + globalThis.MoveTitleWindow = ((offsetX: number, offsetY: number) => { + var xx = (this.titleWindowOffsetX + offsetX * 2) < 0 ? 0 : ((this.titleWindowOffsetX + offsetX * 2) > (this.windowWidth - 500) ? (this.windowWidth - 500) : (this.titleWindowOffsetX + offsetX * 2)) + var yy = (this.titleWindowOffsetY + offsetY * 2) < 0 ? 0 : ((this.titleWindowOffsetY + offsetY * 2) > (this.windowHeight - 330) ? (this.windowHeight - 330) : (this.titleWindowOffsetY + offsetY * 2)) + wm.find("TitleWindow").then((fltWin) => { + fltWin.moveTo(xx, yy) + }) + }) + + globalThis.SetTitleWindowPosition = ((offsetX: number, offsetY: number) => { + this.titleWindowOffsetX = (this.titleWindowOffsetX + offsetX * 2) < 0 ? 0 : ((this.titleWindowOffsetX + offsetX * 2) > (this.windowWidth - 500) ? (this.windowWidth - 500) : (this.titleWindowOffsetX + offsetX * 2)) + this.titleWindowOffsetY = (this.titleWindowOffsetY + offsetY * 2) < 0 ? 0 : ((this.titleWindowOffsetY + offsetY * 2) > (this.windowHeight - 330) ? (this.windowHeight - 330) : (this.titleWindowOffsetY + offsetY * 2)) + }) + + globalThis.DestroyTitleWindow = (() => { + wm.find("TitleWindow").then((fltWin) => { + fltWin.destroy().then(() => { + }) + }) + }) + + globalThis.HideTitleWindow = (() => { + wm.find("TitleWindow").then((fltWin) => { + fltWin.hide() + }) + }) + + globalThis.ShowTitleWindow = (() => { + wm.find("TitleWindow").then((fltWin) => { + fltWin.show() + }) + }) + + globalThis.CreateFPSLineChartWindow = (() => { + //5.5SP2 2106 改成 8 + wm.create(globalThis.abilityContext, 'fpsLineChartWindow', 2106).then((floatWin) => { + floatWin.setWindowMode(102).then(() => { + floatWin.moveTo(this.lineChartWindowOffsetX, this.lineChartWindowOffsetY).then(() => { + floatWin.resetSize(130,90).then(() => { + floatWin.loadContent('pages/FpsLineChartPage').then(() => { + floatWin.show().then(() => { + floatWin.hide() + }) + }) + }) + }) + }) + }) + }) + + globalThis.MoveFPSLineChartWindow = ((offsetX: number, offsetY: number) => { + var xx = (this.lineChartWindowOffsetX + offsetX * 2) < 0 ? 0 : ((this.lineChartWindowOffsetX + offsetX * 2) > (this.windowWidth - 200) ? (this.windowWidth - 200) : (this.lineChartWindowOffsetX + offsetX * 2)) + var yy = (this.lineChartWindowOffsetY + offsetY * 2) < 0 ? 0 : ((this.lineChartWindowOffsetY + offsetY * 2) > (this.windowHeight - 200) ? (this.windowHeight - 200) : (this.lineChartWindowOffsetY + offsetY * 2)) + + wm.find("fpsLineChartWindow").then((fltWin) => { + fltWin.moveTo(xx, yy) + }) + }) + + globalThis.SetFPSLineChartWindowPosition = ((offsetX: number, offsetY: number) => { + this.lineChartWindowOffsetX = (this.lineChartWindowOffsetX + offsetX * 2) < 0 ? 0 : ((this.lineChartWindowOffsetX + offsetX * 2) > (this.windowWidth - 200) ? (this.windowWidth - 200) : (this.lineChartWindowOffsetX + offsetX * 2)) + this.lineChartWindowOffsetY = (this.lineChartWindowOffsetY + offsetY * 2) < 0 ? 0 : ((this.lineChartWindowOffsetY + offsetY * 2) > (this.windowHeight - 200) ? (this.windowHeight - 200) : (this.lineChartWindowOffsetY + offsetY * 2)) + }) + + globalThis.DestroyFPSLineChartWindow = (() => { + wm.find("fpsLineChartWindow").then((fltWin) => { + fltWin.destroy().then(() => { + globalThis.showFPSLineChartWindow = false + }) + }) + }) + + globalThis.HideFPSLineChartWindow = (() => { + wm.find("fpsLineChartWindow").then((fltWin) => { + fltWin.hide() + }) + }) + + globalThis.ShowFPSLineChartWindow = (() => { + wm.find("fpsLineChartWindow").then((fltWin) => { + fltWin.show() + }) + }) + } +} + diff --git a/device/device_ui/entry/src/main/ets/common/ui/floatwindow/utils/FloatWindowUtils.ets b/device/device_ui/entry/src/main/ets/common/ui/floatwindow/utils/FloatWindowUtils.ets new file mode 100644 index 0000000..171bdcd --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/ui/floatwindow/utils/FloatWindowUtils.ets @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import wm from '@ohos.window' + +export let floatingWindowOffsetX: number = 50 +export let floatingWindowOffsetY: number = 200 +export let titleWindowOffsetX: number = 300 +export let titleWindowOffsetY: number = 200 +export let lineChartWindowOffsetX:number=1 +export let lineChartWindowOffsetY:number=1 +export let windowWidth: number = 2560 +export let windowHeight: number = 1600 + +export function initFloatWindow() { + createFloatWindow("currentNow") + createFloatWindow("shellBackTemp") + createFloatWindow("ddrFrequency") + createFloatWindow("gpuFrequency") + createFloatWindow("cpu0Frequency") + createFloatWindow("cpu1Frequency") + createFloatWindow("cpu2Frequency") + createFloatWindow("RAM") +} +export function createFloatWindow(floatName:string) { + //5.5SP2 2106 改成 8 + wm.create(globalThis.abilityContext, floatName, 2106).then((floatWin) => { + floatWin.setWindowMode(102).then(() => { + floatWin.moveTo(lineChartWindowOffsetX, lineChartWindowOffsetY).then(() => { + floatWin.resetSize(240,180).then(() => { + if(floatName=="currentNow"){ + floatWin.loadContent('pages/CurrentNowLineChartPage').then(() => { + floatWin.show().then(() => { + floatWin.hide() + }) + }) + }else if(floatName=="shellBackTemp"){ + floatWin.loadContent('pages/ShellBackTempLineChartPage').then(() => { + floatWin.show().then(() => { + floatWin.hide() + }) + }) + } + else if(floatName=="ddrFrequency"){ + floatWin.loadContent('pages/DDRLineChartPage').then(() => { + floatWin.show().then(() => { + floatWin.hide() + }) + }) + } + else if(floatName=="gpuFrequency"){ + floatWin.loadContent('pages/GPULineChartPage').then(() => { + floatWin.show().then(() => { + floatWin.hide() + }) + }) + } + else if(floatName=="cpu0Frequency"){ + floatWin.loadContent('pages/CPU0LineChartPage').then(() => { + floatWin.show().then(() => { + floatWin.hide() + }) + }) + } + else if(floatName=="cpu1Frequency"){ + floatWin.loadContent('pages/CPU1LineChartPage').then(() => { + floatWin.show().then(() => { + floatWin.hide() + }) + }) + } + else if(floatName=="cpu2Frequency"){ + floatWin.loadContent('pages/CPU2LineChartPage').then(() => { + floatWin.show().then(() => { + floatWin.hide() + }) + }) + } + else if(floatName=="RAM"){ + floatWin.loadContent('pages/RAMLineChartPage').then(() => { + floatWin.show().then(() => { + floatWin.hide() + }) + }) + } + }) + }) + }) + }) +} +export function destoryFloatWindow(floatName:string) { + + wm.find(floatName).then((fltWin) => { + fltWin.destroy().then(() => { + globalThis.showFPSLineChartWindow = false + }) + }) + +} +export function hideFloatWindow(floatName:string) { + + wm.find(floatName).then((fltWin) => { + fltWin.hide() + }) + +} +export function showFloatWindow(floatName:string) { + + wm.find(floatName).then((fltWin) => { + fltWin.show() + }) + +} +export function moveFloatWindow(floatName:string,offsetX: number, offsetY: number) { + var xx = (floatingWindowOffsetX + offsetX * 2) < 0 ? 0 : ((floatingWindowOffsetX + offsetX * 2) > (windowWidth - 200) ? (windowWidth - 200) : (floatingWindowOffsetX + offsetX * 2)) + var yy = (floatingWindowOffsetY + offsetY * 2) < 0 ? 0 : ((floatingWindowOffsetY + offsetY * 2) > (windowHeight - 200) ? (windowHeight - 200) : (floatingWindowOffsetY + offsetY * 2)) + + wm.find(floatName).then((fltWin) => { + fltWin.moveTo(xx, yy) + }) + +} +export function setFloatWindow(offsetX: number, offsetY: number) { + this.floatingWindowOffsetX = (floatingWindowOffsetX + offsetX * 2) < 0 ? 0 : ((floatingWindowOffsetX + offsetX * 2) > (windowWidth - 200) ? (windowWidth - 200) : (floatingWindowOffsetX + offsetX * 2)) + this.floatingWindowOffsetY = (floatingWindowOffsetY + offsetY * 2) < 0 ? 0 : ((floatingWindowOffsetY + offsetY * 2) > (windowHeight - 200) ? (windowHeight - 200) : (floatingWindowOffsetY + offsetY * 2)) +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/ui/main/Home.ets b/device/device_ui/entry/src/main/ets/common/ui/main/Home.ets new file mode 100644 index 0000000..fbf96d9 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/ui/main/Home.ets @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import deviceInfo from '@ohos.deviceInfo'; +import router from '@system.router'; +import prompt from '@system.prompt'; +import { TopComponent } from '../main/TopComponent'; +import { otherSupportList, OtherSupport, TestMode } from '../../entity/LocalConfigEntity'; + + +/* + home页 + */ +@Component +export struct Home { + @State private deviceStr: string = '' + @State private versionStr: string = '' + @State private circleHeight: string = '180vp' + @State private circleWidth: string = '180vp' + @State private circleRadius: string = '90vp' + @State opacity: number = 0.6 + + build() { + Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start, justifyContent: FlexAlign.Start }) { + Column() { + Column() { + TopComponent({ title: "SmartPerf " }) + + Text('性能/功耗测试') + .fontSize('18fp') + .fontColor($r("app.color.color_fff")) + .textAlign(TextAlign.Start) + .margin({ top: '20vp' }) + + Stack() { + Circle() + .width('180vp') + .height('180vp') + .fill(Color.White) + .fillOpacity(0) + .border({ radius: '90vp', width: '0.5vp', color: $r("app.color.color_fff") }) + .linearGradient({ + angle: 135, + direction: GradientDirection.Left, + colors: [[$r("app.color.color_80B3193F"), 0.0], [$r("app.color.color_80fff"), 1.0]] + }) + + Text('开始测试') + .fontColor($r('app.color.color_fff')) + .fontSize('20fp') + + Column() { + }.width('220vp') + .height('220vp') + + Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) { + } + .width(this.circleWidth) + .height(this.circleHeight) + .border({ width: '1vp', radius: this.circleRadius, color: $r("app.color.color_80fff") }) + .opacity(this.opacity) + .animation({ + duration: 1000, + iterations: -1, + curve: Curve.FastOutLinearIn }) + .onAppear(() => { + this.circleWidth = '220vp' + this.circleHeight = '220vp' + this.circleRadius = '100vp' + this.opacity = 0 + }) + }.onClick(() => { + if (!globalThis.showFloatingWindow) { + router.push({ uri: 'pages/StartTestPage' }) + } else { + prompt.showToast({ message: "已经有应用正在测试,请关闭测试再进行操作", duration: 1000 }) + } + }) + + Text(this.deviceStr) + .fontSize('25fp') + .fontColor($r("app.color.color_fff")) + .fontWeight(FontWeight.Bold) + .textAlign(TextAlign.Start) + .margin({ top: '20vp', bottom: '40vp' }) + }.backgroundColor($r('app.color.colorPrimary')).width('100%') + .alignItems(HorizontalAlign.Center) + .onAppear(() => { + this.deviceStr = deviceInfo.brand + " " + deviceInfo.productModel + }) + + GridExample() + } + }.width('100%').height('100%') + } +} + +@Component +struct GridExample { + @State support: Array = otherSupportList + + build() { + Column({ space: 5 }) { + Grid() { + ForEach(this.support, (otherSupport) => { + GridItem() { + Column() { + Image(otherSupport.resource).width(60) + .height(60) + Text(otherSupport.testName) + .fontSize('13fp') + .fontColor($r("app.color.color_333")) + .height('30%') + .textAlign(TextAlign.Center) + + Text(otherSupport.testSrc) + .fontSize('10fp') + .fontColor($r("app.color.color_999")) + .height('30%') + .textAlign(TextAlign.Center) + } + } + .backgroundColor($r("app.color.color_fff")) + .width('100%') + .margin({ top: '10vp' }) + .padding({ top: '8vp', right: '20vp', bottom: '8vp', left: '20vp' }) + .onClick(() => { + if (otherSupport.testMode == TestMode.ONLINE) { + prompt.showToast({ message: "开发中" }) + } else if (otherSupport.testMode == TestMode.BRIGHTNESS) { + router.push({ uri: 'pages/LightAdjust' }) + } + }) + }, otherSupport => otherSupport.testName) + } + .columnsTemplate('1fr 1fr 1fr') + .rowsTemplate('1fr 1fr 1fr') + .width('100%') + .height(450) + }.width('100%').height('100%').backgroundColor('#EEEEEE') + } +} diff --git a/device/device_ui/entry/src/main/ets/common/ui/main/HomeBottomPage.ets b/device/device_ui/entry/src/main/ets/common/ui/main/HomeBottomPage.ets new file mode 100644 index 0000000..e5432d6 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/ui/main/HomeBottomPage.ets @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * 主页底部tab + */ +@Component +export struct HomeBottomPage { + @Link currentPage: number + imgW = '60px' + imgH = '60px' + + build() { + Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { + + Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center, direction: FlexDirection.Column }) { + if (this.currentPage == 0) { + Image($r('app.media.icon_home_selected')).width(this.imgW).height(this.imgH) + Text('首页').fontSize('12vp').fontColor($r("app.color.colorPrimary")) + } else { + Image($r('app.media.icon_home_unselected')).width(this.imgW).height(this.imgH) + Text('首页').fontSize('12vp').fontColor(Color.Black) + } + } + .onClick(() => { + this.currentPage = 0 + }) + .backgroundColor($r("app.color.color_fff")) + .width('180px') + .height('100%') + + Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center, direction: FlexDirection.Column }) { + if (this.currentPage == 1) { + Image($r('app.media.icon_report_selected')).width(this.imgW).height(this.imgH) + Text('报告').fontSize('12vp').fontColor($r("app.color.colorPrimary")) + } else { + Image($r('app.media.icon_report_unselected')).width(this.imgW).height(this.imgH) + Text('报告').fontSize('12vp').fontColor(Color.Black) + } + } + .onClick(() => { + this.currentPage = 1 + }) + .backgroundColor($r("app.color.color_fff")) + .width('180px') + .height('100%') + + Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center, direction: FlexDirection.Column }) { + if (this.currentPage == 2) { + Image($r('app.media.icon_mine_selected')).width(this.imgW).height(this.imgH) + Text('我的').fontSize('12vp').fontColor($r("app.color.colorPrimary")) + } else { + Image($r('app.media.icon_mine_unselected')).width(this.imgW).height(this.imgH) + Text('我的').fontSize('12vp').fontColor(Color.Black) + } + } + .onClick(() => { + this.currentPage = 2 + }) + .backgroundColor($r("app.color.color_fff")) + .width('180px') + .height('100%') + } + .backgroundColor($r("app.color.color_fff")) + .width('100%') + .height('12%') + } +} diff --git a/device/device_ui/entry/src/main/ets/common/ui/main/Mine.ets b/device/device_ui/entry/src/main/ets/common/ui/main/Mine.ets new file mode 100644 index 0000000..6b90577 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/ui/main/Mine.ets @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import router from '@system.router'; +import { TopComponent } from '../main/TopComponent'; +import { secToTime } from '../../utils/TimeUtils'; + +/** + * 我的页面 + */ +@Component +export struct Mine { + build() { + Column({ space: '15vp' }) { + TopComponent({ title: "我的" }) + + Image($r('app.media.person')) + .width('80vp') + .height('80vp') + .margin({ top: '30vp' }) + + Text('test') + .height('20vp') + .fontSize('14vp') + .fontColor(0xeeeeee) + .alignSelf(ItemAlign.Center) + + Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceAround }) { + Column() { + Image($r('app.media.test_apps_count')) + .width('40vp') + .height('40vp') + + Text('测试总app数') + .fontSize('16vp') + .margin('20vp') + .fontColor(0xeeeeee) + + Text(String(globalThis.sumTestApp)) + .fontColor(0xeeeeee) + } + + Column() { + Image($r('app.media.test_session_count')) + .width('40vp') + .height('40vp') + + Text('测试总局数') + .fontSize('16vp') + .margin('20vp') + .fontColor(0xeeeeee) + + Text(String(globalThis.sumTest)) + .fontColor(0xeeeeee) + } + + Column() { + Image($r('app.media.test_times_count')) + .width('40vp') + .height('40vp') + + Text('测试总时长') + .fontSize('16vp') + .margin('20vp') + .fontColor(0xeeeeee) + + Text(secToTime(globalThis.sumTestTime).toString()) + .fontColor(0xeeeeee) + } + + }.margin({ top: '15vp' }) + + Column({ space: '10vp' }) { + Row() { + Image($r('app.media.question')).width('20vp').height('20vp').margin({ left: '10vp' }) + Text('常见问题').fontSize('16vp').margin('5vp') + } + .width('100%') + .backgroundColor(0xeeeeee) + .height('35vp') + .borderRadius('5vp') + .onClick( + res => { + router.push({ uri: 'pages/Question' }) + } + ).margin({bottom:5}) + + Row() { + Image($r('app.media.settings')) + .width('20vp') + .height('20vp') + .margin({ left: '10vp' }) + Text('设置').fontSize('16vp').margin('5vp') + } + .width('100%') + .height('35vp') + .backgroundColor(0xeeeeee) + .borderRadius('5vp') + .onClick( + res => { + router.push({ uri: 'pages/SettingsPage' }) + } + ) + }.width('95%') + + }.backgroundColor($r('app.color.colorPrimary')) + .width('100%') + .height('100%') + } +} diff --git a/device/device_ui/entry/src/main/ets/common/ui/main/Report.ets b/device/device_ui/entry/src/main/ets/common/ui/main/Report.ets new file mode 100644 index 0000000..108b43f --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/ui/main/Report.ets @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import prompt from '@system.prompt'; +import router from '@system.router'; +import database from '../../database/DatabaseUtils'; +import { secToTime } from '../../utils/TimeUtils'; +import { ReportItem } from '../../entity/LocalConfigEntity'; +import { TopComponent } from '../main/TopComponent'; + +@Component +@Preview +export struct Report { + @State private reportItem: Array = globalThis.reportList + + build() { + Column() { + Row() { + TopComponent({ title: "报告" }) + }.backgroundColor($r("app.color.colorPrimary")) + .width('100%') + .padding({ bottom: '15vp' }) + + List({ space: '20vp', initialIndex: 0 }) { + ForEach(this.reportItem, (item) => { + + ListItem() { + + Flex({ + direction: FlexDirection.Row, + alignItems: ItemAlign.Start, + justifyContent: FlexAlign.SpaceBetween + }) { + Row() { + Image(globalThis.iconMap.get(item.packageName)) + .width('60vp') + .height('60vp') + .padding('10vp') + + Flex({ + direction: FlexDirection.Column, + alignItems: ItemAlign.Start, + justifyContent: FlexAlign.SpaceBetween + }) { + Text(`${item.name}`).fontSize('15fp') + + Text(`${item.appName}`).fontSize('15fp').backgroundColor(0xFFFFFF) + + Text(`${item.startTime}`).fontSize('15fp').backgroundColor(0xFFFFFF) + + Text(secToTime(item.testDuration).toString()).fontSize('15fp').backgroundColor(0xFFFFFF) + } + + Image($r("app.media.report_upload")) + .width('40vp') + .height('40vp') + .padding('10vp') + .margin({ left: 800 }) + .onClick(function (item) { + prompt.showToast({ message: "报告上传功能正在开发中", duration: 1000 }) + }) + + }.margin({ left: '5%', top: '2%', bottom: '2%', right: '2%' }).onClick(() => { + let databasePath: string = item.dbPath + //截取时间戳 + let timeStamp = databasePath.substring(databasePath.lastIndexOf("/") + 1, databasePath.length) + database.queryData(timeStamp + ".db").then(data => { + router.push({ uri: 'pages/ReportDetail', params: { + gpData: data, + reportItem: item + } }) + }) + }) + } + } + }, item => item.toString()) + } + .edgeEffect(EdgeEffect.None) // 滑动到边缘无效果 + .chainAnimation(false) // 联动特效关闭 + .listDirection(Axis.Vertical) // 排列方向 + .divider({ strokeWidth: '2vp', color: 0xdddddd, startMargin: '10vp', endMargin: '12vp' }) // 每行之间的分界线 + + }.height('88%') + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/ui/main/TopComponent.ets b/device/device_ui/entry/src/main/ets/common/ui/main/TopComponent.ets new file mode 100644 index 0000000..9146a69 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/ui/main/TopComponent.ets @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * home页、报告页、我的页面top栏 + */ +@Component +export struct TopComponent { + @State title: string = "SmartPerf" + + build() { + //开始测试title + Text(this.title) + .width('100%') + .height('8%') + .fontSize('20fp') + .fontWeight(FontWeight.Bold) + .fontColor($r("app.color.color_fff")) + .alignSelf(ItemAlign.Start) + .textAlign(TextAlign.Start) + .margin({ left: '15vp', top: '30vp' }) + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/utils/AbilityUtils.ts b/device/device_ui/entry/src/main/ets/common/utils/AbilityUtils.ts new file mode 100644 index 0000000..2a26d16 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/utils/AbilityUtils.ts @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import SPLogger from './SPLogger' +const TAG = "AbilityUtils" +/** + * 启动ability since API8 + * @param bundleName + * @param abilityName + */ +export function commonStartAbility(bundleName: string, abilityName: string, parameters?: { [key: string]: any }) { + SPLogger.INFO(TAG, "Operation bundleName:" + bundleName + "Operation abilityName:" + abilityName); + let str = { + "bundleName": bundleName, + "abilityName": abilityName, + "parameters": parameters + } + console.info(abilityName + ' Operation after. Cause:'); + globalThis.abilityContext.startAbility(str, (err, data) => { + if (err) { + SPLogger.ERROR(TAG,abilityName + ' Operation failed. Cause:' + JSON.stringify(err)); + return; + } + SPLogger.INFO(TAG,abilityName + ' Operation successful. Data: ' + JSON.stringify(data)) + }); +} + + + + + diff --git a/device/device_ui/entry/src/main/ets/common/utils/BundleMangerUtils.ts b/device/device_ui/entry/src/main/ets/common/utils/BundleMangerUtils.ts new file mode 100644 index 0000000..b87ed0a --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/utils/BundleMangerUtils.ts @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import bundle from '@ohos.bundle'; +import ResMgr from '@ohos.resourceManager'; +import { AppInfoItem } from '../entity/LocalConfigEntity'; +import SPLogger from '../utils/SPLogger' +/** + * 包管理 获取应用列表、icon、app名称 + */ +const TAG = "BundleManager" +export default class BundleManager { + static async getResourceManager(context, bundleName, then?: Function) { + if (!then) { + return await ResMgr.getResourceManager(context, bundleName); + } + ResMgr.getResourceManager(context, bundleName).then(then) + } + + //根据包名获取base64 + static async getIconByBundleName(mBundleNameArr: Array): Promise> { + let mBundleNames = Array.from(new Set(mBundleNameArr)) + let mMap = new Map + bundle.getAllApplicationInfo(8, 100).then(async data => { + await + SPLogger.INFO(TAG,'getIconByBundleName data length [' + data.length + ']'); + for (let j = 0; j < data.length; j++) { + let bundleName = data[j].name + for (let i = 0; i < mBundleNames.length; i++) { + if (mBundleNames[i] == bundleName) { + let item = await BundleManager.getResourceManager(globalThis.abilityContext, mBundleNames[i]) + let icon = await item.getMediaBase64(data[j].iconId) + mMap.set(mBundleNames[i], icon) + } + } + } + }) + return mMap + } + + //获取app列表 + static async getAppList(): Promise> { + let appInfoList = new Array() + bundle.getAllApplicationInfo(8, 100).then(async data => { + await + SPLogger.INFO(TAG,'xxx getAllApplicationInfo data length [' + data.length + ']') + for (let i = 0; i < data.length; i++) { + let bundleName = data[i].name + let item = await BundleManager.getResourceManager(globalThis.abilityContext, bundleName) + try { + let appName = await item.getString(data[i].labelId) + let icon = await item.getMediaBase64(data[i].iconId) + bundle.getBundleInfo(bundleName, 1).then(bundleData => { + BundleManager.getAbility(bundleName).then(abilityName => { + console.info("index[" + i + "].getAbility for begin data: ", abilityName); + appInfoList.push(new AppInfoItem(data[i].name, appName, bundleData.versionName, icon, abilityName)) + }); + }) + } catch (err) { + SPLogger.ERROR(TAG,"index[" + i + "] getAllApplicationInfo err" + err); + } + } + }) + return appInfoList + } + //获取启动ability + static async getAbility(bundleName: string): Promise { + let abilityName = ""; + try { + await bundle.queryAbilityByWant({ + bundleName: bundleName, + entities: [ + "entity.system.home", + ], + action: + "action.system.home", + }, 1, 100).then(abilityInfo => { + if (abilityInfo != null) { + abilityName = abilityInfo[0].name; + } + }) + } catch (err) { + SPLogger.ERROR(TAG,"index[" + bundleName + "] getAbility err" + err); + } + SPLogger.INFO(TAG,"index[" + bundleName + "] getAbility abilityName: " + abilityName); + return abilityName; + } +} + + + + + + diff --git a/device/device_ui/entry/src/main/ets/common/utils/CSVUtils.ts b/device/device_ui/entry/src/main/ets/common/utils/CSVUtils.ts new file mode 100644 index 0000000..c802132 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/utils/CSVUtils.ts @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TGeneralInfo } from '../entity/DatabaseEntity'; +import { TIndexInfo } from '../entity/DatabaseEntity'; + +//垂直生成 TGenneralInfo to string +export function csvGeneralInfo(tGeneralInfo: TGeneralInfo): string { + let data = "" + for (let k of Object.keys(tGeneralInfo)) { + data += k + "," + tGeneralInfo[k] + "\n" + } + return data +} + +//水平生成 TIndexInfo to string +export function csvTIndexInfo(tIndexInfos: Array): string { + let tittle = csvTIndexInfoTittle() + let data = "" + for (var index = 0; index < tIndexInfos.length; index++) { + const t = tIndexInfos[index]; + for (let k of Object.keys(t)) { + data += t[k] + "," + } + data += "\n" + } + let result = tittle + data + return result +} + +//水平生成 TIndexInfo TITTLE to string +export function csvTIndexInfoTittle(): string { + var tIndexInfo: TIndexInfo = new TIndexInfo() + let data = "" + for (let k of Object.keys(tIndexInfo)) { + data += k + "," + } + data = data.substring(0, data.lastIndexOf(",")) + data += "\n" + return data +} + + + + + diff --git a/device/device_ui/entry/src/main/ets/common/utils/CalculationUtils.ts b/device/device_ui/entry/src/main/ets/common/utils/CalculationUtils.ts new file mode 100644 index 0000000..812e876 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/utils/CalculationUtils.ts @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * 计算工具类 + * @param fpsList + * @param gameFPS + */ +export default class CalculationUtils { + public TAG: String = "CalculationTest" + public size: number + public gameFPS: number + //所有fps的集合 + public fpsList: Array = [] + //最低帧数 + public point: number + //低于point帧数的数量 + public count: number = 0 + //所有帧数总和 + private fpsSum: number + + constructor(fpsList: Array, gameFPS: number) { + let removeList: Array = new Array() + for (let i = 0;i < fpsList.length; i++) { + if (i < 0) { + removeList.push(i) + } + } + fpsList.filter((item) => { + return removeList.indexOf(item) < 0 + }) + this.fpsList = fpsList + this.size = fpsList.length + this.gameFPS = gameFPS + this.point = gameFPS == 30 ? 25 : gameFPS == 40 ? 30 : gameFPS == 60 ? 45 : 10 + } + + //抖动率计算 + Jitter_rate(): number { + let jitterRite = 0; + let size = this.fpsList.length; + + let numOfDiff3To = 0; + let numOfDiff5To = 0; + let numOfDiff8To = 0; + let numOfDiff12To = 0; + let numOfDiff16To = 0; + + let numOfDiff3To5 = 0; + let numOfDiff5To8 = 0; + let numOfDiff8To12 = 0; + let numOfDiff6To10 = 0; + let numOfDiff10To16 = 0; + + for (let i = 1; i < size; i++) { + let diff = Math.abs(this.fpsList[i] - this.fpsList[i - 1]); + if (diff > 3) { + numOfDiff3To++; + } + if (diff > 5) { + numOfDiff5To++; + } + if (diff > 8) { + numOfDiff8To++; + } + if (diff > 12) { + numOfDiff12To++; + } + if (diff > 16) { + numOfDiff16To++; + } + if (diff > 3 && diff <= 5) { + numOfDiff3To5++; + } + if (diff > 5 && diff <= 8) { + numOfDiff5To8++; + } + if (diff > 8 && diff <= 12) { + numOfDiff8To12++; + } + if (diff > 6 && diff <= 10) { + numOfDiff6To10++; + } + if (diff > 10 && diff <= 16) { + numOfDiff10To16++; + } + } + + let percentOf3To = 0; + let percentOf5To = 0; + let percentOf8To = 0; + let percentOf12To = 0; + let percentOf16To = 0; + + let percentOf3To5 = 0; + let percentOf5To8 = 0; + let percentOf8To12 = 0; + let percentOf6To10 = 0; + let percentOf10To16 = 0; + + if (size != 1) { + percentOf3To = numOfDiff3To / size; + percentOf5To = numOfDiff5To / size; + percentOf8To = numOfDiff8To / size; + percentOf12To = numOfDiff12To / size; + percentOf16To = numOfDiff16To / size; + + percentOf3To5 = numOfDiff3To5 / size; + percentOf5To8 = numOfDiff5To8 / size; + percentOf8To12 = numOfDiff8To12 / size; + percentOf6To10 = numOfDiff6To10 / size; + percentOf10To16 = numOfDiff10To16 / size; + } + if (this.gameFPS == 25 || this.gameFPS == 30) { + jitterRite = percentOf3To * 100; + } + if (this.gameFPS == 40 || this.gameFPS == 45) { + jitterRite = (percentOf3To5 * 0.4 + percentOf5To * 0.6) * 100; + } + if (this.gameFPS == 60) { + jitterRite = (percentOf3To5 * 0.2 + percentOf5To8 * 0.3 + percentOf8To * 0.5) * 100; + } + if (this.gameFPS == 90) { + jitterRite = (percentOf5To8 * 0.2 + percentOf8To12 * 0.3 + percentOf12To * 0.5) * 100; + } + if (this.gameFPS == 120) { + jitterRite = (percentOf6To10 * 0.2 + percentOf10To16 * 0.3 + percentOf16To * 0.5) * 100; + } + return jitterRite; + } + + //低帧率计算 + Low_Frame_Rate(): number { + for (let i = 0; i < this.fpsList.length; i++) { + if (this.fpsList[i] < this.point) { + this.count++; + } + } + return this.count / this.size * 100; + } + + //卡顿次数 100ms + calculateCaton(fpsJitterList: Array): number{ + let num = 0 + for (let i = 0; i < fpsJitterList.length; i++) { + let jitter = fpsJitterList[i].split(","); + for (let j = 0; j < jitter.length; j++) { + var n = Number(jitter[j]); + if (!isNaN(n) && (n / 1e6 > 100)) { + num++ + } + } + } + return num + } + + //满帧计算 + static calculateFPSNew(fpsList: Array): number { + let FPS = 30 + let moreThan34Count = 0 + let moreThan44Count = 0 + let moreThan70Count = 0 + let moreThan100Count = 0 + for (let i in fpsList) { + if (fpsList[i] >= 100) { + moreThan100Count++ + } else if (fpsList[i] >= 70) { + moreThan70Count++ + } else if (fpsList[i] >= 44) { + moreThan44Count++ + } else if (fpsList[i] >= 34) { + moreThan34Count++ + } + } + if (moreThan100Count >= 1) { + FPS = 120 + } else if (moreThan70Count >= 1) { + FPS = 90 + } else if (moreThan44Count >= 1) { + FPS = 60 + } else if (moreThan34Count >= 1) { + FPS = 45 + } + return FPS + } +} diff --git a/device/device_ui/entry/src/main/ets/common/utils/CheckEmptyUtils.ts b/device/device_ui/entry/src/main/ets/common/utils/CheckEmptyUtils.ts new file mode 100644 index 0000000..fb5f3ff --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/utils/CheckEmptyUtils.ts @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export default class CheckEmptyUtils { + + /** + * Check obj is empty. + * + * @param {Object} obj need checked object + * @return {boolean} true(empty) + */ + static isEmpty(obj) { + return (typeof obj === 'undefined' || obj === null || obj === '' || Object.keys(obj).length === 0); + } + + /** + * Check str is empty. + * + * @param {string} str need checked string + * @return {boolean} true(empty) + */ + static checkStrIsEmpty(str) { + return str.trim().length === 0; + } + + /** + * Check array is empty. + * + * @param {Array} arr need checked array + * @return {boolean} true(empty) + */ + static isEmptyArr(arr) { + return arr.length === 0; + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/utils/GameUtils.ets b/device/device_ui/entry/src/main/ets/common/utils/GameUtils.ets new file mode 100644 index 0000000..be1993d --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/utils/GameUtils.ets @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * 计算工具类 + */ +export default class GameUtils { + // 30,40,60 连续丢帧>=3帧 为一般卡顿 + static GENERAL_CATON_FLAG_LOW: number = 3 + // 30,40,60 连续丢帧>=6帧 为严重卡顿 + static CRITICAL_CATON_FLAG_LOW: number = 6 + // 30,40,60 连续丢帧>=9帧 为致命卡顿 + static FATAL_CATON_FLAG_LOW: number = 9 + // 90,120 连续丢帧>=4帧 为一般卡顿 + static GENERAL_CATON_FLAG_HIGHT: number = 4 + // 90,120 连续丢帧>=8帧 为严重卡顿 + static CRITICAL_CATON_FLAG_HIGHT: number = 8 + // 90,120 连续丢帧>=12帧 为致命卡顿 + static FATAL_CATON_FLAG_HIGHT: number = 12 + + // 连续丢帧数组长度 + private static JANK_SIZE: number = 21; + private static JANK_RANGE_ARRAY_120 = new Array(GameUtils.JANK_SIZE); + private static JANK_RANGE_ARRAY_90 = new Array(GameUtils.JANK_SIZE); + private static JANK_RANGE_ARRAY_60 = new Array(GameUtils.JANK_SIZE); + private static JANK_RANGE_ARRAY_40 = new Array(GameUtils.JANK_SIZE); + private static JANK_RANGE_ARRAY_30 = new Array(GameUtils.JANK_SIZE); + private constructor() { + } + + /** + * 方法描述 计算满帧 + * + * @param maxFps maxFps + * @return java.lang.Integer get full fps + */ + static getFullFps(maxFps: number): number { + /*关于满帧判定,把所有帧率做一次遍历: + * 1、只要有>=100帧,则判断为120帧 + * 2、否则只要有>=70 且所有帧都小于100,则判断为90帧 + * 1、否则只要有>=44 并且 所有帧都小于70的情况,就按照满帧60帧 + * 2、只要有>=34,且所有帧率均小于44的,就按照满帧40帧 + * 3、如果所有帧率均小于34帧的,就按照满帧30 + */ + + if (maxFps >= 100) { + return 120; + } else if (maxFps >= 70) { + return 90; + } else if (maxFps >= 44) { + return 60; + } else if (maxFps >= 34) { + return 40; + } + return 30; + } + + /** + * 获得低帧率的计算标准 + */ + private static getBaseLowFPS(gameFPS: number): number { + if (gameFPS >= 60) { + return 45; + } else if (gameFPS == 40) { + return 30; + } else if (gameFPS == 30) { + return 25; + } + return 20; // 25时候返回20 + } + + /** + * 方法描述 计算帧率中位数 + * + * @param fpsList fpsList + * @return java.lang.Integer fps + */ + private static getMedianFrameRadio(fpsArray: Array): number { + if (null == fpsArray || fpsArray.length == 0) { + return null; + } + + fpsArray.sort() + + let length = fpsArray.length; + + if (length % 2 == 0) { + return ((fpsArray[fpsArray.length / 2] + fpsArray[fpsArray.length / 2 - 1]) / 2); // 偶数个取中间两个数的平均数 + } else { + return fpsArray[fpsArray.length / 2]; // 奇数个取最中间那个数 + } + } + + /** + * 判断电池容量/平均电流等数据是否有效 + * @param value + */ + public static isFloatDataValid(value: number): boolean { + if (null == value || value < 0 || Math.abs(value - 0) < 0.001) { + return false; + } + return true; + } + + /** + * 方法描述 计算kpi抖动率的得分(y=100*EXP(-0.458*x)) + * + * @param param param + * @return java.lang.Integer 得分 + */ + private static getFpsJitterScore(param: number): number{ + if (null == param) { + return null; + } + return Math.round(100.0 * Math.exp(-0.458 * 100 * param)); + } + + public static createJankStr(jankSrcStr: string, fullFPS: number, JankMap: Map): string { + if (null == jankSrcStr || jankSrcStr == "") { + return "NA"; + } + + // 初始化连续丢帧数组 + GameUtils.initJankArray(fullFPS); + + let allDrawFrame: Array = jankSrcStr.split(","); + let jitters = new Array(allDrawFrame.length); + for (let i = 0; i < allDrawFrame.length; i++) { + try { + jitters[i] = parseInt(allDrawFrame[i]); + } catch (e) { + e.printStackTrace(); + } + } + + let jitterStr = "NA"; + let jankCountMap = new Map(); + jitters.forEach(jank => { + let doubleJank = jank / 1e6; + let jankRange = 0; + if (fullFPS == 120) { + jankRange = GameUtils.getJankRange(GameUtils.JANK_RANGE_ARRAY_120, 2, doubleJank); + } else if (fullFPS == 90) { + jankRange = GameUtils.getJankRange(GameUtils.JANK_RANGE_ARRAY_90, 2, doubleJank); + } else if (fullFPS == 60) { + jankRange = GameUtils.getJankRange(GameUtils.JANK_RANGE_ARRAY_60, 2, doubleJank); + } else if (fullFPS == 40) { + jankRange = GameUtils.getJankRange(GameUtils.JANK_RANGE_ARRAY_40, 2, doubleJank); + } else { + jankRange = GameUtils.getJankRange(GameUtils.JANK_RANGE_ARRAY_30, 2, doubleJank); + } + + if (jankRange != 0) { + if (jankCountMap.get(jankRange) == null) { + jankCountMap.set(jankRange, 1); + } else { + jankCountMap.set(jankRange, jankCountMap.get(jankRange) + 1); + } + } + }); + + for (let j = 2; j <= 22; j++) { + if (!jankCountMap.has(j)) { + jankCountMap.set(j, 0); + } + } + + let jitterBuilder = new String(); + + let allKeysIterator = jankCountMap.keys(); + + for (var i = 0; i < jankCountMap.size; i++) { + let key: number = allKeysIterator.next().value + let jankKey = key == 22 ? ">20" : String(key - 1); + jitterBuilder.concat(jankKey).concat(":").concat(String(jankCountMap.get(key))).concat(";"); + JankMap.set(jankKey, jankCountMap.get(key)); + } + + jitterStr = jitterBuilder.slice(0, jitterBuilder.length); + return jitterStr; + } + + private static initJankArray(fullFps: number) { + switch (fullFps) { + case 120: + GameUtils.calJankArray(GameUtils.JANK_RANGE_ARRAY_120, 8.333); + break; + case 90: + GameUtils.calJankArray(GameUtils.JANK_RANGE_ARRAY_90, 11.1111); + break; + case 60: + GameUtils.calJankArray(GameUtils.JANK_RANGE_ARRAY_60, 16.667); + break; + case 40: + GameUtils.calJankArray(GameUtils.JANK_RANGE_ARRAY_40, 25.0); + break; + case 30: + GameUtils.calJankArray(GameUtils.JANK_RANGE_ARRAY_30, 33.333); + break; + default: + break; + } + } + + /** + * 方法描述 + * + * @param jankRangeArray 连续丢帧数组 + * @param defaultJankTime 默认单帧时间 + */ + private static calJankArray(jankRangeArray: Array, defaultJankTime: number) { + if (jankRangeArray[0] < 0.01) { + for (let i = 1; i <= GameUtils.JANK_SIZE; i++) { + jankRangeArray[i - 1] = (defaultJankTime * i + defaultJankTime * (i + 1)) / 2; + } + } + } + + private static getJankRange(jankRangeArray: Array, currRange: number, jank: number): number{ + if (currRange > 22) { + return 0; + } + if (currRange == 2) { + if (jank < jankRangeArray[currRange - 2]) { + return 0; + } + } + if (currRange == 22) { + if (jank >= jankRangeArray[currRange - 2]) { + return currRange; + } else { + return 0; + } + } + if (jank >= jankRangeArray[currRange - 2] && jank < jankRangeArray[currRange - 1]) { + return currRange; + } else { + return GameUtils.getJankRange(jankRangeArray, currRange + 1, jank); + } + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/utils/IOUtils.ts b/device/device_ui/entry/src/main/ets/common/utils/IOUtils.ts new file mode 100644 index 0000000..49030ca --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/utils/IOUtils.ts @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import fileio from '@ohos.fileio'; +import SPLogger from '../utils/SPLogger' +/** + * create file by path + * @param path + * @param data + */ +const TAG = "IOUtils" +export function createFilePath(path: string, data: string) { + SPLogger.INFO(TAG,"createFilePath called:" + path); + let writer + try { + fileio.mkdirSync(globalThis.abilityContext.getApplicationContext().filesDir + "/" + globalThis.dbTime) + } catch (err) { + SPLogger.INFO(TAG,"createFilePath:mkdirSync" + err); + } + + try { + writer = fileio.openSync(path, 0o102, 0o666); + fileio.writeSync(writer, data); + SPLogger.INFO(TAG,"createFilePath:WRITER SUCCESS"); + } catch (err) { + SPLogger.INFO(TAG,"createFilePath:err" + err); + } finally { + fileio.closeSync(writer); + } +} + + + + + diff --git a/device/device_ui/entry/src/main/ets/common/utils/JsonUtils.ets b/device/device_ui/entry/src/main/ets/common/utils/JsonUtils.ets new file mode 100644 index 0000000..8edf9df --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/utils/JsonUtils.ets @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export default { + _mapToJson(map: Map): string{ + var obj = Object.create(null); + + var iterator = map.keys(); + for (var i = 0; i < map.size; i++) { + var key = iterator.next().value; + obj[key] = map.get(key); + } + return JSON.stringify(obj); + } +} + + + + diff --git a/device/device_ui/entry/src/main/ets/common/utils/SPLogger.ts b/device/device_ui/entry/src/main/ets/common/utils/SPLogger.ts new file mode 100644 index 0000000..b6c905d --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/utils/SPLogger.ts @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default class SPLogger { + //控制所有 + private static isPrintLog = true + //控制debug + private static isPrintDebugLog = true + //控制info + private static isPrintInfoLog = true + //控制warn + private static isPrintWarnLog = true + //控制error + private static isPrintErrorLog = true + + private static bastTag = "SmartPerf:" + + //debug debug日志 + static DEBUG(tag: string, msg: string) { + if (SPLogger.isPrintLog && SPLogger.isPrintDebugLog) { + console.debug(SPLogger.bastTag + tag + "," + msg) + } + } + + //info 级别日志 + static INFO(tag: string, msg: string) { + if (SPLogger.isPrintLog && SPLogger.isPrintInfoLog) { + console.info(SPLogger.bastTag + tag + "," + msg) + } + } + + //warn 级别日志 + static WARN(tag: string, msg: string) { + if (SPLogger.isPrintLog && SPLogger.isPrintWarnLog) { + console.warn(SPLogger.bastTag + tag + "," + msg) + } + } + + //error 级别日志 + static ERROR(tag: string, msg: string) { + if (SPLogger.isPrintLog && SPLogger.isPrintErrorLog) { + console.error(SPLogger.bastTag + tag + "," + msg) + } + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/utils/StringUtils.ts b/device/device_ui/entry/src/main/ets/common/utils/StringUtils.ts new file mode 100644 index 0000000..4c26e13 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/utils/StringUtils.ts @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * 字符串工具类 + */ +export default class StringUtils { + static s2L(src: string): number { + let result = -1; + if (typeof src == "undefined" || src == null || src == "") { + return result; + } + try { + result = parseInt(src); + return result; + } catch (e) { + e.printStackTrace(); + return result; + } + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/common/utils/SystemUtils.ets b/device/device_ui/entry/src/main/ets/common/utils/SystemUtils.ets new file mode 100644 index 0000000..bb23167 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/utils/SystemUtils.ets @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { fileOpen, travelFile } from '../profiler/base/BaseProfilerUtils'; +import { ProcessRunningInfo } from '../utils/../entity/SystemEntity' +import appManager from '@ohos.application.appManager'; + +export function getCpuCoreInfo(): Array { + const basePath = "/sys/devices/system/cpu/cpufreq" + let defaultPolicy = new Array( + "policy0", "policy1", "policy2" + ) + var supportPolicy = [] + let policyArr = travelFile(basePath, "policy") + + policyArr.forEach((policy) => { + defaultPolicy.forEach((defaultItem) => { + if (defaultItem == policy) { + supportPolicy.push(defaultItem) + } + }) + }) + var coreArr = new Array() + for (var index = 0; index < supportPolicy.length; index++) { + const policy = supportPolicy[index]; + var affected_cpus = fileOpen(basePath + "/" + policy + "/affected_cpus") + coreArr.push(affected_cpus.charAt(0)) + } + return coreArr +} + +export function getPidOfAbility(processName: string): Promise { + return appManager.getProcessRunningInfos().then(data => { + let processArr: Array = data + var pid = "-1" + processArr.forEach(process=>{ + if (process.processName == processName) { + pid = process.pid.toString() + } + }) + return pid + }) +} + + diff --git a/device/device_ui/entry/src/main/ets/common/utils/TimeUtils.ts b/device/device_ui/entry/src/main/ets/common/utils/TimeUtils.ts new file mode 100644 index 0000000..41eb2ba --- /dev/null +++ b/device/device_ui/entry/src/main/ets/common/utils/TimeUtils.ts @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * 日期格式化 + * @param Number time 时间戳 + * @param String format 格式 + */ +export function dateFormat(time?: number, format: string='Y-m-d h:i:s') { + const t = new Date(time) + // 日期格式 + format = format || 'Y-m-d h:i:s' + let year = t.getFullYear() + // 由于 getMonth 返回值会比正常月份小 1 + let month = t.getMonth() + 1 + let day = t.getDate() + let hours = t.getHours() + let minutes = t.getMinutes() + let seconds = t.getSeconds() + + const hash = { + 'y': year, + 'm': month, + 'd': day, + 'h': hours, + 'i': minutes, + 's': seconds + } + // 是否补 0 + const isAddZero = (o) => { + return /m|d|h|i|s/.test(o) + } + return format.replace(/\w/g, o => { + let rt = hash[o.toLocaleLowerCase()] + return rt > 9 || !isAddZero(o) ? rt : `0${rt}` + }) +} +/** + * 计时器 转 时分秒字符串 HH:mm:ss + * @param time + */ +export function secToTime(time: number): String { + var timeStr: String = null + var hour: number = 0 + var minute: number = 0 + var second: number = 0 + if (time <= 0) { + return "00:00" + } else { + minute = parseInt((time / 60).toString()) + if (minute < 60) { + second = time % 60 + timeStr = unitFormat(minute) + ":" + unitFormat(second) + } else { + hour = parseInt((minute / 60).toString()) + minute = minute % 60 + second = time - hour * 3600 - minute * 60 + timeStr = + unitFormat(hour) + ":" + unitFormat(minute) + ":" + unitFormat(second) + } + } + return timeStr +} + +export function unitFormat(i: number): String { + var retStr: String + if (i >= 0 && i < 10) { + retStr = "0" + i.toString() + } else { + retStr = "" + i.toString() + } + return retStr +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/pages/AppSelectPage.ets b/device/device_ui/entry/src/main/ets/pages/AppSelectPage.ets new file mode 100644 index 0000000..84cab70 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/AppSelectPage.ets @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { StartTestTitleComponent } from '../common/ui/StartTestTitleComponent'; +import { AppInfoItem } from '../common/entity/LocalConfigEntity'; +import router from '@system.router'; + +/** + * app应用选择页 + */ +@Entry +@Component +struct AppSelectPage { + build() { + Column() { + StartTestTitleComponent({ title: "选择应用" }) + appInfoComponent() + }.width('100%').height('100%') + } +} + +@Component +struct appInfoComponent { + @State appInfoList: Array = globalThis.appList + + build() { + List() { + ForEach(this.appInfoList, (appInfoItem) => { + + ListItem() { + Row() { + Image(appInfoItem.appIcon).width('40vp').height('40vp').margin({ left: '2%' }) + Flex({ + justifyContent: FlexAlign.SpaceBetween, + alignItems: ItemAlign.Start, + direction: FlexDirection.Column + }) { + Text(appInfoItem.appName) + .fontSize('15fp') + .fontColor($r("app.color.color_333")) + .fontWeight(FontWeight.Bold) + Text(appInfoItem.appVersion).fontSize('12fp').fontColor($r("app.color.color_333")) + Divider().vertical(false).margin({ top: '5vp' }).height('1vp') + }.margin({ left: '4%' }).height('100%').padding({ top: '20vp' }) + }.alignSelf(ItemAlign.Start).width('100%').height('80vp').onClick(() => { + if (router.getParams()["StartPage"] == "StartTest") { + router.back({ uri: "pages/StartTestPage", params: { + selectPackageName: appInfoItem.packageName, + selectAbilityName: appInfoItem.abilityName, + appName: appInfoItem.appName, + appVersion: appInfoItem.appVersion, + appIconId: appInfoItem.appIcon + } }) + } + }) + } + }, appInfoItem => appInfoItem.packageName) + }.edgeEffect(EdgeEffect.None) // 滑动到边缘无效果 + .chainAnimation(false) // 联动特效关闭 + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/pages/CPU0LineChartPage.ets b/device/device_ui/entry/src/main/ets/pages/CPU0LineChartPage.ets new file mode 100644 index 0000000..13c701f --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/CPU0LineChartPage.ets @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { hideFloatWindow} from '../common/ui/floatwindow/utils/floatwindowutils' +import { FloatWindowComponent } from '../common/FloatWindowComponent' +import { TaskStatus } from '../common/profiler/base/ProfilerConstant'; + +@Entry +@Component +struct CPU0LineChartPage { + data: number[]= [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] //数据集合 + @State random: number= 0 //用于刷新的随机数 + @State cpu0Frequency: number= 0 //数值 + + offsetX: number = -1 //悬浮框移动触摸点 X + offsetY: number = -1 //悬浮框移动触摸点 X + private floatName: string= "cpu0Frequency" + task_state = 1 + + aboutToAppear() { + globalThis.LineChartCollect = setInterval(() => { + this.task_state = globalThis.task_status + if (this.task_state == TaskStatus.task_running) { + if (this.data.length >= 22) { + this.data.shift() //移除第一个元素 + } + if (globalThis.tTndex.cpu0Frequency == undefined) { + this.cpu0Frequency = 0 + } else { + this.cpu0Frequency = parseInt(globalThis.tTndex.cpu0Frequency) / 1e3 + + if (this.cpu0Frequency == 0) { + this.data.push(0) + } else { + let lineCount: number = this.cpu0Frequency / 30 + this.data.push(Math.abs(lineCount)) //在末尾填充一个元素 + } + this.random = Math.random() + } + } else { + //暂停 + } + + }, 1000) + } + + build() { + Stack({ alignContent: Alignment.Top }) { + FloatWindowComponent({ title: `cpu0Frequency`, data: this.data }) + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text(this.floatName + ":" + this.cpu0Frequency + "Hz") + .fontSize('6fp') + .fontColor($r("app.color.color_333")) + .margin({ left: 5, top: 3 }) //文本显示 + Text(this.random + "") + .fontSize('1fp') + .fontColor($r("app.color.color_333")).visibility(Visibility.None) + Image($r("app.media.icon_close_small")).width('15vp').height('15vp').onClick(() => { + hideFloatWindow(this.floatName) + + }).align(Alignment.TopEnd) + }.height('15vp').width('100%').opacity(0.2) + + } + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/pages/CPU1LineChartPage.ets b/device/device_ui/entry/src/main/ets/pages/CPU1LineChartPage.ets new file mode 100644 index 0000000..7c8f8a0 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/CPU1LineChartPage.ets @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { hideFloatWindow } from '../common/ui/floatwindow/utils/floatwindowutils' +import { FloatWindowComponent } from '../common/FloatWindowComponent' +import { TaskStatus } from '../common/profiler/base/ProfilerConstant'; + +@Entry +@Component +struct CPU1LineChartPage { + data: number[]= [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] //数据集合 + @State random: number= 0 //用于刷新的随机数 + @State cpu1Frequency: number= 0 //数值 + + offsetX: number = -1 //悬浮框移动触摸点 X + offsetY: number = -1 //悬浮框移动触摸点 X + private floatName: string= "cpu1Frequency" + task_state = 1 + + aboutToAppear() { + globalThis.LineChartCollect = setInterval(() => { + this.task_state = globalThis.task_status + if (this.task_state == TaskStatus.task_running) { + if (this.data.length >= 22) { + + this.data.shift() //移除第一个元素 + } + if (globalThis.tTndex.cpu4Frequency == undefined) { + this.cpu1Frequency = 0 + } else { + this.cpu1Frequency = parseInt(globalThis.tTndex.cpu4Frequency) / 1e3 + + if (this.cpu1Frequency == 0) { + this.data.push(0) + } else { + let lineCount: number = this.cpu1Frequency / 30 + this.data.push(Math.abs(lineCount)) //在末尾填充一个元素 + } + this.random = Math.random() + + } + } else { + //暂停 + } + }, 1000) + } + + build() { + Stack({ alignContent: Alignment.Top }) { + FloatWindowComponent({ title: `cpu1Frequency`, data: this.data }) + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text(this.floatName + ":" + this.cpu1Frequency + "Hz") + .fontSize('6fp') + .fontColor($r("app.color.color_333")) + .margin({ left: 5, top: 3 }) //文本显示 + Text(this.random + "") + .fontSize('1fp') + .fontColor($r("app.color.color_333")).visibility(Visibility.None) + Image($r("app.media.icon_close_small")).width('15vp').height('15vp').onClick(() => { + hideFloatWindow(this.floatName) + console.log("hideFloatWindow---------------------" + this.floatName) + }).align(Alignment.TopEnd) + }.height('15vp').width('100%') + + } + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/pages/CPU2LineChartPage.ets b/device/device_ui/entry/src/main/ets/pages/CPU2LineChartPage.ets new file mode 100644 index 0000000..4f25eb5 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/CPU2LineChartPage.ets @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {hideFloatWindow} from '../common/ui/floatwindow/utils/floatwindowutils' +import { FloatWindowComponent } from '../common/FloatWindowComponent' +import { TaskStatus } from '../common/profiler/base/ProfilerConstant'; + +@Entry +@Component +struct CPU2LineChartPage { + data: number[]= [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] //数据集合 + @State random: number= 0 //用于刷新的随机数 + @State cpu2Frequency: number= 0 //数值 + + offsetX: number = -1 //悬浮框移动触摸点 X + offsetY: number = -1 //悬浮框移动触摸点 X + private floatName: string= "cpu2Frequency" + task_state = 1 + + aboutToAppear() { + globalThis.LineChartCollect = setInterval(() => { + this.task_state = globalThis.task_status + if (this.task_state == TaskStatus.task_running) { + if (this.data.length >= 22) { + + this.data.shift() //移除第一个元素 + } + if (globalThis.tTndex.cpu7Frequency == undefined) { + this.cpu2Frequency = 0 + } else { + this.cpu2Frequency = parseInt(globalThis.tTndex.cpu7Frequency) / 1e3 + + if (this.cpu2Frequency == 0) { + this.data.push(0) + } else { + let lineCount: number = this.cpu2Frequency / 30 + this.data.push(Math.abs(lineCount)) //在末尾填充一个元素 + } + this.random = Math.random() + } + } else { + //暂停 + } + }, 1000) + } + + build() { + Stack({ alignContent: Alignment.Top }) { + FloatWindowComponent({ title: `cpu2Frequency`, data: this.data }) + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text(this.floatName + ":" + this.cpu2Frequency + "Hz") + .fontSize('6fp') + .fontColor($r("app.color.color_333")) + .margin({ left: 5, top: 3 }) //文本显示 + Text(this.random + "") + .fontSize('1fp') + .fontColor($r("app.color.color_333")).visibility(Visibility.None) + Image($r("app.media.icon_close_small")).width('15vp').height('15vp').onClick(() => { + hideFloatWindow(this.floatName) + console.log("hideFloatWindow---------------------" + this.floatName) + }).align(Alignment.TopEnd) + }.height('15vp').width('100%') + + } + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/pages/CurrentNowLineChartPage.ets b/device/device_ui/entry/src/main/ets/pages/CurrentNowLineChartPage.ets new file mode 100644 index 0000000..e7ddaf0 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/CurrentNowLineChartPage.ets @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {hideFloatWindow} from '../common/ui/floatwindow/utils/floatwindowutils' +import { FloatWindowComponent } from '../common/FloatWindowComponent' +import { TaskStatus } from '../common/profiler/base/ProfilerConstant'; + +@Entry +@Component +struct CurrentNowLineChartPage { + data: number[]= [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] //数据集合 + @State random: number= 0 //用于刷新的随机数 + @State currentNow: number= 0 //数值 + + offsetX: number = -1 //悬浮框移动触摸点 X + offsetY: number = -1 //悬浮框移动触摸点 X + private floatName: string= "currentNow" + task_state = 1 + + aboutToAppear() { + globalThis.LineChartCollect = setInterval(() => { + this.task_state = globalThis.task_status + if (this.task_state == TaskStatus.task_running) { + if (this.data.length >= 22) { + + this.data.shift() //移除第一个元素 + } + if (globalThis.tTndex.currentNow == undefined) { + this.currentNow = 0 + } else { + this.currentNow = globalThis.tTndex.currentNow + + if (this.currentNow == 0) { + this.data.push(0) + } else { + let lineCount: number = this.currentNow / 30 + this.data.push(Math.abs(lineCount)) //在末尾填充一个元素 + } + this.random = Math.random() + } + } else { + //暂停 + } + }, 1000) + } + + build() { + Stack({ alignContent: Alignment.Top }) { + FloatWindowComponent({ title: `currentNow`, data: this.data }) + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text(this.floatName + ":" + this.currentNow + "mA") + .fontSize('6fp') + .fontColor($r("app.color.color_333")) + .margin({ left: 5, top: 3 }) //文本显示 + Text(this.random + "") + .fontSize('1fp') + .fontColor($r("app.color.color_333")).visibility(Visibility.None) + Image($r("app.media.icon_close_small")).width('15vp').height('15vp').onClick(() => { + hideFloatWindow(this.floatName) + console.log("hideFloatWindow---------------------" + this.floatName) + }).align(Alignment.TopEnd) + }.height('15vp').width('100%') + + } + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/pages/DDRLineChartPage.ets b/device/device_ui/entry/src/main/ets/pages/DDRLineChartPage.ets new file mode 100644 index 0000000..ba8dff5 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/DDRLineChartPage.ets @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { hideFloatWindow } from '../common/ui/floatwindow/utils/floatwindowutils' +import { FloatWindowComponent } from '../common/FloatWindowComponent' +import { TaskStatus } from '../common/profiler/base/ProfilerConstant'; + +@Entry +@Component +struct DDRNowLineChartPage { + data: number[]= [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] //数据集合 + @State random: number= 0 //用于刷新的随机数 + @State DDR: number= 0 //数值 + + offsetX: number = -1 //悬浮框移动触摸点 X + offsetY: number = -1 //悬浮框移动触摸点 X + private floatName: string= "ddrFrequency" + task_state = 1 + + aboutToAppear() { + globalThis.LineChartCollect = setInterval(() => { + this.task_state = globalThis.task_status + if (this.task_state == TaskStatus.task_running) { + if (this.data.length >= 22) { + + this.data.shift() //移除第一个元素 + } + if (globalThis.tTndex.ddrFrequency == undefined) { + this.DDR = 0 + } else { + this.DDR = Number(globalThis.tTndex.ddrFrequency / 1e6).valueOf() + + if (this.DDR == 0) { + this.data.push(0) + } else { + let lineCount: number = this.DDR / 30 + this.data.push(Math.abs(lineCount)) //在末尾填充一个元素 + } + this.random = Math.random() + } + } else { + //暂停 + } + }, 1000) + } + + build() { + Stack({ alignContent: Alignment.Top }) { + FloatWindowComponent({ title: `ddrFrequency`, data: this.data }) + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text(this.floatName + ":" + this.DDR + "MHz") + .fontSize('6fp') + .fontColor($r("app.color.color_333")) + .margin({ left: 5, top: 3 }) //文本显示 + Text(this.random + "") + .fontSize('1fp') + .fontColor($r("app.color.color_333")).visibility(Visibility.None) + Image($r("app.media.icon_close_small")).width('15vp').height('15vp').onClick(() => { + hideFloatWindow(this.floatName) + + console.log("hideFloatWindow---------------------" + this.floatName) + }).align(Alignment.TopEnd) + }.height('15vp').width('100%') + + } + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/pages/FloatBall.ets b/device/device_ui/entry/src/main/ets/pages/FloatBall.ets new file mode 100644 index 0000000..a0c5f08 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/FloatBall.ets @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { secToTime } from '../common/utils/TimeUtils'; +import { TaskStatus,CatchTraceStatus} from '../common/profiler/base/ProfilerConstant'; +import { MainWorker } from "../common/profiler/MainWorkTask" +import { ProfilerTask } from "../common/profiler/ProfilerTask" +import { initFloatWindow } from '../common/ui/floatwindow/utils/floatwindowutils' +let traceTime = 0 +let socketCollectItems +let isCollectRam = false + +@Entry +@Component +struct FloatBall { + @State playerState: number = TaskStatus.task_init + @State timerNum: number = 0 + @State FpsTimer: number = -1 + timerId: number = -1 + @State windShowState: boolean = false + @State windFpsShowState: boolean = false + offsetX: number = -1 + offsetY: number = -1 + //解決手势失效的问题 + COUNTS = 2 // 点击次数 + DURATION: number = 300 // 规定有效时间 + mHits = Array(this.COUNTS) // 数组 + isDoubleFlag = false // 是否是双击 + singleClickId = 0 // 单击事件ID + + aboutToAppear() { + + ProfilerTask.getInstance().initModule() + ProfilerTask.getInstance().taskInit() + //创建TiTile窗 + globalThis.CreateTitleWindow() + globalThis.CreateFPSLineChartWindow() + globalThis.task_status = TaskStatus.task_init + initFloatWindow() + if (globalThis.collectConfigs.trace) { + globalThis.catchTraceState = CatchTraceStatus.catch_trace_start + globalThis.jitterTrace = false + } + + } + + initAllCollect() { + if (globalThis.collectConfigs != -1 && globalThis.collectPkg != -1) { + globalThis.collectIntervalCollect = setInterval(() => { + if (globalThis.timerFps == undefined) { + } else { + this.FpsTimer = globalThis.timerFps + } + if (this.playerState == TaskStatus.task_running) { + ProfilerTask.getInstance().taskStart() + } + }, 1000) + globalThis.collectSocketCollect = setInterval(() => { + if (this.playerState == TaskStatus.task_running) { + this.timerNum++ + isCollectRam = false + if ((globalThis.collectIntervalNum % 5 == 0) && globalThis.collectConfigs.RAM) { + isCollectRam = true + } else { + isCollectRam = false + } + + socketCollectItems = { + "pkg": globalThis.collectPkg, + "ram": isCollectRam, + "fps": globalThis.collectConfigs.FPS, + "screen_capture": globalThis.collectConfigs.screen_capture, + "is_video": globalThis.collectConfigs.is_video, + "is_camera": globalThis.collectConfigs.is_camera + } + + if (globalThis.collectConfigs.trace) { + this.taskTrace(socketCollectItems) + } + MainWorker.postMessage(socketCollectItems) + } + }, 1000) + } + globalThis.task_status = TaskStatus.task_running + this.playerState = TaskStatus.task_running + } + + getJitterTrace() { + if (globalThis.fpsJitterArr != undefined && globalThis.fpsJitterArr != null && globalThis.fpsJitterArr != "") { + let tempQueue: Array = globalThis.fpsJitterArr + let curJitter = tempQueue.pop() + let tempJitterArr = curJitter.split("==") + for (var i = 0; i < tempJitterArr.length; i++) { + let tmp = tempJitterArr[i] + let jitter = parseInt(tmp) / 1e6 + if (jitter > 100) { + globalThis.jitterTrace = true + return + } + } + } + } + + taskTrace(defaultSocketCollectItems) { + if (globalThis.catchTraceState == CatchTraceStatus.catch_trace_start) { //开始 + defaultSocketCollectItems["catch_trace_start"] = true + globalThis.catchTraceState = CatchTraceStatus.catch_trace_first_running + } else if (globalThis.catchTraceState == CatchTraceStatus.catch_trace_finish) { //抓取 + defaultSocketCollectItems["catch_trace_finish"] = true + defaultSocketCollectItems["traceName"] = this.timerNum + "" + globalThis.catchTraceState = CatchTraceStatus.catch_trace_running; + traceTime = 0 + } else if (globalThis.catchTraceState == CatchTraceStatus.catch_trace_first_running) { //第一次运行 + if (globalThis.jitterTrace) { //抓取 + defaultSocketCollectItems["catch_trace_finish"] = true + defaultSocketCollectItems["traceName"] = this.timerNum + "" + globalThis.catchTraceState = CatchTraceStatus.catch_trace_running; + traceTime = 0 + globalThis.jitterTrace = false + } + } else if (globalThis.catchTraceState == CatchTraceStatus.catch_trace_running) { //有时间间隔的运行 + traceTime++ + if (traceTime > CatchTraceStatus.catch_trace_times) { + globalThis.catchTraceState = CatchTraceStatus.catch_trace_start + } + } + socketCollectItems = defaultSocketCollectItems + } + + singleEvent() { + if (this.playerState == TaskStatus.task_running) { + globalThis.task_status = TaskStatus.task_pause + this.playerState = TaskStatus.task_pause + } + else if (this.playerState == TaskStatus.task_pause) { + globalThis.task_status = TaskStatus.task_running + this.playerState = TaskStatus.task_running + } + } + + fpsEvent(){ + if (this.windFpsShowState) { + globalThis.HideFPSLineChartWindow() + this.windFpsShowState = false + } else { + globalThis.ShowFPSLineChartWindow() + this.windFpsShowState = true + } + } + + doubleEvent() { + // 双击启动悬浮TITLE + if (this.windShowState) { + globalThis.HideTitleWindow() + this.windShowState = false + } else { + globalThis.ShowTitleWindow() + this.windShowState = true + } + } + + longEvent() { + ProfilerTask.getInstance().taskStop() + this.destroyAllWindow() + this.clearAllInterVal() + } + + destroyAllWindow(){ + globalThis.DestroyFloatingWindow() + globalThis.DestroyTitleWindow() + globalThis.DestroyFPSLineChartWindow() + } + + clearAllInterVal(){ + clearInterval(globalThis.collectIntervalCollect) + clearInterval(globalThis.collectSocketCollect) + clearInterval(globalThis.collectOtherSocket) + } + + MoveWindow(offsetX: number, offsetY: number) { + globalThis.MoveFloatingWindow(offsetX, offsetY) + } + + SetWindowPosition(offsetX: number, offsetY: number) { + globalThis.SetFloatingWindowPosition(offsetX, offsetY) + } + + build() { + Stack({ alignContent: Alignment.Center }) { + if (this.playerState == TaskStatus.task_init) { + Circle() + .width('90vp') + .height('90vp') + .fill(Color.White) + .fillOpacity(0) + .opacity(0.8) + .border({ radius: '90vp', width: '0.5vp', color: $r("app.color.colorPrimary") }) + .linearGradient({ + angle: 135, + direction: GradientDirection.Left, + colors: [[$r("app.color.colorPrimary"), 1.0], [$r("app.color.colorPrimary"), 1.0]] + }) + Text('start') + .fontSize(20) + .textAlign(TextAlign.Center) + .fontColor($r("app.color.color_fff")) + .width('100%') + .height('100%') + .onClick(() => { + this.initAllCollect() + }) + .gesture( + GestureGroup(GestureMode.Exclusive, + TapGesture({ count: 2 }) + .onAction(() => { + this.doubleEvent() + }), + PanGesture({}) + .onActionStart(() => { + }) + .onActionUpdate((event: GestureEvent) => { + this.offsetX = event.offsetX + this.offsetY = event.offsetY + }) + .onActionEnd(() => { + this.MoveWindow(this.offsetX, this.offsetY) + this.SetWindowPosition(this.offsetX, this.offsetY) + }) + )) + } + + if (this.playerState == TaskStatus.task_running || this.playerState == TaskStatus.task_pause) { + if (this.playerState == TaskStatus.task_pause) { + Circle() + .width('90vp') + .height('90vp') + .fill(Color.White) + .fillOpacity(0) + .opacity(0.8) + .border({ radius: '90vp', width: '0.5vp', color: $r("app.color.color_666") }) + .linearGradient({ + angle: 135, + direction: GradientDirection.Left, + colors: [[$r("app.color.color_666"), 0.7], [$r("app.color.color_666"), 0.7]] + }) + }else{ + Circle() + .width('90vp') + .height('90vp') + .fill(Color.White) + .fillOpacity(0) + .opacity(0.5) + .border({ radius: '90vp', width: '0.5vp', color: $r("app.color.colorPrimary") }) + .linearGradient({ + angle: 135, + direction: GradientDirection.Left, + colors: [[$r("app.color.colorPrimary"), 0.7], [$r("app.color.colorPrimary"), 0.7]] + }) + } + Text(secToTime(this.timerNum).toString()) + .fontSize(20) + .textAlign(TextAlign.Center) + .fontColor($r("app.color.color_fff")) + .width('100%') + .height('100%') + .onClick(res => { + this.isDoubleFlag = false + for(let i = 0; i < this.mHits.length - 1; i++) { + this.mHits[i] = this.mHits[i + 1] + } + this.mHits[this.mHits.length - 1] = new Date().getTime() + if(this.mHits[0] >= new Date().getTime() - this.DURATION) { + this.doubleEvent() + this.isDoubleFlag = true + this.mHits = Array(this.COUNTS) + } else { + this.singleClickId = setTimeout(()=>{ + if(!this.isDoubleFlag) { + this.singleEvent() + } + }, 300) + } + }) + .gesture( + GestureGroup(GestureMode.Exclusive, + LongPressGesture({ fingers: 1, repeat: false, duration: 1000 }) + .onAction(() => { + this.longEvent() + }), + PanGesture({}) + .onActionStart(() => { + }) + .onActionUpdate((event: GestureEvent) => { + this.offsetX = event.offsetX + this.offsetY = event.offsetY + }) + .onActionEnd(() => { + this.MoveWindow(this.offsetX, this.offsetY) + this.SetWindowPosition(this.offsetX, this.offsetY) + }) + )) + } + }.width('100%').height('100%') + + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/pages/FpsLineChartPage.ets b/device/device_ui/entry/src/main/ets/pages/FpsLineChartPage.ets new file mode 100644 index 0000000..c94fe4a --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/FpsLineChartPage.ets @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@Entry +@Component +struct FpsLineChartPage { + private settings: RenderingContextSettings = new RenderingContextSettings(true) + private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings) + private XPoint: number = 5 //x起始坐标 + private YPoint: number = 73 //Y起始坐标 + private XScale: number = 5 //刻度 + private YScale: number = 15 //刻度 + private XLength: number = 110 //X轴长度 + private YLength: number = 70 //Y轴长度 + data: number[]= [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] //数据集合 + private MaxDataSize: number= this.XLength / this.XScale //数据集合的最大长度 + @State LineFps: number= 0 //FPS数值 + offsetX: number = -1 //悬浮框移动触摸点 X + offsetY: number = -1 //悬浮框移动触摸点 X + + MoveWindow(offsetX: number, offsetY: number) { + globalThis.MoveFPSLineChartWindow(offsetX, offsetY) + } + + SetWindowPosition(offsetX: number, offsetY: number) { + globalThis.SetFPSLineChartWindowPosition(offsetX, offsetY) + } + + aboutToAppear() { + globalThis.LineChartCollect = setInterval(() => { + if (this.data.length >= this.MaxDataSize) { + console.log("GestureEvent--------------shift:" + this.data); + this.data.shift() //移除第一个元素 + } + if (globalThis.timerFps == undefined) { + this.LineFps = 0 + this.data.push(0) //在末尾填充一个元素 + } else { + this.LineFps = globalThis.timerFps + if(this.LineFps==0){ + this.data.push(0) + }else{ + let lineCount: number = this.LineFps / 2 + this.data.push(lineCount)//在末尾填充一个元素 + } + } + + }, 1000) + } + + build() { + Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start, justifyContent: FlexAlign.Center }) { + + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text("FPS:" + this.LineFps).fontSize('6fp').fontColor($r("app.color.color_333")).margin({ left: 5, top: 3 }) //文本显示 + Image($r("app.media.icon_close_small")).width('15vp').height('15vp').onClick(() => { + globalThis.HideFPSLineChartWindow() + }) + + }.height('15vp').width('100%') + Canvas(this.context) + .width('100%') + .height('100%') + .onReady(() => { + //Y轴 + this.context.clearRect(this.XPoint + 0.5, this.YPoint - this.YLength, this.XLength, this.YLength) + this.context.beginPath() + this.context.moveTo(this.XPoint, this.YPoint - this.YLength) + this.context.lineTo(this.XPoint, this.YPoint) + this.context.stroke() + //X轴 + this.context.beginPath() + this.context.moveTo(this.XPoint, this.YPoint) + this.context.lineTo(this.XPoint + this.XLength, this.YPoint) + this.context.stroke() + //K线绘制 + if (this.data.length > 1) { + for (let i = 1; i < this.data.length; i++) { + this.context.beginPath() + console.log("GestureEvent--------------beginPath:" + this.data[i-1]); + this.context.moveTo(this.XPoint + (i - 1) * this.XScale, this.YPoint - this.data[i-1]) + this.context.lineTo(this.XPoint + i * this.XScale, this.YPoint - this.data[i]) + this.context.stroke() + } + } + }) + } + .width('100%') + .height('100%').gesture( + GestureGroup(GestureMode.Exclusive, + PanGesture({}) + .onActionStart((event: GestureEvent) => { + + }) + .onActionUpdate((event: GestureEvent) => { + this.offsetX = event.offsetX + this.offsetY = event.offsetY + + }) + .onActionEnd(() => { + this.MoveWindow(this.offsetX, this.offsetY) + this.SetWindowPosition(this.offsetX, this.offsetY) + + + }) + )) + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/pages/GPULineChartPage.ets b/device/device_ui/entry/src/main/ets/pages/GPULineChartPage.ets new file mode 100644 index 0000000..d28efd4 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/GPULineChartPage.ets @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { hideFloatWindow } from '../common/ui/floatwindow/utils/floatwindowutils' +import { FloatWindowComponent } from '../common/FloatWindowComponent' +import { TaskStatus } from '../common/profiler/base/ProfilerConstant'; + +@Entry +@Component +struct GPULineChartPage { + data: number[]= [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] //数据集合 + @State random: number= 0 //用于刷新的随机数 + @State gpuFrequency: number= 0 //数值 + + offsetX: number = -1 //悬浮框移动触摸点 X + offsetY: number = -1 //悬浮框移动触摸点 X + private floatName: string= "gpuFrequency" + task_state = 1 + + aboutToAppear() { + globalThis.LineChartCollect = setInterval(() => { + this.task_state = globalThis.task_status + if (this.task_state == TaskStatus.task_running) { + if (this.data.length >= 22) { + + this.data.shift() //移除第一个元素 + } + if (globalThis.tTndex.gpuFrequency == undefined) { + this.gpuFrequency = 0 + } else { + this.gpuFrequency = Number((globalThis.tTndex.gpuFrequency / 1e6).toFixed(2)).valueOf() + + if (this.gpuFrequency == 0) { + this.data.push(0) + } else { + let lineCount: number = this.gpuFrequency / 30 + this.data.push(Math.abs(lineCount)) //在末尾填充一个元素 + } + this.random = Math.random() + } + } else { + //暂停 + } + }, 1000) + } + + build() { + Stack({ alignContent: Alignment.Top }) { + FloatWindowComponent({ title: `gpuFrequency`, data: this.data }) + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text(this.floatName + ":" + this.gpuFrequency + "MHz") + .fontSize('6fp') + .fontColor($r("app.color.color_333")) + .margin({ left: 5, top: 3 }) //文本显示 + Text(this.random + "") + .fontSize('1fp') + .fontColor($r("app.color.color_333")).visibility(Visibility.None) + Image($r("app.media.icon_close_small")).width('15vp').height('15vp').onClick(() => { + hideFloatWindow(this.floatName) + + console.log("hideFloatWindow---------------------" + this.floatName) + }).align(Alignment.TopEnd) + }.height('15vp').width('100%') + + } + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/pages/LightAdjust.ets b/device/device_ui/entry/src/main/ets/pages/LightAdjust.ets new file mode 100644 index 0000000..2045b90 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/LightAdjust.ets @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import brightness from '@ohos.brightness'; +import { StartTestTitleComponent } from '../common/ui/StartTestTitleComponent'; + +/** + * 亮度调整 + */ +@Entry +@Component +struct LightAdjust { + @State outSetValue: number = 40 + + build() { + Column() { + + StartTestTitleComponent({ title: "亮度调整" }) + + Row() { + Slider({ + value: this.outSetValue, + min: 0, + max: 255, + step: 1, + style: SliderStyle.OutSet + }) + .blockColor(Color.Blue) + .trackColor(Color.Gray) + .selectedColor(Color.Blue) + .showSteps(true) + .onChange((value: number, mode: SliderChangeMode) => { + this.outSetValue = value + brightness.setValue(value) + console.info('value:' + value + 'mode:' + mode.toString()) + }) + }.padding({ top: 50 }) + .width('80%') + + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + + Text("+").fontSize('25fp').fontWeight(FontWeight.Bold).onClick(() => { + if (this.outSetValue == 255) { + return + } + ++this.outSetValue + brightness.setValue(this.outSetValue) + }) + + Text(this.outSetValue.toFixed(0)).fontWeight(FontWeight.Bold).fontSize('25fp') + + Text("-").fontSize('25fp').fontWeight(FontWeight.Bold).onClick(() => { + if (this.outSetValue == 0) { + return + } + --this.outSetValue + brightness.setValue(this.outSetValue) + }) + + }.width('50%').padding({ top: 30 }) + + Row() { + }.backgroundColor($r('app.color.color_fff')).width('50%').height('50%') + }.width('100%').height('100%') + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/pages/LoginPage.ets b/device/device_ui/entry/src/main/ets/pages/LoginPage.ets new file mode 100644 index 0000000..fdc3572 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/LoginPage.ets @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import router from '@system.router'; + +@Entry +@Component +struct Login { + aboutToAppear() { + console.log("cur router length:" + router.getLength()) + console.log("cur router state:" + router.getState()) + } + + build() { + Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Start }) { + Column() { + Image($r('app.media.logo')).width('100vp').height('100vp').margin({ top: '180vp' }) + Blank() + Text($r('app.string.login')) + .fontSize('22fp') + .fontColor($r('app.color.color_fff')) + .fontWeight(FontWeight.Bold) + .margin({ bottom: '150vp' }) + .border({ width: '1vp', color: $r('app.color.color_fff'), radius: '20vp' }) + .width('300vp') + .height('45vp') + .textAlign(TextAlign.Center) + .onClick(() => { + router.push({ uri: 'pages/MainPage', params: { + title: "123" + } }) + }) + } + .width('100%') + .height('100%') + } + .width('100%') + .height('100%') + .linearGradient({ + angle: 135, + direction: GradientDirection.Left, + colors: [[$r("app.color.colorPrimary"), 0.0], [$r("app.color.colorPrimary"), 1.0]] + }) + } +} + diff --git a/device/device_ui/entry/src/main/ets/pages/MainPage.ets b/device/device_ui/entry/src/main/ets/pages/MainPage.ets new file mode 100644 index 0000000..0d2005c --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/MainPage.ets @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Mine } from '../Common/ui/main/Mine'; +import { Home } from '../Common/ui/main/Home'; +import { Report } from '../Common/ui/main/Report'; +import { HomeBottomPage } from '../Common/ui/main/HomeBottomPage'; +/* + * 主页 程序主页面 + */ +@Entry +@Component +struct Main { + @State currentPage: number = 0 + + build() { + Column() { + Column() { + if (this.currentPage == 0) { + Home() + } else if (this.currentPage == 1) { + Report() + } else if (this.currentPage == 2) { + Mine() + } + }.width('100%').height('88%') + .flexGrow(1) + + HomeBottomPage({ currentPage: $currentPage }) + } + } +} + + + + + + + + + diff --git a/device/device_ui/entry/src/main/ets/pages/Question.ets b/device/device_ui/entry/src/main/ets/pages/Question.ets new file mode 100644 index 0000000..de9f585 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/Question.ets @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { StartTestTitleComponent } from '../common/ui/StartTestTitleComponent'; +import { questionList, QuestionItem } from '../common/entity/LocalConfigEntity'; + +@Entry +@Component +struct Question { + build() { + Column() { + StartTestTitleComponent({ title: "常见问题" }) + QuestionComponent() + } + } +} + + +@Component +struct QuestionComponent { + @State questionList: Array = questionList + + build() { + Column() { + List() { + ForEach(this.questionList, (questionItem) => { + ListItem() { + Flex({ + direction: FlexDirection.Column, + alignItems: ItemAlign.Start, + justifyContent: FlexAlign.SpaceBetween + }) { + Text(questionItem.question) + .fontSize(18) + .fontColor('#333333') + .margin({ top: 10, left: 15, right: 15, bottom: 5 }) + Text(questionItem.answer) + .fontSize(15).fontColor('#666666') + .margin({ left: 18, right: 18, bottom: 10 }) + }.width('100%') + // .borderWidth(1).borderRadius(5) + .margin({ bottom: 2, top: 2 }) + .shadow({ radius: 10, color: Color.Gray, offsetX: 10, offsetY: 5 }) + } + }, QuestionItem => QuestionItem.question) + }.edgeEffect(EdgeEffect.None) // 滑动到边缘无效果 + .chainAnimation(false) // 联动特效关闭 + }.width('95%').height('88%') + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/pages/RAMLineChartPage.ets b/device/device_ui/entry/src/main/ets/pages/RAMLineChartPage.ets new file mode 100644 index 0000000..6967014 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/RAMLineChartPage.ets @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { hideFloatWindow } from '../common/ui/floatwindow/utils/floatwindowutils' +import { FloatWindowComponent } from '../common/FloatWindowComponent' +import { TaskStatus } from '../common/profiler/base/ProfilerConstant'; + +@Entry +@Component +struct RAMLineChartPage { + data: number[]= [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] //数据集合 + @State random: number= 0 //用于刷新的随机数 + @State pss: number= 0 //数值 + + offsetX: number = -1 //悬浮框移动触摸点 X + offsetY: number = -1 //悬浮框移动触摸点 X + private floatName: string= "RAM" + task_state = 1 + + aboutToAppear() { + globalThis.LineChartCollect = setInterval(() => { + this.task_state = globalThis.task_status + if (this.task_state == TaskStatus.task_running) { + if (this.data.length >= 22) { + + this.data.shift() //移除第一个元素 + } + if (globalThis.tTndex.pss == undefined || isNaN(globalThis.tTndex.pss)) { + this.pss = 0 + } else { + this.pss = globalThis.tTndex.pss + + if (this.pss == 0) { + this.data.push(0) + } else { + let lineCount: number = this.pss / 2000 + console.log("shellBackTemp---------------------lineCount:" + lineCount) + this.data.push(Math.abs(lineCount)) //在末尾填充一个元素 + + } + this.random = Math.random() + } + } else { + //暂停 + } + }, 1000) + } + + build() { + Stack({ alignContent: Alignment.Top }) { + FloatWindowComponent({ title: `RAM`, data: this.data }) + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text(this.floatName + ":" + this.pss + "KB") + .fontSize('6fp') + .fontColor($r("app.color.color_333")) + .margin({ left: 5, top: 3 }) //文本显示 + Text(this.random + "") + .fontSize('1fp') + .fontColor($r("app.color.color_333")).visibility(Visibility.None) + Image($r("app.media.icon_close_small")).width('15vp').height('15vp').onClick(() => { + hideFloatWindow(this.floatName) + + }).align(Alignment.TopEnd) + }.height('15vp').width('100%') + + + } + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/pages/ReportDetail.ets b/device/device_ui/entry/src/main/ets/pages/ReportDetail.ets new file mode 100644 index 0000000..b109879 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/ReportDetail.ets @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { secToTime } from '../common/utils/TimeUtils'; +import { TIndexInfo } from '../common/entity/DatabaseEntity'; +import router from '@system.router'; +import { Summary } from '../common/ui/detail/Summary'; +import { StartTestTitleComponent } from '../common/ui/StartTestTitleComponent'; +import { ReportItem } from '../common/entity/LocalConfigEntity'; + + +@Entry +@Component +struct ReportDetail { + private controller: TabsController = new TabsController() + private gpData: Array = new Array() + private reportItem: ReportItem = null + + aboutToAppear() { + let data = router.getParams()["gpData"] + let report = router.getParams()["reportItem"] + if (data != null) { + this.gpData = data + } + if (report != null) { + this.reportItem = report + } + } + + build() { + + Column() { + StartTestTitleComponent({ title: "报告详情" }) + + Row() { + Flex({ justifyContent: FlexAlign.SpaceBetween }) { + Column() { + Image(globalThis.iconMap.get(this.reportItem.packageName)) + .width('60vp') + .height('60vp') + .margin({ top: '20vp', left: '20vp' }) + }.margin({ left: '4%' }) + + + Column() { + Text(`SP工具`).fontSize('15fp').margin({ top: '30vp' }) + Text(`应用版本:v1.0.2`).fontSize('15fp').margin({ top: '10vp' }) + }.margin({ right: '4%' }) + } + } + + Row() { + Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start, justifyContent: FlexAlign.Start }) { + Text(`开始时间:${this.reportItem.startTime}`) + .fontSize('13fp') + .fontColor($r("app.color.color_666")) + .margin({ top: '5vp' }) + Text(`测试时长:${secToTime(Number(this.reportItem.testDuration))}`) + .fontSize('13fp') + .fontColor($r("app.color.color_666")) + .margin({ top: '5vp' }) + Text(`文件路径:${this.reportItem.dbPath}/t_index_info.csv`) + .fontSize('13fp') + .fontColor($r("app.color.color_666")) + .margin({ top: '5vp' }) + }.width('100%').margin({ left: '10vp', top: '10vp' }).height('80') + }.margin({ left: '4%' }) + + Row() { + Flex() { + Tabs({ barPosition: BarPosition.Start, index: 0, controller: this.controller }) { + TabContent() { + Summary({ gpData: this.gpData }) + }.tabBar('概览') + } + .barWidth(360) + .scrollable(true) + .barHeight(60) + .width('100%') + .height('100%') + } + } + } + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/pages/SettingsPage.ets b/device/device_ui/entry/src/main/ets/pages/SettingsPage.ets new file mode 100644 index 0000000..f759678 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/SettingsPage.ets @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import router from '@system.router'; +import { StartTestTitleComponent } from '../common/ui/StartTestTitleComponent'; + +/** + * 设置页面 + */ +@Entry +@Component +struct SettingsPage { + build() { + Column() { + //开始测试title + StartTestTitleComponent({ title: "设置" }) + + Row({ space: '15vp' }) { + Image($r('app.media.icon_language')).width('25vp').height('25vp').margin({ left: '2%' }) + + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text('语言切换').fontSize('15fp').fontColor($r("app.color.color_333")) + + Image($r('app.media.icon_enter')).width('15vp').height('15vp').margin({ left: '15vp' }) + }.height('47vp').width('82%') + }.height('47vp').width('95%').backgroundColor($r("app.color.color_fff")).margin({ top: '10vp' }) + + Divider().layoutWeight(1).visibility(Visibility.Hidden) + + Text('退出登录') + .fontSize('15fp') + .fontColor($r('app.color.color_fff')) + .margin({ bottom: '45vp' }) + .border({ radius: '20vp' }) + .backgroundColor($r("app.color.colorPrimary")) + .width('80%') + .height('40vp') + .textAlign(TextAlign.Center) + .onClick(res => { + router.replace({ uri: 'pages/LoginPage' }) + router.clear() + }) + } + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/pages/ShellBackTempLineChartPage.ets b/device/device_ui/entry/src/main/ets/pages/ShellBackTempLineChartPage.ets new file mode 100644 index 0000000..a3008a0 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/ShellBackTempLineChartPage.ets @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { hideFloatWindow } from '../common/ui/floatwindow/utils/floatwindowutils' +import { FloatWindowComponent } from '../common/FloatWindowComponent' +import { TaskStatus } from '../common/profiler/base/ProfilerConstant'; + +@Entry +@Component +struct ShellBackTempLineChartPage { + data: number[]= [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] //数据集合 + + @State shellBackTemp: number= 0 //数值 + @State random: number= 0 //用于刷新的随机数 + offsetX: number = -1 //悬浮框移动触摸点 X + offsetY: number = -1 //悬浮框移动触摸点 X + private floatName: string= "shellBackTemp" + task_state = 1 + + aboutToAppear() { + globalThis.LineChartCollect = setInterval(() => { + this.task_state = globalThis.task_status + if (this.task_state == TaskStatus.task_running) { + if (this.data.length >= 22) { + + this.data.shift() //移除第一个元素 + } + if (globalThis.tTndex.shellBackTemp == undefined) { + this.shellBackTemp = 0 + } else { + this.shellBackTemp = globalThis.tTndex.shellBackTemp / 1e3 + console.log("shellBackTemp---------------------" + this.shellBackTemp) + if (this.shellBackTemp == 0) { + this.data.push(0) + } else { + console.log("shellBackTemp---------------------" + this.shellBackTemp) + let lineCount: number = this.shellBackTemp + console.log("shellBackTemp---------------------lineCount:" + lineCount) + this.data.push(Math.abs(lineCount)) //在末尾填充一个元素 + + } + this.random = Math.random() + } + } else { + //暂停 + } + }, 1000) + } + + build() { + Stack({ alignContent: Alignment.Top }) { + FloatWindowComponent({ title: `shellBackTemp`, data: this.data }) + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text(this.floatName + ":" + this.shellBackTemp + "℃") + .fontSize('6fp') + .fontColor($r("app.color.color_333")) + .margin({ left: 5, top: 3 }) //文本显示 + Text(this.random + "") + .fontSize('1fp') + .fontColor($r("app.color.color_333")).visibility(Visibility.None) + + Image($r("app.media.icon_close_small")).width('15vp').height('15vp').onClick(() => { + hideFloatWindow(this.floatName) + + }).align(Alignment.TopEnd) + }.height('15vp').width('100%') + + + } + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/pages/StartTestPage.ets b/device/device_ui/entry/src/main/ets/pages/StartTestPage.ets new file mode 100644 index 0000000..3f8fe3a --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/StartTestPage.ets @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { commonStartAbility } from '../common/utils/AbilityUtils'; +import router from '@system.router'; +import { switchList, SwitchItem, CollectItem } from '../common/entity/LocalConfigEntity'; +import { StartTestTitleComponent } from '../common/ui/StartTestTitleComponent'; +import { ProfilerTask } from '../common/profiler/ProfilerTask' +import { CollectorType } from '../common/profiler/base/ProfilerConstant' +import prompt from '@system.prompt'; + + +/* + * 测试配置页 + */ +@Entry +@Component +struct StartTestPage { + @State selectApp: string = "请选择一个应用" + @State selectAppIcon: string = "" + @State private collectConfigs: Array = new Array() + @State private switchList: Array = new Array() + @State private testName: string = "" // 测试名称 + + dialogController: CustomDialogController = new CustomDialogController({ + builder: CustomDialogCollect({ cancel: () => { + }, confirm: () => { + }, collectConfigs: $collectConfigs }), + cancel: () => { + }, + autoCancel: true + }) + textController: CustomDialogController = new CustomDialogController({ + builder: TextInputDialog({ cancel: () => { + }, confirm: () => { + }, testName: $testName }), + cancel: () => { + }, + autoCancel: true + }) + + aboutToAppear() { + let supportMap = ProfilerTask + .getInstance() + .getSupports([ + CollectorType.TYPE_CPU.toString(), + CollectorType.TYPE_GPU.toString(), + CollectorType.TYPE_DDR.toString(), + CollectorType.TYPE_FPS.toString(), + CollectorType.TYPE_POWER.toString(), + CollectorType.TYPE_TEMPERATURE.toString(), + CollectorType.TYPE_RAM.toString(), + CollectorType.TYPE_NET.toString(), + ]) + + var iterator = supportMap.keys() + for (var i = 0; i < supportMap.size; i++) { + let key = iterator.next().value + let val = supportMap.get(key) + console.log("supportMap:item" + key + " " + val); + this.collectConfigs.push( + new CollectItem(key, val, val) + ) + } + this.switchList = switchList + } + + build() { + + Column() { + //开始测试title + StartTestTitleComponent({ title: "开始测试" }) + + Scroll() { + Column() { + //请选择一个应用 + Row({ space: '15vp' }) { + if (this.selectAppIcon == "") { + Image($r('app.media.logo')).width('70vp').height('70vp').margin({ left: '2%' }) + } else { + Image(this.selectAppIcon).width('70vp').height('70vp').margin({ left: '2%' }) + } + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text(this.selectApp).fontSize('15fp').fontColor($r("app.color.color_333")) + + Image($r('app.media.icon_enter')).width('15vp').height('15vp').margin({ right: '35vp' }) + }.height('70vp').width('90%') + } + .height('100vp') + .width('95%') + .borderRadius('5vp') + .backgroundColor($r("app.color.color_fff")) + .margin({ top: '20vp' }) + .onClick(() => { + router.push({ uri: 'pages/AppSelectPage', params: { + StartPage: "StartTest" + } }) + }) + + //测试指标 + Row({ space: '15vp' }) { + Image($r('app.media.icon_test_index')).width('25vp').height('25vp').margin({ left: '2%' }) + + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text('测试指标').fontSize('15fp').fontColor($r("app.color.color_333")) + + Image($r('app.media.icon_enter')).width('15vp').height('15vp').margin({ right: '15vp' }) + }.height('60vp').width('90%').onClick(() => { + if (this.selectApp == "请选择一个应用" || this.selectApp == "SmartPerf") { + prompt.showToast({ message: "please choose app!", duration: 1000 }) + return + } + this.dialogController.open() + }) + } + .height('60vp') + .width('95%') + .borderRadius('5vp') + .backgroundColor($r("app.color.color_fff")) + .margin({ top: '10vp' }) + + //测试名称 + Row({ space: '15vp' }) { + Image($r('app.media.icon_test_name')).width('25vp').height('25vp').margin({ left: '2%' }) + + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text('测试名称').fontSize('15fp').fontColor($r("app.color.color_333")) + + Row() { + Text(this.testName).fontSize('15fp').fontColor($r("app.color.color_333")) + + Image($r('app.media.icon_enter')).width('15vp').height('15vp').margin({ right: '15vp' }) + } + }.height('60vp').width('90%').onClick(() => { + this.textController.open() + }) + + }.height('60vp').width('95%').borderRadius('5vp').backgroundColor($r("app.color.color_fff")) + + SwitchComponent({ switchList: $switchList }) + Blank() + Button('开始测试') + .fontSize('15fp') + .fontColor($r('app.color.color_fff')) + .border({ radius: '20vp' }) + .width('80%') + .height('60vp') + .backgroundColor($r("app.color.colorPrimary")) + .onClick(() => { + + let taskConfig = this.resolveTaskConfig() + console.log("console.log:" + JSON.stringify(taskConfig)); + + if (this.selectApp == "请选择一个应用" || this.selectApp == "SmartPerf") { + prompt.showToast({ message: "please choose app!", duration: 1000 }) + return + } + + if (this.search("is_camera", this.switchList).isOpen && this.search("is_video", this.switchList).isOpen) { + prompt.showToast({ message: "video and camera can't be all selected", duration: 1000 }) + return + } + + //启动app + commonStartAbility(router.getParams()["selectPackageName"], router.getParams()["selectAbilityName"]) + //启动悬浮窗 + globalThis.CreateFloatingWindow() + router.back({ uri: "pages/MainPage" }) + + }) + + Divider().height('15%').width("80%").visibility(Visibility.Hidden) + }.height("100%") + }.width('100%').scrollable(ScrollDirection.Vertical).scrollBar(BarState.Auto) + + }.height('100%').width('100%').backgroundColor('#EEEEEE') + } + + onPageShow() { + console.info("startTestPage" + "onPageShow调用了"); + if (router.getParams()["appName"] == null) { + this.selectApp = "请选择一个应用" + this.testName = "" + this.selectAppIcon = "" + } else { + this.selectApp = router.getParams()["appName"] + globalThis.appName = router.getParams()["appName"] + globalThis.appVersion = router.getParams()["appVersion"] + globalThis.packageName = router.getParams()["selectPackageName"] + this.selectAppIcon = router.getParams()["appIconId"] + let date = new Date() + let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1); + let D = date.getDate() + '-'; + let h + if (date.getHours() < 10) { + h = "0" + date.getHours(); + } else { + h = date.getHours(); + } + let m = date.getMinutes(); + globalThis.testTaskName = "游戏测试" + M + D + h + m + this.testName = "游戏测试" + M + D + h + m + } + } + + resolveTaskConfig() { + let collects = this.collectConfigs + let collectStr = "" + for (var i = 0; i < collects.length; i++) { + const collect = collects[i]; + if (i != collects.length - 1) { + collectStr += collect.name + "::" + collect.isSelect + "," + } else { + collectStr += collect.name + "::" + collect.isSelect + } + } + + let switchs = this.switchList + let switchStr = "" + for (var j = 0; j < switchs.length; j++) { + const st = switchs[j]; + if (j != switchs.length - 1) { + switchStr += st.id + "::" + st.isOpen + "," + } else { + switchStr += st.id + "::" + st.isOpen + } + } + + let taskConfig = { + "selectAppName": globalThis.packageName, + "allConfigs": collectStr + "," + switchStr + } + + let configItems: { [key: string]: boolean } = {} + let allConfigsArr: string[] = [] + let curSelectPkg = "" + if (taskConfig !== undefined) { + allConfigsArr = taskConfig.allConfigs.split(",") + curSelectPkg = taskConfig.selectAppName + } + for (var index = 0; index < allConfigsArr.length; index++) { + const config = allConfigsArr[index]; + let params = config.split("::") + if (params[1] == "true" || params[1] == "1") { + configItems[params[0]] = true + }else if (params[1] == "false" || params[1] == "0") { + configItems[params[0]] = false + }else { + configItems[params[0]] = false + } + } + globalThis.collectConfigs = configItems + globalThis.collectPkg = curSelectPkg + + return taskConfig + } + + search(id: string, myArray: Array): SwitchItem{ + for (var i = 0; i < myArray.length; i++) { + if (myArray[i].id === id) { + return myArray[i]; + } + } + } +} + + +@Component +struct SwitchComponent { + @Link private switchList: Array + + build() { + Column() { + List() { + ForEach(this.switchList, (switchItem) => { + ListItem() { + Row({ space: '15vp' }) { + Image(switchItem.switchSrc).width('25vp').height('25vp').margin({ left: '2%' }) + + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text(switchItem.switchName).fontSize('15fp').fontColor($r("app.color.color_333")) + + Toggle({ type: ToggleType.Switch, isOn: switchItem.isOpen }) + .width('60vp') + .height('25vp') + .enabled(switchItem.enable) + .onChange((isOn) => { + console.log("isOn" + isOn) + switchItem.isOpen = isOn + }) + .margin({ right: '10vp' }) + }.height('60vp').width('90%') + } + .height('60vp') + .width('100%') + .borderRadius('5vp') + .backgroundColor($r("app.color.color_fff")) + .margin({ top: '10vp' }) + } + }, switchItem => switchItem.switchName) + } + }.width('95%') + } +} + +@CustomDialog +struct CustomDialogCollect { + @Link private collectConfigs: Array + controller: CustomDialogController + cancel: () => void + confirm: () => void + + build() { + Column() { + List() { + ForEach(this.collectConfigs, (Item) => { + ListItem() { + if(Item.isSupport) { + Row({ space: '15vp' }) { + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text(Item.name).fontSize('18fp').fontColor($r("app.color.color_333")).margin({ left: 20 }) + Toggle({ type: ToggleType.Switch, isOn: Item.isSupport }) + .width('60vp') + .height('25vp') + .enabled(true) + .onChange((isOn) => { + Item.isSelect = isOn + }) + .margin({ right: '5vp' }) + }.height('60vp').width('90%') + } + .height('60vp') + .width('100%') + .borderRadius('5vp') + .backgroundColor($r("app.color.color_fff")) + .margin({ top: '10vp' }) + } else { + Row({ space: '15vp' }) { + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text(Item.name).fontSize('18fp').fontColor($r("app.color.color_333")).margin({ left: 20 }) + Toggle({ type: ToggleType.Switch, isOn: Item.isSupport }) + .width('60vp') + .height('25vp') + .enabled(false) + .margin({ right: '5vp' }) + }.height('60vp').width('90%') + } + .onClick(() => { + prompt.showToast({ duration: 1000, message: "not support" }) + }) + .height('60vp') + .width('100%') + .borderRadius('5vp') + .backgroundColor($r("app.color.color_eee")) + .margin({ top: '10vp' }) + } + } + }, Item => Item.name) + } + + Flex({ justifyContent: FlexAlign.SpaceAround }) { + Button('cancel') + .onClick(() => { + this.controller.close() + this.cancel() + }).backgroundColor(0xffffff).fontColor(Color.Black) + Button('confirm') + .onClick(() => { + this.controller.close() + this.confirm() + }).backgroundColor(0xffffff).fontColor(Color.Red) + }.margin({ bottom: 10 }) + } + } +} + +@CustomDialog +export struct TextInputDialog { + @Link private testName: String + controller: CustomDialogController + cancel: () => void + confirm: () => void + + aboutToAppear() { + console.log("TextInputDialog called") + } + + build() { + Column() { + TextArea({ placeholder: '请输入测试名称', text: this.testName.toString() }) + .placeholderFont({ size: 15 }) + .fontSize('15fp') + .textAlign(TextAlign.Center) + .fontSize(30) + .onChange((value: string) => { + this.testName = value + }) + .padding(20) + Flex({ justifyContent: FlexAlign.SpaceAround }) { + Button('cancel') + .onClick(() => { + this.controller.close() + this.cancel() + }).backgroundColor(0xffffff).fontColor(Color.Black) + Button('confirm') + .onClick(() => { + this.controller.close() + this.confirm() + }).backgroundColor(0xffffff).fontColor(Color.Red) + }.margin({ bottom: 10 }) + } + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/pages/TitleWindowPage.ets b/device/device_ui/entry/src/main/ets/pages/TitleWindowPage.ets new file mode 100644 index 0000000..3a2b9e6 --- /dev/null +++ b/device/device_ui/entry/src/main/ets/pages/TitleWindowPage.ets @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TIndexInfo } from '../common/entity/DatabaseEntity' +import CheckEmptyUtils from '../common/utils/CheckEmptyUtils' +import { showFloatWindow, hideFloatWindow } from '../common/ui/floatwindow/utils/floatwindowutils' +import {FPS, GPUFREQUENCY, CPU0FREQUENCY, CPU1FREQUENCY, CPU2FREQUENCY, RAM } from '../common/ui/floatwindow/FloatWindowConstant' +import SPLogger from '../common/utils/SPLogger' + +//展示配置项 +export enum ShowItemConfig { + ITEM_CPU = 0X01, + ITEM_GPU = 0X01, + ITEM_DDR = 0X01, + ITEM_FPS = 0X00, + ITEM_POWER = 0X01, + ITEM_SOC_TEMP = 0X01, + ITEM_SHELL_FRAME = 0X01, + ITEM_SHELL_BACK = 0X01, + ITEM_SHELL_FRONT = 0X01, + ITEM_RAM = 0X01, + ITEM_UNKNOWN = 0X00, +} + + +@Component +export struct ItemContent { + private icon + private tittle: string + @State value: string = "-1" + private onClickCallBack: () => void + + build() { + Row() { + Image(this.icon).width('20vp').height('20vp').margin({ right: '2%' }) + Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { + Text(this.tittle).fontSize(20).fontColor(Color.White) + Text(this.value).fontSize(20).fontColor(Color.White) + }.width('88%').height('25vp') + } + .height('26vp') + .width('88%') + .onClick(() => { + this.onClickCallBack() + }) + } +} + +@Entry +@Component +struct TitleWindowPage { + private data: boolean[]= [false, false, false, false, false, false, false, false] + //计时器 + @State timerNum: number= 0 + @State tIndexInfo: TIndexInfo = new TIndexInfo() + offsetX: number = -1 + offsetY: number = -1 + + aboutToAppear() { + globalThis.floatIntervalCollect = setInterval(() => { + if (CheckEmptyUtils.isEmptyArr(globalThis.tTndex)) { + this.tIndexInfo.setDefaultValue() + SPLogger.DEBUG("TitleWindowPage","this.tIndexInfo isEmpty" + JSON.stringify(this.tIndexInfo)) + } else { + this.tIndexInfo = globalThis.tTndex + SPLogger.DEBUG("TitleWindowPage","this.tIndexInfo isEmpty" + JSON.stringify(this.tIndexInfo)) + } + }, 999) + } + + MoveWindow(offsetX: number, offsetY: number) { + globalThis.MoveTitleWindow(offsetX, offsetY) + } + + SetWindowPosition(offsetX: number, offsetY: number) { + globalThis.SetTitleWindowPosition(offsetX, offsetY) + } + + floatWindowEvent(floatName: string, flag: number) { + if (this.data[flag]) { + hideFloatWindow(floatName) + this.data[flag] = false + } else { + showFloatWindow(floatName) + this.data[flag] = true + } + } + + build() { + Stack({ alignContent: Alignment.Center }) { + Rect({ width: '100%', height: '100%' }).radius(10).opacity(0.5) + Column({ space: 2 }) { + + Row() { + Image($r("app.media.logo")).width('20vp').height('20vp').margin({ left: '2%' }) + Text( "SmartPerf") + .fontSize('20fp') + .fontColor($r("app.color.color_fff")).margin({ left: '2%' }) + Image($r("app.media.icon_close_small")).height('18vp').width('18vp').margin({ left: '55%' }).onClick(() => { + //关闭实时悬浮框 + globalThis.HideTitleWindow() + }) + } .height('26vp') + .width('95%') + + ItemContent({ + icon: $r("app.media.icon_average_frame_b"), + value: (this.tIndexInfo.fps.toString()) + "FPS", + tittle: "帧率", + onClickCallBack: () => { + this.floatWindowEvent("FPS",FPS) + } + }) + + ItemContent({ + icon: $r("app.media.icon_counter"), + value: (parseInt(this.tIndexInfo.cpu0Frequency.toString()) / 1e3).toString() + "MHz", + tittle: "CPU0频率", + onClickCallBack: () => { + this.floatWindowEvent("cpu0Frequency", CPU0FREQUENCY) + } + }) + ItemContent({ + icon: $r("app.media.icon_counter"), + value: (parseInt(this.tIndexInfo.cpu1Frequency.toString()) / 1e3).toString() + "MHz", + tittle: "CPU1频率", + onClickCallBack: () => { + this.floatWindowEvent("cpu1Frequency", CPU1FREQUENCY) + } + }) + ItemContent({ + icon: $r("app.media.icon_counter"), + value: (parseInt(this.tIndexInfo.cpu2Frequency.toString()) / 1e3).toString() + "MHz", + tittle: "CPU2频率", + onClickCallBack: () => { + this.floatWindowEvent("cpu2Frequency", CPU2FREQUENCY) + } + }) + ItemContent({ + icon: $r("app.media.icon_frame_score"), + value: (parseInt(this.tIndexInfo.gpuFrequency.toString()) / 1e6).toString() + "MHz", + tittle: "GPU频点", + onClickCallBack: () => { + this.floatWindowEvent("gpuFrequency", GPUFREQUENCY) + } + }) + ItemContent({ + icon: $r("app.media.icon_jank_each_hour"), + value: this.tIndexInfo.pss + "KB", + tittle: "RAM", + onClickCallBack: () => { + this.floatWindowEvent("RAM", RAM) + } + }) + ItemContent({ + icon: $r("app.media.icon_max_temperature"), + value: (parseInt(this.tIndexInfo.socThermalTemp.toString()) / 1e3).toString() + "℃", + tittle: "SOC温度" + }) + ItemContent({ + icon: $r("app.media.icon_counter"), + value: this.tIndexInfo.networkUpSpeed.toString() + "kbps" + " " +this.tIndexInfo.networkDownSpeed.toString() + "kbps", + tittle: "带宽" + }) + // ItemContent({ + // icon: $r("app.media.icon_max_temperature"), + // value: (ParseIntValue(this.tIndexInfo.shellBackTemp.toString()) / 1e3).toString() + "℃", + // tittle: "后壳温" + // }) + // ItemContent({ + // icon: $r("app.media.icon_max_temperature"), + // value: (ParseIntValue(this.tIndexInfo.shellFrontTemp.toString()) / 1e3).toString() + "℃", + // tittle: "前壳温" + // }) + // ItemContent({ + // icon: $r("app.media.icon_max_temperature"), + // value: (ParseIntValue(this.tIndexInfo.shellFrameTemp.toString()) / 1e3).toString() + "℃", + // tittle: "壳温" + // }) + // ItemContent({ + // icon: $r("app.media.icon_normalized_current"), + // value: this.tIndexInfo.currentNow + "mA", + // tittle: "电流" + // }) + // ItemContent({ + // icon: $r("app.media.icon_counter"), + // value: (ParseIntValue(this.tIndexInfo.ddrFrequency.toString()) / 1e6).toString() + "MHz", + // tittle: "DDR频点" + // }) + }.width('100%') + .gesture( + GestureGroup(GestureMode.Exclusive, + PanGesture({}) + .onActionStart((event: GestureEvent) => { + }) + .onActionUpdate((event: GestureEvent) => { + this.offsetX = event.offsetX + this.offsetY = event.offsetY + }) + .onActionEnd(() => { + this.MoveWindow(this.offsetX, this.offsetY) + this.SetWindowPosition(this.offsetX, this.offsetY) + }) + )) + } + } +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/ets/workers/worker.js b/device/device_ui/entry/src/main/ets/workers/worker.js new file mode 100644 index 0000000..a2146ba --- /dev/null +++ b/device/device_ui/entry/src/main/ets/workers/worker.js @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import worker from '@ohos.worker'; // 导入worker模块 +import net_socket from '@ohos.net.socket'; + + +let parentPort = worker.parentPort; // 获取parentPort属性 + +export let IPv4 = 1 + +export let IPv4BindAddr = { + address: "127.0.0.1", + family: IPv4, + port: 8283 +} + +export let UdpSendAddress = { + address: "127.0.0.1", + family: IPv4, + port: 8283 +} + +export let flagPackageNum = 0 + +export let udp = net_socket.constructUDPSocketInstance() +udp.bind(IPv4BindAddr, err => { + if (err) { + console.log('Worker socket bind fail'); + return; + } + console.log('Worker socket bind success'); + udp.getState((err, data) => { + if (err) { + console.log('Worker socket getState fail'); + return; + } + console.log('Worker socket getState success:' + JSON.stringify(data)); + }) +}) + + +parentPort.onexit = function (e) { + console.log("Worker onexit") +} + +parentPort.onerror = function (e) { + console.log("Worker onerror") +} + +parentPort.onmessageerror = function (e) { + console.log("Worker onmessageerror") +} + +udp.on('listening', () => { + console.log("Worker socket on listening success"); +}); +udp.on('close', () => { + console.log("Worker socket on close success"); +}); + +udp.on('error', err => { + console.log("Worker socket on error, err:" + JSON.stringify(err)) +}); +parentPort.onmessage = function (e) { + + let socketCollectItems = e.data + console.log("sub worker recv:" + JSON.stringify(e.data)); + + let messageSetPkg = "set_pkgName::" + socketCollectItems.pkg + udp.getState((err, data) => { + if (err) { + parentPort.postMessage("FPS$-1") + console.log("Worker socket getState error", err); + } + console.log('Worker socket getState success:' + JSON.stringify(data)); + + if (flagPackageNum < 2) { + udp.send({ + address: UdpSendAddress, + data: messageSetPkg + }) + } + flagPackageNum++ + + if (socketCollectItems.fps) { + let messageFps = "get_fps_and_jitters::0::0" + if (socketCollectItems.is_video) { + messageFps = "get_fps_and_jitters::1::0" + } else if (socketCollectItems.is_camera) { + messageFps = "get_fps_and_jitters::0::1" + } + udp.send({ + address: UdpSendAddress, + data: messageFps + }) + console.log("sub worker messageFps :" + messageFps); + + } + if (socketCollectItems.ram) { + let messageRam = "get_ram_info::" + socketCollectItems.pkg + udp.send({ + address: UdpSendAddress, + data: messageRam + }) + console.log("sub worker messageRam :" + messageRam); + } + + if (socketCollectItems.screen_capture) { + udp.send({ + address: UdpSendAddress, + data: "get_capture" + }) + console.log("sub worker screen_capture :" + screen_capture); + } + + if (socketCollectItems.power) { + let messagePower = "get_power" + udp.send({ + address: UdpSendAddress, + data: messagePower + }) + console.log("sub worker messagePower :" + messagePower); + } + if (socketCollectItems.catch_trace_start) { + let messageTrace = "catch_trace_start" + udp.send({ + address: UdpSendAddress, + data: messageTrace + }) + } + if (socketCollectItems.catch_trace_finish) { + let messageTrace = "catch_trace_finish::" + socketCollectItems.traceName + udp.send({ + address: UdpSendAddress, + data: messageTrace + }) + } + + + }) +} + +udp.on("message", function (data) { + let buffer = data.message + let dataView = new DataView(buffer) + let str = "" + for (let i = 0;i < dataView.byteLength; ++i) { + str += String.fromCharCode(dataView.getUint8(i)) + } + try { + if (includes(str, "Pss")) { + parentPort.postMessage("RAM$" + str) + } else if (includes(str, "fps")) { + if (str.indexOf("::") != -1) { + let arrStr = str.split("::") + console.log("SockOnMessage solveFps1:" + JSON.stringify(arrStr)) + if (arrStr[1].indexOf(";") != -1) { + let fpsDataArr = arrStr[1].split(";") + console.log("SockOnMessage solveFps2:" + JSON.stringify(fpsDataArr)) + if (fpsDataArr[1].indexOf("|") != -1) { + let fps = fpsDataArr[1].split("|") + console.log("SockOnMessage solveFps fps:" + JSON.stringify(fps)) + if (fpsDataArr.length > 1 && fpsDataArr[2].indexOf("|")) { + let fpsJitter = fpsDataArr[2].split("|") + console.log("SockOnMessage solveFps fpsJitter:" + JSON.stringify(fpsJitter)) + parentPort.postMessage("FPS$" + fps[1].toString() + "$" + fpsJitter[1].toString()) + } + } + } + } + } + } catch (e) { + console.log("SockOnMessage recv callback err:" + e) + } + +}) + + +function includes(all, sub) { + + all = all.toLowerCase(); + sub = sub.toLowerCase(); + + var firstChar = sub.substring(0, 1); + var subLength = sub.length; + + for (let i = 0; i < all.length - subLength + 1; i++) { + + if (all.charAt(i) == firstChar) + { + if (all.substring(i, i + subLength) == sub) + { + return true; + } + } + } + return false; +} + diff --git a/device/device_ui/entry/src/main/module.json5 b/device/device_ui/entry/src/main/module.json5 new file mode 100644 index 0000000..fe95e0f --- /dev/null +++ b/device/device_ui/entry/src/main/module.json5 @@ -0,0 +1,69 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "./ets/Application/AbilityStage.ts", + "mainElement": "MainAbility", + "deviceTypes": [ + "phone", + "tablet", + "wearable", + "car", + "tv", + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "uiSyntax": "ets", + "requestPermissions": [ + { + "name": "ohos.permission.INTERNET" + }, + { + "name": "ohos.permission.GET_BUNDLE_INFO_PRIVILEGED" + }, + { + "name": "ohos.permission.KEEP_BACKGROUND_RUNNING" + }, + { + "name": "ohos.permission.GET_BUNDLE_INFO" + }, + { + "name": "ohos.permission.READ_USER_STORAGE" + }, + { + "name": "ohos.permission.WRITE_USER_STORAGE" + }, + { + "name": "ohos.permission.SYSTEM_FLOAT_WINDOW" + }, + { + "name": "ohos.permission.GET_RUNNING_INFO" + }, + { + "name": "ohos.permission.GET_NETWORK_INFO" + } + ], + "abilities": [ + { + "name": "MainAbility", + "srcEntrance": "./ets/MainAbility/MainAbility.ts", + "description": "$string:MainAbility_desc", + "icon": "$media:logo", + "label": "$string:MainAbility_label", + "visible": true, + "skills": [ + { + "entities": [ + "entity.system.home", + ], + "actions": [ + "action.system.home", + ], + }, + ], + }, + ], + + }, +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/resources/base/element/color.json b/device/device_ui/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000..da117bc --- /dev/null +++ b/device/device_ui/entry/src/main/resources/base/element/color.json @@ -0,0 +1,37 @@ +{ + "color": [ + { + "name": "colorPrimary", + "value": "#B3193F" + }, + { + "name": "color_fff", + "value": "#ffffff" + }, + { + "name": "color_80fff", + "value": "#80ffffff" + }, + { + "name": "color_eee", + "value": "#eeeeee" + }, + { + "name": "color_80B3193F", + "value": "#80B3193F" + }, + { + "name": "color_333", + "value": "#333333" + }, + { + "name": "color_666", + "value": "#666666" + }, + { + "name": "color_999", + "value": "#999999" + } + + ] +} \ No newline at end of file diff --git a/device/device_ui/entry/src/main/resources/base/element/string.json b/device/device_ui/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000..20ce5c7 --- /dev/null +++ b/device/device_ui/entry/src/main/resources/base/element/string.json @@ -0,0 +1,56 @@ +{ + "string": [ + { + "name": "entry_desc", + "value": "SmartPerf" + }, + { + "name": "MainAbility_desc", + "value": "SmartPerf" + }, + { + "name": "MainAbility_label", + "value": "SmartPerf" + }, + { + "name": "entry_MainAbility", + "value": "SmartPerf" + }, + { + "name": "mainability_description", + "value": "SmartPerf" + }, + { + "name": "login", + "value": "登录" + }, + { + "name": "description_collectserviceability", + "value": "hap sample empty service" + }, + { + "name": "description_serviceability", + "value": "hap sample empty service" + }, + { + "name": "description_floatwindowability", + "value": "ETS_Empty Ability" + }, + { + "name": "entry_FloatWindowAbility", + "value": "entry_FloatWindowAbility" + }, + { + "name": "description_floatwindowpage", + "value": "ETS_Empty Ability" + }, + { + "name": "entry_FloatWindowPage", + "value": "entry_FloatWindowPage" + }, + { + "name": "description_profileserviceability", + "value": "hap sample empty service" + } + ] +} diff --git a/device/device_ui/entry/src/main/resources/base/media/icon.png b/device/device_ui/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 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 literal 0 HcmV?d00001 diff --git a/device/device_ui/entry/src/main/resources/base/media/icon_brightness_plus.png b/device/device_ui/entry/src/main/resources/base/media/icon_brightness_plus.png new file mode 100644 index 0000000000000000000000000000000000000000..fa5eaf118bfd4ad3e9515fbb83b2cb88794f1c62 GIT binary patch literal 2890 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 literal 0 HcmV?d00001 diff --git a/device/device_ui/entry/src/main/resources/base/media/icon_camera.png b/device/device_ui/entry/src/main/resources/base/media/icon_camera.png new file mode 100644 index 0000000000000000000000000000000000000000..82629e8a8cab601d5265fe15ea801246fda4a688 GIT binary patch literal 2126 zcmZWqXFS^r7ygT=eTAslLQ6xrC`xJ7O09$>R%?~2d5w?;O^sZnVvEM8wzphOQKQ3b zuA)ZARcdc)l%jUjYH;=I`#$IQoacAWbH1GO>Vgt=5;y$z$tsELFFCeLPD4lf(H|sJ%u+K)?V&E{BJ}7J<(3pRs{v$0`>^F8D>#->{;eX*~)EU@NzWm(Y`$sAi(O}6tLAlOQDp|b1cN227y(j1M zaAh^dyDJ>5H!-USbjK&&SBhxy_xOq#+8dYh(XPzv@$>H7b%*06gs#6Ji(QQ`nm$mY z49rfts5L1vLv!#oc5@TWyit#gh)`Z>u!Sw<4~4Ufycp3$jKQKd>ehOB39Rt`|y z5fKML)RtR=o_wI4x67r2c?LM?QJ%y)2v4X%+56T2EE+1tp z2{LJAdT_Oyf0q@GjmAMRF>Q6$0^hU63PB791;>~6aL4(4*J8{-QsUS~@onxPAYzw}bQYXedtS6MU z!(PHf`}a6%PEkSGGlu(w`>eEEOKh5-OVtu$ep9M}} zN0pLh3X}B?EIM%N@4{0Ik3_fsURw{5+i0ch*ZC}cw7&r#qVK~~lmj`r8==p~C~m)x z#mSWFfj}VPN}>Xa>`3Y9s(tD@G4c!|3@!XTa3&pG$Tk&0#a|4e?I=^pfb|m?+vuZ2 zS2khdFLZ?0Yhwi8K|}fASiwa{!?&|gF>&EVS_!-=Z2&y`FZW94gDYwEZaX8LM?4BD zZuh?6pKec0b=CHMKbMjeYjk_7qpKx5bxz!EOm0fp$2|b!Hiqk>P$e8W#{63l{g`#xGsNFW5 z7C28rbIX+ZRvI!d+P;^FRyitCN z9hrM37L%Lcyk^|~oPDzv9jPQyH&?S{m$Vfwj<_@G!aC~LSDyDK@Rvy<3emf39oTW* zdpPGt#tTx=gs$1H6`8w+dsnKNMY#TKx3XWu<-c&l_8I?FGjL>A29EbSy z64x~rU038(86p!b&HH0N6jVK$4$$1Y_YJQmLApCmKxLm!@eLzAGnUPo>F&YY(Ob+& z2_(j^C}tF-v}|gmQMM;=eE&TYw>H}qn!*9r!zLkckY}N95PVo~3l62M_KAYMFOMby zmr?#>-rFP)PByB0ilOx4L5Hld{`&*2$nm;{Gw{p)KrYJ_T01+}Ph z=Gz#t`mly}o5FCVZX)WY0#Civ@oW)a7^X9qLEmY=#A#A<5RJupo{=h83=V6VF|<=u z3e+AGn^Uco2R;N>JpU&@>tD#acY2 zN_)%(49Pa7tRetCDEFc+QF~6n?=in9{z4!Ua2{9D{LUZ-99ZwzNX`Ed#Lx%K9*j*mf4_&e^jXg-&u@aQ$pd3j%O?Ic;igzH)J8;_nUKCIN@;PJ~IWie%~;nmT%KjnDmI_d&9l6Eg2Q41C(fExR`bG*VQo6t}88 zBF|TaXf_mewnPy2?LEcPb2wfKVVwwvSG{k@ifuV%=QMCahSVh7l4(zkt7VL%Y& zkHC+LR?I~%;b}uL;~b*y$(Q>9A8w`QWt57mPf>YPd%cXBV9*6p%9cIfSdZN(nny^j zm(#jw%E~!CU-8+P9)~U#VH}Unq<5FzC$loXnc>Y*lNsSiDbF$Ecz~rs2_ld1@Q{U- zi|aj4i=5o5qT7vY)Su>9xDx@nXH|1g=EmjMioB3Nh4e*opVSKoX{h@L%@;3nVLfiF z-m?$z1z($&*&hGGtXPT=h?~rQrB%S-3D^uLv04FYVmp^G3~VV^Rx$g$ck_TBT@fBm{hCeyv1s@$L!!i z7S&=|&6QCgt#fmBkdo;FOH9LijWVyA{6AV`cjdH-2K{mIskX;o9I!;%m{pp1#Qg`f Cde@o& literal 0 HcmV?d00001 diff --git a/device/device_ui/entry/src/main/resources/base/media/icon_close_small.png b/device/device_ui/entry/src/main/resources/base/media/icon_close_small.png new file mode 100644 index 0000000000000000000000000000000000000000..c7b9b5e64c1e803cf0f81a299d1ba1278e95c54a GIT binary patch literal 2799 zcmeHJ`8$;D7axp_wJ}L#s=+f^GA)QymIsr0MyV`~?AwgDl3}V>jkT$e#CYr_kwF=h zrHE##FT&WTvb<%fC{x-rrAF#~`XjzSeZN24_qne7ocr@R=UnG=UFZBhHy3r4)hZ|y zN*$s&yCb&i&%rE1?i%mfV#LTDb9Zq>)%N2hD3r<#$QkrJ8Swr}X9hsVCMQmy&MPG; z7&X5XG+kJjFx2mVpxIo5U7oZs5&KafFo^bjc%kIEgb~hYZ8g8fKYYzxD4Yv6XfU{M zf4~1|Vp21Rl6O_cqx3KiK{j&e;24J>Ma0B9pukZD>?h4B^ylbMPnXna88C?VT6*Ll^=DE;(3{iQ;^3-eRgOhlX&LJyJ~v>^kdRMExWJ#9vq9W&q>j_O%Hj8|iW&nLH-pIO_mqqX4>@=11XONPB@r>i$`xz$##23ds z_T0Qi2PZDuew(E#LU}8yvdYqbd5cf^msEaV$Wt66Nra+(+_1!bw^=z^%UxS4;jcWA zk2Y^-!B6(XGa|a2ez%>uwv8xzZRH(VZ%_{>rJ&6$_a%n)(|y1FDWO zF8`&j@T@zRfki`a?+{sLQ#oXAy_63K+?#0M4$!wl)hxe_l06DN-FXZg8q#KZOna9y ztw5DDK2Z?S?0Vz}P$!BmJRklQAQnR*xv$(I zrk?j!k4p!+)i5CioSfrmd5%!oLk}WEM(4dUTmeC7&|^OnPK5I%5H}q)dx5Z6;%_8T zRA@7~0vxc}m?y10O>QX1)NlPqn+RrXm6EwjFZ}HnQTs^4U4IZR0J#H0QI|CxxE;Qw ziV(Lk0i(l_Qt!XAQ9{gmF&>vb|?Ui4vzaA90>RDoK`#)IxId z*vcu0dQTD_6hBT5cCuTKa0z*tYJ$p5WbCANDgyJp-r}Moc110zqerPNZ z3cFgpyMd6ZJR*Ae46qF@=x4#+GuvFmmB|6d{)qZz@_Rha}NLwBgQu;Ko=BvPWM$|R5?)Te5EN`{f&2z&eHlhyF$MLtf$9l8%yS__e zrwd}v1BP$T5njm$x@Z8p)_eiYiTk-^L0<_@&tJt!?`qj=!wqYn+6E@4*q+5x%;O@9 z2!@;Qh@$IxXN-Q<%o`HcEZ~X=ZJ&`RnD;UKedDJ^pe<`nGy|=>?Avx$?~cQt-%KT! z+D=tt#P)uCyb)Cv4CztCiFOsbn(b%#4b6LPUa#u;wBqTNpqMJ8`G(BgS7^xoK)|on z&?c|pJoA8P>jyqwX@sRDynDu}6q+l0Y;Ys26GTF0?5n=d%(ejW-u@ng^DByCbT}uw zkEZK+WSpv`LF|q}E2Hyr!{6M|pW7Nl(M7zgOZm^t>mv~H9@9r0uMx-TXOPY7HNLS9 zIG15eND5=Tppz==NGPn*%y(rAs#s+hNbMHYgfp5%hC?T8;8Al+FGGs$Mn1liBD+xS!`k6}lT4p{?*7&$##Ct@hokMWKyW+pOX^2MY1%=r> z1#M3CGE1V(>kS~T&DNP_!Z9-n%Y9`G-H(x5C;7O_mHjM=w+te8$niJHB0SBuu+>3-zbfMuIkissqk)&_sr}Ui|OR0Cc|<{ zJvvq!iK_ncbmxxxw%F#`gRtG!-t3n^Hvz+X@==DU<3QlVuLj>5B#pND6D7&yM)YG z%iTY+@oB;;&tHpDug;tS*Ot1;o<{{pKxD3TTBWy=BD311_M&wb=8Y-6C8@gqJ+1(M z2)wmKotiv5nOC>kEXH^S%6#9X6kZg1;+cnajjH?R+)8#&Xx15E$#yYp4ePtrU@-gv zK7O5-$e6_c$YE9;N*58jEa8$KF0xaEnOUwphvGOx96Lvx1D9Jj=&2=-!4Z{Z7zhPA zK63Qfk8CM9z^ox4hgQ~A-9tw9lKpT==Knwcmm2m{2`=#;wPdFEBO5FVBD*=)ItFq6 E1!>j2ZU6uP literal 0 HcmV?d00001 diff --git a/device/device_ui/entry/src/main/resources/base/media/icon_counter.png b/device/device_ui/entry/src/main/resources/base/media/icon_counter.png new file mode 100644 index 0000000000000000000000000000000000000000..f95aaf96c33fc31fc9ed2539994214610d1bddd7 GIT binary patch literal 1619 zcmV-Z2CVssP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00004XF*Lt006O$eEU(800001 zb5ch_0Itp)=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01m_fl`9S#000H5 zNkl7%Q6n<}v+t8Az6%axy;*^9y6;>M9%50z4>T#?L$ zi`yQ0$Qi+b3*w3Z34}@%Dt@9$fR(u94;n-pff^Epn(X6E?1*5`de`x;>m}bO*}L8u zuQ%T}^WHb(tcM5y00000000000QM5Np2}y>N=G_PV?XyuOP)Dza99Me6&%xN^OmxIiRMc}_cN|ZvppQZTr&r{R^SXrYSE?WI2|o|p zDF;U$nO9+c^yx8!5WvNY@&C6NCGaG#zd$qi+ z`fznP(R!OAvQ+9-l=l(tu8)jc$_05I_i7KQl^l&RG^pf$vqQCRSNPG^zobuhN@Be^qr|_sn?IDp-?KTak9jM!epZ{Kyujt~vx7cO#B6Vx>uMxN*>8JV zI|(yA7x8(Ej$_un91@bFK5=T!=Xk!s>5;`GxS)>cw4^omiMbkvhJD=6y5@*wcMFz+ zZf3T8QTV>a)m{{)$N8b*@W8{ruPa3hY1yLRWEe46`WrjF6a;O z^gK}T{IL8C0pjWb*1?EeuTef9^*jv(s3e)y zlKNOd%{z0HKqN{4bwg_|n^_*PEkc0ih%j9_l9iHW^&miV0?~>x{+aIFF30{^eSMV@ ziiF-DpvhQDU$!$P8@NLT0zd!&0zd!&0zd!&0zd!&0zd!&0_nz!q#2!&IpvdhNEQ$cls>adeanRhN;Onq9bULdKqC)fpO zh|>1w10F(e2t&TPX3W||F1J7MpM|lMVMHU`-7aMa0000000000004Un{{SrC%1`hP R{ulrN002ovPDHLkV1iHx-+}-D literal 0 HcmV?d00001 diff --git a/device/device_ui/entry/src/main/resources/base/media/icon_enter.png b/device/device_ui/entry/src/main/resources/base/media/icon_enter.png new file mode 100644 index 0000000000000000000000000000000000000000..0a6e9981d0ab7b582f4d2a13ea542aca6f76e386 GIT binary patch literal 1312 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q4M;wBd$a>cDI|LY`7$t6sWC7#v@kII0tz*} zU|=XUU|@Kaz`$TNgMmT3V9u^U8=wSBx}&cn1H;C?n%{wwfqcf|Aa^H*b?0PW0y%6+ z-tI08|3PrU-sK=^&H|6fVg?4jBOuH;Rhv&5XhgZEi(^Q|t+#VGW(y}uv{z3Lu_`Gj z&{*os-u{pA;K_v@&CXI?CE<#h>n`vu)wKwbS{W_cB30x&wNI<$!m^T$&H=}@WMy2e zG_;PmT3Li#R@tYX8R(<>bKin*_ntRC=DTIRE-az3+#3__QMzE?hYN_3PK^84V8YYu2nO30-|PeN*M8&U$wx?>igj z2JDQn(_TGKtSoX`oMtB%#~0?8)k$o-6$P1OxkOCwh;|Bi_?;--u3P4=RJEWeFl@J( z(9eDE7q(prym9Q?IM=LYVm-nA z-$t%KT*dbs(9D@$UqVjvyTHS?L`zN*HIHG&<-q(CZ^Izzl# z9C$toa&!s+QOB{U#V#B!$-NH8SI*(o2f9g`#YrhxkB^_V~P4j;g{)6lIPD!$R#;=-AvX@RTc8kd|6WE6n5SB`*e}rE=&_$ zTs`0B|K|;Oy80(Z#<<} z9GgQ7iuQ%S3A$dYs{Hqd~24Q zoX`oEd8;?gat1QC_tnQ$}1yY1GtPd-^w)ei*fov)F8vUBZE-xWa%st)^{cjh$q zmIGS&FZHIAT<>CoiN}R2*Gy9XQQ>r2HjU+%ZRIna^X9EKT&%0^t8CGVR_PCFbeUHe zUFjjW)VJjCZ=22AK3y=-mO0jKb2)cHI#Z&?pG(uqwA|3*ie#a`bl ix=b^SPzDFd*FCcqxA4?da2AgMaXnrAT-G@yGywp=^co!i literal 0 HcmV?d00001 diff --git a/device/device_ui/entry/src/main/resources/base/media/icon_frame_score.png b/device/device_ui/entry/src/main/resources/base/media/icon_frame_score.png new file mode 100644 index 0000000000000000000000000000000000000000..b528934df62c86c905c99fc61c57571d79225f91 GIT binary patch literal 1631 zcmV-l2B7(gP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00004XF*Lt006O$eEU(800001 zb5ch_0Itp)=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01m_fl`9S#000HH zNkl=D?oOp*h8>=|+*^BMn)SG;tsmEDJ7aCmV&cg zy8dtfkAKHXz|q1;7q#=-4)d@GwZi1V5avnRC|z40%>B_Y0mm0#7#A+R2gBFu6|_a{ ze!SY9yBX9j6Lhe1ar{l?^Iaiofk|sdMqOd7ZQ`lc;??DR%>&V5-Clguze(b}C#T5gE!<+twmAm|jRO7)x8UEC|>) zfB+Bx0ss&I0ss&I0)WSWgZf2_qZR!tO#CbhJrt53bl*?#fc>gCi7&D6kiN+XQ3nxU z*4`XcAHMGnM$`f*3%9Cb9 zoVDDa8W(G4rpiHGZq(t1VFm#VOdHRZiOyP@6#kc=M}0I9vw3K)yMYwB zJqiI@3+0g~D{k)#MQR@0ma zB;D3HQscK{?urKSt)?eHbMG5k7`008tMR*hJREnKPB^^2iJGi;&RE$a#!sUX1S1>#Qbtz_Ml%1xw>m6Quq+qQ~;mLqL9nAnB2643kY()CaR z{yKGY)g|+_9}uhjFi+YR_pxU|59D)i4HO`z;H$RWC|$E1vp$bi{BU_AJJI{Qkh}=< z#WDQpll6f+`N#wqY+hYj%1-obm;d`0h863!{ugV#cfRsF8u+-rl2A}jSkmbId{|qc zT6WlRKUzDJ`#P{)l#@ML$c>y)tgaEs_&ImO{VbM++$w>k4E-zIdY3W;0000000000 d0D!}V{{UV|&V@4j3~c}a002ovPDHLkV1fsO>=^(6 literal 0 HcmV?d00001 diff --git a/device/device_ui/entry/src/main/resources/base/media/icon_home_selected.png b/device/device_ui/entry/src/main/resources/base/media/icon_home_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..13915bec910841b24608c4e5e196c12b2a205f85 GIT binary patch literal 3691 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 f6ZWslet*nrZ+2iSQw6j&4N&zO4w3%>LmJ+d literal 0 HcmV?d00001 diff --git a/device/device_ui/entry/src/main/resources/base/media/icon_jank_each_hour.png b/device/device_ui/entry/src/main/resources/base/media/icon_jank_each_hour.png new file mode 100644 index 0000000000000000000000000000000000000000..cb44bad8f3b6d3324929a8212113f7fcc723e2a7 GIT binary patch literal 1615 zcmV-V2C(^wP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00004XF*Lt006O$eEU(800001 zb5ch_0Itp)=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01m_fl`9S#000H1 zNklsw=lchIr zZM=9kdhlYr8Z{}Jda$udNU9sy$^u%9NYq-O?83ah-|Qv=`|ZpuI|DoAd6M1P%wOhv zp7-~goeU8G00000000000PH7lEjy3V(qU;{IZKY)&mGAc;*_s6~mR}~{0he|8LjMct^NUy3 zFa2iQ>QsyQChUOxXrP|(hT$}{04Cug=0_-ia_JPCUJE6FS86g1gSLo8U>Sbr{@Eje z0(f;kLQgEXujoD-9D=mT0#&iOL4TkCUWW_Bl#DqO>;afwh*N>)P*o7Hdw>8C00ICI z00ICI00IDefP3{ui^D`u>++<2?h4V5y8H-t|>r0 zmD2#u83?dD=HI1Of2eYUbPLHL2oN{_E@!Gc1`k4jn8%~mf1>JEp+_5S%=eZ6!jGt# zH~Hj-R^9111c)pAuyhCKXYPjpVT{>Jm@=T0^@Q7>QzA4FydNFYa~?(`=F5_FYbW)h zlA87?Bc9Xc7y85Xsr|cr!_O8kkW+frZ{Y#e``9N-p2=jvwU@Npv*JN59rXp&wsMco@azj7! zWg)Wmu1)s5Gj6mhs7L&tSuh4;LdW$@4#lFx;*7}L7q2}_l9QnwxnnYe$^ zudH7smUsb_s9p&!OIFF*Q5C zR2nGPc4%mQ-4QJ%^PbT5oRBkCzqL{G60vz`uCorqYHj=^%=Ao@=QXvwrH(h%OS@J- zy;TmHsdBb#omVrLfGQ}uc^37bWhHykzKut0)sL#PqMwB-KBk@_Q#(nddM*E|cVA0D z6~wn{NNhnN$;9y+I{4x3)sd8@CL9Y>b3O#?dLGwfuWM4S>F$^-&LVTAH#VREa#h)R z#U#u6te=;Zs2}v_-EzV`9?pBciMyb4t2L`d*A@yB(+B*mSNjQh4|+JfGs~9^9d6z< zQ>EgACt0L7C}f&^;S$d?B?oc!0a}6ZH0{u8cw0@RQVV%@wuy+vTQeT5JDnlu#t{`GC@mcD`9|+K4H+ zz!NeM00ICI00ICI00ICI00ICI00ICIU_T4MatE};1g5n>0fe^xsTGKlf0+&T0N2TZ zs<;%8T%Z8F)_Ro^dUvVOVcstjD8LvUz0E=spet(Q=Ly@(eXLcGn=XE4-hl$zA|hY8 zG%xkBZL>a)^>yL><7e-U(*Y)5NM2Wuby2sWp zpO3Bfd|7R~@8kO88<%pUP3IPvoX3Lp1uDai!_J2nRx@Au?H5R@@r37E-um9zNVTgU zJOpnDLB7M2ZmRIX*h+8UpM~)%!-zWEoxaKt0000000000008zE{sYYzwu(Gq004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00004XF*Lt006O$eEU(800001 zb5ch_0Itp)=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01m_fl`9S#000l9 zNkl*B;L?DrXI94T4RI0>cMIOq-22dP)@aR-4J}Opm zoK|OAu|s`~Rs~yat7C|~tfXqy1{EdB!)K5;0RsdGc_+JJH}~}aPj-`g?{1RauzT+% zK%btcp)m z;Niie92g!lt6)qnmASOf6Yt#yqw*Wf_%oSluB5ckg%&ub$CPY)JMcl#)JeSTWJEsVU-@b0D zEW=$W^obfZVg!7Qu}xf-Uv@X7ZGgNyU(Y0IJBSmzY@aT9g6f&${dCUV^;aJ!dTej;(7aGLZg+KPDH>LXhFMFnF+W z^vw6pDgqL>z0VPp*>H{k2UN3L!aBf{U2ZO0M z@nIUAdvM`zpBJ|%|Co1W;SSviaBg3FFOG#5bxt3c4XAF}4YozIn|&)+Z`O?fA$-oB z?(zCQd`#>v2@w-OYR5nFdxftd0oBowNYzn91PFuMs`DMa@(yX9C!=BhG!nxcHUZ>n z`n{dBUO{(k6L5e|LKpSza9z~!2*4nV{QPst)jSjL2&1)YoQvkYb$aESO<`E^FjNml zxX`*n)r;`rgo}LK`*c)N5&qoHdLz;ceXMbiWp_cc#Rh%xaetcw5-e7nwhS_CcCfJa z@pI&~8(M%vt<42LQekjhJPdav!YF5w5PURH+v0`Yo_ahQ;9zqDeA!qHpVXfOAC9aTqehZ_BOAx& zS9JK>JNq=Hl#!#D3jYdMA9!x?C2%1MJXRng$0nqSBY+)E)v%~+2kdm$#q#H+$QGf# z=SFOVX)y&Ddn%UxewIA}iiVGe^X>7vtA|XR6Q(3*!aq+Pf@Utq@~bYgFP{B{cjfAR z;j5tHVyn|tvnAFmS$`Xv3v<)X3woVKd#^jB`XHO( zlB`0dks1rwjgdIJavUzW(2)cVNj>JCJhmGiKe{ufWYfx;r#|)4Q0vSgRgixTH|EQT zN|o?sV-?(U_-kQIN!Zy>|GvskYm~4+jnC6_M_@O9o_7I9o!p?UDd09YM}?3Ao5JQQ89< zp|M5|&H&~2kb2VzLt16q1p)rL9ngmWb#>rnI;exZq<*UBdz$O@iwj7$npq%egD%jq zcIP`Zv`S1$&WQPUj!96bvPkXeh!IX#)$|UHu)kbTH|)TGh`brA|4Xk)Oz%W+sql-W z{*Y|7VPxA3n=p{tj-My|JSiRS>7NakV@yH+d+OLO_}8h!Ap<@-M*tqQL8Y;c1A=FP zBrV22Yk~+6g|aPtqMjFX=yVf$D-A*~6E2QVhVwBJrq{Lc!mWMI7pX?VP`ee61h^iB zISM~R{(OK$7yj?%A)|$QBhN~S8L0!?yq{Ljt6D_Ky4qsSC)l^Yo!vb8#(gRkL{-X0 zsV~1FPAz26Gvz7NuJGyx|o5ukJ)dq!!noP^Bnt*QmCFsvp5fcx0H`J_Bf5 zkbziHu}5VLX%;+`srH8FAKV%;y`Tl9GU&ZZ6}BvMq-yljdtR$NWGDe>jjwJeK$gZC zc`YRTx2n_z@u7?y;T0+KJX7~Dv+%tmy-x6Kw<);nTYy#%~8s139m>AQd4vGm%u@V3rnMZZT97m zZnLXMT2Qt<)+y+45_IXlC9_sooWGe*Oe`>ZIJj{{CP-EopV(KWib#LITid>%RNvx* zLUbRG;V8c>K1B%f(Iel({Xc#al*s%0&|KB|2){qN6Fx(-%mAy`@`2UA57!hI^cS=R zbNiepg0TQdeQd?qp)}MqBVIXquuW}+3Z{OSnIi%(ZQ8+b-LK38F*#cjo1jesN*B*R zur+A0WyRp1;Al?`X*)s*pO!fvLBkt#ZZys&(ESQKERB+A2RfJtWD96?D?#bwqTyGn zjB-aH;jhjb7Ze>Y{qZ{^{r(Q70MC$U`#+crOUu6v@^*wl&AMErp0pr>NchTp4}omR zYn5eSI^f`!T6-^;3#5X`<mkWqdw=Vu~07fSd=7mN=QJ|*@Rq1R(l43hkfFH8p# zoHn2$&=nK9j^Y;Sk_6 z^954j7Oijvsi%fYmFuc?Tge-`J~`v8Ai%Wq^xCBULik#ro(%M3M@rkCQ$d&3(9&oA zF>Fl80M10HKmxeOj8{gU%^O)`Lkgcts{(s6P#R0x-Fh($S`|0Z(9bOZEIp zP-gV`6Z;}Q*E7fW2GtyqF}`0>0%Q0D;L<+HU0=J+46y)J$7#z^$iH7&{$01vNl7?u z3i{su*+C>5<^sigw@IQY9cNz~eKC({N>M11CI9|R7Es7=FFCCKX|(3Nq`&dookb(r zQTYx2vJ|<*=+QqdB~$g<@`~M2Nu-fce(A)4AVY70Sy0lR3zFGqMqbI{e#2CIx5(9` zTA{8$TUs;25ab_@?o=&Zk|CxNNP{i#$87|-<9!>AvIC@cv~K6KXh1ttJ%i$$@ayy} zSelsw%La^uyVJ9S%2c$fXq9G}b57cMh6ZO22{yLR0Mtd)zSoEgnU*p@mCNs}YV87t zt5xtBIjVidBb67OgAr=z+T{MK=NlEiI|=?#R58CwA0|e*6sF3cO1SB%1L285qhJ>P zP4U--uGdsqPfs0Sgz$Ybv#bdsfLget=l7wWS3>!ofwuOwT^fzwx@+1}2PF86Gw@by z48826bg_In&Xp!Arj^M$QbT&%0aEpW-7zWx#1jc#(DPy_Nv0IQf9sC7tz@NX)_BhZ zG;@eZIyZfX(i@^WB3=D|S$E+;vNM`FEEH9#+EbAU)3uBQ$o3`Wv{0} z90WV6k`$Z~si?~nEmnxfk((d|_Zg{c@qwGSKn#3ew3}Y zUqlcAbn^mdV%0J|cFQYs{%?1hS$!c4{dTH-^xv$6&uO z&&PnR(Bq2iic#E%BbIDJw3;-90>vBj9nG?=$~oy{efhItcS~uiPkqIiSWO#>W7;RK zxb6gG`FF2SY2l_3V%Lty0hM~{M0=V&sD`LzO^wgnX}cO4(Sd-Ds^*ADQgM{7px5dA zIUxeCEjsKK5bbK{ZXBU_=`d8LyI(N&F2t~Ki}LfEN9F}pwdnX}x9`u2C62!26l-$HII0^$z`-a+GqPrETi)c3Oqm~G}{p#$?hAI)1>Cv>0 zOYGs!`RM}www3P~p&}?}Po5I(9iujt)YK`^wo+Lg9Z)CAww0CWina>gxVDveIdk1( zW)_?g0m2?^TepgtU?bmI1rD#H@NezXK7?~FkJEP_{`tCBs^NP*bf1}U-Wy);=)b;o z+IB~N5t{RxLG(uMMJK4>%yL~*?hWP9BY?c3R|@Oso+*q0wOWbmm2+q@an5dR&Yd3i zJp>}Ue*pExw=JA~5G}`a=)HkM2!qNOquyT^$!8+Ig9eiDy=`%RJ$~J!UXiQtuse6w zbCG_iTlbLhtt>1-5^DWRn7uPW2!H02-T7d*@1i6~SU7tx&bvwHuubp@wbPA}f5rp^ z)DaryEv%J|w?7@+rt&IS_@ zK<_Qh4JJUKxY*iyLkPMj0XtjnLso{z3-0>b=f(bsm`*OefJ9LLe1CiV5hfM_#dWpYVuhBNbN9BT4u_lV9bP~Lw1hm-*>ew#J(?#EE{mCdnC!vTl zsX$q)slR=Xa X^0R2}kTha|00000NkvXXu0mjf)ZOYR literal 0 HcmV?d00001 diff --git a/device/device_ui/entry/src/main/resources/base/media/icon_language.png b/device/device_ui/entry/src/main/resources/base/media/icon_language.png new file mode 100644 index 0000000000000000000000000000000000000000..ae4f3ef596452b0e5dbb5e7bdad40039fe7dafe8 GIT binary patch literal 3343 zcmV+q4e;`bP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00004XF*Lt006O$eEU(800001 zb5ch_0Itp)=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01m_fl`9S#000bT zNkl*r^Tykll5q0w5}~k7#?Iq}jqwjoQ$Jta{ihq#-77d{@uNT8 zu%0pYXV@}T4$$irjIgbHRxG>PI(&1)@eIJlr;MG2nJspZz%GKHATZ%_E+*KX0jPE| zJKsr*KSd0rp}ytY-#piL{pP2-H`sj!vd*VuiBpjp*aIJ-(%ZbIMsyqlWVB;*gPW&K zSQCBJv)OqP08n$jI9!asBjDexTWfm-tc}L?>Br^BF8Lx zu;?Th>bDM;OYhL3AwxA&+7s>v`!g~ZbFwo7Jk~5eiB5VvClu1AhCsZI1-fk5nU6>NU=^-ss__oZ8L3OA=&hb)m+GT(_H0J$e zd{CkLo9Zw$Fon&-8RdkEN?KNs+oKaova}t}#hh*_(QVpdP!>d&p7rPv`+LGDkZ+)QwtD?_W)X)=+ zFX!Aa+L|IVB;BQO;HcOs@Z^3u$NR7tfaM{avh85KHc~=GJ)mtn>`%!-EwbU;cy685 z{AokY0ofVL5UQ|PFwhBqzB1kgv?3|lS% z8J(X??To>0z#aCNR5CRp5FzU84M%LFeVBgA(HzZ2+d1LCwTuB+kp`}Njlp|RH@1{U z;=jq2ReH!Ylu8paZn_ParXsDy?u(DAQJru{=m~0L z^DIBlADi%Mw86dNMTgADm}wZ0sov7X)TN&I3Hw7&+ND;FizX&F7gS-*`EK>T7b0 zX=X)S$>|wjP?44*sT&1Pf>2EHRcC_$xMpPVXAEgMkxuE1e1{pK14d~OZHW#{Iy95e z$g~VFsz^%}dke<+a@il@7gWz{)|(iesc3U@%@;{rR71}KjIVlKd}3nqHH~BThj%aF zvrUr#BW$a#r71v`)!8LaQDQ;^4><*CnUVJ4$JEBRRP+YTY;S^1(WqC%1x{akp6?&a z#}CN6A`S4dCz0^X)EOC(;0Jb8wyoKDr|CzE&;jRs1O~i*2Iyx)G+6gN6?!qb!Gl*t zA1&#H=iF94CO;L2UG;f~BJ!bV!m|cM{CyK<=4T{@wxH#KXwnb(Od2#e391`9tZ455 zC$E0CpNvNuU0i=w&*`l7)AIyyB@UVJR<@_mvxotGzU$^$7mqw=EbjXK2@M%`ahf2XN- zt}$<6egKd2BdX| zCN8u3Zm`x+mouK+I^_fc{y(aYJ|Yv6Dd}k^AS2ZI5@*lklRL4D0eUeK*bztdYyrk> z;8(VUc)*UdsqaH|R2mvs2NP_tBO`E3;K4WfBJp*()Q)K=X!@PN4}o6q&KluU;L2lR zlOnNGB6xSh-4!asZ5UrE3k{%pJFPghFI^&8nHA5n{Lntw2oady4S4pY*&fuz>2^>Ts?;-fU-$sLoz+8j046C4Ctr;qOz=LPR78 z>@0Y_>x%D9_@op$s~!j3|Ei5EE=&$jSWMVq0xPP{C0C2_iQh>{a*n9N-yqH>%0h!f zWP#9}#DYpo;w|bnA33TBHbJd=VySpbA|CC*>Tr^*`b0-&PWyTQ6QX^OPlso2+147k(u!zzIb z+j)HAXP6Y%OW?`pwK0-NrxF$90d=&G2;`S{8*97p1gm_#$_38=xtoU3orKf%a(Ros z*-OCn7I4>3Al8pH)x8JYII1Hdp7hymQZwcRz{Nj_G4=W8ABYC7cHWFZ{hlOjE9}}W zIS|g&z8@3_dAX$ffy-Xn9qxe7xR4~Y8zZ3xZ^VwwnwY=jc(gIlB{6=ctT=IH2Bng= zWt!&f=snd>yg+msGxjWBcCz)uoA)6icF^p7a@lz>o^Qay{ea|X=0G0+81$>sNTQ{v z|H9|$*AEVl+-@5lIkk7i6CcoYI`bO_ALRwZO1tWUG z{opu#AMDA~z`&4RA_i>2DYZQiMOJOF5o`-djOTpLa9U9s#7(8&Xnp5kqfboHNU9lm+X>_N5-|9iak`p8B2 z?Dp4JEPanrcDbzkydoikiJp5tu|v-m!ToQ;d#^Y`0+>KAfHS0rgepMiV|DPGO1&NK zn9Bh#?jFR2*JQ&m(L)VTn4PQaG&Ud;1~3|M8yZNvO+s!cr{u7K{=YbzOx446#3bn$ zVN7qKML;KKqfLQbrN!~L%xYPhEuq?xQ=I9=*G)khRixR43`4ZweZD^(43zuE2)~5g zGuXgkS`UHIm>TN*l9*f~5TObT?o(K=pE1gY{s$(=?d&}pG5{SdQD9e{dQ^do$~b$* z?r|i;Z&7;V!;e1S^R34(Q%ZkA(qGBsBuavGZYm3f|7Q1r=KLC~*6Sbr$7xK+RHQZd zLgBCz#yjozh#?bpFMqsOVDvjVVl=iO`6~oD`?0Sl?3liS8Rbdu1_PS`x9+gnm+K_4 z^OADbjt8?kEnGdTMD;_eNJAQ004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00004XF*Lt006O$eEU(800001 zb5ch_0Itp)=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01m_fl`9S#000Oh zNklHk$|CY-lK{_#nlC50)yXHO01OYt)E&P_R_6Xq5b{wxX#m zA;qF%LHi>b>Q53Qnp&}-qENw~q--paMrztT_~1iIeF!xa+hkWCvdPSx-kaT6mANys zo6XERdk@G?xU+j_&Uepu&OI}pLx>O|LWBs(Re|T_LNQX6AJzPofVdb5lu1Z}2&9+} zDS%`^2Qo}=rV{{fOqWNmLg>_p03cI}SM#<5fd=|#(B{`3&|5pADHGjx}y0&;wFHqw%ujmnRy_b<|w zu{MK0J1?&LO80G=du~sApPEhgD`gu1Y~|CB$|-=hJf``N2@9~`e1iCm1!r~v!X8Rb zjRD_``ursacJ17>u!W$0lSW7RbbOAPB{Xg*xwZeh-hSZ&45FQw>i?7PaIv3W?8d;- z)^1a;d%dm%fH22Bk8{dkT#;XSF?95ipr9vLzv})^aXNdq{LKy_gjFEw+N#7t~drEWW zA^_4y(0Aw(Qv5zW$yPzQVq)PYzVa2%%01!AV0B}*JE5)Bv5cbo< zN5XvFuz=8RmhEgz{xg(lM;!_a5F!8&00_-;piT6kWeSVGlU1 zd?x!(_x{|tzC@#k?;0ZjZ!~Or^~9I$eIuTvAit_LFwW)C&QvOYLJF3)e8J}83>V94 zeuF1{K(abXnCB!5*fgP=Y$D zs!GZkEQB4e8%izfvZTY2>C2n%Sp5d8zB1Gi-Yn5&VK5w|4*3K6dDv3`Q01@VL;+nv zrMTPLf><%XzYZdSaN@e*sZf$x7Y%KzsiSR+EAppSC2k6rHh0l;StS*N`gFL?DF7Uy zprlEcazc-$MUjj>>7;5R84!!GXUsBT4@~l;3X^WPbBLgoW``(c2W$K zzRq)~aiwPf08YLKQ0lc*(73}*%U3B9NOjh}34Su3n9TMJ0MrHy0nmunXM;{ECjq?W zcQsub;N%EqdI$h8bZ9}}n2m%R%u0LjVMB?s4+c0EP~;&1h+#yM4aKwrq1FefT=R3C zib!)E-IR(J5$^O7fjh81uL6+Y&*)I%x2LLd>85r|hMjD`F3o$OfY9*?U zdW+Rbd1D&yxx}EC!q9uhlfzs~j$YnM9W^$mF>PDnvg4Lf7D&KZ1Tf$o3m{-vyHl6Q zB3q79nZdG`DNqq~vV4#&nnGN_8M)&f0D#;yoLt8WJC6Zbt|*|qw3;3^ThI|3YmO_@ zYP{@LTO5{U&T*02@KEczgB0Kj7HXh&UVLTS%Z0NIuby(D}B$;iVSKxrGtD!NQ6 z?mnOU06@IJ;o96Qq|DpWQxSmR0L}+$lKshhrA}H-k{H`+Oza#*P85{&yV=zjhSjSWvewJkW&TaEgPNIg)7<6xKCltb5~lz8al^1YgZux+ayiZTWN#T!Eeo`4%=DRQJX!c zl+^s~bd|D1S*G4n9^Zv$V~_c)?J3WIkjL`YAV}|1Fv>_8nOo|t>81+LpfcK=ZHmu& z0G^_zNNo|(q*AKUMO2}sltyNeMEAYm;}y=HJL7noqlfyETdJ&fzYuC0AVP!)5h6rL amdbw`A^~tH+b6vM0000004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00004XF*Lt006O$eEU(800001 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 P00000NkvXXu0mjfTV62p literal 0 HcmV?d00001 diff --git a/device/device_ui/entry/src/main/resources/base/media/icon_mine_unselected.png b/device/device_ui/entry/src/main/resources/base/media/icon_mine_unselected.png new file mode 100644 index 0000000000000000000000000000000000000000..03f579500831a8380c867c5a543a334da672249e GIT binary patch literal 3588 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 literal 0 HcmV?d00001 diff --git a/device/device_ui/entry/src/main/resources/base/media/icon_net.png b/device/device_ui/entry/src/main/resources/base/media/icon_net.png new file mode 100644 index 0000000000000000000000000000000000000000..434e4bce41aeb6f5e2b9c4224432f08e98987d77 GIT binary patch literal 3035 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 FV1mo3Oius+ literal 0 HcmV?d00001 diff --git a/device/device_ui/entry/src/main/resources/base/media/icon_report_selected.png b/device/device_ui/entry/src/main/resources/base/media/icon_report_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..aaf522afbd9299877337f19054939d6f99c81b23 GIT binary patch literal 1713 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 i^EEUyG&D4_f&T;5D@pXro6|%90000004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00004XF*Lt006O$eEU(800001 zb5ch_0Itp)=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01m_fl`9S#000Ry zNkl4(A=TBT@3p@kMIM2yfj zQQ0rj3<@sWCT+lqfrctZh*)Vv!G2K42bFxN5TOhGP~2f=vIH%XrUA$H-Fx=lH?Q5v zd+)xvGxO$6CUXx=XF7l0dpW;*&hMOa&wZB=BSwrEF=E7s5hF&7w3|R&2f`6z-&@+T z(*oFy6do0C^&?_05a<-Iw~5E57SoL*9t9*=1cXJQsjT>0eq@}@c5GR=4kPa8%?AKq zPxm;S4FZxErPAAC*rEUi$h2+qaroClp(y~!5%1hdofe8e*@B}xE-4}*@7i6~n=q0s zCI$e$v7?tc^on@7%Xo32M1Xn9`SWnDaI@OqT2(9mWID}|d?Q}&G^&3fJxJi$%#&NM zWagHZ8U}zn@=MC^H#!gak}g5mhcbWZ`XV#8{HyNIXf;0Hb2~=)Y7lxkrJv$Rx-)hF zB;gvLuQ5;&A9t47Td@KlLrpK*F`^gQyei#wYyikOzl}r9TBOr)=!sYWkiOqbvW!$? z1wv<1cXb0G%L|5hYa?V&cl7}vYdc1pwTVToJ@vbwQen+VEfEpHKUVsVt#O?Gzi!Sy4t1-e|Gg!A>r~5F0ANqu0C@HMBxP>H z`GtAahn(?=)`^5*Q{4av=}GviC!;==;D|)$dW%FtJ#he#qy?;4%)YF_5u+_)1a=$% zBspTH;C!}XIoIm4rp43ZdWe+P6abu)NiCd4vPF?;UN8W}IrstQJG%4p+80hO>8cz9 zKy1SwC#vZ*fYIy?;j(580P#qXQWi?SvJ|e)7jV=75RU_za+9nOVT@f;RTVqV6|RbO z|AqmeUhhL#DEZz!m69`X``De-0rek1Ct4W*yXj+JBvl<<3)e!G3*@Nx@&)mj3H^>m zr-hZgqm!@5b6I2g$I9PxhEm7G)1|hx00bNlBVRn3-Xflkc$wEBJk=Xo6ofNbnGa0s zoh%r-dO5{Xy8_^-GG9j7LGiTZzNI=qkErtpKCTgxE>X0m!*U9dSzl~Z0AN~~`Qiij zQ~D<2aTOW6#kl6Sr`2x`WRUS-fEv1YXd?gM( zz|ofB?JCQ0sN4DN3rXGb$l|&KKt;Z#9E%*O@^HJ|j9FP9@}EnLcEsV3^9H(@z53A9 zdILa7zRpmejC|779?;8}iLyTA_vMm&gQV0O7FF3`C6W^)HXk6uzK>PpYmeo=Br&eq zU+v4`qsmbgk#6Yfs`o~-H=I+cS#ec+)`JBU<-|$S5trx7Z)psVz25wTUnbUx3yR2m z8+q1}FaJO%s^Y+24FK4kzuf5hP$B?Ck#EC&EO*P$70U|<A!(7eLFc53RH;39p5&1rdIZ13>v?&Z5{J;cK>>%9e$?=~Ampen#)uFbROI2mnl} z$d}z_2SxaII<_-W__KI=I-HD&!-mKOLC9yyh)J&F)dIlo&dmw{B^md}?;#pXzLODW zJ{~jJd!tsTdo`$HSz@v$DwFmDC^F{Jma>2-@|E>@##r15PA9)dkBR5EBYgQxxi4qP z-f@Ofp9jKEZq0h+{eJ){yUfs%7PP4)EFg+}z50CY;EPalFlu!=1}EO-e846DgHay1 z*@m1|YefH0qX1a(O2Q%JD<|F6-3@h0k2~R_kdD#Zw{jhV29z&`WyZ+)ha~1H%LW0U zZn*LaUkzJ(41^CvyGE{)6xv>A-YW5;tobz$Kot3k5%@x511~rjKFQ+w_fr3mPGU8v zc>lth20(ToE6KNH){VaNip?#qJV&X2NXwV+x;NYxxKD;i;AAv)YYkg~y0?Z=N7nrt zzU)B!Y2O( zfU%Kr%mAp#*X!?3@}6@QxCV4;RNKNP|6KqHUR`ql1m|JP+z-GBlH`gU*+&|&z|Y#8 zlS!G*ZHEUHNk=SY>Wb5VB2z?Sr1pqlED!ZsK+&kNW~s;%IshyhJ=W|77wZN<-ap@H zvPS&_uG9^H$Xp9Xl{FHq-1s2|7j)C8vgQycTfYTx{I}6$t@?)_>jr=!err@&o5=lU z>IOh=SYzB!O%YJR(_Nh|NEV-fOdE|>8v&7NowgUPQiM(2?y?o5R&6QD9hP*ysI`Sj z=KH0UH~-rG2a!|%&FEIg`;hb2*}`W!-_=H<3ly5HB{#3wUL zc;>0iUm=HQ#DBLMRfiL=g}5L7nfp0*09+8hyR)2mV(S+o#Xev_2vffG6r9ifRL#$- z-bYOClO-(Sim(L92iuM2OK!&<`Bf^vCIB8-!rzxK@w?qr5c8kJTN~On+HncD%Q{?* z+gY70*K2uZGtcI1<{7vq|8};VJR004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00004XF*Lt006O$eEU(800001 zb5ch_0Itp)=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01m_fl`9S#000Yf zNklZ!bcLp;6?P=4lSR%*rZ@- zJsADh;a*EzNa3cT)@;o8J~NDfE>#VOsJ-8mD`G(e9X53RiWjRg81CIb5b1W|;I9~( zzNOrn;47Qi3BPzDhwx$I_#hR6nc6=De$w*dzGm{)pUvd*-}nRmIDG#ycuC=0y4bHM z&zi~p8?%mpK)821sG5__AN3KkGOoA(-YdaR33@CL>|H6)d=Y?uJ}C8AK(LR)HES-JmbC)@{1V3|xA@hTelu}v zbQTcMCHn))b(qvwnEN7xh7^o{7@O$)N4Zv z?PjGkJ)!L&<)yrp55jMZM%x}&AnjqOvVS#=wAedlv@{s1*;S5_l#GD7x}$Zl@(=QS zBP^}Wl@WdhVGJ*2_Zf_=1@1&&I9c*dV#&f#H5}dOb zt0C&#tA+qM(9{ise2%qp5_(5FsxrVHVMJCMTR2gX6A?7DZg@fjL{5XC;CX4b_f8lb z7YuAIXUL~f)dKI!?8XbS<8{>Ef!Y02aL$os6(t~O#A))2wdpd0vhPZ@ir>)M2Ov6l zgBERy_?kt;vOcs^?Ui!E4p9X--}aSgCIPfL6w?=4SXyWyvOJr{a9J)Tzc93p13AsL z*JB3Zf1C5xM`)(uvmKfaehX!O-lZ>vLOn~;NctJV_5wmVF^!AIM5|FLsQaeXG7+ggT z7RKL5M%x~sS%yGu&st3IWyCM78j@vwFBa>(UvwBs(+fmi)r~FbJj&R^Ic`6j2-fUF zu@927jevBr|DmKbT?Z1@z;do*nR$Zo_swV4zr&ipYXWFx$+WCKP?;YT;5$rneAfXq zO93SOOAf8PhAfGO-_J5GnJb&A6zn$!3JOEPnVcbrTt@stMlRQU9?f#l78j@F9$=4c zy7SWjn$vl)4_w8D0{09C?0!!qf}zgYXnt6l;0rW<437E740$QUlIS&kuAQ(Nj`U}3 zeJ z9Gi{NO)-$tWGC_AmZ{-5O<5#TCXF6{?P!9sy_&yrFgwFOi0k8e$M0yJRfv3Q#BqB< z_BSsCi7#O!f98TSKEu@j8fqG*dB#CI2K*RrjB4!_5j<_W%I-urWH)^#OmrAF8n>~P zJ{ga9j#dK+K4cwBBfOQdFgvJ- zwerjkUHO%0-^)0*NUKH2)f}O47!iC9!sbKOpao#YZ?n?^(hJOeKpIQi5GR!kHh)W{ zis)4Mpp{46f5J|LRh$F~;d|`ik%gR%82Je&VRTB@JBBJO{5*HxlmxpmpmGEdi&wl~ zFpU5hEVcWKm>Ud?PgY_Aw6;)~@c)N{l_Wv03GoQ1pGE-T7aHzXBF?8(C4x}|6<@PB zSTS?N_m?uu1iipf<-SS?ADrnK9Q74)KKJQC*jlWyu-(x zf`o(Aq?J{={LVU!r2z|=5tCv)F6+w2=$w=AeU{b8-TV;J=tKgoRR?Q(_Lgftw4Tg) zYm`L*lPzOBFUI;%g5~wDWfdtw~ z!5P5=76Hukv04|HUsB<_u=#FTxE4Ign7V^#1cTdZTatFf9YjI8AnM%F+iQCW1 zN)t1&W+waJa`SmwF0_v?Py!#r{VxY(xm};w^GQ)F-;%DuuV3R_GHhtEEv%;-B^5etjjR8;?=&0`J@yGR4m8xX8?Xp=Fgw(>;LY)gJ%haWeQXoa|4>#M>*3?Pr`+ce zjJNs0T=~@1e@iF&{$0+=7i)rw-QoI_=58e6--2)#JLS_Lu-Iv6?dKim!g~X3*3Lk< zrrzNJ1Q~A7imi038jgS|!FPPbdOz(?k7qKd)qiGZPB0!(O55oLlJlcf_zA83M9#(J zWFr$=*G88oTs6Qg8z53TgiIN!VfwX^LZ?j|dQT;SH3z(Gfyo||wiQjP$L2&lM(g3W zIP-Xt#iVA((AwU0bFq}mQ6)5eGwlH_pO9m z!goPHh6F?FcoiJ;x*X*|ud&s;ufnCy{hOJmBK2?OyO(iY^iD)O-iM9YSCfS7hZVXF zmH4e1}2YBSwb&!qycS8tvg!u65DARmel7bEO1KxcZK9R>UnQq25 zWM?%ka;4a2;DXvdvNt^d=IO2AoZ6+tvS`iV>nL+XBu%ubMNE#Gx%0LY_Q(Yrx^_Sf zg=tG*Yo3}*M?mK~1|xd}YI%vAV5G6AP5i;1PB5I1ObA8nE}(~Yl48%fYm-TGf!mk&B*1STz-Qd%bq<7z004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00004XF*Lt006O$eEU(800001 zb5ch_0Itp)=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01m_fl`9S#000M? zNkl&!yyRpX94iD|JPs^ zK0EFf!)#Vu0aM;tI1`&@uT40K7l5%xs#nbE_V)cYeC_L!O*3rC_rhrIfypG#U&!x0 zduo)ynx+p5U`-L-&1Ti&6yG~AFiO7_)Blp6w<1IjM|x@vBFvG0OA?N~iZ#uco_ciF zIe+)S5>-wv_kdnrb?=Cp(Y>UW3+o;G-TDPLmVlOqX@SSz zLcm+DD>M+xb^E>c{;U9TA-b1|(5T`l4n>w{!AiZn1D9mM{n0R+IGC_rOSLVYb7Kfl zIiImtWg)8>+hDj9G7D=$yd|>1RJ1v6#8Fx zIsvxdxK?%o^9q&g;D|9y5umz`~Wf{l49sRqd*IV~@3|sZ%x- zm4KSJ)3-{md8`;=KKOC=tb8~gIadl5Rr+p27V2&zc{)$c$i@qxH%1hU6lPT`i$;K( z#MP?xiW=tjQ2pKd8iCy=;a%nDmuP!J7Y-Oil~Yw+vs6!x)eSM>=Yz+KLxA)W24aa( zi~O)l4ZL(-*ir}QxF^=B^RZ{9Lb@Oa z;(`wW?VNk;f3b%boz$bEiCOnbw<;?=Pl2HATyc6ZO>w<%mD-_7ef)0JrmpQC|=A zxAz~GL^KEi>*eN8M8E{W>&SlP+BZug#AdmfBLXG@nXbIo(YKA|;Z6Z!7ZET)82%{x ztJmK5JTmr3Ii;nFn*(y&db?zOSRm8Vx^f_*Vh1sqmcDlFON$Y4TPfFv%ASNXCv&;& zM>os1b!DlNFe#QIRNIOGUlRcn2vMR_|D+(mX}L%J%)zQs)Gblbme0&hrM%h}>HTlX ztr{X=T)jTM20YjyjbC5NdR80(srMH(iy+tn2+if7Z^aN$^=|sMAc0#2=NkdBY|g9v z3CJ`jZ1<11If^y}QB!W}mo))b^8CdeGTqdd>p7G>0jZWnb90@&C;z`M*;aoq?Db(Pe$l}in% zs}zr)luHpr0D%Y~0tm(gjQE@SNPL zCQEBE$dRw+TeByMPk=|J?~9mRS7dXD$@?Ja3n0_)@|5J=X6fDlkvaC~6LLAI+1c9o650c;~&D*kgS zE|jDa`>&KR_`qDMiW6*?gOyP65&2kIb~p^uN=O(3f|Z1Yv^w_C?%RFu%}nR}dfu+I z((Y<|UeCOqxBWpgZDJzxL&yZY;|8Rr~SllSGPTJf?H4CzMQ&NuGvR((^Z0z?R! zb$ID01Lr0)R@&2UY@O5)R9mYCBJY8xmf&S0w)NOr%($eM`mqs^Ham~p-IWN4ya%2n z@NB|%r{RCi(RLCH&Lf}iiVZ~Gh9?O;4;fz9JUvW;!g=)beab+D;8{`@c`FQmXAB*{ zwhn>RRWXQ48=hEMHo}bEGc)a_jqam=yCpJ+O7}lOM)T8#mouL3a=$q_kR}CD>Hf#b zZU3?1pP|!0x@XI@K~(zE<3@!Unuu<8zjaUNc|n8#qG&T2Q;QAH3{7AoEkPiS^V*zq z{*3cY20(@;p_a0j-Z7~pDt+<0^gn-Hldqq_`QkdjTpOp31z=g>)yAJ)yfSJK{+_N3 zM*l-IOZ+QZ5R^aT)x!_IkBsxf-+KXY`dsqxGyRU&Dwsaue*5076G|e))25nvO@;ty z%mS;g!}*g7!BNJ+v}oKPveOp=ogf0=dG0YZ0=#bsV6Y&VTSK(DERn`bqT&}GOTQGd z7_cS;47G-A2qYbdcsztaIyDR&Ta8GlyE5OoZ_9W|lx8V}DvHfTb=`nc$2x29bRhyY zHZ~XimO+$I{dpm&QcQ%GEn||Xc;917@bf#VlsvN<0cJO#cCpeo3G3c)_d-fbY#(>VY5a11NYpcX*|PU2fG<%Fk&+gSip7 z#bCx#4kyo|b^ugy{KP4JL4?59YgYzEl`m)s8AudxnDK;@i6KAuk^V3muA8Zc-CbyJsSS@SJ}wC!u?!9yf=L%X=^=^G zb%(YlgP!RUN^Iu&VK_hw5LJ8JNK9YY8b+Lr3X=d*L%6V8DCr=Hs^z{TcxE+(>s`zU zA!(u|p9OQmOo85^s)EU>#_VU!qXu-@05SlIPPC|7pA8M}f$A9C4zB`$_}u2xNnVE3 z5b=~)S_L2_YZFlCcHF!n|Z zx2HxD08!ko{G7(J8Ds#IC{e~|TQlOxirr(2FEN=sF$yLcsxHwOWZhUF;qaIw1e@J} z<`y@p96At<#o^v`Ng|O@mT}DB=|VUbVocIvSThoxw8zQ_Aiao|M`-h83~Oxa#lZb6AAQwYQ@!%cNVOwQF(D&?^d{PNMav=z)tWj^};@+eg6PdkA>%)JgBzFMK$bccSM~!hL{1r!?1~4EWep8jhJrtJMNT zw6IF6#xY7Ed(EZbtX(>xq>0Q#lw1TzYVOCc0srOwQ2fDfr;vK{VtTuI=YktS?e9B? z;64~1ifGLGhXMRR0f;I@5o^w!&Xrazc`msCGJkVpSc53;nMpN{G>AjxzQ6j-+FnLG)a4bbJv4|=y5(H`OMWqd4&7UJAQ55oCdK0|Py%7BA#59=S1omYYg1vmP zXy@oNMejp>@?U&o5|1bY)`B_WozLxq#K2v98I*qdiaKD@dRoNQ{b)&PirwKT%p}kh zzqfr_;2G=oi2?Aaz3@kow&HXw$F?j?VNp0Ks5GAc=HW^IzrUxuY*&VTaBp25Ng5H~ zM(URboXD|oqN(+TWoMxG z#-Eh;f#+3j^5fF|^LD=t4uXknwrPV1|5hmqsq$z?q6LE-t({P zEs7%Wh#!})Pw}42=WbTsk11OecRB9o z0H}XYktYwb3$U^Qnn1Zq3l1Fzd$!yTP3QW{fz@ZAu;tfi6c_Z9MF|P(AYWAbtO)tn z^1(*3;1wr&d%NBNd&Ncawh?|O_b>CI^xW^E^o!kkAt=JgM%n5d96YAjF!QDRE+6oT z!LY6*U`94U=Jp$){%fBF=g+T5wP9^vK3_7%6~|@+`SF!^@kPpnT3O8pN5K2P*DC@6 z&Eo9(TOjlCYoPewFM*HJt!ip6B9ENw<=#IycwBwe6(*nF6EP`Gy}~bke+SsBE|v~F zP6(#EJ^=iVB6!=npoHLz;`LOHA}%e3?91gs7k<}%^GM!Q2+JMLH6#yL~u1%)<@Zqkhr(CupP1+J|=HV?tDl6SVVC6oJdHG$@TDe z?uXL0-Qa$3h0^kt85U2gew+ zEu z65>RgW%07V@c1?e4jjW7`WNaa8hLY{UMuXc!V}wKv6tN2t`#jV5_{2JdsiE;(vprY zixz)?xcKuf82;L`ILPsweQIAZA9DZn8DMCS#1Tl2Ex3ptS@Fj&#>jj5FIk{5`LDkV z`LF&56jA-eK_r!OfA=X=FYT#_BT-yv1f_JWHeG}nBM+j0C%R_Ehen|I>KXdbRlFv+YwnZv9@M#R$l_%D{ochJt*8j&n6%T z#|EmyOac+e#))Kidr%dLf(JSh#ZK~H`JUJvn)%e9g?*J3&1)_N@8$i80EU1 zy~qa}&s1p|x1hTRhX3)0@(fwru4me65{FrD=mq5zNmZ#WWV@P;hx7ayR5brF#2qoV@uW2qVQPy8=eQe+Vq#kWbq=IaE%1Fk0QlNQFOxz9Fz1b3*g0Cf42z+52w= z_x2k^?H&SC6nlv(iPhnKWaC6s1dVGNc7QuZ8yGiUM&LC`ddV! zWeqx_z}t5SCB@N6K}C|sS+)q=j%DI=6e18+98A@4X=uvsmb{7%i;0W^Zd(GuNsxWz zLeb7lWfw~Z$NI;>6J?3x9LE#E8uaw38k+Qg$ga%Wjt(M}1{M{ItkMz>Nh+YGkP1^A z5L2r}^^JlG!zUdw8!N z1pBH*iAtZ5Cw>9`J4a(0AW&beLv#d&tC3i1Nr!v%#PQl=wP)F?Tb~7{=}r(sWwKeg*s5^c9hHa-j`o3t?mmlA8X2l! zl6yV`{Uge4en<5kIpe^DvChn>Y37d6*u=3Mqdb>`D!~G!q!x`@(~9WK-s3~D+}epF zoZ%lI0IQzVnq>x03pEx%Ffb5X=*o$w?q7J`7ZbrFGl*5zV&R!+Jigs0PF(1u;#9n~ z0a#$hv8oti;aJ2;PMpDcpK`Ht88taahU5W+cBvoB(GBNFqIlPM=q2oydSFAvRIc== z6Fq27G-SZ1BgzvcQtT|ZnBaL*DNhD?o>S({UQPi_BN=>;ZFG@%RWCtZo ziHn7xZx8|o>Dy+kw;hcD;)1u;Ag3BzTSRhrO9NP(RkfUP&DcPw!h*hh)%xI6OCb3D zLl899f#o{DL~U#X2s!jH2(0qHvB8n5cBQIOKzeyf6{juG4z05pqkH<&oZ>)-PioN@ zM~ibShDIQm(}eE7StFSifC`?FlfGMTZUT5{bhDOvuxU%uV4-X;)tai~!tUXXS&`04DaI*@>US|Ja&>&o zgH&U#B`xfxBR8qopOtY_AMqysQ?W2ww%`dQkg2A&m;aSH&{eXM4M_Z z#&V<={QL-b`C;&LF8Ct_Q7M(jP{mA~;*9sEXV9}s_}mh@=hTN91Q3)p7mSlcLK>~A zEon5lm;$kJfK(r;JVw=1N%hfZt9&KFHL4E9X3X+6IO7ybDtOx32tn_uu+Z1cNwnME zwrg|8=nu|Wk&2CYS(jGZNFpR7Duo3q7BcfAFav4hnLmv4i`#cSjP3%ewX~ibXj&5O z9}p*9;{^Le74?s;`0~~?#f=Olr^ecMf1~5I#~BTvj`lr|bWQdLQ!Y5+7e^)1cEigV zNu-IEOa-9xN}_R6Y54=2@bZx1>x`j?v2AL9^^x004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00004XF*Lt006O$eEU(800001 zb5ch_0Itp)=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01m_fl`9S#000ZJ zNkl-Hn^jD_tQTxFB#4MHZd{Gv7vJuh?WGPNyj`TCu& zrB8Askl6F}ea?CB=X^ZGUGVXki-#v3V)9dnhX@ZI9v}aIhVU4` zW1Nd$lE*QD`{E%XLrnh06m*1$K0JE3LI`aFJc-bK@bF?-;M1WW4=o-VvSVfy0U&S% z9#Rf0f(f!Ky2;L%a8&|eE*TyHc+BCb-O=Omj_isFS0MoAkdqb9RlyqrR8l9|730oK zfHxkW5pTvB0fzf69=*;+0Jw{RMCI_1^8-}Ue;i?~9U&YrunUQY<4PFAqm3A9P7(kv zWDp+nTx8W-+6`(F=*<`c2L1U6vieno-BeEiq zXDtB>$@X%?`Z&SUnzV&Ay4;7P7u>MjS@E4I0YbOOGb4nM-C$9`mJoo&Ye3K3O#6WaIpKshYxR?o15D%U%vc9MMXs~M;=)~WY}uSYFqMa!)dSn z6)RSV3kwUkhKGlLDV0hWS@VB18ckhoZEg9^ojbq7#Efx(h$dvFsKc{B%u+fAZwX0<3OAnfgp$U*GWEyLbPh zs;cUHwOTzwi2$f0ytjq`a5?3)grA?E|IM5^bNm_q_G0mZu=3BF7O&07pO1405 zE)s@-OG?At+_Y&^7OROG4-fCtr%$I)A^>D8GDUzdji3t$SqV6z4PtxVWy_YO1P2Gx zi1I6%{{Hd;}3S^#geN^r@OA0Xm(ogK~{POmzno7Z-QHDx@{PR;z8N ztO12U0JToH_V)JSo}Qk1TJtwGH9ezDfF}?jqWJ{1wYA?;(*I+}j~}n%0s&OJ9-wnU zuh(}{*nQ<3XkXz00Uk7;0E}QwO-&h_bB>LT>5d&cR)*-qgZd5ttY5$W4x6^1p`qc1 zN~P-L00EO3IVBe!1ncC+b?eq`;Pd$o-H||aVq)ScN`-lo1T5gfE~u`q?v=@8bulq9 zUtkg(;u_h@- zT3XtTxPuW8yaI9P)XS|~x87l|s45{LA=<;kgOMtoMx%L_nVFf-G2u`813-We?O4E+ zI=XxJ?)|lE*9!go{6g*B4a393nkP@5T);+N_44IQJ>nj8hBibQ;wZARviuJmIPi5~ zVBi@P9u$PyY5{@m| zlB63N8tTTRs5?75o4UHXI&R*)S$FpA*$$2fA5{KiHg@WYH(bKRI-pG)I+cHkfrb91 zy~k3g161BHo_&XL=+L1$iX9Pf2kARw0$_DTvZ;n3&5)g)y#adlp-qrY35<-4^foj! z{2f2TO_tLYpz5LcG=xkDJAw^01g9bQ?c0~GR4UhEwL&!&4&}+#*4Fa8yu9mJO=(z| z66{lx;TK~9ATt{7NF#dn>eZd0p`jaTwxydRqPn_TwPVMQatEuX7WC6yLM90SEJqHw zn5m$kVA-BMd;UPY5da6ZLztZP@#DuS*nP~Rt!}Kv z=;-Lc!-o(5wt4gB2Mm978y+8~{y|`dB^hF%N(L$9=g*(-kBp2==ka(>bI~8TyJ2Br ztG8^~GOSXmnCjMmq~|BCkOB*qddB5I92gy5xNzYHXAM;sELae|ZQHi-ix)4xWZ>gE zOl2I*5uhUk*j*TZ_3Bj)7Jibm=L-S?0#>YCxl&zPS~`F0D%G&>utKU>C~xH%eQaee#Dt{{&c|K zMF3qIm6eqb?6~d*yeYF?c+&|9-k)Oigy84z+O_M5vv@nU0NiJ+hJJ5rctO$*W-*yJY2m0lQ(b)?$sCfVAnf4NW+d4~d9~NI@=+ zl$4a;fqQMG>X|l}vsO<-n}@}IN*q1zbVb!;47j-wtD@;#YaNjpVu}DzMa|~y!TIy& zqZA570-7a=i;GLQunR_rtVWq6z~Gj)A`=%blXN!QJe#^|S0XngfIcZ$qS9SXfvW9UZL>4-b!Z^eGowfGp{?YuEm~b?esq zCWQaap3j_#*D^qK-N%==m6esj7W`*?eEhG3LZQ@^gx}ZK_xi|@BiP%{`?rzKe`C+e z{}^R6;hCYIntMO4dML8vpezUf+~^9zZ)s_{m6VjEf`jEo-cGBnI^Pil0HB~BhK%w? zMMXs`ap02U$jewA2{_ht>C&Z>xw*NGChTCdE#8i!2{3q6pS;Wy?&rL4;X*n@$<9;} ztFN!WVWwk((U*BT>UFju$f%;0lar%7c<|tF0s;a)=PD6!eL-7W+a0XyOHI^-$A}Bg z==C8`YOLZro@rDJij(TGx|+N`qKBHAJf&-Ed95)S71i0}gkL zV#sAOnOrOuOGF~k&w5Gw`}=#a+t{nsYJ0K2{;smJvIFjev!cswg81@4Ob=rDxEAg6 z01!X;5<;XbKMzFmTC&=QXbFHz2Qv7C3|QH4k*t{E=hG9R_mVNng)hzJ6ETK6O-00J zc%uKmqmhR`y=-ul7|dakCKw@J4E)~jXk%(G{s)8q!Snp1UZns4002ovPDHLkV1kb4 B;fMeL literal 0 HcmV?d00001 diff --git a/device/device_ui/entry/src/main/resources/base/media/question.png b/device/device_ui/entry/src/main/resources/base/media/question.png new file mode 100644 index 0000000000000000000000000000000000000000..d302fe7787be8853a43619f997788d07719dbd35 GIT binary patch literal 3581 zcmV004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00004XF*Lt006O$eEU(800001 zb5ch_0Itp)=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01m_fl`9S#000eC zNkl*%2cK@eOU>-Ixu{Dg@1hHLNiJu zgb3HsJB{>qBMrC#1qNy8^Dg<533{5iFvPF`hzViD=(_`Hz;hFy&N=gy@AoCj0{{`u01sw=LTS|t&;kftxExBR8Ebs`FYds0~a#)9^~iCHax zPmZpbmrhP?pa*`8?YYOy@i4KYQ~E1ko_|;0_^c8DJJhlG)WhT$pHw(cuepC_{!`~C zs`nMcal0;nvF>HdQ9u{nDO8B_BolE0q&SNx-bX2hB*nKM)jY zTx5_h0Whuu0WE}>S_%_73>pGE>ci2#87Tm^^K)yaenA@bNM1G`#TgRV3qp!sCmbZt zzIZ&bTnce3J?<)cXGxw{crK0APq^hDToJ%0-8Z%v!gz(Q-fZ9g3Sv6Gc&TPPKDI-l zMTTEIm=EeACY?)zvA(|Ty+O|lU^Ei?5&harKUFrqegXvkdVa9Frxdt+EYf-hnJ5MX z%k7v$3J5!b?fv^aDS(WlP5g{rK566Irv%C!b?yCcdcp0+kAzl&fnCIo7R!+XJWYYz z6HY0NBLXmSeN7@l7rVpiS;?7p)YVrV^hzXp(TwO>wt=*KH@ys6_^ICW|Eapu36um* zX#QwCanzdm(38g0<^}D49`-T_J^%Y}1e1Y^=jqAXtb~vjRd-F-IYSXN-xb^G{%iRhz0ZG1{k;2hX-C4GmJXBV}m=wSN{pO9Pboy24tb; zv$>|G3{2QO`A@@FjXo2Jz2~|BILM-Dc|;rC zW}Q>8t?#^;CDauex;)ydCNOSM7(aZAVD3*YMM{@_E`1i((Wq9S>mM7~gGb~2hj zdzk+@v5lvha7;pSHL<%v@fE)maLc3Nrk7Y?uJ`~mx1&!l#M5ZpUd&W(v56sPAep)? zn>tZUe$nKE$-Ml5Z5V21H38n%n4Scooo~A7aLssQbm|Te_#I)0#aTtjq7xja5cMob zF=&7|R##iq=VqX1=DNUmM~99G4&7`wiwbrherzQ1jxME4IDfNSINxOa$kavve4PH< zU{kGzNFaOCSoxw`S2){QYzjIdef4~QPm@NSv{;5A&`iV`Q*+{o>iNihMh2xrPtssQn5Hg<88 z=6>%laq>x9HPWAFq^2I6mH-%bW*5+vkdw2TPCwM>k!4(n^&8=QrNr}Yn4{Y8z+ru) zjE{ZXy`rXI0dVPv&{Z#>+~G9RV23NxCi8PF=D;8YJ)4BYtLnm0;2$yt^`t2G(TBXg zpc|8)SLv)6QqZ_1?$CVH;D^baS>l{pmaW@S(Fr#eQ!;2r7iZu&LAFh`;pkzNDWG3G z8xOL*CzHcM461wrIp%Hj>gP_5JGd^|I~ED`5wBJ#5aJfSv(7OCxMB@;OLd5Q+<==B zaEs>S2?Ox9%yhS4MqB^uG9_+cfS~!xLu^`|cggUq1_*>mDHi7~0JB_BJwt|T>#H44 zFOQ8(u0?IK3xAwXkT-vQl;eQ=bY>xIS{?QU{co7@hngP9-v@y9k6v&i$^;2FX(Giw zJDPtfv56#8q*N4oe~lZk6byVpbH5Fyf3qt9u2R;PY)E|3m5A3+<9&jo$EFbnys8P* zLT)@mN*FI|KtLmx+S?WYmxybjr(1P98t0233U>DXWU5zluLd_B!HkJeD04s*5U#Z? z0Itth`(F85;T|<;oNB?oNgkZIvomNw~S00Z??>c`qO0f?dr=u9ZKU2u!UwRdeS~wpa=vEl`8~#R)ex zr?!3IX*0q_afbd&f`+a0>#Kh?ooo7q)a7D^+?@vyRlaS=kYyxHOt?l36NT$rd9o?q z{Z0jTWAVfTc`?X20gw`*BGxdjvEhWVQSE@m`!mTF6Z1KRzF3V&(yzM8elRq=l8tsy zkXiuJ?P=>A#k)gndbaDJbg*vDDL3y=(D|Guh0|zlg3Num3fX}i_B!J;n?e%I_{W95 zZ00Gq3xnka7&iP$6xm=Yfb1q{h49jzg@kPHKN*?ahLCvF0^`5wMjq=@O8U|XpQ_>q zah?qmq+b1Ud*@2Qw*Ivnn|a+B0d`q1KSmgRQ!&co^{a%J^VTnBFlBevzilJ3jY*Vu z&R>7hbAD{b3c#%6H;g}6GoOoujoa(mqn`9(Ggbf>0)gF-dy^fR8r| zwu-}guk3E&6(&HuM-Rls%OK^mwckB?rWE7Lkph{p768CFL7~f?1#g1Z*T58&Nb`TF znY}3xa93?x{~IOMYT_07CY55utx=GnVR9N5c?fuh)DfuIZ!Qg%nsmHU@K!3GBBqXC z)a$h+B4vsJk~ubw6p;*_Z&L79+6|)t@H!6(K#6=ZsbCM7NH8?KGOPtaG@zUThFBuX zWKRH^4d0;P?PUV+88D=|YO_1_G_#8`D~hNT0Qax66u`XtiZd#>L{w8#lL&d_IaTBH zL{mQQ%%Vx>B)EB?r2sfrLQ_Rq1SJKm@r0HEDE1@ZelLtI(-uc*exj-k-Ri zukyk}anvtCeiO+lbc*Fy&%7>@QR0O2qF#5$Vl^@`KJEcY&*^K6n?2l*2>_w!p z|J*K9Gh;MxPE+TY4!}cYSE~V;+z3jo)G_W=YFhx=$m@VQ4S)~o>Kx1XQ(aHXJxX=! z5KwAy1&zE@`CpMb7PUv3Du?p3iY^-zo%rjHnUHc zeH7VStfIi@R2qzq(l!RcM>Uot8Vu_T+H!7DG5~ZNF_w>Zx2`H7^Lh6q-@kz|H!F!m z+0XmSN`n25gjOmQHIu^GV*y+B^y#OST$!w13nmRLg)cCK(IwQSlYY{eu9BF319oXX z8s?VB1q*-!2Sm`+XF|*9NMb_?99@o9nAlwG;qZQDeJ@hzv|5d`+$Zb{R36k-DFcYE z9IHRGo_Jjvh1uj!r3~ZThuTKuoSQP;PvlO`$3Y+X_ZHXJi5$y}P}C*JWFU~Mg~C&I zI>*@R6;T_k+?QiBa$CH+_2J^>T4aY%C*x8=Vn@+TPfutg{nD&U!yq^RwsRd@4V$=G z(f06_eV@n_GffHH=n5F zjN@6oKaj`WXa(RE;%7?+Dvs-z&rQCizj|jr*UYPD+@51YDVqN8BA_Pe1PxN8mTn6- z9nGh2+Dsshd8Q^4N9pCaG+*JaVQcEvz55T>1n{{HeyqLm-}Lg^8l!gYY!d=&eqz$> z5WHF|AcU=jG^j18Z|F0T*k4Hq>_1j$g#gLb+rkTvbwxg$n90mHvA$3I33SgYG9A@} z;r?BPp}GR8Hdysb6e35M4J|(KogV2~I%BIT80};nZjMtoQ1tQ&U7`0VxOX%|(@m?J zj#gBpT1Y#-s`=U+5ODwYyBkNj7xPSFzS)#712t=NF`t`vQ@~ZQsV!>z`atr+rX0K1 zy#zD&>VvHRGvA5!^}gv2*yc=N`kLYM{b2juSANvl{lVWnsr8X%_oRJ3ZVnQ%Vm@nr znUA)wFkgWOQ8q$ggI-*{%=L2eg0HV2O~ zG)NQ448(jsBT+ueFmDH4KFqUZ0{EZ(EgxrFS^<2~Ka>x2E};M}w%z3;&Ak(eW?B!l zaf7>T-#VujcM@Em&n4nqrObu0UO7t9Qv$fuadZDVZlRXx;$`;QX`~T1X1b1C=FBI| zeYm_8)ZC3|#CT5@Zwum`DaCN8Whzsd%2cK@004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00004XF*Lt006O$eEU(800001 zb5ch_0Itp)=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01m_fl`9S#000Yf zNklZ!bcLp;6?P=4lSR%*rZ@- zJsADh;a*EzNa3cT)@;o8J~NDfE>#VOsJ-8mD`G(e9X53RiWjRg81CIb5b1W|;I9~( zzNOrn;47Qi3BPzDhwx$I_#hR6nc6=De$w*dzGm{)pUvd*-}nRmIDG#ycuC=0y4bHM z&zi~p8?%mpK)821sG5__AN3KkGOoA(-YdaR33@CL>|H6)d=Y?uJ}C8AK(LR)HES-JmbC)@{1V3|xA@hTelu}v zbQTcMCHn))b(qvwnEN7xh7^o{7@O$)N4Zv z?PjGkJ)!L&<)yrp55jMZM%x}&AnjqOvVS#=wAedlv@{s1*;S5_l#GD7x}$Zl@(=QS zBP^}Wl@WdhVGJ*2_Zf_=1@1&&I9c*dV#&f#H5}dOb zt0C&#tA+qM(9{ise2%qp5_(5FsxrVHVMJCMTR2gX6A?7DZg@fjL{5XC;CX4b_f8lb z7YuAIXUL~f)dKI!?8XbS<8{>Ef!Y02aL$os6(t~O#A))2wdpd0vhPZ@ir>)M2Ov6l zgBERy_?kt;vOcs^?Ui!E4p9X--}aSgCIPfL6w?=4SXyWyvOJr{a9J)Tzc93p13AsL z*JB3Zf1C5xM`)(uvmKfaehX!O-lZ>vLOn~;NctJV_5wmVF^!AIM5|FLsQaeXG7+ggT z7RKL5M%x~sS%yGu&st3IWyCM78j@vwFBa>(UvwBs(+fmi)r~FbJj&R^Ic`6j2-fUF zu@927jevBr|DmKbT?Z1@z;do*nR$Zo_swV4zr&ipYXWFx$+WCKP?;YT;5$rneAfXq zO93SOOAf8PhAfGO-_J5GnJb&A6zn$!3JOEPnVcbrTt@stMlRQU9?f#l78j@F9$=4c zy7SWjn$vl)4_w8D0{09C?0!!qf}zgYXnt6l;0rW<437E740$QUlIS&kuAQ(Nj`U}3 zeJ z9Gi{NO)-$tWGC_AmZ{-5O<5#TCXF6{?P!9sy_&yrFgwFOi0k8e$M0yJRfv3Q#BqB< z_BSsCi7#O!f98TSKEu@j8fqG*dB#CI2K*RrjB4!_5j<_W%I-urWH)^#OmrAF8n>~P zJ{ga9j#dK+K4cwBBfOQdFgvJ- zwerjkUHO%0-^)0*NUKH2)f}O47!iC9!sbKOpao#YZ?n?^(hJOeKpIQi5GR!kHh)W{ zis)4Mpp{46f5J|LRh$F~;d|`ik%gR%82Je&VRTB@JBBJO{5*HxlmxpmpmGEdi&wl~ zFpU5hEVcWKm>Ud?PgY_Aw6;)~@c)N{l_Wv03GoQ1pGE-T7aHzXBF?8(C4x}|6<@PB zSTS?N_m?uu1iipf<-SS?ADrnK9Q74)KKJQC*jlWyu-(x zf`o(Aq?J{={LVU!r2z|=5tCv)F6+w2=$w=AeU{b8-TV;J=tKgoRR?Q(_Lgftw4Tg) zYm`L*lPzOBFUI;%g5~wDWfdtw~ z!5P5=76Hukv04|HUsB<_u=#FTxE4Ign7V^#1cTdZTatFf9YjI8AnM%F+iQCW1 zN)t1&W+waJa`SmwF0_v?Py!#r{VxY(xm};w^GQ)F-;%DuuV3R_GHhtEEv%;-B^5etjjR8;?=&0`J@yGR4m8xX8?Xp=Fgw(>;LY)gJ%haWeQXoa|4>#M>*3?Pr`+ce zjJNs0T=~@1e@iF&{$0+=7i)rw-QoI_=58e6--2)#JLS_Lu-Iv6?dKim!g~X3*3Lk< zrrzNJ1Q~A7imi038jgS|!FPPbdOz(?k7qKd)qiGZPB0!(O55oLlJlcf_zA83M9#(J zWFr$=*G88oTs6Qg8z53TgiIN!VfwX^LZ?j|dQT;SH3z(Gfyo||wiQjP$L2&lM(g3W zIP-Xt#iVA((AwU0bFq}mQ6)5eGwlH_pO9m z!goPHh6F?FcoiJ;x*X*|ud&s;ufnCy{hOJmBK2?OyO(iY^iD)O-iM9YSCfS7hZVXF zmH4e1}2YBSwb&!qycS8tvg!u65DARmel7bEO1KxcZK9R>UnQq25 zWM?%ka;4a2;DXvdvNt^d=IO2AoZ6+tvS`iV>nL+XBu%ubMNE#Gx%0LY_Q(Yrx^_Sf zg=tG*Yo3}*M?mK~1|xd}YI%vAV5G6AP5i;1PB5I1ObA8nE}(~Yl48%fYm-TGf!mk&B*1STz-Qd%bq<7z004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00004XF*Lt006O$eEU(800001 zb5ch_0Itp)=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01m_fl`9S#000WF zNkl6@PDL)+|MuZMfn|{lh;FY8#Dvi2*|za#zmNfN2#xZm(*+B!+mX zC|EHf5`vYaG?Iu)3}>hpHMfTbuW%5f2Oh*w!AoyVF3>*?dIhapD)m~V*>-2<>wB|% z=^1x+XJ+S{8Fe14yY9|>^L_8V@4er9-}}A;h$*I+Vu~rIm_i|pYR4`<(bb?EVhadZ zPQ6@0Uumuy5Ky3hCuxX-noy3kesStrEC7cz+?-h>OgKcnYxJqf+X7%~doFuCYGYL+ z$NY5HDq_|EG5@WaV4P5|5EM$C)c-_FEr1k9kB^7b8r8`-eDl{fdRfk8e=UYcc^jg>J|NMG8 z2;4*eogI$nhXSeKTiV0P{jDR%3pL01WJhD6s3w!CWZud3fe2uDapy__xS1UrYt?j2 zMQ|@qjZgARAiV_sMN#0IL7x>v&^@qCa?s_5ZUTw}La65u%oL5MiICD_VPXTlH&Q?6 zGkl$K{cU31W%~X|TdsdF6alaX7pDp*sDU-`022x3Od^$B?{!9~-k9H_=~Ky%IKc%E zI1vwiYoh?KZ{XN&t$2hh{*{m@4@LVZQ4o z-R)Qa7t)y)3+u;J3l=RcPBpIcRRAT4inn9#I-Lcui{m8U1fbuVvx%Bb$EFTgkLA(+o^3kb-S%SXrY2UkXxmJdCCNUgT zz}!aAa2*7^*mI}eD9!D;4|ASofU4jQ8bC6no}VM`_dJ)&PI|e$Jm?oFI^C-)`XSP_ zQ1Vs?24@Xc+w!>D&FdQ%9;3JK1g`Z${CwHff!9vu$EWITtnY7M{_8~ktAP!39{rq5 z*U#vySpkb+-ir&CyxljM(_J}XYoVO%Siq6wHG%L{+tq;~zuV?EO;bFDR>5wtDGFYw z@+A_M&3psknf9^lsH|r!|6*Xg2+&F&C!#WLTOTmXFei0X3jsM`ou0G$xv}c@p0kZQ z8r@_{o+XjZC#lUV_m9!gD1E-98R~o0O3~S;(Cl8VDEJ2mW+_xp7LA*pP|$^R=2Lq6 ziTub>JYUW1oO`^ZNi)?w&Jm3mG!%2^ia(yV)pTnq74d8U2x)X#F8ib>6ND&^1fU^9 z)tG;Nao0N4P`~6%qjP5AD!zyBt>zO*P(#v{3&jD?k#O7C0Auj1%;`zcV5{v6Z0>xO z2JkyiW{~codrw?g-1Ua7{WZ0%<()LL{Ms4jVO^<+gs#6_jzT41WXRg#d`o);?yK8G zpm^18Ca|wS1X&6;kx;Rx?pzRu`=$^bWk=_)hERlSXY&z6uV40=+u}AW{bqu4P{z!r z2)Lv5^ZuK43t$?)5kBAPcW#H%3zbcSy2n@aMQy33^4lsA^SIA*duO%ugQqxv@cB_P z87F=0a3Q!aoFkgt^zT&X!Q7{Eml^uVjjZi21(FR zSB+OHBbCa1@iMCu_)w3Vq@gPTus<*qW3?~-0CC*r`)4JF+ZAIPQz#QYv|V6d*_u1< zDu3lV6;Tdu>P{DMKHp{}^CiOGCGPs-%FkS8U+MO?&~ic3izyXIrT}KU?2B}Y)-K5! z`KkMnLK6TlBij6QQ{rQ9yX;H!1W*cSMBNnq~8@5O#~^pGjjs!ljB#K(on*wv&o3&br-q=1KrYLgC`9h5qn@s~0LbfLR7F zgX%K)&uayhb)j-!JPc}q0fj?S1Tfs(nW5mR+ed!1RH~tDy>dEFk?^4Z>=%Rf3pME5 z9%YW=&!;opz6zjJ6%FtDicq+E?AF|+6$2y1*zIeYnA+k6no2*FtC@YRAwq!O@|t@6 z0${tZDA+G!PXMBL#V*)G1=jnp)iJIel40$LXO8?qO#u`&trsL!nbCC3e4Nfy z47}RLvS$Fr?z;Vfz&Gtq%CZuF0%YwKZYn~dw=My&-RJ#JGJ`I%X|_&l+1S9Fh|uN9 z9ML^O;QD1(`*+)2VijyjEIQyFq|8Il0-ynG4j>j;$qVFmKk`g4RAUh@@41)_LwryB z)qdObdFh!beWM}qa7PUx+1q|yQ~w@RSqc|q)v5{No$C63)s6J=>GDa64KpHKM`bC~ zpd!e!6b+K!x66{+qMAZNZP0?N3)RSEoF#woOto52hN$%R>v|r?$$ctSznOx<*6uK?T#Vbi_0 zMTwbvye*e~p*s4gL8_|k)(vw_=*JV3Z1(~Lv5BRjCsZyTF5x`uXOm{EBb^G0^di9P zHThfXg1#H$|Ndp;B7;7*LriXH-|}3xW>(ixHdIR`4~MX`BdeHLOy2q-6;oxov!_)YXPux5g0q<3i`j_OWMR!Wn6o%{}@Mk zG1I?c{xHGLqh9XxT$-q+l080e*tl0|NJ~BRyw?KXy5n}<0EZyf0*a+~rYo#e_T>Lx zcpVY=WYbABVFf*z&mfu|5hgP84iJJFQb05b6B*c4SMBmTKh}S4wpg~fK@FZl%pYx{FvMQ zT=%5_m`q`w3OdChgz|nA#vTa)Sex4f3SICGO`2Jom*uh_Ms2*2{G0MjeDJkyh%IC` zI)RTb@Rlzkc#A{YEd5})vO1=iVu~rIm|}{lME?V3+Z-(Ey4=zL0000EaktaqDgL{%jFPiMys^Vva64 zI_q{Qd5BcAPId8Fm|)etMeN|Au#Fv$^d9co5~h^NG>vPS#fe7oErNfT`FskGiiR)Q z@n}NHYw0|_(6s#hXV1Jl`$MiK=Em7~``+LCv*&M|h#-rj00d1{+EzGO=k&A>A4N7c zWhhB?WV7noI`RlAyYLrwyf{)=^F--p%(JRxH-#oYCjgAHrAUt zA|Im2wuDl?g4raMpOcoo)Nq-{tr|7OD{JMU%2!5@ zBq#mud2&qgW5^DLniF|PJ$)Cdo$hD+q+`dvAao_?o9c7>c?9MQ*bCMkF+6tUVo!D4 zr6u{w?^fik30GIMIcR%Z@xz=)iyYRNTRtxn(3G6 zVl6Q5Xm!K93;!foZZjUdKJEJJ+m8|2e2|BqiA09k%w0vF? z<2%W|_nx~qFF0LqFei?)VR=MD>fw*^eBmN<=S*#TZ*!DsMxFjkOP@1aR_~Ex@Xu!A ziAkNhd2K$+fnc2jiyl6FoU!i#_xVfKiA_ua3^2SXk8zXE?nhlS{_+0PnbUG)^9qLJ zhvqKlHED^ipI_0%YLNSMHlMKZ-D(jA|BVc_hYP2RviI{GuV(xp!d;MI@sBC_!J@^5 zrOqdgssmm4t@zmf?|e~L!~CwlV>$3yWYVe*`5!y-En;7#ZeQs7bnlyYk0kk>A6T|7 z(Q?`wY-=#5dg(5uj<+w@EkE|iaiU9MZ&l$!r>pt<<`^b!V|t|dnsL^V?I&vIE-kD$ zD85xr=)95i#^)E8m@Ug%@$_k0gH7tzg_*Pd`A$l?wQF72j4RArRyk$>gW<&!w^aclSKB2x_tQ?!i<T^GteqIv1 z&ut!8$*Jq7C;R>E@T;_X+U=K7aw_^!WE$Us;Cp(&@G3cV`>Cc_gJ0!tptCM$=1*U4 z%J^c+^5A>k^Olw$Vqsmt0K+bKf%5*#_pFC0jA+>R&|>bVl2g?ugLN3@ek!Y+*W~we zR)wGAa+|rIS{;|yxGdk3I}d0*P`<&h&#$uE=H-<2Gl5PkIVB9sIDg`Qu-kt-c(!hR Sr2sH1GkCiCxvX004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00004XF*Lt006O$eEU(800001 zb5ch_0Itp)=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01m_fl`9S#000FP zNklq!$SlvTI~rwK$?w1Nq$Id!(fo8Qd#QkQ1K%7Y7jW99C|V9Y+NJ z2lvU^U3sY(My(hBajC!!UeSE|-7WercZLO>RrF1>(_4b~BX_Br*Sa!fCI` z9>_>xl)B@1UYMod-zcs8ln`ygKHHp7vb;HucD@)A?y$ta!6Gzs;$t0c9+RywBL=$wIkZuJ|ALjIptTPl5^&04Y{S?9@ZLywrHUz z8g}SmE=rl}9{M_BZ=*fEIqD zN%w+9NbAU{1GE81y$=Uyqo2{p-%S?RYRV(~6oy6}xpQ*Bf&fSjHV)8=iN?s^Ll)N> z$|H%C+q9Pc!-oS#EE7x@S<p5-6T4?>0rUMQ};vf&jE*q}Xna5f{aM)0&*6e^+ z%KldBdd(3hbkb%gS$|NSF{;RCu9h$5H2ka!^f$zBd?eUD8Sym49J@yku=ejs2itAP;emHb+oC8F8P;KkBm(`x__7dcm;@PP%PQuQ6uW9eHy8B>4UZcbR@Jmpa4LZrTj^65 zbUJ`8XsXpF>w@Zqv)XfjB;GTImJeM}JkAJrM%e0t8kA$u1?jq=dP&N?wT7VWTIhi; zC{Y)*)kh9kt?Y*`XbPGxXib_9fG$YqViK!-I6xC-K^HU?hBRH!BHxuT;2F>b`6Nt3Nh${GE1D$1L%oB&@oM1NxQy&;?E5uw!y|a=?JHAG)9* zQ5W>qM3#tfrM%d_r#=|gN7{vbrSLlLtUC=dZj4ynIAV#+#p+FZj z9UKvES=A5@H$4(mrz4Ywna~AQ7xlU8)dBJl9PhArj~S`zg4%2nOM$_YQ|&Haa9Tx4 zgu0w~mIW4X`ij=_xgKy`#;TTu+&CxP2#U9YUR=UGPsHDu$(L(g%8+M$c)F68Gh9?K z;wv&byhY@Ho#{nsIIorEWM&3XM}%~p*X)@cpp?3^BfBR0000000000fVqX= Xv^ZP)4>GDO00000NkvXXu0mjfC=z=6 literal 0 HcmV?d00001 diff --git a/device/device_ui/entry/src/main/resources/base/media/test_times_count.png b/device/device_ui/entry/src/main/resources/base/media/test_times_count.png new file mode 100644 index 0000000000000000000000000000000000000000..accc8bf7faa31ca02786d2a415f1b697fefb0e4a GIT binary patch literal 2542 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 Ef|Na^oB#j- literal 0 HcmV?d00001 diff --git a/device/device_ui/entry/src/main/resources/base/profile/main_pages.json b/device/device_ui/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000..d748040 --- /dev/null +++ b/device/device_ui/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,23 @@ +{ + "src": [ + "pages/MainPage", + "pages/StartTestPage", + "pages/AppSelectPage", + "pages/SettingsPage", + "pages/Question", + "pages/ReportDetail", + "pages/LightAdjust", + "pages/FloatBall", + "pages/TitleWindowPage", + "pages/LoginPage", + "pages/FpsLineChartPage", + "pages/CurrentNowLineChartPage", + "pages/ShellBackTempLineChartPage", + "pages/DDRLineChartPage", + "pages/GPULineChartPage", + "pages/CPU0LineChartPage", + "pages/CPU1LineChartPage", + "pages/CPU2LineChartPage", + "pages/RAMLineChartPage" + ] +} diff --git a/device/device_ui/hvigorfile.js b/device/device_ui/hvigorfile.js new file mode 100644 index 0000000..5f2735e --- /dev/null +++ b/device/device_ui/hvigorfile.js @@ -0,0 +1,2 @@ +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +module.exports = require('@ohos/hvigor-ohos-plugin').appTasks \ No newline at end of file diff --git a/device/device_ui/package.json b/device/device_ui/package.json new file mode 100644 index 0000000..6cae941 --- /dev/null +++ b/device/device_ui/package.json @@ -0,0 +1,14 @@ +{ + "devDependencies":{}, + "name":"OpenHarmony_SP", + "ohos":{ + "org":"ohos", + "directoryLevel":"project", + "buildTool":"hvigor" + }, + "version":"1.0.0", + "dependencies":{ + "@ohos/hvigor-ohos-plugin":"1.0.4-rc", + "@ohos/hvigor":"1.0.4-rc" + } +} \ No newline at end of file diff --git a/host/doc/compile_smartperf.md b/host/doc/compile_smartperf.md new file mode 100644 index 0000000..3f583c1 --- /dev/null +++ b/host/doc/compile_smartperf.md @@ -0,0 +1,93 @@ +# SmartPerf 编译指导 + + +## 1. 编译环境搭建: + 注意:在linux编译环境安装时以root或者其他 sudo 用户身份运行下面的命令 +### 1.1 node 环境安装: +##### 1.1.1 下载Node js安装包(windows推荐, linux跳过此步骤) + 从网站 下载node js安装包 https://nodejs.org/en/download/current/ +##### 1.1.2 安装nodejs. +- ubuntu 20.04 与Debian 11系统中, 直接用apt-get安装,命令如下: +``` + 先切换到 root用户下 sudo su + apt-get update + apt-get install nodejs npm +``` + +- centos 系统中 使用yum 安装,命令如下: +``` + 先切换到 root用户下 sudo su + sudo yum -y install nodejs npm +``` + +- windows系统中, 用安装包一路next即可: + + +- 安装完成后运行检查是否安装成功: +``` + node -v + npm -v +``` + 出现版本号就代表安装成功了. + +##### 1.1.3 安装tsc typeScript 编译器 + +- 直接使用npm 安装运行命令: +``` +npm install -g typescript + +备注:如果安装失败可以更换npm源,再次尝试. +验证安装完成: + tsc -v +``` +### 1.2 go 编译环境安装: +- ubuntu 环境下直接使用apt安装: +``` + apt-get install golang-go +``` +- centos 系统中 使用yum 安装,命令如下: + +``` +先切换到 root用户下 sudo su + + sudo yum -y install go +``` +- windows 系统请自行下载安装包并完成安装。 + +- 安装完成后 命令行运行验证是否安装成功: + +``` + go version +``` +## 2. 项目编译: +#### 2.1 先下载sql.js的二进制包,: + 从如下 https://github.com/sql-js/sql.js/releases/download/v1.6.2/sqljs-all.zip 获取到sql.js的二进制包. + 将压缩包解压后, 将文件放置到项目third-party 目录下. + + +#### 2.2 先编译获取trace_streamer 的二进制包: + 参照:smartperf/trace_streamer/compile_trace_streamer.md 编译出wasm 、linux、Windows版本的二进制文件 + 将获取到二进制文件放入到项目bin目录下,如果项目目录中无bin目录 先创建bin目录. + 然后将trace_streamer的二进制文件放入bin目录中. + + +#### 2.3 代码编译(依赖于上面node环境 和 go环境) + 1) 在项目目录安装项目依赖: + npm install + 2) 在项目目录下运行命令: + npm run compile + 编译成功后会有main 可执行文件生成 + +## 3. 项目部署: + 1. linux 版本部署需要给trace_stream程序赋予执行权限: + cd dist/bin 目录下,执行 chmod +x trace_streamer_* + + 直接运行 ./main 可执行程序,完成项目的部署; + + ## 4. 访问项目: + 在浏览器上打开 https://[部署机器ip地址]:9001/application/ + !!! 注意一定是https. + + 备注:如果未出现如图所示网页.而是显示 无法访问此网站 + 可以在window cmd 里执行telnet [部署机器ip地址] 9001 + 如果显示端口连接失败 可能是防火墙未对9001 端口放开即可 diff --git a/host/doc/compile_trace_streamer.md b/host/doc/compile_trace_streamer.md new file mode 100644 index 0000000..b49e34e --- /dev/null +++ b/host/doc/compile_trace_streamer.md @@ -0,0 +1,133 @@ +# 1.如何独立编译Trace_streamer +尽管本工具(trace_streamer)是在ohos工具箱中的一员,但你依然可以独立编译此工具。 + +本工具可以编译linux, mac, windows, WebAssembly版本。 + +本工具默认编译方式是使用gn ++ 编译方式 +``` +./build.sh linux/wasm +``` +如果需要编译WebAssembly版本,您需要在prebuilts/目录下安装emsdk +``` +git clone https://github.com/juj/emsdk.git --depth=1 +cd emsdk +git pull +./emsdk update # this may not work, ignore it +./emsdk install latest +./emsdk activate latest +安装之后,您需要将upstream目录复制到prebuilts/emsdk/emsdk,node复制到prebuilts/emsdk/node +``` +安装之后,目录结构当如: +``` +prebuilts/emsdk +├── prebuilts/emsdk/emsdk +│ ├── prebuilts/emsdk/emsdk/bin +│ ├── prebuilts/emsdk/emsdk/emscripten +│ │ ├── prebuilts/emsdk/emsdk/emscripten/cache +│ │ ├── prebuilts/emsdk/emsdk/emscripten/cmake +│ │ ├── prebuilts/emsdk/emsdk/emscripten/docs +│ │ ├── prebuilts/emsdk/emsdk/emscripten/media +│ │ ├── prebuilts/emsdk/emsdk/emscripten/node_modules +│ │ ├── prebuilts/emsdk/emsdk/emscripten/__pycache__ +│ │ ├── prebuilts/emsdk/emsdk/emscripten/src +│ │ ├── prebuilts/emsdk/emsdk/emscripten/system +│ │ ├── prebuilts/emsdk/emsdk/emscripten/tests +│ │ ├── prebuilts/emsdk/emsdk/emscripten/third_party +│ │ └── prebuilts/emsdk/emsdk/emscripten/tools +│ ├── prebuilts/emsdk/emsdk/include +│ │ └── prebuilts/emsdk/emsdk/include/c++ +│ └── prebuilts/emsdk/emsdk/lib +│ └── prebuilts/emsdk/emsdk/lib/clang +└── prebuilts/emsdk/node + └── prebuilts/emsdk/node/14.18.2_64bit + ├── prebuilts/emsdk/node/14.18.2_64bit/bin + ├── prebuilts/emsdk/node/14.18.2_64bit/include + ├── prebuilts/emsdk/node/14.18.2_64bit/lib + └── prebuilts/emsdk/node/14.18.2_64bit/share +``` +之后调用 +``` +./build.sh wasm进行编译,您需要将sh脚本进行部分修改,因为这个脚本内置了一些库的下载和解析方式 +``` +本工具还支持使用QtCreator来编译。 + +src/trace_streamer.pro 是工程文件,编译本工具需要依赖Sqlite库和一些基于proto的pb.h文件 +## 2 准备工程 +### 2.1 基于proto文件生成pb文件 +您需要自行下载并编译一个当前系统(linux)可用的proobuf/protoc程序,此全路径为位于out/linux/protoc +src/protos目录下有一个protogen.sh文件,运行该文件可以在third_party/protogen目录下生成项目需要的pb相关文件 +序列化二进制的解析依赖于基于proto生成的.pb.cc文件。 +在执行protogen.sh脚本之后 +你的目录结构当类似如下结构: +``` +third_party/protogen/types/plugins/ftrace_data/*.pb.cc +third_party/sqlite/*. +third_party/protobuf/* +``` +### 2.2 获取第三方依赖库 +从 +https://gitee.com/openharmony/third_party_sqlite +获取sqlite3目录到代码根目录的third_party目录 +从 +https://gitee.com/openharmony/third_party_protobuf +获取protobuf目录到代码根目录的third_party目录 +之后,你的目录当如下所示 +trace_streamer/third_party/protobuf +trace_streamer/third_party/sqlite +# 3.(linux和ohos平台)使用gn编译TraceStreamer +在编译WebAssembly目标时,需要将sqlite3和protobuf里面相关的ohos_xxx_library统一修改为source_set +## 3.1 准备gn +在自己的项目中使用gn,必须遵循以下要求: +在根目录创建.gn文件,该文件用于指定CONFIG.gn文件的位置; +在BUILDCONFIG.gn中指定编译时使用的编译工具链; +在独立的gn文件中定义编译使用的工具链; +在项目根目录下创建BUILD.gn文件,指定编译的目标。 +``` +cp prebuilts/gn ./ +``` +不同的操作系统下,你需要获取不同的gn +## 3.2 执行编译 +./build.sh linux debug +或./build.sh linux debug +./build.sh将直接编译linux的release版本 +build.sh wasm 命令将可以编译WebAssembly版本 +特别说明:编译WebAssembly版本需要emSDK支持,你需要将build.sh里面的相关路径做更改,以保证编译时必须的文件是存在的 +# 4 编译Windows版本或Mac版本 +## 4.1 编译依赖文件 +### 4.1.1 编译SqliteLib +使用QtCreator打开prebuiltsprebuilts/buildprotobuf/sqlite.pro +### 4.1.2 编译ProtobufLib +使用QtCreator打开prebuilts/buildprotobuf/protobuf.pro +编译之后,文件结构当如下所示: +``` +lib +├── linux +│ ├── libdl.so +│ └── libsqlite.a +├── linux_debug +│ ├── libprotobuf.a +│ └── libsqlite.a +├── macx +│ ├── libprotobuf.a +│ └── libsqlite.a +├── macx_debug +│ ├── libprotobuf.a +│ └── libsqlite.a +├── windows +│ ├── libprotobuf.a +│ └── libsqlite.a +└── windows_debug + ├── libprotobuf.a + └── libsqlite.a +``` +## 4.2 编译TraceStreamer +之后,使用QtCreator打开src/trace_streamer.pro,选择合适的构建工具,执行 Ctrl + b 即可编译 + +编译之后的可执行文件位于out目录 +``` +- out +---- linux (Linux平台下QtCreator或gn生成) +---- macx (mac平台下QtCreator或gn生成) +---- windows (windows平台下QtCreator或gn生成) +``` \ No newline at end of file diff --git a/host/doc/des_stat.md b/host/doc/des_stat.md new file mode 100644 index 0000000..e1048f5 --- /dev/null +++ b/host/doc/des_stat.md @@ -0,0 +1,413 @@ +# TraceStreamer 解析数据状态表 +TraceStreamer使用stat表统计解析trace数据源过程遇到的重要事件状态。通过stat表可以对trace数据源中各个类型事件的数据有一个基本了解。 +## stat表支持统计的事件列表如下: +|event_name | +| ---- | +|binder_transaction | +|binder_transaction_alloc_buf | +|binder_transaction_lock | +|binder_transaction_locked | +|binder_transaction_received | +|binder_transaction_unlock | +|clk_disable | +|clk_enable | +|clk_set_rate | +|clock_disable | +|clock_enable | +|clock_set_rate | +|cpu_frequency | +|cpu_idle | +|hidump_fps | +|hilog | +|ipi_entry | +|ipi_exit | +|irq_handler_entry | +|irq_handler_exit | +|memory | +|native_hook_free | +|native_hook_malloc | +|oom_score_adj_update | +|other | +|print | +|regulator_disable | +|regulator_disable_complete | +|regulator_set_voltage | +|regulator_set_voltage_complete | +|sched_process_exit | +|sched_process_free | +|sched_switch | +|sched_wakeup | +|sched_wakeup_new | +|sched_waking | +|signal_deliver | +|signal_generate | +|softirq_entry | +|softirq_exit | +|softirq_raise | +|suspend_resume | +|sys_enter | +|sys_exit | +|sys_memory | +|sys_virtual_memory | +|task_newtask | +|task_rename | +|trace_bblock_bio_queue | +|trace_block_bio_backmerge | +|trace_block_bio_bounce | +|trace_block_bio_complete | +|trace_block_bio_frontmerge | +|trace_block_bio_remap | +|trace_block_dirty_buffer | +|trace_block_getrq | +|trace_block_plug | +|trace_block_rq_complete | +|trace_block_rq_insert | +|trace_block_rq_issue | +|trace_block_rq_remap | +|trace_event_clock_sync | +|tracing_mark_write | +|workqueue_execute_end | +|workqueue_execute_start | + +## 事件对应解析状态: +每种事件解析数据都有5种状态,描述如下表: +|stat_type|description| +|---- |---- | +|received | 统计trace数据源中总共有多少该事件。| +|data_lost | 统计TraceStreamer解析过程中发现丢失数据条数。 | +|not_match | 统计有多少数据与上下文其他数据不匹配。 | +|not_supported | 统计有多少暂不支持解析该事件(一个事件可能包含多种类型的子事件, TraceStreamer可能支持该事件的一部分子事件)。| +|invalid_data | 统计收到多少条该事件的非法数据。| + +## 数据状态级别 +数据状态级别总共有4种,分别是:info, warn, error,fatal。由于数据的重要性不同,不同事件的同一种状态可能对应不同的级别。 +例如binder_transaction_received的 not_supported状态的数据为info级别,而binder_transaction_alloc_buf的not_supported状态数据为warn级别。 + +## 事件,状态与级别对应关系 +| event_name | stat_type | serverity | +|---- |---- |---- | +| binder_transaction | received | info | +| binder_transaction | data_lost | error | +| binder_transaction | not_match | info | +| binder_transaction | not_supported | info | +| binder_transaction | invalid_data | error | +| binder_transaction_received | received | info | +| binder_transaction_received | data_lost | error | +| binder_transaction_received | not_match | info | +| binder_transaction_received | not_supported | info | +| binder_transaction_received | invalid_data | error | +| binder_transaction_alloc_buf | received | info | +| binder_transaction_alloc_buf | data_lost | error | +| binder_transaction_alloc_buf | not_match | info | +| binder_transaction_alloc_buf | not_supported | warn | +| binder_transaction_alloc_buf | invalid_data | error | +| binder_transaction_lock | received | info | +| binder_transaction_lock | data_lost | error | +| binder_transaction_lock | not_match | info | +| binder_transaction_lock | not_supported | warn | +| binder_transaction_lock | invalid_data | error | +| binder_transaction_locked | received | info | +| binder_transaction_locked | data_lost | error | +| binder_transaction_locked | not_match | info | +| binder_transaction_locked | not_supported | warn | +| binder_transaction_locked | invalid_data | error | +| binder_transaction_unlock | received | info | +| binder_transaction_unlock | data_lost | error | +| binder_transaction_unlock | not_match | info | +| binder_transaction_unlock | not_supported | warn | +| binder_transaction_unlock | invalid_data | error | +| sched_switch | received | info | +| sched_switch | data_lost | error | +| sched_switch | not_match | info | +| sched_switch | not_supported | info | +| sched_switch | invalid_data | error | +| task_rename | received | info | +| task_rename | data_lost | error | +| task_rename | not_match | info | +| task_rename | not_supported | info | +| task_rename | invalid_data | error | +| task_newtask | received | info | +| task_newtask | data_lost | error | +| task_newtask | not_match | info | +| task_newtask | not_supported | info | +| task_newtask | invalid_data | error | +| tracing_mark_write | received | info | +| tracing_mark_write | data_lost | error | +| tracing_mark_write | not_match | info | +| tracing_mark_write | not_supported | info | +| tracing_mark_write | invalid_data | error | +| print | received | info | +| print | data_lost | error | +| print | not_match | info | +| print | not_supported | info | +| print | invalid_data | error | +| sched_wakeup | received | info | +| sched_wakeup | data_lost | error | +| sched_wakeup | not_match | info | +| sched_wakeup | not_supported | info | +| sched_wakeup | invalid_data | error | +| sched_waking | received | info | +| sched_waking | data_lost | error | +| sched_waking | not_match | info | +| sched_waking | not_supported | info | +| sched_waking | invalid_data | error | +| cpu_idle | received | info | +| cpu_idle | data_lost | error | +| cpu_idle | not_match | info | +| cpu_idle | not_supported | info | +| cpu_idle | invalid_data | error | +| cpu_frequency | received | info | +| cpu_frequency | data_lost | error | +| cpu_frequency | not_match | info | +| cpu_frequency | not_supported | info | +| cpu_frequency | invalid_data | error | +| suspend_resume | received | info | +| suspend_resume | data_lost | error | +| suspend_resume | not_match | info | +| suspend_resume | not_supported | info | +| suspend_resume | invalid_data | error | +| workqueue_execute_start | received | info | +| workqueue_execute_start | data_lost | error | +| workqueue_execute_start | not_match | info | +| workqueue_execute_start | not_supported | info | +| workqueue_execute_start | invalid_data | error | +| workqueue_execute_end | received | info | +| workqueue_execute_end | data_lost | error | +| workqueue_execute_end | not_match | info | +| workqueue_execute_end | not_supported | warn | +| workqueue_execute_end | invalid_data | error | +| clock_set_rate | received | info | +| clock_set_rate | data_lost | error | +| clock_set_rate | not_match | info | +| clock_set_rate | not_supported | warn | +| clock_set_rate | invalid_data | error | +| clock_enable | received | info | +| clock_enable | data_lost | error | +| clock_enable | not_match | info | +| clock_enable | not_supported | warn | +| clock_enable | invalid_data | error | +| clock_disable | received | info | +| clock_disable | data_lost | error | +| clock_disable | not_match | info | +| clock_disable | not_supported | warn | +| clock_disable | invalid_data | error | +| clk_set_rate | received | info | +| clk_set_rate | data_lost | error | +| clk_set_rate | not_match | info | +| clk_set_rate | not_supported | warn | +| clk_set_rate | invalid_data | error | +| clk_enable | received | info | +| clk_enable | data_lost | error | +| clk_enable | not_match | info | +| clk_enable | not_supported | warn | +| clk_enable | invalid_data | error | +| clk_disable | received | info | +| clk_disable | data_lost | error | +| clk_disable | not_match | info | +| clk_disable | not_supported | warn | +| clk_disable | invalid_data | error | +| sys_enter | received | info | +| sys_enter | data_lost | error | +| sys_enter | not_match | info | +| sys_enter | not_supported | warn | +| sys_enter | invalid_data | error | +| sys_exit | received | info | +| sys_exit | data_lost | error | +| sys_exit | not_match | info | +| sys_exit | not_supported | warn | +| sys_exit | invalid_data | error | +| regulator_set_voltage | received | info | +| regulator_set_voltage | data_lost | error | +| regulator_set_voltage | not_match | info | +| regulator_set_voltage | not_supported | warn | +| regulator_set_voltage | invalid_data | error | +| regulator_set_voltage_complete | received | info | +| regulator_set_voltage_complete | data_lost | error | +| regulator_set_voltage_complete | not_match | info | +| regulator_set_voltage_complete | not_supported | warn | +| regulator_set_voltage_complete | invalid_data | error | +| regulator_disable | received | info | +| regulator_disable | data_lost | error | +| regulator_disable | not_match | info | +| regulator_disable | not_supported | warn | +| regulator_disable | invalid_data | error | +| regulator_disable_complete | received | info | +| regulator_disable_complete | data_lost | error | +| regulator_disable_complete | not_match | info | +| regulator_disable_complete | not_supported | warn | +| regulator_disable_complete | invalid_data | error | +| ipi_entry | received | info | +| ipi_entry | data_lost | error | +| ipi_entry | not_match | info | +| ipi_entry | not_supported | warn | +| ipi_entry | invalid_data | error | +| ipi_exit | received | info | +| ipi_exit | data_lost | error | +| ipi_exit | not_match | info | +| ipi_exit | not_supported | warn | +| ipi_exit | invalid_data | error | +| irq_handler_entry | received | info | +| irq_handler_entry | data_lost | error | +| irq_handler_entry | not_match | info | +| irq_handler_entry | not_supported | warn | +| irq_handler_entry | invalid_data | error | +| irq_handler_exit | received | info | +| irq_handler_exit | data_lost | error | +| irq_handler_exit | not_match | info | +| irq_handler_exit | not_supported | warn | +| irq_handler_exit | invalid_data | error | +| softirq_raise | received | info | +| softirq_raise | data_lost | error | +| softirq_raise | not_match | info | +| softirq_raise | not_supported | warn | +| softirq_raise | invalid_data | error | +| softirq_entry | received | info | +| softirq_entry | data_lost | error | +| softirq_entry | not_match | info | +| softirq_entry | not_supported | warn | +| softirq_entry | invalid_data | error | +| softirq_exit | received | info | +| softirq_exit | data_lost | error | +| softirq_exit | not_match | info | +| softirq_exit | not_supported | warn | +| softirq_exit | invalid_data | error | +| oom_score_adj_update | received | info | +| oom_score_adj_update | data_lost | error | +| oom_score_adj_update | not_match | info | +| oom_score_adj_update | not_supported | warn | +| oom_score_adj_update | invalid_data | error | +| sched_wakeup_new | received | info | +| sched_wakeup_new | data_lost | error | +| sched_wakeup_new | not_match | info | +| sched_wakeup_new | not_supported | warn | +| sched_wakeup_new | invalid_data | error | +| sched_process_exit | received | info | +| sched_process_exit | data_lost | error | +| sched_process_exit | not_match | info | +| sched_process_exit | not_supported | warn | +| sched_process_exit | invalid_data | error | +| sched_process_free | received | info | +| sched_process_free | data_lost | error | +| sched_process_free | not_match | info | +| sched_process_free | not_supported | warn | +| sched_process_free | invalid_data | error | +| trace_event_clock_sync | received | info | +| trace_event_clock_sync | data_lost | error | +| trace_event_clock_sync | not_match | info | +| trace_event_clock_sync | not_supported | warn | +| trace_event_clock_sync | invalid_data | error | +| memory | received | info | +| memory | data_lost | error | +| memory | not_match | info | +| memory | not_supported | warn | +| memory | invalid_data | error | +| hilog | received | info | +| hilog | data_lost | error | +| hilog | not_match | info | +| hilog | not_supported | warn | +| hilog | invalid_data | error | +| hidump_fps | received | info | +| hidump_fps | data_lost | error | +| hidump_fps | not_match | info | +| hidump_fps | not_supported | warn | +| hidump_fps | invalid_data | error | +| native_hook_malloc | received | info | +| native_hook_malloc | data_lost | error | +| native_hook_malloc | not_match | info | +| native_hook_malloc | not_supported | warn | +| native_hook_malloc | invalid_data | error | +| native_hook_free | received | info | +| native_hook_free | data_lost | error | +| native_hook_free | not_match | info | +| native_hook_free | not_supported | warn | +| native_hook_free | invalid_data | error | +| sys_memory | received | info | +| sys_memory | data_lost | error | +| sys_memory | not_match | info | +| sys_memory | not_supported | warn | +| sys_memory | invalid_data | error | +| sys_virtual_memory | received | info | +| sys_virtual_memory | data_lost | error | +| sys_virtual_memory | not_match | info | +| sys_virtual_memory | not_supported | warn | +| sys_virtual_memory | invalid_data | error | +| signal_generate | received | info | +| signal_generate | data_lost | error | +| signal_generate | not_match | info | +| signal_generate | not_supported | warn | +| signal_generate | invalid_data | error | +| signal_deliver | received | info | +| signal_deliver | data_lost | error | +| signal_deliver | not_match | info | +| signal_deliver | not_supported | warn | +| signal_deliver | invalid_data | error | +| trace_block_bio_backmerge | received | info | +| trace_block_bio_backmerge | data_lost | error | +| trace_block_bio_backmerge | not_match | info | +| trace_block_bio_backmerge | not_supported | warn | +| trace_block_bio_backmerge | invalid_data | error | +| trace_block_bio_bounce | received | info | +| trace_block_bio_bounce | data_lost | error | +| trace_block_bio_bounce | not_match | info | +| trace_block_bio_bounce | not_supported | warn | +| trace_block_bio_bounce | invalid_data | error | +| trace_block_bio_complete | received | info | +| trace_block_bio_complete | data_lost | error | +| trace_block_bio_complete | not_match | info | +| trace_block_bio_complete | not_supported | warn | +| trace_block_bio_complete | invalid_data | error | +| trace_block_bio_frontmerge | received | info | +| trace_block_bio_frontmerge | data_lost | error | +| trace_block_bio_frontmerge | not_match | info | +| trace_block_bio_frontmerge | not_supported | warn | +| trace_block_bio_frontmerge | invalid_data | error | +| trace_bblock_bio_queue | received | info | +| trace_bblock_bio_queue | data_lost | error | +| trace_bblock_bio_queue | not_match | info | +| trace_bblock_bio_queue | not_supported | warn | +| trace_bblock_bio_queue | invalid_data | error | +| trace_block_bio_remap | received | info | +| trace_block_bio_remap | data_lost | error | +| trace_block_bio_remap | not_match | info | +| trace_block_bio_remap | not_supported | warn | +| trace_block_bio_remap | invalid_data | error | +| trace_block_dirty_buffer | received | info | +| trace_block_dirty_buffer | data_lost | error | +| trace_block_dirty_buffer | not_match | info | +| trace_block_dirty_buffer | not_supported | warn | +| trace_block_dirty_buffer | invalid_data | error | +| trace_block_getrq | received | info | +| trace_block_getrq | data_lost | error | +| trace_block_getrq | not_match | info | +| trace_block_getrq | not_supported | warn | +| trace_block_getrq | invalid_data | error | +| trace_block_plug | received | info | +| trace_block_plug | data_lost | error | +| trace_block_plug | not_match | info | +| trace_block_plug | not_supported | warn | +| trace_block_plug | invalid_data | error | +| trace_block_rq_complete | received | info | +| trace_block_rq_complete | data_lost | error | +| trace_block_rq_complete | not_match | info | +| trace_block_rq_complete | not_supported | warn | +| trace_block_rq_complete | invalid_data | error | +| trace_block_rq_insert | received | info | +| trace_block_rq_insert | data_lost | error | +| trace_block_rq_insert | not_match | info | +| trace_block_rq_insert | not_supported | warn | +| trace_block_rq_insert | invalid_data | error | +| trace_block_rq_remap | received | info | +| trace_block_rq_remap | data_lost | error | +| trace_block_rq_remap | not_match | info | +| trace_block_rq_remap | not_supported | warn | +| trace_block_rq_remap | invalid_data | error | +| trace_block_rq_issue | received | info | +| trace_block_rq_issue | data_lost | error | +| trace_block_rq_issue | not_match | info | +| trace_block_rq_issue | not_supported | warn | +| trace_block_rq_issue | invalid_data | error | +| other | received | info | +| other | data_lost | error | +| other | not_match | info | +| other | not_supported | warn | +| other | invalid_data | error | diff --git a/host/doc/des_support_event.md b/host/doc/des_support_event.md new file mode 100644 index 0000000..bde55dc --- /dev/null +++ b/host/doc/des_support_event.md @@ -0,0 +1,224 @@ +# TraceStreamer支持解析事件列表 +## ftrace事件 +``` +binder_transaction +binder_transaction_received +binder_transaction_alloc_buf +binder_transaction_lock +binder_transaction_locked +binder_transaction_unlock +sched_switch +task_rename +task_newtask +tracing_mark_write +print +sched_wakeup +sched_waking +cpu_idle +cpu_frequency +suspend_resume +workqueue_execute_start +workqueue_execute_end +clock_set_rate +clock_enable +clock_disable +clk_set_rate +clk_enable +clk_disable +sys_enter +sys_exit +regulator_set_voltage +regulator_set_voltage_complete +regulator_disable +regulator_disable_complete +ipi_entry +ipi_exit +irq_handler_entry +irq_handler_exit +softirq_raise +softirq_entry +softirq_exit +sched_wakeup_new +sched_process_exit +trace_event_clock_sync +``` +## 内存事件 +``` +mem.vm.size +mem.rss +mem.rss.anon +mem.rss.file +mem.rss.schem +mem.swap +mem.locked +mem.hwm +mem.oom_score_adj +``` +## 系统内存事件 +``` +sys.mem.unspecified +sys.mem.total +sys.mem.free +sys.mem.avaiable +sys.mem.buffers +sys.mem.cached +sys.mem.swap.chard +sys.mem.active +sys.mem.inactive +sys.mem.active.anon +sys.mem.inactive.anon +sys.mem.active_file +sys.mem.inactive_file +sys.mem.unevictable +sys.mem.mlocked +sys.mem.swap.total +sys.mem.swap.free +sys.mem.dirty +sys.mem.writeback +sys.mem.anon.pages +sys.mem.mapped +sys.mem.shmem +sys.mem.slab +sys.mem.slab.reclaimable +sys.mem.slab.unreclaimable +sys.mem.kernel.stack +sys.mem.page.tables +sys.mem.commit.limit +sys.mem.commited.as +sys.mem.vmalloc.total +sys.mem.vmalloc.used +sys.mem.vmalloc.chunk +sys.mem.cma.total +sys.mem.cma.free +``` +## 系统虚拟内存事件 +``` +sys.virtual.mem.unspecified +sys.virtual.mem.nr.free.pages +sys.virtual.mem.nr.alloc.batch +sys.virtual.mem.nr.inactive.anon +sys.virtual.mem.nr.active_anon +sys.virtual.mem.nr.inactive.file +sys.virtual.mem.nr.active_file +sys.virtual.mem.nr.unevictable +sys.virtual.mem.nr.mlock +sys.virtual.mem.anon.pages +sys.virtual.mem.nr.mapped +sys.virtual.mem.nr.file.pages +sys.virtual.mem.nr.dirty +sys.virtual.mem.nr.writeback +sys.virtual.mem.nr.slab.reclaimable +sys.virtual.mem.nr.slab.unreclaimable +sys.virtual.mem.nr.page_table.pages +sys.virtual.mem.nr_kernel.stack +sys.virtual.mem.nr.overhead +sys.virtual.mem.nr.unstable +sys.virtual.mem.nr.bounce +sys.virtual.mem.nr.vmscan.write +sys.virtual.mem.nr.vmscan.immediate.reclaim +sys.virtual.mem.nr.writeback_temp +sys.virtual.mem.nr.isolated_anon +sys.virtual.mem.nr.isolated_file +sys.virtual.mem.nr.shmem +sys.virtual.mem.nr.dirtied +sys.virtual.mem.nr.written +sys.virtual.mem.nr.pages.scanned +sys.virtual.mem.workingset.refault +sys.virtual.mem.workingset.activate +sys.virtual.mem.workingset_nodereclaim +sys.virtual.mem.nr_anon.transparent.hugepages +sys.virtual.mem.nr.free_cma +sys.virtual.mem.nr.swapcache +sys.virtual.mem.nr.dirty.threshold +sys.virtual.mem.nr.dirty.background.threshold +sys.virtual.mem.vmeminfo.pgpgin +sys.virtual.mem.pgpgout +sys.virtual.mem.pgpgoutclean +sys.virtual.mem.pswpin +sys.virtual.mem.pswpout +sys.virtual.mem.pgalloc.dma +sys.virtual.mem.pgalloc.normal +sys.virtual.mem.pgalloc.movable +sys.virtual.mem.pgfree +sys.virtual.mem.pgactivate +sys.virtual.mem.pgdeactivate +sys.virtual.mem.pgfault +sys.virtual.mem.pgmajfault +sys.virtual.mem.pgrefill.dma +sys.virtual.mem.pgrefill.normal +sys.virtual.mem.pgrefill.movable +sys.virtual.mem.pgsteal.kswapd.dma +sys.virtual.mem.pgsteal.kswapd.normal +sys.virtual.mem.pgsteal.kswapd.movable +sys.virtual.mem.pgsteal.direct.dma +sys.virtual.mem.pgsteal.direct.normal +sys.virtual.mem.pgsteal_direct.movable +sys.virtual.mem.pgscan.kswapd.dma +sys.virtual.mem.pgscan_kswapd.normal +sys.virtual.mem.pgscan.kswapd.movable +sys.virtual.mem.pgscan.direct.dma +sys.virtual.mem.pgscan.direct.normal +sys.virtual.mem.pgscan.direct.movable +sys.virtual.mem.pgscan.direct.throttle +sys.virtual.mem.pginodesteal +sys.virtual.mem.slabs_scanned +sys.virtual.mem.kswapd.inodesteal +sys.virtual.mem.kswapd.low.wmark.hit.quickly +sys.virtual.mem.high.wmark.hit.quickly +sys.virtual.mem.pageoutrun +sys.virtual.mem.allocstall +sys.virtual.mem.pgrotated +sys.virtual.mem.drop.pagecache +sys.virtual.mem.drop.slab +sys.virtual.mem.pgmigrate.success +sys.virtual.mem.pgmigrate.fail +sys.virtual.mem.compact.migrate.scanned +sys.virtual.mem.compact.free.scanned +sys.virtual.mem.compact.isolated +sys.virtual.mem.compact.stall +sys.virtual.mem.compact.fail +sys.virtual.mem.compact.success +sys.virtual.mem.compact.daemon.wake +sys.virtual.mem.unevictable.pgs.culled +sys.virtual.mem.unevictable.pgs.scanned +sys.virtual.mem.unevictable.pgs.rescued +sys.virtual.mem.unevictable.pgs.mlocked +sys.virtual.mem.unevictable.pgs.munlocked +sys.virtual.mem.unevictable.pgs.cleared +sys.virtual.mem.unevictable.pgs.stranded +sys.virtual.mem.nr.zspages +sys.virtual.mem.nr.ion.heap +sys.virtual.mem.nr.gpu.heap +sys.virtual.mem.allocstall.dma +sys.virtual.mem.allocstall.movable +sys.virtual.mem.allocstall.normal +sys.virtual.mem.compact_daemon.free.scanned +sys.virtual.mem.compact.daemon.migrate.scanned +sys.virtual.mem.nr.fastrpc +sys.virtual.mem.nr.indirectly.reclaimable +sys.virtual.mem.nr_ion_heap_pool +sys.virtual.mem.nr.kernel_misc.reclaimable +sys.virtual.mem.nr.shadow_call.stack_bytes +sys.virtual.mem.nr.shmem.hugepages +sys.virtual.mem.nr.shmem.pmdmapped +sys.virtual.mem.nr.unreclaimable.pages +sys.virtual.mem.nr.zone.active.anon +sys.virtual.mem.nr.zone.active.file +ys.virtual.mem.nr.zone.inactive_anon +sys.virtual.mem.nr.zone.inactive_file +sys.virtual.mem.nr.zone.unevictable +sys.virtual.mem.nr.zone.write_pending +sys.virtual.mem.oom.kill +sys.virtual.mem.pglazyfree +sys.virtual.mem.pglazyfreed +sys.virtual.mem.pgrefill +sys.virtual.mem.pgscan.direct +sys.virtual.mem.pgscan.kswapd +sys.virtual.mem.pgskip.dma +sys.virtual.mem.pgskip.movable +sys.virtual.mem.pgskip.normal +sys.virtual.mem.pgsteal.direct +sys.virtual.mem.pgsteal.kswapd +sys.virtual.mem.swap.ra +sys.virtual.mem.swap.ra.hit +``` \ No newline at end of file diff --git a/host/doc/des_tables.md b/host/doc/des_tables.md new file mode 100644 index 0000000..efe0a6f --- /dev/null +++ b/host/doc/des_tables.md @@ -0,0 +1,306 @@ +# ___概述TraceStreamer生成的数据库___ +``` + TraceStreamer虽然对外提供了各种各样的使用方式,但核心的业务仍是将trace数据源转化为易于理解和使用的数据库。用户可以通过SmartPerf界面直观的研究系统跟踪数据,也可在理解TraceStreamer生成的数据库的基础上,在TraceStreamer的交互模式或者Smartperf的数据库查询模式下,使用SQL查询语句自由组装查看用户关心的数据。 下文将对TraceStreamer生成的数据库进行详细描述,给用户使用SQL查询系统跟踪数据提供帮助。 +``` + +## ___TraceStreamer输出数据库包含以下表格___ +* trace_range : 记录ftrace数据与其他类型数据的时间交集,供前端展示数据时使用。 +* process : 记录进程信息。 +* thread : 记录线程信息。 +* thread_state : 记录线程状态信息。 +* instant : 记录Sched_waking, sched_wakeup事件, 用作ThreadState表的上下文使用。 +* raw : 此数据结构主要作为ThreadState的上下文使用,这张表是sched_waking,sched_wakup, cpu_idle事件的原始记录。 +* callstack : 记录调用堆栈和异步调用,其中depth,stack_id和parent_stack_id仅在非异步调用中有效。当cookid不为空时,为异步调用,此时callid为进程唯一号,否则为线程唯一号。 +* irq : 记录中断相关事件。 +* measure : 记录所有的计量值。 +* log : 记录hilog打印日志数据。 +* heap : 记录堆内存申请与释放相关的数据。 +* heap_frame : 记录堆内存申请与释放相关的调用栈。 +* hidump : 记录FPS(Frame Per Second)数据。 +* symbols : 记录系统调用名称和其函数指针的对应关系,trace中用addr来映射function_name来节省存储空间 +* syscall : 记录用户空间函数与内核空间函数相互调用记录 +* args : 记录方法参数集合 +* sys_event_filter : 记录所有的filter +* clk_event_filter : 记录时钟事件 +* cpu_measure_filter : cpu事件过滤器表。 +* measure_filter : 记录一个递增的filterid队列,所有其他的filter类型在获取过程中,均从此数据列表中获取下一个可用的filter_id并做记录。 +* process_measure_filter : 将进程ID作为key1,进程的内存,界面刷新,屏幕亮度等信息作为key2,唯一确定一个filter_id +* data_type : 记录数据类型和typeId的关联关系。 +* data_dict : 记录常用的字符串,将字符串和索引关联,降低程序运行的内存占用,用作辅助数据。 +* meta : 记录执行解析操作相关的基本信息。 + +## ___表格关系图___ + +### 进程表与线程表关系图: +![GitHub Logo](../figures/process_thread.png) +### 描述: +当一个进程或者线程结束后,系统可能再次将该进程号或者线程号分配给其他进程或者线程,造成一个进程号或线程号代表多个进程或线程的情况。 +Process和Thread表中的id字段可以唯一标识进程和线程。process表中的id在其他表中用作ipid字段。thread表中的id在其他表中用作itid字段。 +thread表通过ipid字段关联process表的id字段,可以查询线程归属进程。 +### 举例: +已知pid = 123,查看当前进程下的所有线程信息,可以使用如下SQL语句: +select thread.* from thread, process where process.pid = 123 and thread.ipid = process.id + +### 线程表与线程运行状态表关系图 +![GitHub Logo](../figures/thread_state.png) +### 描述: +thread_state表记录所有线程的运行状态信息,包含ts(状态起始时间),dur(状态持续时间),cpu, itid, state(线程状态)。 thread表的id字段与thread_state表的itid字段相关联。 +### 举例: +已知tid = 123, 查看当前线程的所有运行状态信息,可以使用如下SQL语句: +select thread_state.* from thread, thread_state where thread.tid = 123 and thread.id = thread_state.itid + +### 堆内存数据变化表关系图 +![GitHub Logo](../figures/dump_and_mem.png) +### 描述: +heap表记录堆内存申请(AllocEvent)和释放(FreeEvent)数据。heap表通过ipid和itid字段分别与process和thread表的id字段关联,通过eventId与heap_frame表的eventId字段相关联。 +heap表字段解释如下: + eventId: 唯一标识一次堆内存申请或释放, 通过与heap_frame表关联可以拿到当前申请或释放的函数调用堆栈。 + addr: 堆内存申请/释放的地址 + heap_size: 堆内存申请/释放的大小 +heap_frame表记录内存申请/释放的调用堆栈。通过eventId区分一组调用堆栈,depth为堆栈深度,depth为0时,表示当前行为栈顶数据。 +### 举例: +已知tid = 123, 查看当前线程的所有堆内存变化信息,可以使用如下SQL语句: +select heap.* from thread, heap where thread.tid = 123 and thread.id = heap.itid +已知eventid = 0, 查看当前内存变化调用堆栈 +select * from heap_frame where eventId = 0 + +### 日志表与进程线程表关系图 +![GitHub Logo](../figures/log.png) +### 描述: +log表记录日志信息。可以根据seq字段的连续性,来判断是否存在日志丢失的情况。 +### 举例: +已知tid = 123, 查看当前线程的所有error级别的日志,可以使用如下SQL语句: +select * from log where tid = 123 and level = "error" + + +## TraceStreamer输出数据库表格详细介绍 +### trace_range表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|start_ts |NUM | +|end_ts |INT | +#### 关键字段描述: +start_ts: trace的开始时间,纳秒为单位 +end_ts: trace的结束时间,纳秒为单位 +### process表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |NUM | +|type |INT | +|pid |NUM | +|name |INT | +|start_ts |NUM | +#### 关键字段描述: +id: 进程在数据库重新重新定义的id,从0开始序列增长 +pid: 进程的真实id +name: 进程名字 +### thread表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |INT | +|type |NUM | +|tid |INT | +|name |NUM | +|start_ts |INT | +|end_ts |INT | +|ipid |INT | +|is_main_thread|INT | +#### 字段详细描述: +id: 线程在数据库重新重新定义的id,从0开始序列增长 +ipid: 线程所属的进程id, 关联进程表中的ID +name: 线程名字 +is_main_thread: 是否主线程,主线程即该线程实际就是进程本身 + +### thread_state表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |INT | +|type |NUM | +|ts |INT | +|dur |INT | +|cpu |INT | +|itid |INT | +|state |NUM | +#### 字段详细描述: +id: 线程状态在数据库中的id,从0开始序列增长 +ts: 该线程状态的起始时间 +dur: 该线程状态的持续时间 +cpu: 该线程在哪个cpu上执行(针对running状态的线程) +itid: 该状态所属的线程所属的进程id, 关联进程表中的ID +state: 线程实际的的状态值 +``` +'R', Runnable状态 +'S', interruptible sleep +'D', uninterruptible sleep +'T', Stoped +'t', Traced +'X', ExitedDead +'Z', ExitZombie +'x', TaskDead +'I', TaskDead +'K', WakeKill +'P', Parked +'N', NoLoad +``` +### instant表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|ts |INT | +|name |NUM | +|ref |INT | +|ref_type |NUM | +#### 表描述: +记录了系统中的waking和wakeup事件。 +### raw表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |INT | +|type |NUM | +|ts |INT | +|name |NUM | +|cpu |INT | +|itid |INT | +#### 表描述: +记录了系统中的waking、wakup、cpu_idel、cpu_frequency数据。 + +### callstack表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |INT | +|ts |INT | +|dur |INT | +|callid |INT | +|cat |NUM | +|name |NUM | +|depth |INT | +|cookie |INT | +|parent_id |INT | +|argsetid |INT | +|chainId |NUM | +|spanId |NUM | +|parentSpanId |NUM | +|flag |NUM | +|args |NUM | +#### 字段详细描述: +dur: 调用时长 +callid: 调用者的ID,比如针对线程表里面的id +name: 调用名称 +depth: 调用深度 +parent_id: 父调用的id + +### measure表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|type |NUM | +|ts |INT | +|value |INT | +|filter_id |INT | +#### 字段详细描述: + +### heap表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|eventId |INT | +|ipid |INT | +|itid |INT | +|event_type |NUM | +|start_ts |INT | +|end_ts |INT | +|dur |INT | +|addr |INT | +|heap_size |INT | +|all_heap_size |INT | +#### 字段详细描述: + +### heap_frame表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|eventId |INT | +|depth |INT | +|ip |INT | +|sp |INT | +|symbol_name |NUM | +|file_path |NUM | +|offset |INT | +|symbol_offset |INT | +#### 表描述: +记录了内存的申请和释放的堆栈。 +### hidump表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|ts |INT | +|fps |INT | +#### 表描述: +此表记录了设备的帧率信息,fps。 +### symbols表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |INT | +|funcname |NUM | +|addr |INT | +#### 表描述: +此表记录了数值和函数调用名称的映射关系。 + +### measure_filter表 +记录一个递增的filterid队列,所有其他的filter类型在获取过程中,均从此数据列表中获取下一个可用的filter_id并做记录。 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |NUM | +|type |NUM | +|name |INT | +|source_arg_set_id |INT | + +#### 字段详细描述: +过滤分类(type),过滤名称(key2),数据ID(key1)。 +数据ID在process_measure_filter, sys_event_filter中作为id。 +### process_measure_filter表 +将进程ID作为key1,进程的内存,界面刷新,屏幕亮度等信息作为key2,唯一确定一个filter_id, filter_id同时被记录在measure_filter表中。 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |INT | +|type |NUM | +|name |NUM | +|ipid |INT | +#### 字段详细描述: +filterid: 来自measure_filter表 +name: cpu状态名 +ipid: 进程内部编号 +### data_type表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |INT | +|typeId |INT | +|desc |NUM | +#### 表描述: +此表记录了一个数据类型ID和数据描述的映射。 +### data_dict表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |INT | +|data |NUM | +#### 表描述: +此表记录了一个数据类型ID和字符串的映射。 +### meta表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|name |NUM | +|value |NUM | +#### 表描述: +此表记录了数据解析或导出时的一些现场数据,比如使用的trace_streamer版本, 工具的发布时间,数据解析的时间,数据的持续时长,以及原始数据的格式。 diff --git a/host/doc/quickstart_hiprofiler_cmd.md b/host/doc/quickstart_hiprofiler_cmd.md new file mode 100644 index 0000000..f9a798f --- /dev/null +++ b/host/doc/quickstart_hiprofiler_cmd.md @@ -0,0 +1,39 @@ +# 1.hiprofiler_cmd 使用说明 +## 1.1参数说明 + 执行hiprofiler_cmd 为调优业务的离线命令行抓取工具,具体使用方法及命令行参数介绍如下。 + +可以使用`-h`或者`--help`参数查看命令的使用描述信息: + +```sh +# ./hiprofiler_cmd -h +help : + --getport -q : get grpc address + --config -c : start trace by config file + --time -t : trace time + --out -o : output file name + --help -h : make some help +``` + +其余参数使用说明如下: +* `-q`或者`--getport`选项,用于查询服务的端口信息; +* `-c`或者`--config`选项,用于指定配置文件; +* `-t`或者`--time`选项,用于指定抓取时间,单位是秒; +* `-o`或者`--out`选项,用于指定输出的离线数据文件名; +* `-h`或者`--help`选项,用于输出帮助信息; + +# 2.使用具体场景 +![GitHub Logo](../figures/systraceconfig.jpg) +说明: +* Record setting:设置trace的抓取模式,buffer size大小,抓取时长 +* Trace command:生成的抓取命令行 +* Probes config:trace的抓取参数配置 +* Allocations:Nativehook的抓取参数配置 +## 2.1命令行的生成和trace文件的抓取 +点击Probes config,如选择抓取Scheduling details,再点击Record setting,设置buffer size大小,抓取时长 +![GitHub Logo](../figures/Scheduling.jpg) +如下图,就会根据上面的配置生成抓取命令,点击复制按钮,会将命令行复制 +![GitHub Logo](../figures/command.jpg) +进入设备,在执行命令前,需要手动拉起hiprofilerd,hiprofiler_plugins,如果要抓取nativehook数据,还要手动拉起native_daemon,进程都拉起后执行命令 +![GitHub Logo](../figures/excutecommand.jpg) +执行完成后,进入指定目录查看,如/data/local/tmp下就会生成trace文件 +![GitHub Logo](../figures/htrace.jpg) \ No newline at end of file diff --git a/host/doc/quickstart_smartperf.md b/host/doc/quickstart_smartperf.md new file mode 100644 index 0000000..b550d64 --- /dev/null +++ b/host/doc/quickstart_smartperf.md @@ -0,0 +1,66 @@ +# 网页加载trace 使用说明 + +## 1.打开版本包里面的main.exe可执行文件,启动web页面 +![GitHub Logo](../figures/main.jpg) + +## 2.Web页面展开 +![GitHub Logo](../figures/opentrace.jpg) + +说明: ++ Open trace file:导入离线trace文件入口 ++ Record new trace:抓取新的trace文件入口 + +## 3.导入文件后显示页面 +![GitHub Logo](../figures/trace.jpg) +说明: ++ 操作说明:在当前页面可以通过键盘上的wasd四个键位操纵当前的时间轴进行缩放,w放大,s为缩小,a为左移,d为右移。 +## 功能介绍 +模块从上往下主要展示时间轴和cpu使用率、cpu使用情况、进程间通讯数据的方法调用情况、进程、线程和方法调用情况 +### 1. 时间轴和cpu使用率 +![GitHub Logo](../figures/figures/time.jpg) +最上方带刻度的为时间轴,主要展示当前抓取数据的总时长和时间刻度的分布情况,如上图所示,左下角展示总时长, +中间区域展示的是抓取数据时间段内的cpu使用率,颜色越深代表cpu使用率越高,颜色越浅代表cpu使用率越低。 +![GitHub Logo](../figures/highlit.jpg) +在白色背景时间轴区域内可以点击后拖拽鼠标,可以对从鼠标按下到拖拽完成鼠标松开的区域内的数据进行筛选,高亮显示的部分为当前所选区域,如上图所示 +### 2.cpu使用情况 +![GitHub Logo](../figures/cpu.jpg) + +如上图所示,当前抓取数据有4个cpu工作,前四组数据对应的是当前调用cpu的线程和对应的进程情况,以颜色作为区分。后四组数据则为cpu的使用频率信息。鼠标移动到相应的线程上还会将当前选中的进程信息全部置为高亮,其他的进程会置灰,如下图所示 +![GitHub Logo](../figures/gray.jpg) +#### 2.1.cpu使用情况的框选功能 +可以对cpu的数据进行框选,框选后在最下方的弹出层中会展示框选数据的统计表格,总共有三个tab页 +CPU by thread的Tab页,主要显示了在框选时间区间内的进程名,进程号,线程名,线程号,总运行时长,平均运行时长和调度次数信息 +![GitHub Logo](../figures/cpubythread.jpg) +CPU by process的Tab页,主要显示了在框选时间区间内的进程名,进程号,总运行时长,平均运行时长和调度次数信息 +![GitHub Logo](../figures/cpubyprocess.jpg) +CPUUsage的Tab页,主要显示了在框选时间区间内,该频率时间占比前三的信息 +![GitHub Logo](../figures/cpusage.jpg) +#### 2.2.cpu使用情况的单选功能 +单选CPU使用情况数据,单击方法会在选中的方法外层加上深色边框,能够突出当前选中色块,弹出层中会展示当前CPU上的进程名,线程名,开始时间和运行时长,线程运行状态等信息 +![GitHub Logo](../figures/cpuclick.jpg) +### 3.FPS数据 +FPS是帧率的显示,每秒产生画面的个数 +![GitHub Logo](../figures/fps.jpg) +#### 3.1FPS的框选功能 +可以对fps的数据进行框选,框选后在最下方的弹出层中会展示框选时间区间内的统计表格,主要显示了time(时间),FPS(帧率) +![GitHub Logo](../figures/fpsselect.jpg) +### 4.进程,线程和方法数据 +下图是进程数据,左边部分展示进程名称和id +![GitHub Logo](../figures/process.jpg) +点击进程名前面向下箭头可以展开对应的线程进行查看,展开后的线程如下图,如果存在堆内存占用情况,就会显示在第一行,如果出现两个名字和id一样的线程,则第一个为线程的使用情况,第二为线程内的方法栈调用情况 +![GitHub Logo](../figures/threadinfo.jpg) + +#### 4.1进程,线程和方法数据的框选功能 +可以对线程的数据进行框选,框选后在最下方的弹出层中会展示框选数据的统计表格,包含线程运行状态,线程调用栈的统计情况。当框选的数据中同时存在线程运行状态和线程调用栈数据,下方的弹出层中就会出现多个tab选项,可以进行切换 + +下图是线程运行状态框选统计信息,包括进程名,进程号,线程名,线程号,线程状态,状态持续时间,平均持续时间,该线程状态发生的次数 +![GitHub Logo](../figures/threadselect.jpg) + +下图是线程调用栈框选统计信息,包括方法名,持续时间,平均持续时间,调用的次数 +![GitHub Logo](../figures/callstackselect.jpg) +#### 4.2进程,线程和方法数据的单选功能 +单选线程的state数据时,会展示当前选中线程的状态数据,开始时间和持续时长,线程状态,所在进程名称、 +![GitHub Logo](../figures/threadclick.jpg) +单选调用栈数据,单击方法会在选中的方法外层加上黑色边框,能够突出当前选中的方法,弹出层中会展示当前方法的名称、开始时间和运行时长信息。 +![GitHub Logo](../figures/callstackclick.jpg) + diff --git a/host/doc/quickstart_trace_streamer.md b/host/doc/quickstart_trace_streamer.md new file mode 100644 index 0000000..eb0deec --- /dev/null +++ b/host/doc/quickstart_trace_streamer.md @@ -0,0 +1,309 @@ +# trace_streamer工具说明 +trace_streamer工具可以2种方式使用 +1. 可以将系统离线trace文件解析并转为db,此工具支持基于文本的trace和基于proto的trace。 +2. trace_streamer工具还可以WebAssembly的方式在浏览器中运行,需暴露相关接口给js文件。 + +## 关于trace解析工具的使用说明: +### 导出db模式 +在导出db模式下,trace_streamer.exe trace文件路径名 -e 导出db路径名.db +此命令可以将trace文件转为db +本应用支持在ohos, linux, windows, mac使用。 +关于db文件的说明: +使用db查看工具查看stat表,可以浏览当前数据一共有多少类数据,各类数据都收到多少条,数据是否正常等情况。在meta表会记录数据库导出时的一些系统信息,比如导入和导出的文件全路径,解析时间等信息。 +meta表可以选择不导出(有些情况下会暴露系统敏感信息),在导出时添加 -nm选项即可。 +在数据导出之后,会在本地目录下生成一个trace_streamer.log文件,在导出db的目录下生成一个数据库文件同名,.db.ohos.ts后缀的文件 +文件内容如下: +时间戳:执行结果(数字) +应用运行时间 + +执行结果解释如下:0 代表执行成功 1 表示输入文件不匹配, 2 表示解析错误, 3其他错误 +### 内置浏览器方式 +trace_streamer可以WebAssembly方式在浏览器中运行,暴露如下接口给js +``` +extern "C" { +/* 上传trace数据 + * + * @data: 数据的缓冲区 + * @dataLen: 数据长度 + * + * return: 0:成功; -1:失败 +*/ +EMSCRIPTEN_KEEPALIVE int TraceStreamerParseData(const uint8_t* data, int dataLen); + +/* 通知TS上传trace数据结束 + * + * return: 0:成功; -1:失败 +*/ +EMSCRIPTEN_KEEPALIVE int TraceStreamerParseDataOver(); + +/* 通过sql语句操作数据库 + * + * @sql: sql语句 + * @sqlLen: sql语句长度 + * + * return: 0:成功; -1:失败 +*/ +EMSCRIPTEN_KEEPALIVE int TraceStreamerSqlOperate(const uint8_t* sql, int sqlLen); + +/* 通过sql语句查询数据库 + * + * @sql: sql语句 + * @sqlLen: sql语句长度 + * @out: 查询结果的缓冲区,查询结果为json + * @outLen: 缓冲区长度 + * + * return: >0:查询成功,返回查询结果数据长度; -1:失败 +*/ +EMSCRIPTEN_KEEPALIVE int TraceStreamerSqlQuery(const uint8_t* sql, int sqlLen, uint8_t* out, int outLen); + +} // extern "C" +``` + +### 你也可以执行如下命令查看应用帮助 +./trace_streamer --help +-i 选项可查看应用支持的事件源和具体的事件名列表 + +#### trace_streamer支持的事件解析 +本工具支持基于文本的trace(# TRACE)和基于proto的二进制日志文件的解析,支持的事件列表如下: +##### ftrace事件 +``` +binder_transaction +binder_transaction_received +binder_transaction_alloc_buf +binder_transaction_lock +binder_transaction_locked +binder_transaction_unlock +sched_switch +task_rename +task_newtask +tracing_mark_write +print +sched_wakeup +sched_waking +cpu_idle +cpu_frequency +suspend_resume +workqueue_execute_start +workqueue_execute_end +clock_set_rate +clock_enable +clock_disable +clk_set_rate +clk_enable +clk_disable +sys_enter +sys_exit +regulator_set_voltage +regulator_set_voltage_complete +regulator_disable +regulator_disable_complete +ipi_entry +ipi_exit +irq_handler_entry +irq_handler_exit +softirq_raise +softirq_entry +softirq_exit +sched_wakeup_new +sched_process_exit +trace_event_clock_sync +``` +##### 内存事件 +``` +mem.vm.size +mem.rss +mem.rss.anon +mem.rss.file +mem.rss.schem +mem.swap +mem.locked +mem.hwm +mem.oom_score_adj +``` +##### 系统内存事件 +``` +sys.mem.unspecified +sys.mem.total +sys.mem.free +sys.mem.avaiable +sys.mem.buffers +sys.mem.cached +sys.mem.swap.chard +sys.mem.active +sys.mem.inactive +sys.mem.active.anon +sys.mem.inactive.anon +sys.mem.active_file +sys.mem.inactive_file +sys.mem.unevictable +sys.mem.mlocked +sys.mem.swap.total +sys.mem.swap.free +sys.mem.dirty +sys.mem.writeback +sys.mem.anon.pages +sys.mem.mapped +sys.mem.shmem +sys.mem.slab +sys.mem.slab.reclaimable +sys.mem.slab.unreclaimable +sys.mem.kernel.stack +sys.mem.page.tables +sys.mem.commit.limit +sys.mem.commited.as +sys.mem.vmalloc.total +sys.mem.vmalloc.used +sys.mem.vmalloc.chunk +sys.mem.cma.total +sys.mem.cma.free +``` +##### 系统虚拟内存事件 +``` +sys.virtual.mem.unspecified +sys.virtual.mem.nr.free.pages +sys.virtual.mem.nr.alloc.batch +sys.virtual.mem.nr.inactive.anon +sys.virtual.mem.nr.active_anon +sys.virtual.mem.nr.inactive.file +sys.virtual.mem.nr.active_file +sys.virtual.mem.nr.unevictable +sys.virtual.mem.nr.mlock +sys.virtual.mem.anon.pages +sys.virtual.mem.nr.mapped +sys.virtual.mem.nr.file.pages +sys.virtual.mem.nr.dirty +sys.virtual.mem.nr.writeback +sys.virtual.mem.nr.slab.reclaimable +sys.virtual.mem.nr.slab.unreclaimable +sys.virtual.mem.nr.page_table.pages +sys.virtual.mem.nr_kernel.stack +sys.virtual.mem.nr.overhead +sys.virtual.mem.nr.unstable +sys.virtual.mem.nr.bounce +sys.virtual.mem.nr.vmscan.write +sys.virtual.mem.nr.vmscan.immediate.reclaim +sys.virtual.mem.nr.writeback_temp +sys.virtual.mem.nr.isolated_anon +sys.virtual.mem.nr.isolated_file +sys.virtual.mem.nr.shmem +sys.virtual.mem.nr.dirtied +sys.virtual.mem.nr.written +sys.virtual.mem.nr.pages.scanned +sys.virtual.mem.workingset.refault +sys.virtual.mem.workingset.activate +sys.virtual.mem.workingset_nodereclaim +sys.virtual.mem.nr_anon.transparent.hugepages +sys.virtual.mem.nr.free_cma +sys.virtual.mem.nr.swapcache +sys.virtual.mem.nr.dirty.threshold +sys.virtual.mem.nr.dirty.background.threshold +sys.virtual.mem.vmeminfo.pgpgin +sys.virtual.mem.pgpgout +sys.virtual.mem.pgpgoutclean +sys.virtual.mem.pswpin +sys.virtual.mem.pswpout +sys.virtual.mem.pgalloc.dma +sys.virtual.mem.pgalloc.normal +sys.virtual.mem.pgalloc.movable +sys.virtual.mem.pgfree +sys.virtual.mem.pgactivate +sys.virtual.mem.pgdeactivate +sys.virtual.mem.pgfault +sys.virtual.mem.pgmajfault +sys.virtual.mem.pgrefill.dma +sys.virtual.mem.pgrefill.normal +sys.virtual.mem.pgrefill.movable +sys.virtual.mem.pgsteal.kswapd.dma +sys.virtual.mem.pgsteal.kswapd.normal +sys.virtual.mem.pgsteal.kswapd.movable +sys.virtual.mem.pgsteal.direct.dma +sys.virtual.mem.pgsteal.direct.normal +sys.virtual.mem.pgsteal_direct.movable +sys.virtual.mem.pgscan.kswapd.dma +sys.virtual.mem.pgscan_kswapd.normal +sys.virtual.mem.pgscan.kswapd.movable +sys.virtual.mem.pgscan.direct.dma +sys.virtual.mem.pgscan.direct.normal +sys.virtual.mem.pgscan.direct.movable +sys.virtual.mem.pgscan.direct.throttle +sys.virtual.mem.pginodesteal +sys.virtual.mem.slabs_scanned +sys.virtual.mem.kswapd.inodesteal +sys.virtual.mem.kswapd.low.wmark.hit.quickly +sys.virtual.mem.high.wmark.hit.quickly +sys.virtual.mem.pageoutrun +sys.virtual.mem.allocstall +sys.virtual.mem.pgrotated +sys.virtual.mem.drop.pagecache +sys.virtual.mem.drop.slab +sys.virtual.mem.pgmigrate.success +sys.virtual.mem.pgmigrate.fail +sys.virtual.mem.compact.migrate.scanned +sys.virtual.mem.compact.free.scanned +sys.virtual.mem.compact.isolated +sys.virtual.mem.compact.stall +sys.virtual.mem.compact.fail +sys.virtual.mem.compact.success +sys.virtual.mem.compact.daemon.wake +sys.virtual.mem.unevictable.pgs.culled +sys.virtual.mem.unevictable.pgs.scanned +sys.virtual.mem.unevictable.pgs.rescued +sys.virtual.mem.unevictable.pgs.mlocked +sys.virtual.mem.unevictable.pgs.munlocked +sys.virtual.mem.unevictable.pgs.cleared +sys.virtual.mem.unevictable.pgs.stranded +sys.virtual.mem.nr.zspages +sys.virtual.mem.nr.ion.heap +sys.virtual.mem.nr.gpu.heap +sys.virtual.mem.allocstall.dma +sys.virtual.mem.allocstall.movable +sys.virtual.mem.allocstall.normal +sys.virtual.mem.compact_daemon.free.scanned +sys.virtual.mem.compact.daemon.migrate.scanned +sys.virtual.mem.nr.fastrpc +sys.virtual.mem.nr.indirectly.reclaimable +sys.virtual.mem.nr_ion_heap_pool +sys.virtual.mem.nr.kernel_misc.reclaimable +sys.virtual.mem.nr.shadow_call.stack_bytes +sys.virtual.mem.nr.shmem.hugepages +sys.virtual.mem.nr.shmem.pmdmapped +sys.virtual.mem.nr.unreclaimable.pages +sys.virtual.mem.nr.zone.active.anon +sys.virtual.mem.nr.zone.active.file +ys.virtual.mem.nr.zone.inactive_anon +sys.virtual.mem.nr.zone.inactive_file +sys.virtual.mem.nr.zone.unevictable +sys.virtual.mem.nr.zone.write_pending +sys.virtual.mem.oom.kill +sys.virtual.mem.pglazyfree +sys.virtual.mem.pglazyfreed +sys.virtual.mem.pgrefill +sys.virtual.mem.pgscan.direct +sys.virtual.mem.pgscan.kswapd +sys.virtual.mem.pgskip.dma +sys.virtual.mem.pgskip.movable +sys.virtual.mem.pgskip.normal +sys.virtual.mem.pgsteal.direct +sys.virtual.mem.pgsteal.kswapd +sys.virtual.mem.swap.ra +sys.virtual.mem.swap.ra.hit +``` + +## trace_streamer开发环境搭建和编译运行指引 + +本应用使用gn作为构建工具,支持在linux环境同时编译linux,windows和mac使用QtCreator作为开发IDE +### 1、开发环境 +ubuntu使用vscode,windows和mac使用QtCreator +# 对外部的依赖 +本应用依赖与sqlite,protobuf(htrace解析部分依赖) + +本应用同时依赖于src/protos目录下文件来生成相关pb.h,pb.cc文件 + +### 2.1、 编译linux版应用 +在根目录下执行相关命令进行编译 + +### 2.2、编译Windows版和Mac应用 +在项目目录下有pro文件,为QtCreator的工程文件,但部分内容赖在于上面所添加的外部依赖,如果要编译相关平台应用,开发者需自行补充相关工程文件,或者在论坛留言 + +### 2.3、开始编译 +具体方法可参考《compile_trace_streamer.md》 \ No newline at end of file diff --git a/host/figures/Scheduling.jpg b/host/figures/Scheduling.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f7c5dd57bfcb038656d0fbe80cc7e685701ae479 GIT binary patch literal 71861 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�?ibVRll|Zg@8snd$Y?&T5o%J6VD& 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 KNo%kij{g^Lp5yZX literal 0 HcmV?d00001 diff --git a/host/figures/callstackclick.jpg b/host/figures/callstackclick.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bd9ed65526a34d967a1ac45b15aa8ecf96a0d1a4 GIT binary patch literal 19084 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 YTyNi}BYyCueS3HB4Bk=y>7l>=FXCc1b^rhX literal 0 HcmV?d00001 diff --git a/host/figures/callstackselect.jpg b/host/figures/callstackselect.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ccdbde9c618790875c1d1646f49504eca1f39583 GIT binary patch literal 127570 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 literal 0 HcmV?d00001 diff --git a/host/figures/command.jpg b/host/figures/command.jpg new file mode 100644 index 0000000000000000000000000000000000000000..553077d0f5dcea3276b090162992f95f650cae1c GIT binary patch literal 63489 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 literal 0 HcmV?d00001 diff --git a/host/figures/cpu.jpg b/host/figures/cpu.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f21a80d736774623f24a8f6a1472d5d7b4a2ad30 GIT binary patch literal 113908 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 z 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 z zHwq-Z#I7sDe1E7Hfj@zD^yEuZR&&YHYVer^35@H~&(W;vclw*pu2xb1$i~-bkkv!^ z_mXs#K4<$|o+UOQo52==t$8+=v_;4(IDlyxZZ8g~2u869ij*xjbpG8fPUd0A8-)Fp zLwqTK5-csc%#7Gw!09nB6Hzxd9v4r^c1^JLI-#lk*r^Bx{GeC?dv!e>&f}FELiQ~+ z^qQ%TjH&&NafSE$Bgg>EQ{cP2^-~3$Msrd~|BMj;e=02Cv%fY^aEDcb(ht;auz#M` zrN@bjACyIM6wqQ?$;+3U_P( zvfZAZr;t~mt1B)=={Hl0WyYj~2BV-KU0*{GSqT3(XXD?K#~AQU zmTw;hi{OCsqJXd0T{x8SX4@5-3HACB1DExnd#xY{w%B26+49NrmYc>EkQHl*jfedG zhO?@JbR=?02gZRU@8#DlzyTl=>^U^k%DK+}smqa70fq4W_h&n+q1XGIBt7P7+PM!4 z3L6lDVs|<_Jm8?0D(k()4}g*e<-yj*cO@O=k|p{L8vW|N82|hV11#@;L1YHS@sUpLIsVrXkDMBYcRDV)&swZ27uRUtH82LcXB#*Of6LnOk%*O$MQ8j6Hu^tTCqcx6 zn5}Ki*z0Rgn$E2>h6H;(N(C;$Msdh?_HQ`-cO+gb82`~p!jD7H<&(ozo4b0B)myA} z*z7l~s9pno|5|qM?n2WH2iZp6#%UGk7H{9XI(eJjEp0g#{@#4;oHN2UZC|oVAk8CTSgE3#hr6Q==yjak z#&=y?uHhC=;oXyVx9#7rFZCQw?zmo6aRSudnXIbHHJ)nE9j-TsQWFd87%0DY;45mp zrfdER^$lt3-QAdHm(y}{GBYwvCz1C)bZpi)Yb%lAb}hYU&dL6`<*v9|fbErxBmtF& zf|a^amTg;~=Xe(9eU$9)uzopLV%+RL+$4I4J5-V*+tmUyp>(zXgYULxwH_;U&_^Sd z$j4zl(}@9#qoOwL4R-r{cIP8nI?h|kwaI*yu*XIdoFjFE8ngvWd$yVHuJAFMmNmi2 zWug)#FJiS1??#qkSsr#Hn@v$?t~Hn1#Uj&%-=F=Og>c+OC1=wj6vA3G8*4GLI7?TX zEq(Jsk_m(DeiQmdU5Ug?b$Yi4_A5U2uNeE}R!Yq=eOk1;I5{l(%QEpYT)ooE;02JxB>3>-}vO{`oBhMBGKCyL@r4?jD=*=G*=fGv+wVo8uqelLA}JM3MY z`-#kFW}hKN71WHwj%q52XC*a^V+~Xu404#>&C9P%N!;HhI`ybjyhlXv;yAYl@|fAJ z+6Td|SAxxAUcYsthD?8%9h)XKY9*rprdZl)0%QJn$Z`Q*L z2eWO+GQrm1Vw=b`1O3a8I~=ATEDuA81(Y-i7i$g1sboe|^oGkf%ofEiaoXF!Pi z6DJn>EI{NHYQ7eGs1TbY3zfEvEJpR)svK|KZ~?^PBH$)IPMY=y&D}O;_flP8p9%kN zdiZLN>@{gC-}&U;_tR$G(xper4H2K|3_u?f*$+7S0c+PKWC{Eb{nihIGz{`KVxrt2 ziu!5767iuPh&?$PbCJV>u3*eJq^;i1r&;D!XL=HZ1oWLB#-Y2o3cwj5 z2t&bD!ae!pwMHRGwQ(CPEjb}f|Fr=x+GqQz)?H_4a@t?8X7HliV^q$7%cq-}4kkmy zzfKAUoWGi*5|63Xe}+G%-8oayNnEg)ijw5XyO{L#d}Y=~s>9zyY^ornH?0%f!R_l; zYNof~qBRp4Tde(7Uy4d(;P5nD6t0@YrWa$=&H;{#fysNkeU zvp#oM_D*X^nYoEMk2o9{8B>PSLyr!Wqr@NM^s-BlkT@*{%n*wwT`}5YqCx0LW>=&0 zh+Qc%izzBC_VDZgn3yzHK|lhDS2n**gN(*xcvkUty#~%yGtys`npkm8upP3k2{hG#vpVmaVOknx>3%(#g-7kIqcJp~0F8n&T z0JvR?>Tj!gC9(IsPB7K?@4F7vgAn(~t8l$2Te<^3(jqIszI>IX-wQjLcepd2o$pbev(U#8L|`om15naUCa<6E2)VVA<~13 z~W5B`@g!<5ie%wy&YIFkQ811oDN zZRKO1+$%7H?6xW0uw5NdD!6k#6E}8b|IYhKjE@fco|1K1s=Ioqss-4AR@>;^)pxCf zf6{TxOYye=QS!)6Nn!($j3=X9+;Hqp@YzbVkH&rC0#&g^`Nsu4yvf-q%~9h+&&{gk z*=+J_kZdHciL!mI4Z@Tu4A>aiUD;>Es)YVxiPQUvfSQm&N)9~x9A3xC197}P%sR*5L%KvQJc&^mR8WYf9N;C4H zIy0Da5ggz=XyO+3IBxo4OOon>!g*Gc(Dn_ZupwXjn5oyUZd4%1_&{seEDxP|AvWP! zbtVKWPaKTZW(_-8ur0Cz^&M`5E^+CuEaIx#yes$7;f;H?m+IFqdXjwbeMHt&cV#J0 z<>no^ g$Zvg)@2CzZi7#VS^oazXd)gI9hfLOlY==lPykrCPSQ<`0dnFluxJi5R^ zQ|(8&fXgIt2arU0cjbU5AQ`4dAefQ-iJHRw$QbR@2*MGU6UZ&o>?5$5{oKw6*?{e- zm3Fs=l=z_vNX@%-DMgT1A`5v0{bwelC~&a0_p%*4-s{FG;Xz**RJ#EGvLt!;L$QQy{)0| zLd-0tq5;EJ#QJRQ-p)yVuqP8%^~1es*O--YY;nLRX_e!huas?3_Z*jgs2@WR_#J2Q zd7+7W)zgf9=OeI@hl?$0e!L%0Ai%gNIRn7!S}g_K#ENp&gTi>KgHmRXxTk>p_l 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 s>H2e)2h+E04a8UHZn+a literal 0 HcmV?d00001 diff --git a/host/figures/cpuclick.jpg b/host/figures/cpuclick.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f1a3c6d358474a1c649993cd673b66fade9871d6 GIT binary patch literal 100490 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
  • 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 Wj`qT+-P;;9Z!}GjlLnao-vj{MV()hV literal 0 HcmV?d00001 diff --git a/host/figures/cpusage.jpg b/host/figures/cpusage.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0aba4c0ee62c02bc846ab17d3f2cdb244dbe66d2 GIT binary patch literal 156691 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

    8%JY`z!Ugeiw-@%a1eA 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 E4=MAVv;Y7A literal 0 HcmV?d00001 diff --git a/host/figures/excutecommand.jpg b/host/figures/excutecommand.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08335f77d94cb19252bb4cee92944339b8bab64c GIT binary patch literal 101387 zcmeFZbyQs4mM^*scXyYJaKA!u-i!l7`Ngg_vH;2Ja#T!R-L2=1;01Sb%j1VTv2 z&wXlNL~ylf0i+{`pI?4lgp`~pHk zLiB9n(qa%PUO^$qUq1rE#KgqG#-YH)rGPNfFhc%6zkYQCgcwNUDC@`|CIE>LgiHwf z)d$c600{N|YX7?6|N8|(LPkMFL&w0x!oL4N69Iq(LPkbHK}JPILAn2Q=>7Wu3Lz>H zqktS5v5qx5lP3w}SyBN8vwU3-sqXX{SkT5R921L-oPv^yg_Vt+gHuRYL{v;%LP1eU zSw&Ut;bT3014AQY6R547y@R8Zv$v10pMOALP{i}ds24AzW0F%+)6z3uXJ)-EEGjN3 zEi136Z)j|4hPSk~edz7$9~c}O9+{b)n_pO5T3*@Q+TPjS+y8WMcz$vD%n9e)IB4$@tVdW0}E}koc|Tt-y-|_0t^3#BKzNg{XcQ70655?`-g{22s{9?@2Iry ziXZ$a`vqK8JlHJy1sp#91&k#80-WE`&c6?~gZTzuIH!b6ptCv-jf^tfk;``ex{J7%WCe_}^=u6R6ac z`~rH_egSi(zkoFQyLk&cf0Urf1fTX$wG$T8Pp-`Aim8~`(j5wZd$=hnDM^#f-o|YP zDNf~1&(>;~TJC1xO%SG=SGyzN9c8qw?!l$Y9H2eJd+}q=D-S2DS3oPK`e3w3h@4D?gMM1MZ0hwTZyW6 zE2Dw{;6U4&kMH>lh)VedlxzJ0I3N71NciUT;Ct#X;G*ysuu?h1Q`MMj_;~iqXSB|6 zm4yvjLO4l%h{rB1A@t_w!>2bozkp-?9U>Daz7pY|RsLQ2)pPuhSkjQqxT5iP zKiqw~`*apE*�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 Q=kfo%{%@WE(eK6o0NFZRWB>pF literal 0 HcmV?d00001 diff --git a/host/figures/filters.png b/host/figures/filters.png new file mode 100644 index 0000000000000000000000000000000000000000..a02d9416f08382ff7a03e176e37e6479f5922c08 GIT binary patch literal 49833 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 literal 0 HcmV?d00001 diff --git a/host/figures/fps.jpg b/host/figures/fps.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bfc18dfe881b738f6e9b100e4e32d5ab04dcb383 GIT binary patch literal 7002 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 g(=}ho_F^9(nG(4uU=TKmErVUH||9 literal 0 HcmV?d00001 diff --git a/host/figures/fpsselect.jpg b/host/figures/fpsselect.jpg new file mode 100644 index 0000000000000000000000000000000000000000..14300f78ce384f8ad0366657e3b0eed2177e7505 GIT binary patch literal 92018 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 literal 0 HcmV?d00001 diff --git a/host/figures/log.png b/host/figures/log.png new file mode 100644 index 0000000000000000000000000000000000000000..dbe3780eb647493dec252d69f23e71cf66ac15a8 GIT binary patch literal 4037 zcmZ`+2UJr_w>}_9l`5!!NQa1`(g}i6LnxtLLQ_CGO0UwTDMflH3IT;AfOJ866V!k} zP@42m&85bRKVduzS*zqkH>);j0RIx~CknZ4)x_Ur^B1ndGM4C>GuZvLEc{Kqoc)6y`8olno^IZc{QR9B zULwo_0IL;TQ|+&(`J1z$`lcjax?eEQFa0=z2V!kpEHG)+(b~?%rp!rVj2$AzQpAom z%~o5oU${Jg92tFN!N)A)5ov0VXmuWWr4Qj1ys4=B&wI-NhcxZ_mOv|<-Zuj@3;X`b zcatr~kSWSH)xyXdw@OH13oA-Pp)A;x^{uV*v21!mI&}9XFq*+cRb z)fBPWb0ROQyLTt5#~WA})7LUcG78E+(gG({nW6A$Vn1@vHH6kl!7+8`-6{0x)ehxtE+Bs1Hj$qvRQKsfB?i{)PxwXmrkdP z=92gzY)BnwFmzby!V8qZ3y=#uwaxc7(uxu3L5}|R zuKt$K?SKx*_vVXQ0W7#G=~?VwSMmV>DN}k?A01OBSzOT(yjh1W*75G+Kd!!0u``e! z#2EIQ`Wy*?Ekv7>?AG|0i(Pjwy)=X5JaGt@vFP*5qk5k2S^}?fAFOg*CYlZq2s0Jhu^8kBM;)oj=j(#0T?3Sl zycpUpFm4z}i9*)h6iqA}zAd#Z^Ip3hv{q18a$hj>S^FIGyzG^MwvDd^B}`7dm>z`e z{h}&oM2CU;GPlah{#7bS z*R$qz1>st1K?^iLkji9#&`o7{?Qv0@G;SahgR-WgNfgrl$ZH?VCxsjpW{9m7z~p`{ zNLh9oUq!1Vd4aKUrg)U?HUTs-lxXVmu@#!AXuA1TWvHumh$sp>V??ZcpXSWo%&L1Y+QjJ&IVaYKBdutFic$NxVgzA zY6GLqK5O}bm9vbplHM1$)`cJH$Lc+K&p4u>6P^SOj~H^Q8*#kND)ZeLO;*V6RuG-r z=Dn-VLKlKUWjsR&XFLfZ@2YTMtMqe9$};MKIqUoL51Y!{Bxw?a9@;gqPGy+n7$cpY zbuiNa0_oA(B0*HlO$k5fuz|}_IyoXB_8)~9GA(+PI_~Q>Kltmr3`>tey?{Z%G8*P) zgT4ryPi2!K9BtV{Advk68nqgw;!*or8j^)tp(%ORFGEsF===cW!0ej(Tbe&;@tKD* z#?k9;+Ud9mu!jD7FcI53nth7wI{H*3x8~Y;xwyDEIW?fY5+H18e_J>_K`2B2G%-ZJ z4z%}g=G+k


      w6VmI(#OH)&>KidxG^7GnBcV?NNY40t{t)Tb z!Mog|7yGPEAc2-4v%E<6rqPK8`dp`Ze{WdWA&FByfyx5j$_?^qZQR z{-}DW2i3ESb57mPQw?visgB%gCd~ztuD<9|gCcUF;fMbA_KWyyk$l@LG=jw9si)$GK4x2qPF>-HXGW=*SorOb0aIW~+nUH_5`@{C$?x?BstBu9aNtmVB zq?wr+0)g-gB2Hh(zd`{Oev?I`P^i2-(7c3li>6|c5zvCHEW}9^z9Afh#aYhMQ%_zxquOz0j4e%)!p6g zQV=ZW9?5w*Q01!^xG-2MzlWEXZJl#6ekpi|^ILI!LxV1MczJob26PUIe5&H`-8X6jQ*MFRWo5L)4zlLEF_MNaka(H%==$^ZQfkAGYzdw;obbFz}w zR)ufUXZu>z@7cFVlLa&5h9VSSG4IS5KJw~6K8E?Nft$Jr1VTrr#;P)kp#L?QS7CT~ zn1h33^zp81p(L{GQwPk_GOV%HOKb9_cB5i{5YLi}Q^F*DzaXg0J_~y{iS_u8iJ~>- zoMKfU90oJ78JO9JF)5?Il#6n}r?ek!wOUom-@LimM;UL#%y=sNIuHy~;j-b9tH48j zv^6zRSy?wNYV1yr55y7q^sZfY*Kgjetg2EC+?YtZ)v^Ccnb$%sO!eBKLM2-a7ws=c zal+{6=;7gE3?1uZS)ObM+F7vo{{4IBfm~w~lWmcs+AHCDZjIkv&($`O8FX)OQzGV& z7kwMWYKnk@{ZC5ZscR3VbetZpCnO}47#2FqSh7uS?$I)FU*zVlvTodB2omLvp%lsD zd(C%kZO+A;tytO(!2h(`1hcn+SGlyH_iGN4N^D^qNuP}zWm`U59v5(uN?o8TD&ecc zSXx@z4<9~U3*TW6O^FOb-spHidHAf3n@JhJ9RF+G*uHim)Vi7& z5`*R>E%#+oHm51u{kO!$#S4{#QnK3A-WW-MoDMRLAyh|4;H10z+vxcC-dreobLvBq ze;Uly&CRmL?qdR{_{6l+iFki@xXIAm=@*{kqf+4TXP#Hb;aYh{Mn(zO( z*PAXJ(jL(r%K(xFOGRl{OT-D(V1$8zA@wY=scWLaV|ujAw9#W)8>&;v(79eZ|N4%@ z^Y5vmW#8cWl%lc6S6N;l%P-6b8re=1#|N)unO~Qb^dfe& zd(O5z`B6o?$d)JX!E-O-Znmir`^ky)+Sb;Vr>Cb)M_BWBS4W>h&}Y`_>fK;IvzQU* zVR*3Xqvhu2=I7@J=G2|lkwR~C?c!P-?)jtK-d-K#`?u*miHm8rktbQWRd62z0|WRu zxSASOD!zYc$mPyu9;LRF#WaPvr^R4SqYxt_&rT>_vn_H{C9Shtjl>9LIXSNW1d-u> zglz0ciDA;`MPXRVK8#R!FLJ&ss$=^2ghB+Dy&Gv^Y%G}B=Q-2F-iC6Om?&D~Z3SJnv*SPfi2c8%Kzi;3 zl`Y{@8yg$Xb&Zvqx0BhLg2*=HJS}2t5czO-eIKf_)y4lgz`k~WZ*%&TwFH5FET4Bt z>cPb1t=*2}rW@`*8a-Z$iYvM{16aS(N1oP58$pMhG? z>zm@Asf0`u&;B^?#i zCjGDgr#nmyV5N#HbfX2+S&TQFNSpAyvs3ixSY8{8r;VrVuzO z=O$)8P=gNLrQx6-y*uCZhc{Igq0k<1S+_6sgU_|Fwer z7eCq%XB3V1+xk@0{FXGq(7>C{ycFnbnf{Le?(Cf0Q@IZkYTIC;4ghd1gl3hxUG#qe DzZtlV literal 0 HcmV?d00001 diff --git a/host/figures/main.jpg b/host/figures/main.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5209395cb5c65c857a192b7e0ec768cd2170181e GIT binary patch literal 7630 zcmd5=WmH_-l0LX6fk1*KNE0Aff;$aK@CyV98r*5zy+a5RG)N$LaCi4Wa1Bl%G#adN zX}o*-zBjq=y*smJy*oc<%{i;q`qnvn*RF3%)viL#qLzR=3NrFC00ssIV1#}F)I9JU zz{kbK!^OeJ!^0yWz$YXoCm|*xBBmm{dz<_LHQmDp)U>qpOk6DVj2s|ZT2{fw98bCV z`1t5pgvEt;#JG6*cz#!cK|nx2OhimcLPE*IK+C}Mmmky@fD|8C#=yqHU<5EpF|bH6 zP+b5W0AS#tt^M8L|9mhov9NJ)@$d-yd?*Nz>SXh|YSU5P?*yz@N=>GsVDGnI} zw-oMORbxCxCvu*Em`r@ov&s$%wXp*xUK8iH1cdkQQ&LesVrF4|%*MwrASfg(^8AIg zjI5lzg1W{lO)YI5T~jl23rj0&8y8nMcMnf5@4$CK!6BjV!(!v&KPDu8N=nYk&dJTo zFDNXks;;T6t8Zv*>ipW(-P7CGKQKNqIW;{q`+aVCWp(Z6`o`wg_Tka-$tmLO{NnO= zUKjw@-)a59>`%N%(Y!FRv9Yl6e&>aO>46q3QfwRsZd@`cRXk&-yNo;m_~g%GGAla> zK)h-P6eiANg!h>EmLDDdPVH~Z{yk!E|5wcZAodSla{w_G272+ZNC7Z#b{V*v(0+V; z`{tTL+_)1uwu%BAp=_(&E9TNYgFeXMnyo_E`jCsXfN!Dd;S2;T%+F#gUr>;4kLB1# z$$e>FTLxZm^QLx)^nFXTplHsk@6m|rdhvS8T1apaTwUD{I?^>FR#llV?M#gVBJ4h= zPY)_Lr2eSOcYrXxw*);O3S&|cG>&!`#1u56jlwUDKjiwzTzHdO-Dkl=8F-Vf@+gOD zfWZC2JouJ|fHQuNROxgP3YbwjX4u@lf$CCEYQ72yJsDPf=|f|kcl9hYMpMt4k&K@H zUSyhNjI%78+?sdT)Mv~v*1s+lATkctJc1)~tg8eRK)6`)b28UV^WIi46;(V6ION}0 z%~Vj97}s3Py`mX&)~aeW{7oJ=7gVFh7B5ysF}l!e*ppOZ<>B4ZC6HhBO%cN9j+7KN zc-+z>T1D^!v%HRX*jjg?_G@1x<+C>rX@p1oLUNT5p-TOAcV#!;kx|x@bjW`Zf=2|7 z*4n*&$DrgR=;jlnsWhk_FL8U4C*gfbXOo6*=p-m`=f%M7l=XNXuTSg^brO=Yw5hb! z%I8qad`OiclefBzWx|RuxN2fIbWa|}IY`kHw$_jh(4KpnC^_LkHo9Z5W zQmhuKo>MhB?5NLVw*PuYH!4;vv63&_>fg94KH&F?KV&7YtEQ0SCD{r3CiO@4izX@ZBG5~5f0E(DGR-+T9|<)$=t zh3Msag}?WTR(Xb~2X@n0Hf!6R)H_ks&1)u=WemVH6sk;^@xbi)^p47&b8QF6@FL~7 zZEESDA7hy>VpqO1nX6A?O3^!eo)v;CPuh2-il7$XQGgZBEZ23=@v~LR#fVi}Q~ueZ zD8;W59EZ&1OoATX%zRhPT8;JELxii8Y?*D}U=}VB@ex=Nt3v2Ab`YZvwbRj6F`4Eb zHFa@5Tp;JkelZp>WIy2ui>Tal0a|X$aD-> zp5#jt)YMK?o7jDFk7bdvF~|%bmhC7caRO#2Jry3shsVMb2c4X(EoqOfrUg~_xIgTe z9m~Itm6xgym}e=2louhU@41`N=*iJD^1t&^4eI1_FQ@C|aTFm=u2fbM{M=stg*7Wo zl3Z09_;-ckJ&owI2#b_QQs7pH!_uZN?@&N?H*yL2>ZgU8_1BHd2jmoch7(B<6NMf| z6!<^7Rz+L=pv8IxsHUi}xPfe3$S`86g)W*qwD8+_cS zZi+de1Tn`Xcevp7dEZ&j*>?LWX6o6(u4eJD*@1t}rSEhi_f2m4s6s%F4)P*+IsGN& zhebBhW7@m($#7rsth^An;^z>{2YDHs1O0Zy^c+TBax^wk_t)_vZh7tNBe;?6Sngr0 zIT+I9FaNUWG}mPDaIVsyU2&s**LVr5%B2`|@Agjvf063ERkO7Hrgl>s?)Ao8YXswt zUDgXD30vukB_!5(>9j@iw*B6HkbBqap~E4?N1v7EG;sHQ#9eDGlKeUAMLS6R(M8XS zcYmsHv*di*7eq&C#YeBL$D*8wqxQ}Bh4*sCk3_=QKeZi}wq<0>mb~8bIMkDKPIu`b zV3lO(kA3xOM3S%zd?iMm{N(<;LBB<^W(Ks7CdF#6zg~OtIeCUtYTveS$Ayf_fag|v zv*_%|jdF0H9-HIq7SQa;2yUWI^!ZG-)1$gwPaWH;vF4EEjW@hwNVQC1i$ z@K>coZntclN!{k+6>!~;T&g{Na6L|Cz0qNyZ!u$jVR>Nhz~0@OxWls zoX@V_QFN>_E&F2!d{`Q?1c%(N2q#fgt-0ba(RB7wp}YJ|^=6EDO$fw!%sg@Y(AzY- z2ys?-roQl;I3=g{gY?rmnQ68%JaKYSg9%0u29b}Yg~ngbN}IPj&EPLw#%jzg~b~} z)~vrK9e*6!R-IM(NDbD8RN$$^KKk&X_hgVlnONGBOB&TwQ=bpku_z#a_@T(>4e$aA zK%fAH^%nh=?=?gjcb;9wYeqFaQ^{ir;AKJqt&0_;$1$Nt!o9RE`ayMR4@!1<`a#v0 zw^q1E#DNnE=emxGG&Tj^V+#510eIiBolDn@`(;`4?dwOFohxG|BKGMOFNQrpFG{P4 zbJuV}Y1D}Y-PTMLTQ&DO;ji3Vkp1-y(+rC9H${tP>XxM2OIbfV^VYFRU^!Y&ki6%X z1%|MmajGZ!4<);dC?vCKy>DF<*G|?TTZR%sQeU4Jtt>uEv1ntiMHAYY)}0q`k!QT{ z8M#oPd~=IRu*5Faj;NXWbH$2!29)YdbYlM&|PS+)IsEr26NvGR^s<%Oy9p6!m> zyeCm#xpLMUVTTa23|zg0w0u)1mn3}wzKr&`b@qU_-m92R$*h_0amZCAJO9dEr$KtA z#>auH|HL)HGc&ZGBbP_(+)C3)Lq4P&S^C{107Z?!>EYo+A)V z6reMHQb-@II$5#_&=`O7d|u-?c*QiIv-)|#&{!LJ`{hS`n3w2?DW&lI=ny+~>0=2c%{sxH zoR!A2@eM7n74h53Z(%;WmddrkTOLwOXL8}JOF!koPuE;eDZG7(eYAby?^l93iuPkV z%j5y#3^mDz>kVmi&t_Z)l|&E&gDMT_`qXm+D~m6amaV9tst^}kU^fnY5LO)((`h)! z%Vr5SK3MW0aNT6A(%@mob&oQ)ett`)vj{V~Kuq0c1ZI5SHL3OXdsmv^T5bMT0nKI$ zJ)=ac9+Q?kpTOJ?4%Hrb$WgZ=t{eHv5X|FFEwaHfMZE81 ziZ==)dje~BK3oVBOTw?}dktD;l%BOwP9;3%3w)S7r?+kBAP9qmXPm;^iG7|LdJMKh zf1`EJX@csvFWr3gp+;Yp!b|v?o9!ePRVOku5{_WF)n1bWWU?=nzjRdBtogf*YZ1fl z&tUfEb9n1iglkjBF4T-}G#p=#=L#4JGTFnNfhEhR_1JT0*&q@h9Dccn0+xQ?X@5z~ z4bKy0Cs>ZS_E)0}HZe>j8t~{M^(ONY78P33F_N+a7I49#k3py9_bO!4fBZ;ss2iDh z>UZ@i;Ejx=c)wVNN-1fOLY&6d+i1oCaFtZip*#LQ{9G?^=w>3Is!JBF)MdTaW!sa>!oE$ZVszwfrmLj;uwKv)v zox;A7=xc@W=-Wbcli$rnEv~zhYduB*1Qwr5rpc@hNX%eFa!Y#SMy5Z#nupr7jWT(! zsYWS#`Gd??d|C;`Z4_Id_~(0+@sZBWi&cMSgb6TJy)d+&8YPX3#Rd9l%HannfcO55 zKX}bsnxb22b<4J9kR5(Qwz6RV(?@P>Fq?lG>K}gD=WoLj=FDSx42G9#eUYakXfDX?(%lo zv_7lx3CXq-+yIoK9wrdUb)rZ0mb+hn)Y0i&OHZj-^r_GM<=nC~x!S)SUdu$FA>I%O zkstObu}^(%+rofKy%;fKy_vedTCq5V$ZKA$k%U8GjjcQ4!|gLRa~%GGp$&z**8K)^ zS*N+Fnoc{>Oc^b`g+(*ox03vv_|ERl|NQif$$>lAH<3s78^T1T-8U>;rl~b1udZF@ z?u*c?6nkxfG0h_k%8CG8z^Z;r@}`SIwIFimEkE*E>mhJ0XM!E(ny`(E%;2Ff3TM@F+aQ}7uzA0 zNZR5-#&B!nNm>a{&Z+uGL?I{N1A)OEI?KB62z+YT{8@x=!@RvLOGZOWYC5}27%)2F zsw65mkD!6Fo42?ZA6J_Q4U!wkZ?!hUNBJ|Q$qknN-TU_M?I@CR>CWS{(YbaD^b)^- zKh0f9Ko8;rk#1;6cVH!Wbo9-CH$#}PH*1x0UpwqsuMgcamm=M9b37t8)p^1^5s+O{99tz^YpgDyX7+8Bjef(Nq z4G%u#Osgsh6!VvOPX?GJ6NT36r96{a=H*Kfo9ID+1@I^V*Y^3QnHx+#6mU)1jsmE% z|2*==besQghFWQ*ZYUr?wzm0ts2qRRrp{8bp{ZeNEdj3VQoy-3E?3NYgfQ0&oiz^u z?v6Nnm2Eec?=C=)0n|_=u_3xHGIrBk3jGFBK>;1%=+Z*6eHQd!NTOT*mCdYu`blS7BLtjhk&1pTRZl-mt6omR%*Y`z{z z2wQKjcPqcdwnhQx+)yO1EMl(~1xQAMQ2_1}6wqV!|6BbAI!z}X=06?IJ&kabCwCgi zBN)mIOr-s(g>8EV*EO80fI6%6K_mGlv}D{K52QAlw=cG}5>39M+tmg)vaEr7qJM!T zoNcyfL8Gd=X%;Jf8DV80@D(s|bo37{ScFLexIO(mK7;YM?Z>T_u!`1Xwh}iMgtbS!Gor*#+G$&0~uWBm4uDd&ZlvC{(-@Vc4 zg9%IFY4O)92`?Z0c0Gnw@%*a%o%;Iap?>4Hl(}`Wu%jcxMK62iMtKYzCUMeGBY`R~=o>REC2fP`-5tJCIn!#P}K+YVV|n;w$7 zGxSh%>SeDLlH>TsYL6>p#^IQsZ}hIUNLN2ap?0Szb0Z513^bu7&ZU;T>-M|@& zN28&9^NRbG!+dXF4wMaIN7C9Zx{3uTFbLCf=7iX0H?S>3ayxc2 z%hDOf)9P+Y9zV2S+8q2D+TeVFWkl;diwAlj4OX5mag&V|wfH1YNo<-9lhd=_{37=` zren=XJ9;TM9j?~U&<4A#d4hwSb!2*NBK{_>;`3_SeB)^C;;nOgapD1_W?=1@hNF1llc)YNr~l23jj93#^bPu<00Vava7jCrtMlg%>LhI_U}%x_uv`uW zY_Xy%H`)JP{KtB(@y&RODOlaj#esvztKeX#Pn6Kr+&F@JS0_AEVY8&EuIg2=)I0?3 zPT++lA<0Nlh?J#mY@t+3HJ}wR54w(8uy$r1)?{>ytZ_e5IhOIu3$CvqFh0Chpp{8u zS9Lq{W(w^d@mn%Z=%&E?DNrQ8Zn9TQf+<_jho=3P&KR?e6hr5UUAnNeg6){C{`{K^ zrSTcnxLy36qwDc??%Fr6L(lCUeFQv}37}uAro39wIoTIeq)g&9;}P^5R09RH*`fX4 zlr&c$E!nd;&jh4k65>TU!pO|_ZqU%{R7^)vEDgGDk=%EQh`ERa3F!CT{2D9HT%$a% zhy0Q8+&p(QoCR=4MMc8AwcejTkU8p{*P5{HUZWmLupK1~{!Orro8aa9amc{S{F`G3^c0~T%R2*=%iG%-XT#{YLtT<_+cRG? zos;WKFHtdUP;~-!%nj;B*ro6`e(y7SzLB-tBVk{&;L@$zTHDqR%^%F|g=EK21#b&l zrbXB+2Zp?Svk#DzHb0Q@6i;q#*V?4h&M-#-Z+fpIqtB(+(7_a0hyr})(XprKUo9!- zeR=q>>d}n7ZO#Nk%^3Ik{Og`w^a&&*qZJ~>q_K%D?_-&3#!u}hlvDjs62!t4r7f|daG@+A)4 zP64=`WgXlZ{R@<E=|n2#j}I==Gl}V#TEKfr5r^R{PBCy4u8n z9lTrtJtE;bi{xZ1wTKdRH{&2- zg{zuEVc#02Ja971Gx2=fmW;1@e5Z7reh4bY)!^&fNe2AOJ~3K~#90?7eBz zB-wQy_MCHWW>(d^bk7P5FaU&=pk;~@8L}vmG)XBGl7kFWlr72*1;esJ{@@?r2uJv* z_^ZN-uq8VD#Ue!+0tlH4M2Uikg$78FzyQQ7y}esiW!`(v;}18ps@|oi3qa3I1Jh?Z zYU-`Z%Dd#fXL-(YWhfpI08~vx1W)NHeK$k_uYH(ve&v-{KJ=jvnHhs>0w8X<@CMpv zDGHHX5c_rUF)nTau8p^D9tH^Z>wXx9!@~npeVpw+cH*Nu7mBQper5_7Dc7%)`XV0Q zulv<%b^F#WGkava;sp0tDpIFjc0VBm4_T8>1l2haFwhUfYPsxYv-xcHPIv2_r1}i& z0~bNx=k8*@w8_?LrXy+>tyvIqBMVHk+y1?tEPNu~GKy z;rBZcI5{~a1^*cZO~!6d0>{rD_JCBPy`DijeSCh;^r@3*igj~gM}r@s}`c=UBd zBWOgDh=9Ss)Z1&j{@tRt3!p+Zs8%Q~P%*W4x?|rF$5w%>4(KR0Mz^1g^q5EpPRdJYLZQ?6putu;-x+i6&?{V2tD|h>9$$ zK1tns+o=Za5K*6-fMAG3}$1H`yWk3{}9L!lSmqU<8o!4_};u?Puf%I-LuIAQOG6n$gO*b z!VSCJ&qG;ld@8646^evNMUCF^?Q^rvn{2Z6Q%c`gQt~N1S#;w97s$b4!5~ak4I{d? zv(UIP(e}IJH8KH5SvBsn8n!2+t%;o=`>S`{Z8au)W1mI?=r>L_aB5L%ivp~bS=kFF z8%B06yMY8~t~S9cc;<+26N>l_?_@}@Kj60#GMy}3gK zZV2Gt_$ojM2@yfW_``aiYZGIkGZvY!Xi;{0urhqLu5ceXrdqupu1R_ZoVDf z_Y}GB4{eA8sRGYuDu^%BA}wxdmyR=>cS3Ebu^0i#EiZtx?vN%32{PjKtfa* zK~hy9${Ii|dTpnC2e-fSw*uAkH0{dC@`w=Cim%}b&nWBAbUdCo8C?hxm`171Zbc^Tc4jdi({41xZWKNw}aoxwOuI>z!?Bd>?W{o z{CO!kr4*!AttU`30HF$Zq?=5w&BKu@*CC=Fp;ZqEib&+5ZS!~+_f|xTkgS;N{hn>1JzIQ1$igGUFAu@D4+{LN|uV>{^&42o-7GlpXKzfU7rj zcU02?5LQulgZ+6!M7m&1HNK(K1YlIBDpYGE5xAotTt^?EF$t9&^}~y+3ISCOho}gy zhffh(_qimGQRCPBe22S!pb>$&#_Jv`O*ZDb)SHAz5hRh=%p!N^N!YY#y)=!PY|#M% zkhs=LqDojDh;SUHN(+p#2TlORxC9hPP!SlA8r@M@-tw66?}B6squl)Ylz~1;_ukKd3#{O~)b7(NFit{!bd!GIa2-iS*j?l6Rh%k*#pP*pM_VD^xl0ahJ~m|R0+*#09a6ABbSZL9QF zJ#ljNUjsrON~5rO0=3L7UF}+WM|$y;ejw=vo-_HKcNK~qNPE}jzs_17BHVX*jR0lB z0{k;l|L0UA3d$O)PY5p+KSV z8)f}ub&nlrF5P4wDpQOXZ)WjO6uaT^1fiyz)+(>0Y5`m(Kg;D|4XR63u}l7`nzXIg zW+foWQuVWJB$WF$ki$IyxiGnaC}1F>B#(pCb)q{G;7ydQcZ^2uYSOWuwC=rwG)@R7 zt!tX7OJBeJbj{aOF~TdmIWVSxR%>GC{RBc?b)$|Xlf7`c4V~1AO$H%cHB_mx6Ck1( zL#d`Z7gY^;m#nHCDI?RL-;ZOjY4`Q9Zr8}QRWa$AAY-$-ZUV_!J!QVN8E9e?z&q;T zy1}Hj?QCJK5xIgQfQ(C*Jw(Llb;Nlqp0+o*+2LO>7O9-V8PbjD{|y%X4!NM305^Ez zfv~37j4g`6t_&R5{9SPAtKBy{2%TO>|0kotV2H_FGz=nUAVmcQDcRKtBoL#s5rHv2 zASP30tQ(|2i185<@LkZZiTji`Boh?!4tHV$#axGErf)fA*-y%~0Pq`1;7av{2xB8c zZrBDJ{Z=N|$;c=Oh>N&LY}Dpmo>22c@|%t*I3jM>CsroT5Rh5~{6^%DV-IC4;!^!i zlM8FZaBx#NPUQ?~>bwWiCA@&&l@%{L|DGheVIT>7TE_1wJr25gu`5uk)s4$6Kv%&P zWoC!3RpO5O`5_IB|7&!ry}5y7IljowpEv3lQ|+2D7JsxbuMOlzIAaw2ynKa>)&M0@ zxE0#FH@b-r7u#0)-SAu5I59cXY=ld$cP6;~NhzM{c?o;)o~maQgDHXtGN~}GIvriG zzSiXcSF7b>u>cSZk`SFV2P~J%*=$BKNJdnrXfTF;Sgu#e`|PD{TL2_$!S>M}+M%po zgt?Ty@4K$6V(jALV%Bx4S_|MW4i2bN!WY(XopmS&mOlEv_up!*8RveSbisZlq2C_j zv_@<2EVZVFy~DkF=cxwVa8h)qSLzn{dFwT>8=USt4evT%uRB>dl}yKI9dM_lglu7+ z>vS{XHfj+YrWC+R5h_#K!?@RLv@so0Ya?<84SZLS)(mAxea|Oov+qeVD%n?ZPIqOD zY$apm_$Hea(rd$YdWI;ZOz|g_G0wI+`lkEb;1?gwfSr%cMq~-oFCXd-dx+H!fPsyu zeb^j8gfU9w2-ua9EN}8uHx`Ia=JK^xsK|y@o7Ri2F=^6{p0Lwb0!){FPK-%uV@4VP zLDX>MYd;?%5>(SPOvNC$D8nqZ^}9Hk!_#o1EcjWd#PjahA{diXniEgGc@Wn=v1#YHgG5T(<|{Yt zyCw@IVQ{unCQa?92jX&9LT~{t`<@K51hAf|$%!s5E~*t>+^-*hNRmN-L(Z$^YBrmR z0M~=&Zy3IWXen7$SF3d{dDhK%4IVCv!LI6Q5pnO{z2}~L4%=J_jev-fJUu->F7vmlqeyqoYGrRn>dBNRUwGm9);0Bh)XieKZc9Ad&ffRZ%Lfzt*6qYUF^m}40@2m-hdw<;1=t{W zGQOuy<)z-%XLlpuxHZOPd)bvY^|H%WfNqL9aJ)hLW@59voYJgr}RNQWf@k8JFP%dA*O9fI$C4472mayyV^LjPeUB#PV zAQak7KaGuoe-k|{6`w#>n81K|crhQrgm(jBNZe~Bhrj~?Bm)&en42JOeYVj{Tefg{`_&zJI-feXAZHXHM;coVPt%rRjxaVQ&FM%bHfmAn2P0aYVH&CbqFbIwOc zM|EPaTq)P4E`qN;xN|XYAQJ8nsF;SBnunjP&ThBn;VK#o5Tc^09^SJA<6d4l7AhJV zcVlo3xt=^%v_tYC4ZX|{I-!V5m#r&JLZY|pL1(4aWM#%7HLh8cttl?%JN}+4zOyHS zHpU4s9r*ID^36VkxP29R`ag0W<`xD z>|5{E_u<&50B>NbAP0(!thb(WQ9p!UQrRQu?kQcZdGA1|Qc*yTb}UpSq8gm6x%5S3 z*w~_5|MYI2gaCB-GCKrbJeLfhZCc1xVG+RE^JpoLw?vQv0CS@#+v)PX%&rZ)QrNv_I9w1ek;~mXcb|`3 zdu65(SCt-;7pG@k*Ujd$!k~znfuR)5Zj@Ab&A447&+)kx22JL{F>;sbQSl~a)2Jt9&{<0%CUi1V{!wMiOWM(=Hx$oCa(_Bey%ejzIwP-ahrCh9* zHtVkJGIt+_o@A4nA?LpD)r_g)m|FN%&tt@0-#G*ALln&8=$mB1U>!{bejcyn+x2U23L?7|FZ+tC}nsOC=9C9A|A=xG$ zTnP4g;N{pJkH|SEGc!|F6&00%plbF)z?#&r`}4E2XKp{kh+0Q?wO*Bo*{n02j-Mah z^1OHN-tnzlO-kV|BBpi?f2d=)YWNpLX+%wz%hmGYVm_aDU02op@E{|XQX)4+?s|U< zvjhfQ!bLV~@lwc`4(qdi6&{Dl8fo1NNiz#D$Wu6M3`M#D4K2A&$O=3IQ)7dW{^(;3 z164ax6P^pRplK2rjNN$xt{}rz4Hd4)wd$sTmWg_}K?p9xXTlKF3b?g6uz*hXAO&ki z7N`hSGT^G4B+2an3zY#ULX^q~0tV9%aTrXgN(rY&^eieOO(JWsFAQkLo==SLYSF|? zBfSH%MRDwj)~)+?yfo} z(FQ>oQ%T%?jFG>#w+Rfv!Ap~pR&gW}5ktu}5>h6c%p<&JvD7_7k~}>Ozj%cZJTE-^;OT<&ycocC)HNDZ4L1c(R*32vN^A$aw{{l$EKFqLS_*hx*0^J>4L- z1B*JDOSY600#YFX`jmR_#mzR1PBzwN9d8j5ETsUbfa&I%`tTzNV8}yEX?+4e;7@>=L(mh-zAw*QL5UZ%z-}=GW!LPQpd7Y6UwZ$5!x>;8c~k zG9{N$UTm)19s#szjc`G^>^HbkYF+R0mG>ST888vRN@69^BWyh8{L=kN^id>DirG|# zW{ZVw1>#jHgRPt*$Wld;ieGy=QMWF`-E%1*s_t6@GwZk02H%ZQVUWF-%hSvCdOhT! zYrA@gTkYz)6yND{rBcdrxdhO(ZAvL3LRg(`>wew$L)UfPtaJC(YDKbM=I#Xuy`-!P zL{$k+rk1YP>(y#`babp!MeW_zi#tTjH0S)_q{Sg>4z3a&#VlPWI?#qz(=N64oRjiJArgilo`#2}GW|bD4kZtAF^3 zPk!R1B(US$K&3YlWro&W2A~lrNqDGeL|94yf-!h`?Tyz@&(A;b!4LlNAAR%Xmp=5| zVV7h5)o*-Zxz<1bv5z$8ul?@VU;FluZl9bU9vpxCul}_IuSvtIzoSejtglLQO?S1WUwMF3#4NLdmO(BjWzqgXf-oRz@6%5tOzi#uGKF zIT7Spzdq=?CN(PclqLW$(cX-v&S0BP1g5HLDv~AoS$jULZ>Q8ycJD^uJf+k$3eh%s zE{$45?32JnfjcFjX+GrT!O_C0UI2Ln*N_k<=Qkh#IkizE+18riC5` zB%vXkW?IC<7%WIq#9hL~=9*Y?(=L@{|gkUHk zrbEs}B`!}gfr{=`OW^GMY`!??6V^(1Czyu}plKSC5HJ`KB9e1nm#k&kq{fofz}nOE zv(z*YG);SUekLj@B~k=h@%`9Hd_i%)cmMvaTem9s?-8zA!d3O&y?aN8N2qB0xgvy{?&Ha8IjtGBU=5u$n#=#V?w-tG>Uq77h4p<-||aqmT& zTA(wp31O=gWRRYh&^UND#pQ@AUl& z{$Q18`rz#JXmOwvjSw=(fkBmvq4!oKB|$wIWZeIZ>OZH04xU|{A1;o=N4RIbUc38Z zv2gcEy^b2ev{t^hI-6T_(9Y_75?FhF^^Mn`f8lw9O7R9dg!idNgW%2^Z#?(xvm|x2 zBBt9_o!unJ)6>(oX$}t#ODSfyQ-NlhNW#oYxTtzWFz(#F`}}jyRn2%)D-=M^1BsM- z(FtNuA^G6^WHz78JM+9uYGWZ@Z5ft! z)Cxr*Bx)%Z$IMoSgLrEdJEr+H!U z)w$Kvf+ugvxSSY&c6xvD%q@ThrF#-(g!{OXcC{wp3OqDsM^=0O&YiorZry59+CT;y zOOF`Ke7eWU>B;D1;$;xxO=(w!UaeMUI*Tk-A-SyZ?gIDDI2%?62M4M&iO>*d-C0F6 zy3R}!;35~KnRQL0$!J<#w_}CKX%CVis-Bc8!H8LNx}sO#-KEQ@$+AF_IRn<-J%gyF z@kFO-xssaN94?}zY0mrgqU&Nep!Xrx7l9H8ZJkL~V+e6XJ=4xBAE{SW4NT}oEKPMr zCx&-LJ*-bRibPQg+(#|Z6m@=8Jv_-njwHA|{{sL(i6nKT2p!Wa2qb|L#xa?ohI`ng ztz8=aIGu9PmlrbKGHcuuO$h zh-w2)Y8iH~gk#6yGnMb*B!)O8Ajwn_H?G|T#O2nvsM$V81Op&Po_6i|%H>6{!{*1G zDN>pLRln{|R`Ym=o9;7xC{xL*9iLX^N#Dte+X<`R#bq~5T62H8}4-#&7J;2MmoErSY%MUv@jz!?MbN=qXpF+q^SMRhf-+qN0S zdJv?<6ivR-6eo>2=dxTb59V_aCQ5gRl&B!D3?&DcOKF-0JA$*0YFT<#AYhu)swxFY zmF%U@SX)u6w!zvsG_Bmp=51ZGZnQ`3c!<-llj3y&6>77KR;L1)HDJyO=HieYGL*M_fH<2 zM;^m)NQ!ubsXaJ9ca^JE_@{NXR$qJR-5nGU5%y6sBkyW)9M9I{6y|`D`LF;0jdTDb zXWQX#W55BgYy>AV0MaquZrc2p*q{ShQFJW!=MX|nG>NQ}oYo6~qEJ*lTU`lAf%h@A z)&lHHKWjTt2xP@9Qjc3A8J^4N*kO1CLQc=m=Zksxr9A06-r_ERGu-3;{rgA9M=s)` z%Zuf~!2u&wlo6-}+BSR&2y!SnOa_UJI6pr>JUD1VB16)oNX$*y*eY@7%w4`}kM_othBgzK-IG`xj^Prkk}bAW}K_RxNL9}WOVDJH0*B%P2hDni8(W|=Q$7e08oR{lW zmU7?M65^#4_v*O?nM-Ng#>o|Z*{|laxr*c=4}ITtU4+-TYD|kY6d?$YU_j;U^z88P zz(IG1$l3YX;o%`rlMDql7-te{Ip?#}v*W|Vn!%LJiaP`Ws##xhMBG0)IXpaQQZoUf z0J-wvB(Dz^2U-IUa~Tcenr}7EKR0vDY03#eBp7Da=V7^A9v&W&BB}GJX8&-jUnR9| z>G_RkBI1pUlfy=YFi7<&atBA=%ya$86|-NYHqD4wdG4Y_jS{aN+i9(a5<8g5Gfr`x~9Ifj;u%0?Xm2qGXpjliekAoX>0OHI0*Y;dKC^J8)b z`sU)xmtpjZY&ph;Z|u(Arffl7!?B-(o)9Y7m>c>MjmeO#7*>E8G_;<6TO&>cC}TBk z31Sm)u$vMTDz)C0i>z8pNoS2{JrGdn6R+!$XWEW(_Tk-gU=12fl?lR#?8PB&;t_cm zy40kAQqH3ErKDsaA|ek%Es{NTaompX<@D@qu~-BgNl&aa%lz2P{+cv(v1Z9R4>=;F zx`@b&iwpF9t|-1(&*(hFQXkf_hq4c$pegGT$t;=G#UvikLJ>K?Sn2UXMN?}S0-|sZ z3?wTKQSENc|Nh_q2aClmNQOqh);(KkAZ6Y6Carr`G0)CYy0!sWa!ICS+`aS0H(&YF zU;5AfZZZLMxnA9U@a^CH>}Nmt!5{wcM?TigDH`v2&jHB4IOZyi>H^n!O){y0Orj+hf~%4TFHuTrnkFSLUege&^C70! zH_lG9O%>8sq6|az7Ma2dZZ>h@dAFlM$zwv?Y%? zKRY`*I83T48pT!3sRY$*J@g(i)wlUo-tj z!!BYB{248;Cx*WZC=6jJORd=tp`}2apPzl{?l*2*HxF58a>P)EibRO%#d>vmcGk^i z;eK**a_jKOR7K?8*?D^~Z!|HYCPfec33rbq1HJch|G|UXw{IUboe|+dm4Y}~ zo_DkEAaz#E!%IX6PcBZ6(^{sqRAsgQ(Ugb?8%jpp=}(u%l#&V*svI5@*@BH*zckX+v%*xyvUk@4x1X4O(U*zy1 z=ULmeV(PJWzkmyo)T&(?wVp7yW;OvgdC(8%%VpcO-Mtyee(2r(_~ETrdo}8W><>RJpJR%}aPEU`~t13J; z;T|z%?0WI@_1SULn<|`5N|L=3BaoV=c**W}*C)pn;=>FaRCKbix1}67U9=C6ZVOhha#P zb`vRT%vVoe`T!t`ua~Q)No{V;QZCuS5)RYsetLScXlJ8BF=59s#X(M8V*&17oXxv= z3Tf5oF?exBB*|XR&(4o#i$)B@YS!F4J3Bl$P_v=$&(F?o-?}}WAvA@#knnQ<-rZZr z$0AUZbIIvs_4vH~5dmpiQ?pV^gnNc|P_x-=md%>R*!SOlaPq(Z+Hd^f=YI3OFTU_g zzw(d&)0dyoiwB?n(jWfnum0-GY4uZo>lc3H$NtJKTzvX>KlkN(>vHGh-q&9_T+IIA z|M))~9L|%%RHF=n`1Y%>{;&V+pPuIaFZ?Gz`d|HxpXl#>>!1Gf&;81;{KKz(?Mwg1 zzxaRd<@D`WUU~lZ;%9#9Fa7M_{4ZYp_MLz7PyV;_^YahB{{w&Lzx;*wzx3YIv$Nyl zLqzne)gOHR^B@1nha1(#G`v4JI{5UbeNB^&X@f-K=J$T`T+xDo!l~3s@J*BsV1QcWsBten~!}=?){@FL7+7D&55*2Hk zp5^S~{P6IgdL1`x=GS(KL-g4C1*cs^h%E^>i|q(y)+Fvj9T49G0zgi3wF> zn9hMOsORNw-3SD-4`;q!u}Me@36>;XPy$R9%yteT^JFqrV%SVV_L^0&4auv1Sqf5W z3niB@OT$>GcB%<3LLkCfi|&~cl12K~(rhd;y+PuO7fXp?$#Icn(OS3+IZVw}Rw^RW zwJ8r-SY7haS_V&O3<)Uc0*b5!3K}CtEEz_v(ditIzZ}gc08lBl28WLNUXYC#RVb*h zqQcStu%)WOO~#o3+#GKFgb;FDgC{eEym9j2kjGuqf@B1ot%SRaX@EKWZtkChgH~cHCAO0(Dx`=M z)jI-7DQQ~w`N4WXS5#Gn?qlTTHh=*DpppP}O$C}Ip_yga;EqtWG(;JY%-WSlt;|-# zH+n`9wPe-H7bFNJR=KCbOfqC`Iy5PXd2xe?NEW*A0%43)Pq5=g?HBF38ns>P-bKkd z2_pcO@SYwFtj+V{0-uSq^>9!@+#fS&|aH`&sUj+N_&N zurUh{9X-c8Wl)16`j~*eG@1Q4atlZqX9(}2xs&XMPiVsNxLCq zO@{FSDGR;SoOcNXL&yjdt?0sLl2x{;Bpdg-M+jJxU7`ZbPnp15P69(D86=X*SqxwJ z#@96a^1*|}Y!+SsKt#BedqmST;eK{@-gVt<)&at)rVoH5njix4;{AG^Qfk|# zY1&+}lOTfsTZhM;Sfd8O8DUz2!ML|PdnW13 z5(DS!{ObA1vyHV)n?2uKuhvJ0GDeV8016ba(KnR2(N?uFS*eIFADh`vQq}r`h=AZv z{^UuP|8IY( z>l)Lh>6-I<_y6^0KK0W-`_mu##1G}Ql;~#%?fFHYn(locc>h28$N&Ahx&6ie@a6yb z?|v0tyg^u;gy@-P3_ zM~nG#wO;oZpZ?5eUi(>5zW$Aa`~2b;zxwAs^2y^{hr;FU z+qYkQ;l+!Ki)Ws>4Mlot`9Gzn^tO{sPUY&lU#*w<&TG%k56f!3zF11Kb}{Q&9-KaS zF_}(*pOaq2>_uTYTqV7+T0YZu?PTR`!rPWA$r9VPN_0sS)y(KsdZ4I*ky3IXP3isB zA_8!K8FR8F6)+KQwEfA~9ApNPYIrfxS`h0(;YrL!_Fa`5t#Nn7ccUT+P&5;vMoUhH zD4@lwl_mu;P%_1)q+bCvHuoG;oj0un=@AgI#!{HNCe>6%eRPwZCL$s!YF276$R-R0 zQKlHxNuDKZ0f}%@5kjxwbSYG$NHhtMtm-MLj+G#&>Oc@;siqT;PWh(MwiQd#Bn+9! z&@_{bn}T}BDqPhatS}Q1h^PhwL8^?-r2wVYY2KkJlwtJMjyC_3Wc5$~CjXcSRnbXF z&1oq|sqw(7c-9D23Y=`(wx4y&l1~Lxy<1ABYN`O1$x=#@EFwe|8oh#03Md-jxysxu zsf|esyK$;A2KSbbA<#RUCe2i5(#_Hwgb3QS4U62Kx`d+ zP=K&)JjGLb7t*fHUx|P-l1K=ou{Hd3Sf54-OJWu&HDXYqA~P<)UL;i4(-{p9`cFYwO)79&(Nt7Jg{3Aja6*$Qup+5Qa&4&Lw2+{gs547U&~(%@jZC+I zHANB#u(0E3e%-JD*E?escGfS3dfQA945BUw`AzUVU9kmtX%H zAonivt8cvVzE8fCR`cKaz286j*1voGwbRp!3*J6HI5_ykC*S+>hd$T}e(cBp%CCLq z*I&PT_k%CLf1Ud_%;`h!zxK61K6vi=kA3*#b3Z+M?Y!w0#W_2iL)%whJ^7vA{rA7{ zxBuqh@%)c}_YXYe3t#%O2;O-8^#@I}TCE$^Su^S*Ua|@pt`ed=Jv+U1`#r@2Qix8p zQ=Q$rclW_pzIZl7l$eXJRu{L98W}=WSF2S7t$DgzpVCu$2Z$aj(uj(xgCUYtPI3-% z4M5naxHT&=4;^iUDZiZ~2A>3K)&Y$)vwl)T#?x3#`hF;8W+|cwAy5-7B7>z~y|GkE znWv^eGGxl;9R2FKX`8Oo)WDfkDKQzTsK$)D55X`cB22}sFnWOiU6Z61-KlBJpi*1t zMvbUiMJ`zjG>bv#j7)1FRFWNy;WfFub(N_QW)11DwPAETy=j~l5+y)EmmmYnnvHYT zn4;E+62;`yiKr$0N<;|egDg@ri)I^6IpQ(3q!lc59;quPqFI2T2QmaiUqWgDNC2Qh z3M52KjRd6*nH~rzL>*E9Bg!@ef!mC`ns;0iBck@W`pmSJkAl?7tCSs|&(4yX;x$Ga z@5u_i7E(wRD#xm66bZ2mIWOL;IiMaz1W5>F_o6zaCZ}dd4ZvPgZre~}Fk8xvg$hC~ zDQo$LgkV6V0E1|6Y7{FKN7}l`)U<>X($HF3M{5hvM>nwR$3vKs8DgBtjXk3fr4~d{ z7&-4bTeCADi;9@YGIPF;d5cZ}j&T`7!F-8>nPPMkZZkqxLkZO;k_Nh>UC~BWN-#nM zmLw(X+*CED^8so_D}c3bMF^_L-cp8IaD)+SRIhImZa3k%VAPUEWPo0)Ny=CnaLm0Y z3m6fWl(j|G2~ol(GqCDt3W=>*@sys@L@KUQf|ksamPU)F!BS)9;$a4r?9L|j-ei%X zX+pHt8qBMusFk+K9;)UDn-1UESfoOkYbzQ}NXHaWozR*Jlo}2cTBeX)SYcWeCEd(c zSr!>8h6&~#t46An0#!*NL*{xN^BKixrcIV1%Ocm)(anUTM>wKFj%7GLTB~VOF)C;fdLy!Xv7#)w;Asv|CfqiJER24bd*2@ zBj^$JOctP4blE-`l3v59CKNz~1UJV+Ocg7pgJ(9sotmC_!IWUP4W)WYZxJy(gd!p# z63Ma;HtQ~ip-qW3!BZtbU$v$kNzhbG(ROJe<6$tIs+A%HMXPv-Vhl?}r;r z7&Hyms9XkiFS`^7xMg5^u7X(20*MIH*d-)qb_v0TrPN|;g&WQV`4?g{w-}=d){K=1gDJ<4ad4oM+*Q?7kDF9g(KgL~>@WYduYdIq{@;J|n~TGrfA&2G7E5CF z!V4ex1<>hrI>cKhJHM=t?ryEd01H)xJe=_x&>w}XsmLPR7S1~sjC zDzj!X^k`zVv;JZk6Ikv#b6TJ zT8n~O#vD`7VomAO&_)`#7zG(7+oK#R)EgneB>X?KM+MAoG2oM zD3OSX6UUA*jX)q0+u&ea-Fx?*v(_Brmk(pEwfAY>d)HFAu*97@y7!#DS2LT@x9(M{ zg}dRTw!R61+eii!y+g1qu5IB*eV`^rq6e2`CgC%HzWn&*dFHI(#W_RKWaW&h)@24` z(gJ35najLl8?;Ao9u@Ei$(pQuVVn*8=ESuJ@`I0qtM$iR>=I09RsD9CDZ?!#Bxx3a z(_}QL3gN;`114u0(*QL#OE;8T-%O1g4CH{rGG#`CrDP$8bI_PxHJfU8ihl-rIV3Y& z!!!chXt^!9_LPfcBB4u#ry7)8NW)-mnPbt~1FXu&X@;%m)?;gFFoVxxRA!^|-lhei ztXDV+cBJOdVbU@OXl5ae$jY*@BQRKpggqN`5Tb-gh6pqmV3thD1U~5Hto<;^smSx9 z#T&0B`orCa%g#O8$cG=j9dE}mN?(Ow+%nU-QJ{4+4tDm^%ngX7rJ3Aho^GiGI^5-0 z)!DJZY0Vq%rvOcY+9@LAT`Mn zPFli>w^S#C8BqhFio#9OaiCtQ@*pfk&_oAaV9v(hE~IGMa|5*-y;18;JK`ruYF6)f z;M&(U;RBBO5ST6kUYQ4Q9aHdj{85Z9won5-AtcSW5kcGb_`Z-p6D&40w@T%z(uHQ+ z!&@$~Yeo^sgrPCb=|&?xQ@xlk8xPdGy7SQ>m`P8wK_(hnM$kP%2})Kj3uvY%TqR2Lp%2y_piV6PW#5!zq#ITv2J6G_rLZ2d!P73Mr@-R z0=^w@$J_B89WM4PEgDrLODTAf@6seKQ(+cW+pSc}-QVVIZ-oVgTL4vweipSZ_dwER z8gi1VV*Y}WfhMy=YfX9N{b9GZ0)Qu58U-gavl%ZP93_#SY%))rTH7K>n!KmAQsaRu zU43fZ!sTD0GLi{cX;{rXVzVlRmm5{uUJyaJ`YWxTyHleyvZ{Azn1S@QB9z{aSwRnTQFU^n&H8OAIap6I z(M8`k#PitE;{c#6r_VGk%<4`L+{82g?U9%@Eduo$9xAN=z6Sxk%B zX0BcEHIFSlX-Q8?Xz-SF8iXeAGDy{3(u}1!mrqQ|vaOH_jLSS`i?1pcp}-&m7=yOD zR*%&;I*q1`10Al^ex#Vr4Y|b_ErtgvnCA$&qG@!Eo))YwnF)y;jpNEtRgZPewqr-= zq=1CH4$@4jJ_LkddBh0pHP-i?6)@F_gn=}=tl~qMd*LeM@r&IWNK_MF@yN-3JN}3VK^J_e zi3BCkEOM*DnE+{~G0KpW(#}OyAaodP4qm{lCf2jKtfg9YqfSV2_bT1$N#DCNf0$KA z1%XLRc&fo7QJk%jDK)tz9C9!)tA2a zv;W|?e&aWO<2QcbM}I0(-}vS?f8{s-;h*~Sr@#MGpZQyV^S}A*pZwexKL5Gd#9?gr zAH4d1e&@H}Z`p2NoEE#ixuqJlxy8Tu=l}e-e)G4!@Z~@EwLkd()(`N>Pw2~E{_@}d z2fz8h|H`j?0Z2Bt$^Vyl+|BF&03ZNKL_t)(^o_55ZM}Q_<3IW%r{3=G@4EB$#fu;M zvp@XJ_uv28H@lZ)weLwS6{msAqKfd^c|NKw=@OHO-{cFGfd%yQ@-u>1e zeEko8|Mte;d+!}@?Sl`#{?qb%tU6_a zNut)SX_voh66;MPCCen%MZP79JpB&AKN};PGKnY9qkBe+gA%Wo#5DoXj zUBi7tg}Xp7!v<`?+OTjs@eUFJT#&1vY|8q8<@^(xrj|!=VONyo0R--VhqofZ+wq4S zk6?_~#A>Igg}hER!Guay3$mq@@{pTMg0d+s)0HkDRkWgPuGr(<(sH=grnO0~bgfO9 z@+LK{p*6KOfrzXco|;)_oK*OMGF&$E`4xmLq@E_Z<10?%yB^pecD}Z!Vu=WY3?a0J zkx1a)(HpWI)DIAbz@1&zhezdnUQv$&F%Nq|eVT&hj?p`B%4;%vh3v;U9kFi*)?W_p z;ZZ&tM0Tw*=*iK)9e+f_DtsepcD3ib(}Od%OG&I<>H?sgqD$HyDY+lF2`$UpuS^+B zL@W!i$XV>DW%iVP#7o zx4-vupZnpT`nk`1_se&F>?eQofByCV;lKM^fA+I4f9@B4;k({_N5Z@Beey?s^xc2< z&;H3j|FvKH!uNjeXMgeM+Z1{M!p&a2|NgK3(|`E&*ROx%&;8^N|IiQI{r>O#&<}m3 zxBTE+A0YX=zw%U3@BE#=`&<9_um7npegDt?^nY^d zO&RVkCCz{A$A99N|NCG5fj{$SLi+4yKlQoKd>Z5Q1AqEY|KczH!T7Ado&mXSS@vdOhCeqa&R@y&MjP@k8VVrssR z4S(~Gtl;6@wJa$2GOJbV8?2<@hpXX5P`Qlc?ql2|`~`$5OvB_Pp)lnP0SAmJCK+Vd z^e7yC0kB&Za414k^Ql@(EGu(~~aA~E^J(F@*;ST}r>gP%#)?QIThasN(MYWVG9}lkl{dly$xWo(m@f<2Av(3}r zmC*u+AZ! zQZ3kX_nc&2q>#fyA{a=rfsy42p@EL7T}Hty%@dyRiDJ++qRq7()++)7N-7K~r72xe zLHSu_nUPfbUax@;qHUGEc4acGM9^|c1 zE*}%C!`6DS#B%m^E!5^!!M+3oDF0&_><;cW2}DJL1xcfnN&-(ghNU-V3TCXRM_59*OZ1ZO>ZrM zJc71#Mf`Sje&j`e_?NhwjX7D*!F!I>F&#YR*tVTB;h;j--yW~kyz9XmKkVq=j_=6e zE-Zr8=iy*y1&Ng(ho1yEdbV%xW~l0V%L_{?WM^ZC#Gg|^;S0>t@z{(&F= z@%0nmeQ!YM3!nR*zxG#usl{L1?q1!zc!6>E$R12VSO5I` z=8n-6T|GSB{p5RJ{*!-$upH6M45$LUIih>}u1|gUCqMP6U;V#+{pWw-=l}9w`AgVr zflKRGzxsneZJ${Htl#*rzWf8`K5M zqCR~j1gfZ7O7p8^;=%x~re2O($#yh_g#=5j(%~3Ms?r$^$}8pJqU|4!`hA_p zk5Ngm0_RZy&xZ?h7mh0V0a)UqJMF8LnYjR2ircV8TaGyGxqId8fI$Y0lu11VJoE{3 z-w#77%RCX6aLHYJ$MHLMHj4U6$NLWFer*wt;0h&prF8*&1H#;G`r@qOHp4y-yTO&J zb{T)n#V=wQHiOTqgvcwIpo&s~K9@H)jaA%GOj#mp_fiZ@_)L|jOqs?+8Ywc7VOfs% zWCfsH1e}_Tz_Q#Vyma+8vPs(LwV$gdR2(HQ4=|e`bg>X#oi(+`=SzEC=0lzr9=ChA9LC`f)V|CE zNAL)(-j289k zR+->=9M&FF#w)YyVPLsF-;3E4yP@ap_)d*Wq$H+ms5~=3^&=J(1}r0P*X&9FO2w8h z)@VLcLJp30@4W97J9$Wn5(I;?wD^%hpSq7g2k?5_pZo^wD-p``sh>Xk z+0Xvc=f3dXcYpT4>9?5scDxC<7Uu!}F|eaj8Ol1m%)U!Ge9XJ~~?; z%+1=y2GXk%)|Jcc*meLiTag>MSs&I|&VB>T2C-S&%*`WBS-Jf{g*Dbj9D2D4ZIh?k zCX>!XH>;XyfhwQbr42f*vZkmS6*?kHAXd^yWezK-Beo~^pJ!uqANl{M?PV9a@Q@U| z9dF0SjzgV226yMA?Zx0rnlBU%0f~ev3M@<^4US4zj8ibX)yM_abG zjOJ)AU$&l^OK*)$TgRqlG=c*RYn%0|`%!2s>B_qZ07=PPph~)EwYFN*ZqVJVZPrO7 z!WfqvF@VG-ZB47IRo0J*q0>;yvM;BA*F&eFj;VT8P|xd}G>sE;k7^sc>VIh3SBA@j z3m5ZF%e0i0L6S3J+K{tV;KcC*qpKAl4;SlxKu54osVYxx%g>+wTmAn#GRlmWBr6-9 zNSmUF&BXWwu=f1cNlO<2rQScuPiiji)gOw&2NebiDWxQ$n)J~W5K43*W;~{S6ck7p z%FQ!8RWXv8bOscLkiv3!LTrF%?_2Amla?9FXnizH3-)EKo?DBpr2vD%WXx}$+E2T? zyYJ^)kbtB27%&IT3bPghnP`of+t`-gG44PETaL}!5+s0SSs?$JpZVFZw`DOb3$}H@ z(QFzo;0_ohBXex6IlNf~F>c}pZ>KQ-2w?Wr@GPIZ7xrFgy>Q+~(+P>baKjxD6&75*d?ei|Z zh!hjdsyx5@H;>KLmApQ+|BJFT4e?=25XJTmnsb{%l>tEY$h$u2eHHT+urP_1W(fu@O)%R%kc@o` z3lf6JeSycp6fQI2iI!}E7HFF2V1x?-d%%3Bt2hlvRy2!vY8uIKbWW@7JSg=3#=Xcm zuJRp9bJWgBFROTVdw8jauuo$$=AExv(}h%+7q2)qFA*LOr4&qNy)&;#8f+&!yT$2Bf;c|fHzz?7wqrU$?x%Jzqsr0= zgLeM1ZyU=3eyK2fPz>&j1ecxWjJKx+o6*Tazlz2m$w+$T50ODj5;QR@`2RX(rj9b9 zx{{MGFu5~OB4}yVza^Cit(_2EsY)Vpu7U=50!fcD532`ypjE8%+zxr}0;Dd~x-jhW z{D;Rn0pub~UK8y-PhxT+UW+JK#*<>|7g=RWAjh5@!o(vezC;8}%5eNV5Ph$9wMgx8 z##fBt!{PRNsoaT_rmuQ8^gnsXG4p-bE`4GjJp2JoyI|8Ua`8$Ex}pO-9ynp*3VyCG z+xKK*^cCN{%#%kF*8Z+Hq7?;`JC14|HzvV4q`qNPCwdjF)#_Z^%#^%V`*-ntLTt%+ z0&O?`?3)4Ms_65xgAuj8%l0K$LUWP z@eY9Y4xlq&m2XJioc$t<)l@Fp=G*bynDUAp2vc0n0i*4J1e7i~+OPK&k;!9F`z@xjvoOIpr0wx{JL#+Ww1k6lEnnVZ?z zHj`84a3S4%i~-54jcr)K!uGyyIQ4Ukz*Y6;nvW5rui6MFeH)q`p5Y|Nwvi4Q<+y4a zyR&Z4EVdx6flO;^POFl)A2%o=M>@^XOvBlZdf3lhef8?BZb0zn1V@bKeT+CQr?Cx_ zBjMJRnL;(W0su)cl);p?jXd=(&3f&Z?VWUlW=3rWQ?U2b{oOreWK$+G#+H(iR*i;F z*0tXEfoAm9)@@DiFux0lP1$@jdw94;i~DfvjcwhwW$S(T-jWCVDWMH_^4ykf-CFMi zMnrQTF_`ph8fj+Luk~^p0Kl`^M)wvYPW_ab%&BEr1!ZM;X0xUZ4qw``ZmWm4;nn5) zA>{2@498ih#MqW;tuK*d9csO0W@Mh1bF7gXV5&5CUi3#@>F~#E%$PYSotJ)pzj=h4 zx%JCTF0W>HA6r&lBA=X+|@e!h)`x@b2Ukwi!znS43O@wR7j*l zh#~W)Op|Nq_V&fLZI(2$u_+In0`HjC&@xhxyJtS={|tbVha2c= zOJDA{d%A;1_26A_pnTPmb~4X(wAPF*QikSerta!aH(*m1J&A2B%X!>|XIs=Fv!ipc zc^@$_oSQf6xy8~?N+H7BV-2HsG>hKyWc1);l48b)fhw&n8PSYLnX(fZp&0I7rS7j` zOk|ox4w8%H5nXWS7#qN)d9G`Z^r6O^jNSt9 z?h%oqw=Ox;Jp2&Kd8}fK<#ZY`wlR8mOH-yKCtX0I(=t;tGvoNq zt!=qF8iUKStlKK?4JhY(u(Ad?Z3lR|Hat5rqTU*z)dGicUw_x`^avPab!a42|ST#5Y zytQo|)_OoOVDvEpq_GJmb7!|$-NzBFv0QrtjM~=cAErxQCzG>3+R~NLx0+Ps_S(f|*%aj}3ldNmNGq@Fn{g z8;vJFM-1q&W_=VC<;ZhCD>b&!qV;G1Xg0>?g9uyHm{jH?cAS}>#=+KnY@_$S)9WjP z&cnT)lL0`KxJaP0c~~pxRa~wq*fWa=Np8*3J-wr4#)q!wYFt!!0f0$mbm{Edy0+%^ z@W*ol06NTMj%H~JnH$L%!`%sGY|R^Gjdhv3EY~z6>B@XG2T3Mt^ljUg*4MFG8h5I7 zeHoIApSEpl?zO{~d~_MfjFfolOKx`_=3&Oir1C>HL410Em{+Wj8BRxxJKg__wj?+%^8r z9y>Pwn;w&gm`Bhb-#@RR0n7D;yKU*gf{qWT!d)NPgAeA#AGOyHANgSZ^IqEikw1OW zlY8Il`yVw_)tetWLxoIaDk5nPQBSV{AW5ovnUS(e$4UqY&R$`XGBYY3I8axHltNAi z)Yao6Bau$i{`-|l_oG)KK$(&CWJQ^2_wpMvmc-mLhm@oV^A!)|-^*NTswpIml2S1p z%X7)4pD3RKoMuu_W@Of6-e?umQ)3m~8=)wc^CrI2L@hi+X*`U`nhUA2rnMWuCGf~* zlhafJa%a@iX6D}R_~AY~A&(PJE5RVEHY(Y=SDc*zLP~)QD4_sEuNEq?v{Po52>3+B zn|hmO9B~z6eM~NtnOPmgAkEzM)ZB1MG^%#-5zz%AA~W0#sI_Rf?;{V-)a(FLdC7VF zT!~WqOrATM{e}!>P~^-kNGcEmk%eEpl3JcZATHks(kx7yQb>+j7yr?{iiDJVhrJN; zDcVk>H<5F{>am(|o>>sS8wqv;z7a9uM1s?JbQYMiN+s)D9^zC%KQjyWV>+F3h}x+~ zhSHFEk;@(&cS#?qNT?7bGPDC~Dk@Flsc9{8q$;Bip-3datf7ex)%D>qC`zid`sPERwH%FNuo zX%9!1S1d9^BNV!j+$i6ra9OM_Yba6)K*}MF%;63&0aR-uX-FY@!B)Y^9!GhF2n2vF zBbWiDA~LJN!{irGwYCgJs$FB~l|xP!ysMMCB8+oI&mNglQYamgGM!S zhV*f!g=_q&eL(py9?ftwog}By>}MAM&Fv61O2D|bSWAgiM#)E-P+GAz9|uCJ z<5gyq7%*KXxgRIA7ueZ5(_Y=28BzcS>JXpa1a1k`^(1x)r?B{m*F`Cmj8Q^ziIHz0 zrq;g?kW8t`-9#i(g7|b@b9#}HN~?TWm zPR0mL)PUWKAZy%pheFa_zx^kDiv*ZjLrv zeJVvmO7Q6eqCFoXB&nn~DPftG?)E0D5Fo>@gDk-`Bfz$JoqO?FAz+n7FECrL zICr&X8X;EEv4@M`My55%lnl54$^};Hdx`;vaxd!l27}Y)9-3WZ^XZ;& za?lWGlLA@J-p3EegwGN}kdJY5`jmDaJg=G;iM?yj+spgC#chMe) zSIn0J@|@JboIqyM_w8+h&&GhyEMG|%C^5|iQdx29vRWyVHVfH2ey^ph)|-itS7W@C zr7UPh6uo+4dEx93os6_#tA6y4*G8zQ98GMX?Yds35Q4!Hq$D$@%1H>TO|ztBVeX_h zSHGA5CLwDkrJI!&Qbdx^?Nv(FDL`8-aBi=vD3P-$;q%t-_Zn%=q21p z$dnmy!LM6%UQZ>>VXjbtjW{_-R7ppgz^Y65)R+bk$qdd{h-q%q$!3z2mJg+=I&9Gd zulj1fPoTCpg_t2sCIN|%i4C$#Wwxh7!olOrSS>W3NaRc@v)~Kr4Z&e^Hp@3_u+ZDggG*@fB-mjds|EAt^d7RNuX$i4Muq2(Dr2|3BQW~FZ zG8AGf0umF_2&ICPmKLMgztV9yaLH0WnNc#}NA}f6pDX~#GL_XhSC*VH+F%1Am0Y1G zq=S&{^AXY#g7xZR`th%oWZeK{#H640L}4zt=#!SpQd1gvV~g7ZXKTTO3XDaGu?M?9 z;bqXiIyYlxCZZs}XhJZcVdk^;P%bBuxwUF|HR3$t(&?-Fi zF>#yw^Ja+v02UreL_t(mek(edS5nHG?a9Fb5p)=lsgl5Ln$xOS9>Db0B*X}FluY%+ zGgwBzB$<|ENZDGGVj2@2C1A^+=pava9G97tjkcm{DTub~G7PgS<(pvkzTDs6AIP|G z&>St|MtK?=8_n2bK;ORH{l- zHe@ld5+5HZg*vhpvJn8vBZrVFfwY8Y+6Ywt>uJ^(k%-6v1ua7NjvTSlBDC5QS-#{? zanK^MMR{d87?69$h96 zIf834<{{EkAs<2nS6VM$5TC>n7GOu!r~?HxbM3K_A~G{$1QV>Z{RkF1jNlt(8mEo) zNZSUIRYzjx+$E3$t6a*F_T2KHj_C;1Kn6E?$Pjye@`KAXotD#7)KWeY+v7u|V4kVu zmL9Of_jAL0fZf&0XO2bh74mswxGw_4JV;08+o zBZrxZkrwhy+k%$})$0i-2Ry|s6aWmC3Ronz>7=bpOFU;HY#H1tsh*@%iR(i?g2PDC zLIciOa+01|SfPj#*QbyyXdnUu2KflC zqQfk5q-{bDt@gwoniMQhEeuu89ST8<#M^TM*7VCk#cEXkAPV^zS0g~;}E2h@r8QfS!-~mBytpwa+c@xN= zUB!otw1Jjs+khU#V(#xv+X`}W&w;Ic<~JFF(qhMV12A&%A=OgFYSA{`4-NKA+sY>N zv8xb;Ry29d>dYcEdz`JKGF`VQDWx)A1QxqYZbK=;s-Vo3{eSwUJ;ae%g-~%nw#*rJ zQJ^IbaT7f?FN09|WwMY)Vk1+?K*)z&K@1=`n|C}mUI8QT#UX3}LkO+1ky=AR3)o1m z023QHQY(PSyP`SIusqG6P9(&ng=~nKk}T0A-Yav@`(0D0&+d%%kE@ zJ!F=sXdp?@6jzXJG{tnGKZFXx%tnsNL&;B$FPA^+Fv4(4_jR#1e@JB{)K>$jEzI1E)uy$`-bl@Db6bEd3nS6whoYcxEn9;qtO%1Z=if$sxP-<((mr zmRbX5zM`G-8G_K;aw~A`>4SpX-r@2iM_Fn!;pPk53t5@=O3){Hzr`5wGUUG8B$2XR zfZ>P4F(iCLo|8V1i42*eUU6o-&At8SbKZCdA_Z8p{;teI_6FpyqSy=?p`fqUZmTTY zo5*sq#`Z2OBHL~;-*2pGTYIknTvKqt1usVy&K7T378q*jbn zz*n|&l0y)h^_MaPBGB009XOE{Gw9Gru5gEAzc*ce@OY86w%ecUXTq z=i086;O6Z;7(!;K&VM&-AWjwSIMedMp>@l3%bNywugy*ICO6kGK2_@D2KKnyD>k*F+L2Td|`W;vu+uQ47_9g9LSWJ&7j~NBdka-4d;$Os7y)non(ZuOqCPpZ$?`k{(jP0}H*5_Jfn`iTjfybJFl2l3PMI@E zU#z_Y(}w|`1meur*qCggXS2?0VwILbDPT)Gz4Ig#jDnA})Qd%XjO?s`c(S%wL*n^Y zs5m1z@}OEiA2Jf%(WxktSseJg49BL_fim+4JD_-q~J&th5+Cr;G|G z>nB;;QUN0?95^q9dk)3`d#oC4W>If;Ia$ik2f>o+3iwUyw@;ANY+eST7(wf&ck&q=C6HoCDXaMj?$2HJ%(l=P+smBt639rt z!+tZ0N>}C_&TLHH3^_T1x9m&sQ2H+bUxlK{h;0|T z|8{&w$0yPM`k(o!CrOfc_3Hli=9G!O*dmV7lh>Es-*2sTK)zSjsCMbB{^kIPG1S_` z|B|p{L_a;LrjZyacV@zD_tU>|i2is>D(Vl53gUZ+qS;@?mH21$AecqrA*~d zNsJM_`{e>c2@-er<8*2>Z^s|sUC*pE-q)}1Z*Nb3s8t{lD*ZD~r-nBZG(=)-qc5#IG9!vH z$cC|TS9iC!=i1ELz%hbkZzf#cOXBWs+~3{b zp3ck3k9Wn8I}6z(LBb7DMB@pqAo2S3dOk0>dU8EyTchvrX?S>VLWx%&yuP`);a+H< z(oEe@=saJ+XKeR%+)wfS_3N$9_d;i$A#O9|xV6h(Kic*b{@vHV@y_}7w)YM|NMaS# z@#_1xX10{!z>qdONM}wdYK&~n>J)Bn&LE5!f;Z#vZuTSlh?D)` zgV!%!+?23)e?QLWcD><`@7;@jqyXXW?*4ppD(JR5n2-8 zdjAy@FJ9cz@cPyL`Fs*kCF2S{>+DcHcb)^4dW9L*HBP5_#s!}eiYz}}#r@rIGh82; z@c#R+ZeQF2Fav=E=m&)v=X0xqG>-uf$OMUX4Kp+2vq*PbX@(gyOKjUS*sa7jzWKhJ zy?pof&{k@(>l*G(;=}NI4E0`p-sg)ZiOb9R)@+|b^0Twuv0y!)mg&iR!j2~c=L0PJ zLsSnSUcX*%Zm#hCIV=P4t#7@`%-h@BLWid$>zRk^vI)m8&z_EiySuITF6n>@X-7Id z6gKR^jS*-K3MkZWFa5)f*#0$B1mr%B&En?!*(uUA@AfX%)S@TW@a{?7(w(x82-yC8lfQ z39DV#wfE&3G7k)`ou7>6>fYku(){*XAbImqYV8=gEOtQb2Or#R+xqh5yX`quX&#xO z^0sU1-UOTgRB+WnAz!_^zqvU*i%=IrD%{;~%hK(Q!uIy@5XL6j@?$ot_H$cDUmA8C zImpQU{kXrsJD<4?dX4SkZ);kl>C;_`ZYyL{z@4a$=U@3A6*bbEpqKc)zuNb8|Xq=0T!ksJ+Rt z2{F_fk7Js9P7ru~x1CPC7^Q$M)EWyJM?51%3s@cBihKGJNP|0~Bil48yd7`HAHmSo zC@^#2jAyLQy)T_OoYa$WJh^P~?&Sv9Ke63QtXW47Y8O*uHC)Z+yrNliaQ1JgnsD_t zzCo5BFny^-bWXiHChI+PIS5U>#az+#E1vNVaJO8vC&DEG;@q0=9d4etUTaSNRLbJV zOJe&f01;U}+tz%4+Q$!~_hor>Wc!yVpA1E~zWA)0~9rH{sOOVVj`-yXE2@(LlDAcN5`%=(Fhi77?9^$Ub_Pz^osBaFJ zpaUt~o;pxb=kxTZC8bmpgIe{?!f6+g$e9jQ0_ZHQeoT8Me0B@2gEi>6R znW@i%XV)u%AV}PJuslKX+5CMlY}cgGpAQGr2^920%- zT2IvD%>w$M`iV$(>VF1KgIpyq>jV|XhI5-JrI=`B4U63fM`|3hRYn2`-rK-@zUP&} z*|eD#vu%%*mD)?1ML01jL?FS9nPYrWof-pVrbCIim|2(aUtoZ!-vP2Ew|_s|0A;TE z^E4$DKt~ys@a!hpj7Bvf1Y1nC??PEtZRZ(n*<3UcqA=$TnE*h1ReDjlMu}E$amwz@ zw1N(M#TJbpL5HNW^H9kV~=h*CVRbF%A@|7yP z-fo*E7DZHeqUXkD787kJP26oV5a7uCGG`}V_*acAe zb)9&eprytxy<3)$D?vFxqHXooeGxq#%HKVo>}$Oa%^E=gnbIns5x-MdvT~V!$V*~+ z;-e-@G66sc3gqb>a^T5MC8?IYaN9ZJ$3m8!SkjGITcSN`Md;P58|NahqlVDTdp{?#g#sp_D|5N)0VfGl00i^RJ&r! z>ZnNp*dd~}OBb9cl+K0eJhRJVNEVH`8ZD6^pFXuJuJi;2oYdNBW;iKi&9_phVNG_# zQX!ipM$oa!ma9DgxYead2_j9~Z7A;)O(t?q;8dB|rHQmsmy&*++0V>yDgw2C9;pBU zk}n);fAufhA-hs;<-cp9a~=0I_fMLHvFY0l%(f`k6ic(cx?e;?zgjIMS&X!A7c!j; zWi*9v?8#V6s(?&~(r6ze2fz+*`>}Ahn8cM4+rg^2F}bZtX-)JVRT-rkL~|C-uPApi z?#5SYvykDg%p)!Apd6H)g%FiyHYfF75c~f<$8B)3`dxfwrmF%+SsqHqIBnl^XLN=p zRIBNh1C|Jhv7}YIw^o{X8s_a$9^MAP2amB^|MM>KqsKZYly*>p+d?XR3Qwn^XFD3rOFn5RHa*UqxnJ~mM5lO2aIcy6n06>0Yw3n`5FI2QkMGQYot6-riyellS-D11#*%W2{RE^XQ@1wA<`MOx}B0FW($g#>YZ|_uFM*caw=1 zRgKCNg#zbH-wxqc#G%QBR113%nyL>px#GW2%~aeyD8|kv1#a&bCvu%8z>e5JcLL+F zP4@6wJQmFS_3Ph6q<_9?x@<7=<`&!E9uN0rs(QIzV+i``jhI!3_F?~*xT}nh?RW%G z)Onugd0t-raL(?8-)4o{_|S(q?q6a*$DvRnUgr7za>2I?I+xu2N(^%HUfKS4|0C-ArigExF5)xpJ_yOQE zfDC|%j*fwjhKYfJfrW*MjYD)3=f({jQUXF;B1$r9DoQd63L1L$do*`h=_n|e1n#qP za`E!=Qr{C26XX_U=i%l4J_r&P78cG89Fm(iNw{e#Xu1E_FZc%l9}`&xwG{>FE`W@W zgo2L*?*ym;00|AD?RSO$_Ci8NK}AEyz{JA7fjFTC4?spjK|w}EK|@1DMV$QvaU4L! zMhe-#(PZnnR)s61q6kJ zWgf`N$tx%-Y3u0f=^GdtSy|iI+SxledU$$y`}q3#KYJb;7XIR8L~LAqLSj;K3OFk} zCpRy@ps?s&Rdr2mU427idq-#2N9d>Sp5c+vvGIw?sp-X~<(1X7^^MJ~{e#1!%5#|5$l_%e_7CvA~2|b`vQCh>a25er5muS3fx~8*Y-y_{&4lA2o8jmFuZR7 z;cWrDhj5_4bSS^>9OgB`D#7Zva>wk8pgv9SUPGz2hJ9zBeRS z<7tYjFVZ33;wWK<>dSh_bl!0md`f5|A|z`_$5Erq&k1^~Sa?%Ud50aBpJ=|{8ed_9 zBv@gypX*}ygg|Y0u=NqRQJ9P=tCA1<`+Ty{8NHs%oN_Jf4fZ%;384~x^0i{1ZduoJ zGUuW6-Er`&x!qbr6E4Y@WAc6wGqB-!KWOhlr-rE2_X!iS@b?k^(*?tbiP9eVGf`^v zsVX@_0Ig{Gk)^OD`kF|F51p+$*N!&{<>TbSl-+zsW)kPfxXqzEX@^FCKi#bQE!foq zEi5M|FWa`ZHZ{Sng0RP0KtM5lF-j}@pkR4uhfeVhfk4XXR9fr;|8A?cejW8LSO6g= z2Q_8Jx#+t?a;kTy75m!OBcu%yBeZhXNjG5|g^!Ld1MmWa7r29!x#+*dEzG?(oCP)7 ze;1E9y+@8|glo!LDZ@M9on73NhB<4N=AGI4HfT9=eq(La!ML(t2-l}3V+@SLLZ81~ zPbhabuu^}-J;H&rQ0wraYR%ZFG7l%3n$%jUqBBud2hUSe0ws#0Pi=+YwfimDy*==WIX$>7|Jx?@;NMP~HyW880rh>aXss;*ERs`@LFO>EfUKuxdHFav76?s$9 zn|y6jbjOq)aL-ndqP*uxZx3c4G8kBL7N>_8;=F&rPSg{Vq=n70+w&|WoYK*-St`H1 zK&4~tP39*$7jlaM3iCnqT;j+;^}JUOWw_Jc((=jTy^I>fvsfgXQ68%5gp*Z*HYw6g zS!?`^(z)`Vfb*(m%V7SEl7cJQylyv~`>dg>(?|!#*amMW} zTGxw5g+Ht+8mOL{vH;P4y4to9W>*E+kVVc^vu9Li*9xCv>+oyo_km8W0RW;9<|w+KM768{w)4d0<*}QhL4c zdSPh~6LI8=W)TPcfm9K$SRvAsFvFOXcfPv@>qlKqL$WCUboKusK-pwD=Bbr{s0gME z= zvb2892=A5wZRA3ET)i5F`+^74r`tgtaA1*RzwR3QCLD;?gacTh*R2~Gu2=p|>9x;4 zfk~|_W*jV{3yPWB;)609xF9rxt3|9pS6V^p?F)zI@0EaP#0CdSsxq#E{<_8FrpVY4h1>> z`^uxBcC%&I3nFqjKvgzRsWoncLv7D&%m$?Zy(m`LzKbD;Q^JiYN*06|qhotiVuMN| zu(j{-+dm(nTqY~2D(R5(zb+*@eh3X>9oaEjJ8a)Z#dv`DDM)8@c}6ctn| zaiwbTQc3!Se^IbgH)xS!Z{ZrZ6b^uruI%B!bB_+tk$BxT%xph*CwFFF77o}9!GZK> zIDq!)=v#%LI+GF(_L{6}E5FAZ9!SbbQKkJswT2>Z(Y+`>)h4w;fDQvAZop*XEgWbU zT5P?bHip3gjx)phm)=uxApj_DmH|nY`o~=GQjq z<)V=2VX)`K0#E735gPLx4wl5ZZ5Z(yXfMjw6&5}Y2U>e!yr6#={ky7C*GclQAc?Cx zbi?MurYN+=q2ewNQe+0x_6IQHx<9KrX*+nQlIt3jqb2ghx2!0xz|+*h{w2Hp%} zW$`rEkNaPl{4lHe6%2m>v5pO1ES#Q$K1TneE@IV9K}-_wHsHVj6b?LH@izOHkwv_I z0>KuOF}ovZL7nb~79(!$oW);6rNUGbicMKuY%P zLWiX#`_;$BeKsPJ6ptm#)ka%ai2dMz2{jy$;_JNP4W7M{fS}Q;#C^(uOd(gKX&1zO z9WcJ3m&=M~s2!&c7IB!3<-qZkTF39rf?|pp?ha6EzORw*Sir`Q$)&}Br7FUKE?uY8 zfad~_UkS}t)lTRLN|bt4BGX3VKPA$G^ialhA77bWT_?d%;XvQ2_6OL_ShM@Av<^sg z_Vt1L6K6%PXZjFggx*SUU}00@tf-YS)msBC^(>dx9ld?30~tNwNc{b1%;s>0w8gF9 zwDtoWWXTR307K?{E!yVRC-GLq;+_5u4tz*Ij|VN9`s%|%SP;ua4jfo2Y~3rry4PxV zV&wAC`~1L;`gF{U%%(#)TxXhs^w!t&R&jUvk@L~<+XOSSB8@cHB%b9I&8XIDiaVvjw#`@0DL8>mwG6?-nC=cI?CU)*~#l%jDpYqA_o{=g<~3=Z>-FaLME?^^*z75T`y5dp zwgxSaA~Y67s4tm-SVo3H`?|&Akl`(HpCr=AcRYj(_7=~!M|BmBdTWxq={1|}&=zr! z8^~K)+Sczyh{cQ0{6uA4I1uW$Xm){GehouJend8mcs&%fLC|`7p$=oNVPmz8IM0g{ zQ$!V{L&f1xNeh6C<;%Ydx287hLWJ}b=!^Q*0MDT3w(7m5fec>D;Ow4 z(a+V?6n0B(65gBKnzXMUaU5t=KNLNM;YA}NW^mIB0W#shz7D7zf2s9?lD%~w4#1dO zup^=^a5t;6FO3PW7p~AHR{XwQ(NZD2Dsy-n4j@8*sQ|H)(shB3gdc*I;lP!yKQU1N zCP+2>r1sur>lqH{)8WFIw5UE}=gFObN&M*WnDP;b{n4`#fdk>dSkgNkH6AN85{>#D_>0}Y&FL~*I!f}*RqFdESw!enl`C)@r)>J+X9}cGbB`^I? zIG*8^6*3;oI`mH%(Vw%Ve~hd|m;AG3JM?ZqJdVV3iu6x<#1pS~M(&P}tsL2j$U5VQ z8D25<=x##2y*%~PO?*eRJzgY|gdCZqk5^7~UUYqOW4OwAHuRXKNXSdCY+4SNliP`FRQ=m!hyHac$J)K^=yMSD8OA$JQ(?X*isC!hAK*Z3 zSc&}7TSo#e5S>*O;AmEW#s7teyb}S@46KyrG{Km*=ePZ`)StYcOGX& zFf092X7m5D*7c{^11Zr}-Hp~?rlgd5p0-C(S&UwCoQ+o(^prGM+G&+Il&Zc~H>(=q zj+}o42M#~u(rpFkQy2Qa(rCqbs*P$FTEuAYemJTK9uyKV`pSbG4_)p_!CRFPKZ^>W8$E?tk>y1{VCsYP@Pe-}Y=%%kQT3chdh7GQNNuV;iQ!Aw@IX}IsQQZM_v+Nt(}uafGK+k$mFH3|O=Q7b6N-5Ch&peFB=Ndw6xJs?!CbFfq3Y7iax0dZkyp6^ zg>CtR5BOJJ^f{ltS&Fsn*^)jrr+m6jZWh-(()Td^e~GAd|%Td$OE0p`_|^~<;a z`F`+!^<6`FNOD{9w*9Ywh5x<;rgPsZVejh~5FvAVf9*DIR|!UV9>!UYEVWj9ZAbBV z#G#yExT98(JQ_Jf@XKjFq{b9udQ$ABDlPQ$q}PT1aJQqM!v=b?!&H`8zg z+`PB9GRAIKLFVeFKY`{X*1>^BFK}zu#G(f)i0&K9v5H$Ml|{>`2J)Z>Jkw%VaWq(o z8Bg^JueH1-hlHx_Jmb}P>1OmKZg5L*+-ry)?F&KYa((f^?xc4bwTW#}w~MU>ts5Lb z`u5;a4Z2dcBT}sImcvas0t^17WeaNW9NuKwuvF@mWa=NS3d;I`l5oWC*ozlNCk z0U+l7kfliC5B&;k^jpP(9&RLP#Z2OO9~TzJ;$IPH1qbe)Bgm&0pd+q_BxcJUV?3>ZU+C7DJP9Q#!cMIQOO-lj#G+zUeDVj5y0lQoOEbaNvL!vkn=DN`<*NozqWZH%8fhDoG=Dm+eg>i^1YB^(>k;gSuQP88r7d*Vg81~pX7udTN47W2gi74F%!Unrh@mzfAU=6u87^WgF|ihi}$$mByt;n?TV!&Tj4m^7ytH325Vub#b>lP}oT8nDRQ zim?qaxTih$hVcA2dwVzpl`;&SS%MR+Af*j1myVJh)wvI9 zFND00h_|WGou_5nP*8SI0g;=3d~|AD+SJN8rE&qEIrZ*Wk8& z{kE0Ylz6_;4&kx-!TyWCCK`T=bN&qE01L)P>I07Si?eV9BS_q0K*c4oPeTWT&?4rp z%hLiBjjLnv5{7e4y3VFh+5C>xEnB3ry(lu)@j-;*nn9NIfzWi3aGYt};&P3aWL&nZ5X3jS!4W=mqvy%P)ZNo?fj}n^3{09vh+r2oJ#q(<^&xqt*N!ROatuqaEwb{J9r1vL21qn z!!hXc5BN6T&C;vjq5BypyhcZm_ei@=)H*|7kE6j`K%5o%vIOTn5#MT(A>rpN8gX2&7x4AFWvN5^n+>r#=&%#H2N>!Dm z#!A_!NOA-c-%3LJ>Q4~00cG`yZLp(M;oJ@7Vk*%!ie|Gps5{Hh^WoR0@}G6H1vJ~r z3}=+5?#-FG%CE>~n@*ki^Vr-6-F3jJ>>b4k<^-|VjLGt9cQQoC`VPox$C#Y7WQz5t z+Itq?$I%FzwU%gk?AE`aCUG$qo_QYg*^SWE1gYVcywENSyTPbWRCwkNv&B+2pGSS- z#XS2vbn#c8rCrwFgb+~~xV+{d-O(_f$ubaWc;!K4ZrFh0qyA{vWEO=+f+^u`bH@$4 z`ybH*Al0LE0c_H`cD;Tgc(dK^L)i-74&5b5G&f;5In5Jy!ZMa?c`4eY!D-2P` zG+ME$GG;g!bAy!l*EqU5-1BiaY_x_{D_7Z4SW_i@)y&(3CNx2wC|Zew=&NoP1(Tx z?a<$gANmt;G)N_F`ivEUM&U60_MKDk5=FqxrP|iYGtPZpca`9m?`ur%0~V?!$rU9Nf9~?ER9~s#|>^GV%W9O!VrL z$JTPsE#C2_YSz1i>TfXLf#_cza8Vl-*4lgpz2#Rl)Ls`vJ?3R%sPtzK%R}e%2Qf>| zAh8_}lroW5LEaXY2{3;YZ%wd$J-I5P;a~arUi@sG9tO?DNc^tQQ6U=3B%2$3bYHZ? z_H?5J7PLtrJvzls;sUcQP+xP=t+pr7CprDfQ=gFywz1K{F;6z6n4Zvz4K7w{VxV~< zt4i)-<~H8!2Q3n#7mr{n`S`mu0RFAX?$_`+|CZLA6xw!b|0l8nQo6+6_bQA*Wz;f; z^Y>rp4#^F@t>fgnz`9-jVa8VbO!wuY;*xzXsoWNiv7AW)Q4?vu=+SIIG@`4BPWr~M zUi)geCL#VgSu9)C=KfBb{tK(fNPN|X*SXbTzRLk}ri&N@)0<C7zS4gjW|2y(%uhyA3u~J%z1hGv2>w+K*j_uI!rFTc){32#p0=AbFa_L zo2>x5QO~mDXL6T=x3y1`MrJ`q)`P|iGdnRdnJZLFMX&v|TG{)&D)vg2cLZK0;D$fC zyE?Wiu_Hj8YcJ#A>rs1*s(n)$0Ztz#==0_?3T!TOP81UQzKUtx(vTk)DnPyWJGE;Um6zS{~1BT@G} zy48OPDsI3qyV=gtfBlj@=;Hc*MJ+W|b#H!OC&<$>g*<3KsAg??U*sYQQLI^K{au=z z|L7GFSqVu;BkSTup7Sx;sh3~%iZ|x1Et*bCzK$i+PiK$Rm)ce{+_ukt$}NN*(U(ys z(+Ot#T=QjuH?~)payTmrG>_6+U-mv?I6+@cU!I@i29Bz@qQjVY;VEY|^=%`^o{E0G z8~v$Ri*R60Vz>^Br)bSUaY_k$U(>Sl`x|;WC^vn88wrV10}nggVOCcx?hCpjDXD7R_3p4qo+lh z0VzE>-m|eMn{?zJiPRk`6)#sjM037ek7e!vnzZD}RI0`i52lMq>?oO2m2SVpX4h(d zg`AlT5h+)TKYjdaOI>Q)7%U#bI%C(0W`)6mzZLX;Y(O|Bry<8vj-cS&Qli6Ok)6ZN zgc%MLV>BF>Wv<|T92Rhatc7B~oYIy5!Xmu;rSGwzq%ZN(SRwPsnq9scIG>z0OuA1| z6ymTKJ3@2JXH;e&-)MOqA5^XUKx=jwvFN=S{3}R)ET9vW;e9-(_B_fGI7c&%L4Bdy zG=?Gfe8=^}#rS>_cO{!a6~!uU!7lT#@F(v4y^_+S>m?s5@s z1qZxBPD-ZjGaX{PDE5=rUen8rJ#@b*9)XlImeN0e#-Z5AkS$;90S81=qd$$a!U5cT z)N9BEBjxiK@&})N;`JYJd1Y5e^Ve!VE81OjRh6LoxLyq&Uo#*Lu}{mS*?;4vip5cQ zwQZ(b-GK0C*DBSTeD_-&PX);GSNepzXb?RX;L}B|eU;S{Y~j20Q8Ek1`K%aCNP3CW z-kUh#8+j%2d8Vz(h7!ck!^}}-Z|v6>L{_2bUsuvji?IO;?4i27Fg+(I` zUq`OK!>Rz9E(=jrz9#-KsweoLRT;;o#rKQ-y~9D?ZHXMrJXh{GxF3AV8Yh(A)re%( zJw$YlxhBJE0%(KwyFApXZ5*Ax34{?Ee@?k@33W1Wm%B!^a~}>t)iZXCO14RQ(>q!u zN0y(Hobo+Pj*E478c564oLAY7v_i9w2HVCjXGJ~DusDG6G0@>A?PuC+k`>Ye-0D(B zgAZt5a0KOL)zlt*Jf-H^5Mv&*(efF(NrQ0fyH9@$ChnKU_%kqd+7EBbPIp9j5h10D zHFbK7J#nO1s%5UOwZF>mc_N}WR5+VN)x{8-XDGIXMH5SRcpqA@se04M{)B!=Ih?xd zd~{pfzu=06&AM=0DV#_IjQl`6doSMx)XW^)qC}P-@55?$oq0Y4b(ath=e11;cbB2C zE6Th=~FrdMkpvZO0YU5+(z4;hOHe)Fb zWn<)Ek--;a^%~=d&(Yiz0oZDaE~uE1>)1T{nRN!|F2?6NFCv~{bE%cs@SogA++$|+ z-v#)7@9_MigWsR}X-4eueTi^~VMA-y8;tV(cs zRI|?3v3;pP|#jyo+b*Y-w%Y&|qf4kaZv3g?L%S1Xrb) z_UfhwnRhGOX@_A}EyT)qLSD5lruz^#`Rw)Kgt&3oK&?~eepooDWNL;#fmdU`eHUW= zJY-cNlzwvo6@NFY_}+7yx_z&=b4{IKQLdcV7$VJQ%UraSm6ff0L`arwuKKZSCnG9@ z!&^77-uKuhp2oE zI{D{LobabVp`7am3(=3Rg*;8rtS%~~lv)?tesJcv8p{_pJ>Jii_1Tg(N^6@Usz(wv zSZU91Tk?+Qj&bLm_Hq3|wx!xMI#P*h6k@%${<*X?s)V#ai{jds;#GITt)7T8#GJmB zl1Y%367lvrS0^pL&_2r=XCXI!eNP$hK_NwKUdGoMN#M)!4#Hpgy zMyP}c>=RcOI0d~eZ!K_Km!)Lf@PY8OhVR5qZzzkEmiBpnW-)!vx=TpSJ_79*8AC1L!=@!9uajv70lHsS5&mQEt zgUNU$MN;Q-;(MjWQ!bg+KJRLbi-YARjm%gDM6V_VuZkgi^P%jig^C{h;a1ik2}!^= zrH#YsXm&aZJ}iq;wKv9$2MazS?=qUWy4&bHww9Q5$0rGSp712sj5ihzaGeL4I70)+ z$J5Ikf4s7^VK+n7tlXw2BPt8?o5$JIzIqI`p-U`JP?W1K86hJM`o*GVn~!dLo*%|l ztx9knmpYNFx~zBMJA&W7eOsb2rRY+OHWXiJCpW-UkK(#5iSatuH&C>Mu|AB)$AvL= zF2&@5HTk)zBsAB&2Ksc&Fx6~aIwV$gbsi{Nx={X9O8S*;xxOCNIm$4wM)dH|d^y_j zLu-0h5<>g@liO*KK#tqrcJ8`0C=6V*`UAxt%PdXyWh)g!i3jcd_NAd6W@rvKo=-p4 zNekTWcl>HfH=2Gahvn%@+w#u7?!oC6enK0~x0yDQ&4^6bMKNiPTMG`WUwBl#oSE^~ z$*l~Y*+sQBJqjYa26f!I2)(}U;k|a``rsgnOQ4&Fx$T`s z6(BdK?*7RWDciX!*Ph3%>^@$l$$YqYim2s`A;=0vI0}3~V7ofa+jI_!(=xrowu`ry z{X_PR>H+Y->C_AHU6&0n<cie&dp^>TTse#L%JZpkBIy{A{q* zhaP`)I`Z!j{g3$jue9NpABp%^X~~}z6Bs{$j8KzjsZo(8 zB|zvQL~1Ar0Ro|<@K5(XE?F+u`p-V+{`cIw$FCz}Fa|Soes6o;=Y76!vN^E%6?9Nt zMNI{?ZQC}`9pFD`69c*e+P!NR^RAt{nVFgQ?Ag6{|Iq{c_wCz%^6(MXqnxL}T%4yk zI8O6k;6KfMo`-|uoV39Ci=q+|5@3E=MHw-L3*r)D-)^#P&z?Q|_w8ppaDYwh496L< zfBE0$2hgG2+Xi=eGHv4qZ9lY)>Cm>#&mb@ev~4G_+HVW~kAK^?Gws;9i+T5+z59R* z$`69JZ)0NGzJqD!&K)~|tNnrBgLWL+dH9UzrCmpK?lN;b9~BFXe!ZLLa`DGwH@g;i z#qYU1+Ozlg3AU4`&hqh}6A+Y;l#-T_mAi6PSw;1ln!4^SJ$(a1BV!9oD{C8DJ9}3* zcMnf5Z=ayYPl7|9hCYjleGwO*@G>#!O?pOVR`%PR+>+9=@(RTJ%BoNGpBoyRnp?hf zqkDS$`UeJwM#sh{Ca3VzGqa0Je~9oH@mihnEq+j@0R^!*CD{J?K^fb?O^_9 z*S77Rz=!G3j-6*jcOAZ@!+h8I2)9_^?xUBZUl)Je!y|rk;n+QwuD!>3B}UIKelzVK zmi^}pd-Pwj?03U{*fj*&&$JB)9@8NZ1hkqY7+;X{G2QNjPim*@&V0@Dk3{2#c4_T| zX!hin`zx{2m8Rswj1pX~g#gW_%hu!d~&&e|-O7R}+neP6sJ@H2b_ZVs9Ih_tRxUyhs3JbE3t z|2KkfU{qh=?XEYuS90xEkC&KUf;kA+*suy-&wu9MEW@cRhq+Nw6JtnpB;via&KhRy zzp-(Um6O$69aO4pW$r-H?~_Jjw4FR9lVZ;wYO8*MXOroG9MXT&Lnu`KP+s-OTeyjI zQ{c)`g@u8c&XwiY(}hYE!d*tEj#qP)reAoqxbNOBtMEFK$iw;+mfTA;JBic)VO7od z$jXTOuY>h3TfMk*4iwBgesYI1rZ2VpUEzHly#$?N->X#`7>^3qa_hr6Dzc5+I`*KB z>2tdD`L5rKyk^`duUARE`DO31^B>4)Ca2roOj^&vPzGgG<%$bOv({)8Y_Z|t9OLlY zW2HoQjUuARJNx6#CT*RGE}*g9f`O3^3VXIkv^M} zTj=>o(Mu2bT4Ou9TgvW8v0VJTB(}?I_|^7j=YId}m*`h`+XpGd-`ST+cV4@>3Ho$t z6QqS)Ke7ob(0|oUm_S4fag?6pomX^T$$^xdhv7pQO1N=Tb+3S8Sby7Bx5b2RhmG~4 zu+h_-plowm=q9KyopG3T!+jIf45!=I(xWXlK?;yX$|mU4kMC>4S!qQZ>tTcAuvC~i zcNC2mygauFiiT|Tz_zTw0Qvh9RVm6*w1rJj6NYAsuipd-G3xFT7LgsuqG;7E&x34P zM;>nQID`yb3wByer*bzjykO%*hJ@?rCMa$bWPXw2g~OZoH>3PLeU4G3#5X~jz~f14 z*B~?fn;>r3O73u#UQFY)kR7lx+_*NQxp)(l2417WA~@DylYsGxY0~TcD@*78;55}A zru6#$cqq)$a1(@LrTHTpBmE#t$hj#l6gzG1U-z7<5_sKwh0JOb!EmShJ%cPfhoOrY zS2sa(I-8(9%yhX;(1bRl+-%Eo!CTz$PrrwhHg1CC7PZ=TV5YE>sA^ zfbLiSe?r*Px4H}b!;N~RE$iKyy|O^|ec1#Rz4TRZA{Wg~*hF};v{RgFqt@+!q|e@( zxBC6+C1Nrc7eu2Hk&W=by9p4qA6|?4;mzFDE&0ABmj7p_p02LxKwv<73JeXTp6!;{ zCmxTp?0R&!1wxemIr!N>3gv%)&HuN9|A)8*_pb_ot!G6Ur2uMf!7NRGzGj|7%%@?0 zuKIrSp;!LNubMVcjPk9?_qqH2Gl7dPs;d~9-B$SesqVJa?f-3c2f(cdZu$NlJlzHW ziqx%DXcDVlV){NL4-{YxbPL#)3iw}(_&+__*eMh_s_0j_6yw4+5|Z=$gsg`3$AZY zDf`hVuQR@skSo>|ps11mAASFTzFE}>&kZRT*MAh9)>arTXv#E_BGh+11&hNXAV0$S zbL0O*8vJJ>_;UyN0|@+2f_GL{b@6t3vdktZ3*cOxTl$5VuwYC2j`|qb(7$i`NUmU~ z4G04$x&*+2^h18YhF#2zzQ##zjzbLe&!kOoYrFoxmb3lq*w3RdSLyCl>OZ^*Y6W_= z{1)n5F{JlA8Wa+gLThILp4`HM{+(FcA8Iy9a#&t^u#N(m%>D^>O$D2qzt(?fRQlG5 zey;mlThZo!vhM#Alh*&zc|pa;q1qFcZo?0m`*~FI(TjC#Q)CMyORu3fK|jW}|F6cC ze?9g8n^UH8IsJ?3U{yFQA3&KKRg7LH0(KXq4;T~2XQVE%J(Xpm0>dR=tfh`xPzm3jl5HN2f0b3je{8;cusb^Ec_(KI3BMt&J0LUPTGSuMr7-mp+m9C~;FM$< zAVK`6RG$A)?D#LlF55MqNXHFsp{3ir*`cJf%{R?%|~4@!d@3&!Vcg3buQSSQXEUg?&oG0h&H9bxTV zrb@87y4o*Z&3xE;D6r+LzIHv5NhGjLwZZ{WSyg%W?8p6n<#*(`(}Hk0Kk#SJv5NuVdQ1sqoT7 z(T?b6{tZvsHk1mu;0LBl?C!=Lw^DL@f4VhF!twq?5H97p>U9ICvOJ(7d#>*+XVh$h z+Qy5{EA_u|(7nQ!=PB7x@=3Y(?bC`qrv-O+g`S*?q;au`;OthJ4Y?b6_Q+j-rz)6r zjNOHBl7eTNGd*@R?G>`9o))Q2vcd;BO3e9Rn3sK1x)iQ?zqM>(Z%!jvW;P>IJS{H3 zK`QFAXpL0Ey;GLaj#od0ixF=#yuijIZ$j<&Y0sf)k$nBvk(v;-G7azJ`>5yc)a|?` zdjE!&|Go4;T;L(0<7?7N%U5E+@Gf!fRMqrWeU#-9wS zT%rl_UlDC}7H+_pp>njjc)ibC2g`K2PBvBk2D0hr&!J3DXD~}n)mPimQzAmm~CCvKNXgm2z*-2~;>#{@n(s`J~U@4G7Lu$3-`jBRv#^(clJ zfuJy#R10Ic9{P~u^oN3BGdh?bYqe}bJpT8XWH_N#XpT$huM$wJwI}d+z&l|{! z!}8Jn?pytxp0EbN9XqQkvH+_)_vcXj?2n)QwKY_`)V8`3aOw8n-C?i3>!^v$RJqG#Bb)X(lLmSICPJ5S`6dJ0taB~>Et#F+g`7f; ztG>jM8#Y0bz?Cu2ETh)}M=F?;%@@<#oYu!SK}nmS2sLGp>XbBX5!McCj1ooS!x#=g z<{jMz{_$0XMqtxQWW8vu=f#a30TwXS30gnR_WLt#>il---}}bK*a73uJG34IJy%um zw>SUpq&U7}im>(-v;yGOp+hay_O&Hi*#R^MUg^zSulh#tLMT0q&O|h~!)=0uE1(zz zJvL>YF7UR35N>0Z!ad3V1_$g0iD!IwZr1ZF#Sn%IH;!Xl0D7Y^Yk~AFQ#m7$ucB zQg7{Yg0wXbGs|Ei1Mo#YdE=~XuuuFHk!99$|7;koVi4hoqiDHSFTYks+-*IiCF@raa_tk?X-lA^bA$}(0luAMdC)3xh+ zUS|}VD=&snnx2(&9wBgP571CJZw!L)0Pbnq=$cj*bHCf2XV;4=f?L|p$$c9au5fn~ zv>QHv>{RNd0MvO2V~wqb4a#S~gW9^pKyTM9mBmzEYV=)6j~g0`FjRL; z@_FO*3zr!KD}g*KBLXY&$vm3TP0z&-6cMAORtCT*5VtU@!7@!{$#yMOn8mN+6A zJP$`YGbE!&MGnJ0>11m7Q?yIHU0-^5CzGG(rhJ9@E@ydFe}*lD+fi0=mQ=0vnu0Xz zHY>G`2mS&s)dNLN+JjURY8L4i<=!XUAF9+oI>%{1NUrPMmi&b*ia4k+&>>4r_VGlO zJ0lm~!)AAGf?{FxCz5*Vpal=J)1DxarPg6$ z`^T9fFia+Iau<7t^JDMK;_~l7`yGth*Z#7Yghc9*harfSIjA_5znQ!mKqksp5N~3o zqO^_e58F?PHFQO2TjFZTvz}Z(hxVp{$eXo#szrlwWGID|pAcX}sv%-1GTPN;7P2T~ zH7N(V=pc1d54mF=WJ@}lG7o8le_vb8JasH#Kx7AOV8kYIhqw*VuyL(-aaG>2Y1Gu< zag7?F&8Wl)uMR4;Pj=iLoO0?orsWz)Cv_)3j;R*Q4oko37wto9FZxAxG;)0_C~boI zG}xXBjQQ)(Qb#P3kb?J|F;40$rbp3JsZsL8gyJE8dbKouYT7m2jc(^ss_mq;WUvN> zCBFW?#x4Fc0l+ARunGE&Dn*iKm^d{hOu|a!){$!@B>PYtp5hVb?LWB=w+@^dOmQ5_ z7+0*iF@}D3IiH`)t9H+y%cML9S~GD=8rE(I)7y)Lj3N z4Mi&esQVHBY?8M+5$$wxaIVo8JrPH^(r{BJJh_WlIs7AyFB7=Uv9dJPHMGPpE2_}5 zLFt=YwAjJ_fT%WL^v6gMF^rZD&wv*%OgvO#^LAeuQk!=FQpL!}7UZ7KKh1(JIt-E4G^slRx^N5JJw zoBcU+*t6EiRMpRYKGGDYZ!qj6kLtj7hB`;otTdU!?A3({w`V^kt84oo>i^x!Ej{<% z8sam2A#6s++&`A6WI4w9ts)KaG7v1?LFAK}0$^&oUVIZ(nlN&E7nBh|8+~EHuDXg; z_bcq@GfdKXns+vC#Huf4e)yx$*NqKb zH%<#Gtp1&SfNC^8gg!#Ycodtx=Sq8#PjsPYc+=q~<`cHO5tBvp=C9_F<67Tah~9E5 z3Ss0uV%A>L$|4T0y>zcRX!Wg1mI?BXEK*HqeT7XGz1?H`)W2Ef0xMP zR!xBZ%JwTcW-uE&gM<2CP)NnNv=aPM%pnR3>nn8>7?{=1b3W|yHhji9^_AT z2FS0(sI`~$*yc@80yKLQE#a!y`Jy|4MF>V>~{0Auu_&J*79e#nFX5^}$b z4@;UgWdCZzK?g36TB*bdi&iqk8(G*JSY?5P&(V}<$M57SXHb*=R1Aj2Yq7A`)WMJdqkDZ57vm!(re;Hm#j2xc&;cQxfeKRF$Q9T=NnG1y2s} zyl0obJWkhweKjP;5Qr{{8Qzt0e|F{#teJn(R3li#b13^FUIz0NOD3&~uJxG~g({(; z{?da|^n_cdJQUjI+ny$GIIohU9LtP?a*h&yf&2f=$6REwLe#no)jSnRuTAzCU+jSG zZSAYMOOF~WkiSZ0PNkqkR!2Or$-~*$A=hx5g1y4e^ky1N>nhe$_dDVs^C`5Z_umdZ z{P5S52+f?HT!^uQ-z6)dVH9ogjz(Pkaco2lpd!z}M_0%5UT1}zicYpyl;-(f^IM2$ z!?&ae&7Gz|EXl~S>?P0jWItr$F6SUPlGt6~0ALM-;yJHCsxWJ^Tcr?i@Z{GWqG*5u zh4wzGt9G>g6#}@3d66wWi1cK*+#c&DINB=TlZ{$SYexqAj#UeC7!M2dKG9gJeM^$n23!&m1t8wy%7Lo@Y;SSI0rMio=v&+vhy!YB)%gsAE;epn)+ zb^W;wn=F-q73_an9z{5?3$MEbfD4b){3;P!`Do?i%5vtHWr5yDQ~jejvc&KC{X2K9 zS=C(2p{M5#0;9TxH_R^e433gd9R6U#E<0yBS%4pMbb7r+p~pM3xTX$9fR%7C@Du41 zoc~y*8O!fR|30o6)(J22)3`To831fhJ5eUYCb9ISzHA38fNkX&bKs#g^Ld2=ivB7L z=AnQEfb|mE#%z5W;EK1499UpB{+g`c4R1*e*Q92Hp8_fORZ`>`;QfNhD2j9<#cqUi z6J*~=%vW(Pjf|Vt{ibjLalKW1bhhFZ30nVy>3@ePhvWU9;dYC0t08qQ2*SHPKzfSH zmmC~-l%X2FtH9X`AN4|YN7pkDnI^;*j|L(z_xYjiY zKz!o;5p*U3(s~e>!VvS=YI<}yly0k5?))Xg;w*DDX5tsDKPt@68({r)dB>tCPD6gp zJ1{i*%hrpHQt)|3d5;|eJ|w4?x|#s+h8@Y)t_YVPAT_RpsgFJJaZP?~mgnO1-9^9W zfBxU)#<@yhI9o9&rt%R>^W|x6Hp2JC!(nI?LzeXM%g6q#WKDuY>36CR$Q}Q2cgZ$W zFJ$u~OF$ht&bScDmtX;$@DZFpz=2< zuKDG|^Sg)eYJciCVPoLKu)DX1>M7a~)T62U>&YJLqNJuPDa(}Abd^PZN^RRkdh8eS zX>#e5W7GUsKQ}NL2VMXNoDeGiLRns~6v_EBg-_dP#O|W%E{RjL%NQis$Wmf|p-cx# zOMIkc*_;h_-z!h2OX=eTJ*thqnpz#uv}e^L+(vdzRfyRv$xgbi>CekN>s$qkQo@e& zFRW4=+e_P7;duP~E?Eqs4rjJ}4)IPaUjUc}ySB2Vd)3}00W(xj@VF7zU)MnPSKJBM zr(3H;3b=$0Zv~iU^{OM5(cFZk+fI7CPvcI?Arz4~rGJ%izwSx;tU)wjhTLO=fpsdi zQlRi9OaKvs9As<6^+g&uMC#o=nG2omi2X%Xge;!;41k6Y{yL-O8-1;zYGE-iq&2hq z`y%+qR4LgB0NsWQ`4eliX@2uf$;c^cdR9j&xkt}e)_BU?^fjuby(nG&`zY{Za?cS; z_`AlUu1SF2%&^OAp836;eg~x2Jt9i=_hY?%SGD)cbSTlDCDNpA7V^Q&YxRu7C}3ar zm45;O1DUL#V-T+9|c#ao~D#-Sl!J zgo9G}COFCCu2zP&re>F2@v4HY%6c#GE z8=86ZcCinzZ7^ZG{#}XLI$X4jX{PZZy6RfK^)zx#NAj7-+vA+HHg{s$T6#m>H*#vO z{)|e1+mZPX639{(f=#j?8KgOlxRD0Ao8h(zfgFUqAI7<$rHNe|fjR zoyz1BQODHxUeF%fD)OLtJ=P&^<5QeXYdtGp)W&FQGk)R}u5i{N1=5BMa-K+&>sG6c z;J!v`Ftf(g(?bx zYZ_H=O!-0q*VfS1wO4&Ceds^tn+{@tfPa?!ToeI{v_c$)QuRHpuU_}>0QPA1l76j- z525f<{K0;XJ=Z?1YBfvGd=*xytD}_uj>Mc zPwAw$(I&`dszb4uq$;qoa$*^KeWUx;kd#%Mb{r|YHf<$q3eV)C7y0ntU>k< zl`I3y06JUM5H+X(Ck)b38P+86Dd&_s7W2LU1-(b+T(ZO(^)zi{zsMBd4oR^SynF0RZo>~WpqAL zL#(eaQObhYFsKcrV_ol%uCs%SK;IL+c4oIfZ;`<1#haib;1X}}5|yG!-2~0xImErInR zC)&M#u>CCq<|t&;V!dl9ijeQ0^~C|XwnnWpN|>b9;J?gaJQ0el!G7Pw&|~kC32>XX zeSoMyw~Q0*grUv@bAD|l2XFbk>}Q!eOAT}6U#JQh`QSZXv5dnJf+qjaQc~(m+i)p@ zDR?F-E>lSUc5jEEx`@XwQ|>QhCg&>c&rw8e5yn|1eJaLRdu<&XB@Js|Rn<(D$G^>b zGgQT$#hog9u4T4k{Lfs(Ap}0A8vCj~=UZoJ1B%O&l9rt0K8HX=&(O?G->Un2D^rhO38`jj_pJ~*VY-$44o72c-%YP^BhL%J@_dUGn;H@F&)p`3E}`XMCb%Lhlu5I3;T?tiOd&e>s1 zD!9v$w<66W9(cp`=J6wCw8hqkECD)*DFw>Mqr#VxjGdlJwBPJ-$n$+)^Y-N9%nbAA z0zA$iWtew7@^#hM)qNM=>zH0w;p+j-6a137r$aPlT((eM!vxW({63f>UE0g#-6tc~ zell)A%<*PXc-|eG>_WlM8@BJu4mZ)$y(tkYgOgO#_uZ@zp0~ ze7P{D^J3{knKO?ap8MdBKD4bXmCkB5{mt>y{KY-H78E^J8hCx4ygV($_ z2`1>wS2oW=rpf>=E(4QMB=#ukG%Jl75@_NiEqWhES)EQYE1X(Q%g$({ww*Jzo*m zZw2Yq1T%*A5fx7d&kV_!UQlS>)i==Hz2)K#+0~E)Pp;38 z?^_+p{<4ogUwLQy3O-@Rfd591MO{zKx|7yq1;SxZywDA=$!8BuI1N)g*yib=p2~iU zpwCi1moHm-*PrBN-Y)VYjyY0o$cxhMU;w6v<{Q~38J&nxMm!sKqi3pHdf3bP^F`^QYq%-Vh^XrF+5H>GV7pv4x`y|OrPAHp_U(lr9-t=Gs=-4Br)5=*=IoX%e$2dUCEL~8%SnMet$hF{*4gjdJK05Z6G5rVd71IWXna*^o{mpdCt!N?C9*jW+cNHFasb#*w88QXQ7yve7_^ zTR^uk+PV;%AX`T=r2C~sYoii{M1re_sV#EB7mv@BMQA7w?my@Ff}RuAw%dGkua7Lr z)UkLJD`dU|D=LZD{?wb8V?7+$6N>TMZ(uWi*aw5eT%+0~9PoOpcUX_vD#AEO;hNne%r_HGN-G#Qx0JBNNk_eL4Nh6F1>-p(TQrRA&vaq~nbkZ3b})~p{CU^!eP^;KHZ{dd2l zIx3%&$k@OHVC7GG7z`OalgNuYVQPzUk|-FtNzaM&%RJ2TlGEfpBuc&KS-<*Xdy40& zv}AZGVW}VGsL*`xIei%B#k$7gJ63+aMn2PSNUt%`!C`W6c%E!QW}6>=t<5{}K>k$T z=pb=Nf4o#Y%qDWb<$C5S6!@b9W^myu1}s!_MY%C0Rwu{Ce;G?}lFjpq(q40St5QjR zLkhM$aXp}?eu}qfahKvj&#ajM?sscp{`A zSmC}*D}38i9(}UugIBTP0!%rQTnVG5CoUsRQl(m(rg@`Z(&$~NTN5H-If9`8p(T=eH*N-8Lt5OOb|j|gpo_|9&E z#&T(9N4xIy*dQZYp*{1w(X{^cwtQQNb3^DDBFj|xdkR|n- zpc^8zWZ-onL3+s3UV6JpJKt2|}fxv1)E z3Bb{VX$+*4vJRWM?mA@y@pM{cE->zK(`H>0{G@$wV1~U=SoEEL)}__vD?QO&QT8KU zH2F+hELedjfZSGDB;9oXDpneBv9U6F=GEfp*yNfbZ+19p$=6(g$K;$p9)}~ zsuzmr+mTE6%o&13vlYgErz8vV#4u5OPmF`mt!}rXL-qw0IRooBi$J~z zTA%q!_}6d&Fu6IvzdBQ>)#isac$ksP#TaCVom%2UPqo5_@Qc#Sm{Aw&JVk(6ik6~#J(v!x1$09);+ zM$B>7JXHpoC*RNXxN0Zy1$?Sa_b4ioN~X+oe(V(~D+1nvMRg(8eMK2B>ggVItS1ar z0$ZtUQytr3oNVXgwxM*lPq#IeevbBRspjf~N`(tPk;n-5s%!xH9Xu&dic+hsVP37P z(!^<1ZP85-unYo_lT5WLzTTE(j%$YX zpBa%GPmlhV+B%)+_=@cSq?#YgnyVp3?@^lr9FC;=a4o@!3= zuj{2+aHPaO99(VS7nT@)-xU+}U_}a*!(dOv$zJh`Jut*5Yey zg4&?*oEd}jk#XadW(Ty@Q3Gd!4Kc(6fI84A3%)S&k_kC#pbenom|AuZsjR)9RpEKH z_tJq8-IpJ~1~y;9kmn5iq%H35tm)VHfZKR;U?8NEtLOK30pqO5T!#MXGC{o3s+`AD+I zf_T`aS(+tZRcG3sk@_v-(u+950dIptHWusMQewLmj`O^hEOm7{Hksd7nzd%F5~WYJWW~2@&_jWv1!aIR3$qNELowP=LzbH$ zgGqXkA`D4_`#GC(c7$b**u~nRF{HA@AvKnmM7V9PuuCTq<9IplhI0OvXIv4MJy((EkYlpGkRE!>vt;xk zkL*p>%eGSn=zHmxi%M$ z2!E7;cu{hb3Oe9#XXQ#q?CS21utzvTC&+`IrTmGV4`hFnZ9gS#RV;8TTsHG;+ejo< zx&j-!4|1ddnq^oc=h0Bza|R)Vk1a3cF&bP_l@U7>HQAkDT#aUcZ9B1sc4_yj3yKo( z?>;e>F128>$&3xku><58WB2hHCmueMa)DLj5%8BxSdqKnXGU%pn7RrdPYz%ffi8*(d(3ij`?DUz zRSh)<^m!S`YhPu4y>cu^I)b~)PX{_`D3M>!{?UO0X$ui7DU=aZ``~2eY#cwrw9uQ{ zBYnfJn(s;9H4N(cXkfF9aAcW-@vg8P31(-Lwa7(8UE-x0ylMNXNiSxm`N$s9zUk0g zi#idLXMIVNz4w)Kye#TBfOCIW=!sKiCQh{|_-QQnB>jOb*Go!%H8>Ou?R1A~Q~7Nu zuvjXx4Btj(D>ALI;0mt7*o2vOo{8~tK0wbci*{5HDiVqg>Ks=vkW~sDrSmb+^_?>( z5q+!cc%qo|M8w>1T6Qrwf*0Mb-1B&>=WRI~@v#`)%1vH}j_z1$nc^N-eKl?bC@D0&XzDSh{*q_DG7U5D~uc7$}ZS z(C2D`xx*&N3`fSyZ_wvmt1-t)Gp4Hop4j(QL6On>Dx(v8r3@d6$MY_45AHU0Jd`cl zB4E@wl;>Yaf!LKu!np#U2Hdj92-(&r3~4zUpV_nEt$>>|C+#)C42muoF_%Lr%%`|Poo(+f%YR+ zIF5|?UXX6%aiX7p!AN_o;rYt8$}Ew?c?RY{UsfB#)HvcP5ILQ(eGlx%uUw_ka+N~8`Y zUOs+T#ES>a-Yw(kI-ipzQNCPi9e$6&6O+Qnvr5V`DurA7HAzb=XZs{~mzm@Vy>^&te`|W_PW#lc zp^Qpqw$oorof}>IGJ95{2y?9(FrGH5iV}1UllVFc4POEFl^>4RsT_z$+Kol+9d__E zHp3BehQ`hXbZS|KG6lWgMkm~Pq*R{ZFz&1AbJD3$;<}pED)SIZM)f@*eXT&Zzo_R# zGxgB<>jw2AVNfMTh_^$wu~GZ^zQy}7=5?M&;HkJu#&RSKd{<{?74C?WPAI(}dnFt{ zuigT0a)h0laEF9~2m3q{z=KLQL!wLcYh6Pa5Z5*eVWCl^jc|eN^VZ1V5m)-;o=n(J&=H~TI?Q6iz zPLZ_A>adiTi1|kvuH-{hMC0N|QkiT2v%LNKNdXlV6bi<&`jJtZ<^;Wk$%47Jkb{x- za&j3S`t9fiP2I0xwSod)Bv*4eZi15MBa`JkQ91^ghC`C!J}u}{3EQr>WYpfiFgMlf z?#gDVr9_Q*4V9apb&~L5G|1QEZ~D1=6r7ZvoQ-GKy5!E;l;nNy8Pbz0=G`ppQ*U{E zT2cffl{2eXH!{BeP#MpD&iGbf|>4L#hlrn@_OSyYlQ`RIc7(U1V2=B_UMb%uKt&7m|ly zRfN!Co+`5LKK2o(_NwDIn#vSCT#O@}=ks=bKGd*=riV)x~O-4el2>i&mZz9r@^Q z!fSc%=x{p4lly3%fLWfFRKHd&g;OM?EDtkL`@S6Kd;kS!Uk|^yb}5A^GCG@6W>vD; z4j3Mkur9#qdyD&+9cUY(5jDDWeBKii)mUXnQw{UCHZOrSD+%y5B{G1dAne5 zm#M90p3hz@?LJ0Y_dF6dtea^j;L8^qU>Y#raNw^euNxI>iN~wmS)!Ffo;qQnmJkO8 zq15a*Or0Cgh|4gR8Dr*DdZGiczvYJ-5qHF-_>cK(-lzbSr~LWXRY97A5*JETM*Kqd zvt0Uk-#tAEJi!vlA1`IU33AY+x)Bb1g&dH~_sG_46Rp0Mhi|FBctgw6=8eeY?R?}B z?#`^(Ye84B$HK}>z~#|#SIXRV2Yiy2Wk}Mqh~r9T3NN{n1AIt!yF`hTjJn>(j2#PD zLW${ZyXq-`^s%F!TZQtb>}uGnK}^sOC;Cag*_hb`fg0Wc%Fo!GNxKHi`KsrgS0TE=)voF+C7KbH`vDEvP+ON$c*z0M7G>&jr)|iy0s@d#!DK;d zLc)PrZ_j;Gl_T$YPV(~vD~k5z&dBWwW6A0^u^YKQq>*e0?(kSQ+7a@|xzCydzka92 z=U|S(?OaWVy5zYTTGdX(Lz<+G`+=TQjxDd~Ogir*c;1ac!tkllbUMhnvS8Mcj%Rjo%6tY6KwXQ;o zQ#2_$tV-JPG8t~1yO&W4;h}d?2?q%E_(?DI5tmfEtCx`gP8vsfn|?gVVgtgK1;ZK%I{SBe6-D7 zihEa=MJR2`zg3C%ZV)<-Q;jBASPR@*OR6g5I{9TyRVbZ$!CIK(x;IKklrok%wK+3xCW?Kk3LQ_fBqxpZ?M;ty7>2xJ5PUbS9+8;6ClqRD2_BI;P~5K zHbg-@M0xkXt04F~*jFW*$ML>JqoI0-?e~L^8jjzNeZmB?BzM2BRXK2?o=-zVlSgRp zzBq^-tVVB=ltyu8z;0Y6y~i`FcRxwTY2vW6ZYL^?1ETq|#|(+m=3xeQ>}_|a1mDU# zH-nwM8(hr)-ykw}>}fe<-;Ag3%wUL76?Mn+b^L^Z&1KJRv7eU5i;R)L*)*FF;M^m! zROurl1R!y(T%<1ub)vaHLHOnFV;YAI5hZ92v_1gSYaTOf> zb#)t{7Tt{!VWVP@b^b!SjAOVbu<2vKI~|8NL5q4h`Cgvt{WLgH-W<}5on$N~I1+5; zqo)NbFK-C%iQq60a*c7Brh3lwvUSZ=i9}o|Dl8Wk5jQkdyHh|3S&w6JeR6t#yn^dW ze(m2*rKXO7$FeE6Jo_qeHjJ(YLQV~m#Ct=Vf8&m?z7ddkG&PBhR7zsiC_d(dlhUQ? zFRp9~2W5B52l(6Z6_M`Wn(29<6|3GK&D-&mjkq=_*{G)U5pm5P};TkW5jy#I5` z*PWaeoUSU-c{3X;y>UHB+6j_#`A_P)n;fh!vs~OZ)dj#3^XGK%IKMc(8&= zB6O;lEG2YM?DKdIZ8|}%8&LDq{Ov3@L8sTaY-8J3Xjwhbn5tZOjbhZqkk6WDc<+&=I#wBo)$vJ8s&m8I7b41&=??l=e zU&a38N&#I{(bdi3rzt1*)1yX6f$CE+kC0;JY1M9XosA2OqHNlwm@y43{}+d&qOYkM zQu_ z$Wdkvx;IPEo1oHs34sCy%)SH@9q`N6;+s;@-W>3iJMN_4c!zunttyh0QWdpasAH-F zWt{xFj20mM@c|^F%o43)U%!(q>Q#>8nJzbfHm@;Gu=ISWn*mW|HS2?;mg*vWl$J|f zv#4K<Y?4tS?1(C zqOCHCrh09Wz~+Z*dBGw#$6;60`_*=P2Yus!Qg??8d;K)O3o)hwN zv&Kf}W@;VMeH*t??S2tj18mp|h#*Tn<^!_^@pv|q*l;57_h+4GFif&>L1CP$>)Sa` zW2aQ_H?UloVuD9=-`x*iUDhVJ;7@MEa2{`*DSNu?SI-`HBcK~lBEs$cH_Tgb2()8I zkYaYIg7jJP;JWB+GNX$%$;1QV<~^SKY9?y#7NK0_h6{Xt3Z0Pcl;C?*0(Vq-Ok_81 zmTZSP-Y|5DIZbQo>G@nw5J1eiQaAJt+yhi=y9t0s+hPD!O4=Jfb6s?D9PI14ipn%r z=`J(%0q-1|F}c4Y!)0eq-0ze3=%S1Fq48i&2VcX5`M9EW$dE@JEV{atK7Ii^fWq>0 z>bSw5yY%ERf9aR&rVOsk478wYOeuNo!2Bj3@DX;bkR&ZnYpJQld4^la07tOqfHNUm zG;Ig!6O^OU@-?BwVdlpJ;MqDlqa05i(CU-O__isuvNl_zelLJZ1idhXqq~a zd))|knih2f;+%ma_fdRauYn`?)qPh|2>+m14ytwCb!XRcLq1X{0=>?BLziPc5rxIj zKh7Co0J$)DR`IkQq_Rq-%7PNAw5)s!cHHMWa70}RBlj2EcCpSH*x_8aVzC%K%M_nH z*oWndPA2LL?oaRoZuw2w-qLU3woXZEVV}HZ8;^o@P?!2jhyjBBSwkC6-%htZ)Ql>( z@vyww1nh$17YY%&CFqp{S7-X;)9i7^@o_bcxAVmW^ggVgZNlXw4qDf}A?9BJk zGSG2#;NQ5Ka!x;iI#Mi>z0B=Qy^^zc*1R%CXL$~t{rZYVU3U!(LA{~^yd8+z&;_r^ zODzKdh5-8-{o}`Aqta!JUf`gnD)k7R70@8Dlu|~KX9B!BEp2_3WE!6UY#}OR=x~P# zA}(q~+@GFY72Dsc-8$TBzIH_cLeHaTQ&^W#5F(dND*r}1rZi$;4#Sq7B+YyoDOh?N z>xT7j(17fpK2?~sk#3jF+Y9Y;ctW0#9nrrj7d6K8iW9O|()4EkM;}M11aRK`05Zj( zgGYYrtH%tw=9sAjf=4#{|0cp5-dk{C^ zXvi&!@3>p(w|Y~iQMN*#<~;Wzt%~4q?ktzFhc3wx>6vI=@96^zb$rv?kvL)c#y`g# zWnF*GjH!uf{zGl`#uC�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 F{}1SNu>t@9 literal 0 HcmV?d00001 diff --git a/host/figures/threadselect.jpg b/host/figures/threadselect.jpg new file mode 100644 index 0000000000000000000000000000000000000000..38fda253bd3bab6d3388b1e19cd39d488386d2ba GIT binary patch literal 138639 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 BGc+@!B6?UTEYQSM|n~I)Vn7p2!B&>h@M)N0} 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 literal 0 HcmV?d00001 diff --git a/host/figures/time.jpg b/host/figures/time.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a0221cb47e5874a05280a7315831fd61ec13cdd0 GIT binary patch literal 25236 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 literal 0 HcmV?d00001 diff --git a/host/figures/trace.jpg b/host/figures/trace.jpg new file mode 100644 index 0000000000000000000000000000000000000000..378dba3e866095c0b1155175d09eeb615b3dbf9a GIT binary patch literal 120151 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 literal 0 HcmV?d00001 diff --git a/host/ide/LICENSE b/host/ide/LICENSE new file mode 100644 index 0000000..e454a52 --- /dev/null +++ b/host/ide/LICENSE @@ -0,0 +1,178 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/host/ide/README_zh.md b/host/ide/README_zh.md new file mode 100644 index 0000000..3f583c1 --- /dev/null +++ b/host/ide/README_zh.md @@ -0,0 +1,93 @@ +# SmartPerf 编译指导 + + +## 1. 编译环境搭建: + 注意:在linux编译环境安装时以root或者其他 sudo 用户身份运行下面的命令 +### 1.1 node 环境安装: +##### 1.1.1 下载Node js安装包(windows推荐, linux跳过此步骤) + 从网站 下载node js安装包 https://nodejs.org/en/download/current/ +##### 1.1.2 安装nodejs. +- ubuntu 20.04 与Debian 11系统中, 直接用apt-get安装,命令如下: +``` + 先切换到 root用户下 sudo su + apt-get update + apt-get install nodejs npm +``` + +- centos 系统中 使用yum 安装,命令如下: +``` + 先切换到 root用户下 sudo su + sudo yum -y install nodejs npm +``` + +- windows系统中, 用安装包一路next即可: + + +- 安装完成后运行检查是否安装成功: +``` + node -v + npm -v +``` + 出现版本号就代表安装成功了. + +##### 1.1.3 安装tsc typeScript 编译器 + +- 直接使用npm 安装运行命令: +``` +npm install -g typescript + +备注:如果安装失败可以更换npm源,再次尝试. +验证安装完成: + tsc -v +``` +### 1.2 go 编译环境安装: +- ubuntu 环境下直接使用apt安装: +``` + apt-get install golang-go +``` +- centos 系统中 使用yum 安装,命令如下: + +``` +先切换到 root用户下 sudo su + + sudo yum -y install go +``` +- windows 系统请自行下载安装包并完成安装。 + +- 安装完成后 命令行运行验证是否安装成功: + +``` + go version +``` +## 2. 项目编译: +#### 2.1 先下载sql.js的二进制包,: + 从如下 https://github.com/sql-js/sql.js/releases/download/v1.6.2/sqljs-all.zip 获取到sql.js的二进制包. + 将压缩包解压后, 将文件放置到项目third-party 目录下. + + +#### 2.2 先编译获取trace_streamer 的二进制包: + 参照:smartperf/trace_streamer/compile_trace_streamer.md 编译出wasm 、linux、Windows版本的二进制文件 + 将获取到二进制文件放入到项目bin目录下,如果项目目录中无bin目录 先创建bin目录. + 然后将trace_streamer的二进制文件放入bin目录中. + + +#### 2.3 代码编译(依赖于上面node环境 和 go环境) + 1) 在项目目录安装项目依赖: + npm install + 2) 在项目目录下运行命令: + npm run compile + 编译成功后会有main 可执行文件生成 + +## 3. 项目部署: + 1. linux 版本部署需要给trace_stream程序赋予执行权限: + cd dist/bin 目录下,执行 chmod +x trace_streamer_* + + 直接运行 ./main 可执行程序,完成项目的部署; + + ## 4. 访问项目: + 在浏览器上打开 https://[部署机器ip地址]:9001/application/ + !!! 注意一定是https. + + 备注:如果未出现如图所示网页.而是显示 无法访问此网站 + 可以在window cmd 里执行telnet [部署机器ip地址] 9001 + 如果显示端口连接失败 可能是防火墙未对9001 端口放开即可 diff --git a/host/ide/build.js b/host/ide/build.js new file mode 100644 index 0000000..6a5fbd5 --- /dev/null +++ b/host/ide/build.js @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const path = require('path'); +const fs = require("fs"); +const child_process = require("child_process"); +const os = require("os"); + +const compileServer = true +const outDir = "dist" + +const staticPath = [ + "/src/img", + "/server/cert", +] + +const staticFiles = [ + "/server/version.txt", + "/src/index.html", + "/src/base-ui/icon.svg" +] + +const thirdParty = [ + {srcFilePath: "/third-party/sql-wasm.wasm", distFilePath:"/trace/database/sql-wasm.wasm" }, + {srcFilePath: "/third-party/sql-wasm.js", distFilePath:"/trace/database/sql-wasm.js" }, + {srcFilePath: "/third-party/worker.sql-wasm.js", distFilePath:"/trace/database/worker.sql-wasm.js"} +] + +function cpFile(from, to) { + fs.writeFileSync(to, fs.readFileSync(from)) +} + +function main() { + // clean outDir + let outPath = path.normalize(path.join(__dirname, "/", outDir)); + if (checkDirExist(outPath)) { + removeDir(outPath) + } + // run tsc compile + let rootPath = path.join(__dirname,"/"); + child_process.execSync("tsc -p "+ rootPath) + // run cp to mv all staticFile + staticFiles.forEach(value => { + let filePath = path.join(__dirname, value) + let distFile; + if(value.startsWith("/src")) { + distFile = path.join(__dirname, outDir, value.substring(4, value.length + 1)) + } else if (value.startsWith("/server")){ + distFile = path.join(__dirname, outDir, value.substring(7, value.length + 1)) + } + cpFile(filePath, distFile); + }) + staticPath.forEach(value => { + let pa = path.join(__dirname, value) + let distPath; + if(value.startsWith("/src")) { + distPath = path.join(__dirname, outDir, value.substring(4, value.length + 1)) + } else if (value.startsWith("/server")){ + distPath = path.join(__dirname, outDir, value.substring(7, value.length + 1)) + } + copyDirectory(pa, distPath); + }) + thirdParty.forEach(value => { + let thirdFile = path.join(__dirname, value.srcFilePath) + let thirdDistFile = path.join(__dirname, outDir, value.distFilePath) + cpFile(thirdFile, thirdDistFile); + }) + let traceStreamer = path.normalize(path.join(__dirname, "/bin")); + if (checkDirExist(traceStreamer)) { + let dest = path.normalize(path.join(__dirname, outDir, "/bin")); + copyDirectory(traceStreamer, dest) + // to mv traceStream Wasm and js + cpFile(traceStreamer + "/trace_streamer_builtin.js", rootPath + outDir +"/trace/database/trace_streamer_builtin.js") + cpFile(traceStreamer + "/trace_streamer_builtin.wasm", rootPath + outDir + "/trace/database/trace_streamer_builtin.wasm") + } else { + throw new Error("traceStreamer dir is Not Exits") + } + // compile server + if (compileServer) { + let serverSrc = path.normalize(path.join(__dirname, "/server/main.go")); + if (os.type() === "Windows_NT") { + child_process.spawnSync("go", ["build", "-o", outPath, serverSrc]) + } else if (os.type() == "Darwin"){ + child_process.spawnSync("go", ["build", "-o", outPath + "/main", serverSrc]) + } else { + child_process.spawnSync("go", ["build", "-o", outPath + "/main", serverSrc]) + } + } +} + + +function copyDirectory(src, dest) { + if (checkDirExist(dest) == false) { + fs.mkdirSync(dest); + } + if (checkDirExist(src) == false) { + return false; + } + let directories = fs.readdirSync(src); + directories.forEach((value) =>{ + let filePath = path.join(src, value); + let fileSys = fs.statSync(filePath); + if (fileSys.isFile()) { + fs.copyFileSync(filePath, path.join(dest, value)); + } else if (fileSys.isDirectory()){ + copyDirectory(filePath, path.join(dest, value)); + } + }); +} + +function checkDirExist(dirPath) { + return fs.existsSync(dirPath) +} + +function removeDir(outPath) { + let files = []; + if(fs.existsSync(outPath)){ + files = fs.readdirSync(outPath); + files.forEach((file, index) => { + let curPath = outPath + "/" + file; + if(fs.statSync(curPath).isDirectory()){ + removeDir(curPath); + } else { + fs.unlinkSync(curPath); + } + }); + fs.rmdirSync(outPath); + } +} +main(); \ No newline at end of file diff --git a/host/ide/package.json b/host/ide/package.json new file mode 100644 index 0000000..bc4e0ea --- /dev/null +++ b/host/ide/package.json @@ -0,0 +1,50 @@ +{ + "name": "SmartPerf", + "version": "1.0.0", + "description": "SmartPerf", + "main": "index.js", + "scripts": { + "compile": "node ./build.js", + "test": "jest", + "test-c": "jest --coverage" + }, + "jest": { + "testEnvironment": "jsdom", + "collectCoverageFrom": [ + "/dist/**/*.js", + "!/dist/bin/*", + "!/dist/trace/database/pixi.js", + "!/dist/trace/database/sql-wasm.js", + "!/dist/trace/database/uuidv4.min.js", + "!/dist/trace/database/worker.sql-wasm.js", + "!/dist/trace/database/worker.sql-wasm-debug.js", + "!/node_modules/" + ], + "globals": { + "useWb": true + }, + "setupFiles": [ + "jsdom-worker", + "jest-canvas-mock" + ] + }, + "repository": { + "type": "git", + "url": "" + }, + "author": "", + "license": "Apache License", + "devDependencies": { + "@babel/plugin-proposal-class-properties": "^7.16.7", + "@babel/plugin-proposal-decorators": "^7.17.2", + "@babel/preset-env": "*", + "@babel/preset-typescript": "*", + "@types/jest": "*", + "@types/node": "^17.0.10", + "jest": "*", + "jest-canvas-mock": "^2.3.1", + "typescript": "^4.2.3", + "jsdom-worker": "^0.2.1" + }, + "dependencies": {} +} diff --git a/host/ide/server/go.mod b/host/ide/server/go.mod new file mode 100644 index 0000000..aaa88e4 --- /dev/null +++ b/host/ide/server/go.mod @@ -0,0 +1,21 @@ +// Copyright (C) 2022 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module dist + +go 1.17 + +require ( + github.com/djimenez/iconv-go v0.0.0-20160305225143-8960e66bd3da + golang.org/x/text v0.3.7 +) \ No newline at end of file diff --git a/host/ide/server/main.go b/host/ide/server/main.go new file mode 100644 index 0000000..995cffe --- /dev/null +++ b/host/ide/server/main.go @@ -0,0 +1,350 @@ +// Copyright (C) 2022 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +//遇到报错请在当前目录下执行这个命令: go mod download golang.org/x/text +import ( + "bufio" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "net" + "bytes" + "fmt" + "io" + "io/ioutil" + "log" + "mime" + "net/http" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "strings" + "time" +) + +const HttpPort = 9000 + +var exPath string + +// CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go +// CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go +func cors(fs http.Handler, version string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // return if you do not want the FileServer handle a specific request + r.Header.Add("Cross-Origin-Opener-Policy", "same-origin") + r.Header.Add("Cross-Origin-Embedder-Policy", "require-corp") + w.Header().Add("Cross-Origin-Opener-Policy", "same-origin") + w.Header().Add("Cross-Origin-Embedder-Policy", "require-corp") + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Credentials", "true") + w.Header().Set("Access-Control-Allow-Headers", "x-requested-with, authorization, blade-auth") //* + w.Header().Set("Access-Control-Allow-Methods", "*") //* + w.Header().Set("Access-Control-Max-Age", "3600") + w.Header().Set("data-version", version) + fs.ServeHTTP(w, r) + } +} + +func exist(path string) bool { + _, err := os.Stat(path) + if err != nil { + if os.IsExist(err) { + return true + } + return false + } + return true +} +func genSSL() { + if exist("cert/keyFile.key") || exist("cert/certFile.pem") { + fmt.Println("keyFile.key exists") + return + } + max := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, _ := rand.Int(rand.Reader, max) + subject := pkix.Name{ + Organization: []string{"www.smartperf.com"}, + OrganizationalUnit: []string{"ITs"}, + CommonName: "www.smartperf.com", + } + certificate509 := x509.Certificate{ + SerialNumber: serialNumber, + Subject: subject, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, + } + chekDir("cert") + pk, _ := rsa.GenerateKey(rand.Reader, 1024) + derBytes, _ := x509.CreateCertificate(rand.Reader, &certificate509, &certificate509, &pk.PublicKey, pk) + certOut, _ := os.Create("cert/certFile.pem") + pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + certOut.Close() + keyOut, _ := os.Create("cert/keyFile.key") + pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(pk)}) + keyOut.Close() +} +func main() { + genSSL() + exPath = getCurrentAbPath() + fmt.Println(exPath) + go func() { + version := "" + readVersion, versionErr := ioutil.ReadFile(exPath + "/version.txt") + if versionErr != nil { + version = "" + } else { + version = string(readVersion) + } + mux := http.NewServeMux() + mime.TypeByExtension(".js") + mime.AddExtensionType(".js", "application/javascript") + log.Println(mime.TypeByExtension(".js")) + mux.HandleFunc("/upload", uploadHandler) + mux.Handle("/upload/", http.StripPrefix("/upload/", http.FileServer(http.Dir(exPath+"/upload")))) + fs := http.FileServer(http.Dir(exPath + "/")) + mux.Handle("/application/", http.StripPrefix("/application/", cors(fs, version))) + ser := &http.Server{ + Addr: fmt.Sprintf(":%d", HttpPort), + Handler: mux, + } + log.Println(fmt.Sprintf("HTTP[%d]服务启动", HttpPort)) + open(fmt.Sprintf("https://localhost:%d/application", HttpPort)) + err := ser.ListenAndServeTLS("cert/certFile.pem", "cert/keyFile.key") + CheckErr(err) + }() + select {} +} + +func uploadHandler(w http.ResponseWriter, r *http.Request) { + defer func() { + var err = recover() + fmt.Println(err) + }() + chekDir(exPath + "/upload") + contentType := r.Header["Content-Type"] + if len(contentType) > 0 { + contentTypeName := contentType[0] + if strings.HasPrefix(contentTypeName, "multipart/form-data") { + err := r.ParseMultipartForm(32 << 20) + CheckErr(err) + file, header, err := r.FormFile("file") + CheckErr(err) + filename := header.Filename + index := strings.LastIndex(filename, ".") + distFileName := fmt.Sprintf("%d", time.Now().Unix()) + distFileSuffix := filename[index:] + path := fmt.Sprintf("/upload/%s%s", distFileName, distFileSuffix) + dst, err := os.OpenFile(exPath+path, os.O_WRONLY|os.O_CREATE, 0666) + CheckErr(err) + defer dst.Close() + if _, err := io.Copy(dst, file); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + databaseUrl := transformDatabase(distFileName, distFileSuffix) + if databaseUrl != "" { + ohosTsPath := fmt.Sprintf("./upload/%s.ohos.ts", databaseUrl) + result, _ := PathExists(ohosTsPath) + if result { + readFile, readErr := ioutil.ReadFile(ohosTsPath) + if readErr == nil { + fmt.Println(string(readFile)) + split := SplitLines(string(readFile)) + fmt.Println(split) + if len(split) > 1 { + if strings.HasSuffix(split[0], ":0") { + fmt.Fprintf(w, fmt.Sprintf("/upload/%s", databaseUrl)) + return + } + } + } + } + } + http.Error(w, "文件生成失败", http.StatusNotFound) + return + } + } +} +func SplitLines(s string) []string { + var lines []string + sc := bufio.NewScanner(strings.NewReader(s)) + for sc.Scan() { + lines = append(lines, sc.Text()) + } + return lines +} + +func readFileFirstLine(path string) string { + file, err := os.Open(path) + if err != nil { + return "" + } + defer file.Close() + + readFile := bufio.NewReader(file) + line, readErr := readFile.ReadString('\n') + if readErr != nil || io.EOF == err { + return "" + } + return line +} + +func PathExists(path string) (bool, error) { + _, err := os.Stat(path) + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} + +func chekDir(path string) { + _, err := os.Stat(path) + if err != nil { + err := os.Mkdir(path, os.ModePerm) + if err != nil { + fmt.Printf("mkdir failed![%v]\n", err) + } else { + fmt.Printf("mkdir success!\n") + } + } +} +func CheckErr(err error) { + if err != nil { + log.Panicln(err) + } +} + +func open(url string) error { + if isWindows() { + return openUrlWindows(url) + } else if isDarwin() { + return openUrlDarwin(url) + } else { + return openUrlOther(url) + } +} + +func openUrlWindows(url string) error { + cmd := "cmd" + args := []string{"/c", "start", url} + return exec.Command(cmd, args...).Start() +} +func openUrlDarwin(url string) error { + var cmd = "open" + var args = []string{url} + return exec.Command(cmd, args...).Start() +} +func openUrlOther(url string) error { + var cmd = "xdg-open" + var args = []string{url} + return exec.Command(cmd, args...).Start() +} + +func isWindows() bool { + return runtime.GOOS == "windows" +} +func isDarwin() bool { + return runtime.GOOS == "darwin" +} + +func transformDatabase(name string, suffix string) string { + if isWindows() { + cmd := exec.Command( + "cmd", + "/c", + `.\bin\trace_streamer_windows.exe`, + fmt.Sprintf(`.\upload\%s%s`, name, suffix), + "-e", + fmt.Sprintf(`.\upload\%s.db`, name), + ) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout // 标准输出 + cmd.Stderr = &stderr // 标准错误 + err := cmd.Run() + if err != nil { + return "" + } + return fmt.Sprintf("%s.db", name) + } else if isDarwin() { + cmd := exec.Command( + "/bin/bash", + "-c", + fmt.Sprintf("%s/bin/trace_streamer_mac %s/upload/%s%s -e %s/upload/%s.db", exPath, exPath, name, suffix, exPath, name), + ) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout // 标准输出 + cmd.Stderr = &stderr // 标准错误 + err := cmd.Run() + outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes()) + fmt.Printf("out:\n%s\n :\n%s\n", outStr, errStr) + if err != nil { + return "" + } + return fmt.Sprintf("%s.db", name) + } else { + cmd := exec.Command( + "/bin/bash", + "-c", + fmt.Sprintf("%s/bin/trace_streamer_linux %s/upload/%s%s -e %s/upload/%s.db", exPath, exPath, name, suffix, exPath, name), + ) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout // 标准输出 + cmd.Stderr = &stderr // 标准错误 + err := cmd.Run() + outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes()) + fmt.Printf("out:\n%s\n :\n%s\n", outStr, errStr) + if err != nil { + return "" + } + return fmt.Sprintf("%s.db", name) + } + return "" +} +func getCurrentAbPath() string { + dir := getCurrentAbPathByExecutable() + tmpDir, _ := filepath.EvalSymlinks(os.TempDir()) + if strings.Contains(dir, tmpDir) { + return getCurrentAbPathByCaller() + } + return dir +} + +func getCurrentAbPathByCaller() string { + var abPath string + _, filename, _, ok := runtime.Caller(0) + if ok { + abPath = path.Dir(filename) + } + return abPath +} +func getCurrentAbPathByExecutable() string { + exePath, err := os.Executable() + if err != nil { + log.Fatal(err) + } + res, _ := filepath.EvalSymlinks(filepath.Dir(exePath)) + return res +} diff --git a/host/ide/server/version.txt b/host/ide/server/version.txt new file mode 100644 index 0000000..13eb9fd --- /dev/null +++ b/host/ide/server/version.txt @@ -0,0 +1 @@ +v1.0.001 \ No newline at end of file diff --git a/host/ide/src/base-ui/BaseElement.ts b/host/ide/src/base-ui/BaseElement.ts new file mode 100644 index 0000000..a566c9a --- /dev/null +++ b/host/ide/src/base-ui/BaseElement.ts @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +export function element(tag: string) { + return (el: any) => { + if (!customElements.get(tag)) { + customElements.define(tag, el); + } + } +} + +export abstract class BaseElement extends HTMLElement { + args: any; + + public constructor(args: any | undefined | null = null) { + super(); + this.args = args; + this.attachShadow({mode: 'open'}).innerHTML = this.initHtml(); + this.initElements(); + } + + abstract initElements(): void; + + abstract initHtml(): string; + + public connectedCallback() { + } + + public disconnectedCallback() { + } + + public adoptedCallback() { + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + } +} diff --git a/host/ide/src/base-ui/button/LitButton.ts b/host/ide/src/base-ui/button/LitButton.ts new file mode 100644 index 0000000..516e1b8 --- /dev/null +++ b/host/ide/src/base-ui/button/LitButton.ts @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement} from "../BaseElement.js"; + +export class LitButton extends BaseElement { + initHtml(): string { + return ""; + } + + initElements(): void { + + } +} \ No newline at end of file diff --git a/host/ide/src/base-ui/checkbox/LitCheckBox.ts b/host/ide/src/base-ui/checkbox/LitCheckBox.ts new file mode 100644 index 0000000..a6b5d91 --- /dev/null +++ b/host/ide/src/base-ui/checkbox/LitCheckBox.ts @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../BaseElement.js"; + +@element('lit-check-box') +export class LitCheckBox extends BaseElement { + + private checkbox: HTMLInputElement | undefined + + static get observedAttributes() { + return ['checked', 'value'] + } + + get indeterminate() { + return this.checkbox!.indeterminate; + } + + set indeterminate(value) { + if (value === null || value === false) { + this.checkbox!.indeterminate = false; + } else { + this.checkbox!.indeterminate = true; + } + } + + get checked() { + return this.getAttribute('checked') !== null; + } + + set checked(value: boolean) { + if (value === null || !value) { + this.removeAttribute('checked'); + } else { + this.setAttribute('checked', ''); + } + } + + get value() { + return this.getAttribute('value') || ''; + } + + set value(value: string) { + this.setAttribute('value', value); + } + + initHtml(): string { + return ` + + + + `; + } + + initElements(): void { + this.checkbox = this.shadowRoot?.getElementById('checkbox') as HTMLInputElement; + } + + connectedCallback() { + this.checkbox!.addEventListener('change', (ev) => { + this.checked = this.checkbox!.checked; + this.dispatchEvent(new CustomEvent("change", { + detail: { + "checked": this.checked + } + })) + }) + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + if (name == 'checked' && this.checkbox) { + this.checkbox.checked = newValue !== null; + } + if (name == 'value') { + let slot = this.shadowRoot?.getElementById("slot") + slot!.textContent = newValue + } + } +} diff --git a/host/ide/src/base-ui/checkbox/LitCheckBoxWithText.ts b/host/ide/src/base-ui/checkbox/LitCheckBoxWithText.ts new file mode 100644 index 0000000..2de00a3 --- /dev/null +++ b/host/ide/src/base-ui/checkbox/LitCheckBoxWithText.ts @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../BaseElement.js"; +import {SpCheckDesBox} from "../../trace/component/setting/SpCheckDesBox.js"; + +@element('lit-check-text') +export class LitCheckBoxWithText extends BaseElement { + private _checkBox: SpCheckDesBox | undefined; + private _lowerLimit: HTMLInputElement | undefined; + private _upLimit: HTMLInputElement | undefined; + + static get observedAttributes() { + return ['text', 'lowerLimit', 'upLimit', 'checked'] + } + + get text(): string { + return this.getAttribute("text") || "" + } + + set text(text: string) { + this.setAttribute("text", text) + } + + get lowerLimit(): string { + return this.getAttribute("lowerLimit") || "0" + } + + set lowerLimit(lower: string) { + this.setAttribute("lowerLimit", lower) + } + + get upLimit(): string { + return this.getAttribute("upLimit") || "∞" + } + + set upLimit(upLimit: string) { + this.setAttribute("upLimit", upLimit) + } + + get checked() { + return this.getAttribute("checked") != null; + } + + set checked(checked: boolean) { + if (checked) { + this.setAttribute('checked', ''); + } else { + this.removeAttribute('checked'); + } + } + + initElements(): void { + this._checkBox = this.shadowRoot?.getElementById('checkbox') as SpCheckDesBox; + this._lowerLimit = this.shadowRoot?.getElementById('textLowerLimit') as HTMLInputElement; + this._upLimit = this.shadowRoot?.getElementById('_upLimit') as HTMLInputElement; + } + + initHtml(): string { + return ` + + + + `; + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + if (name == 'checked') { + this._checkBox!.checked = newValue !== null; + } + if (name == 'text') { + this._checkBox?.setAttribute('value', newValue); + } + if (name == 'lowerLimit') { + this._lowerLimit!.textContent = newValue + } + if (name == 'upLimit') { + this._upLimit!.textContent = newValue + } + } + +} \ No newline at end of file diff --git a/host/ide/src/base-ui/checkbox/LitCheckGroup.ts b/host/ide/src/base-ui/checkbox/LitCheckGroup.ts new file mode 100644 index 0000000..ec9f31e --- /dev/null +++ b/host/ide/src/base-ui/checkbox/LitCheckGroup.ts @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../BaseElement.js"; +import {LitCheckBox} from "./LitCheckBox.js"; + +@element('lit-check-group') +export class LitCheckGroup extends BaseElement { + + get direction() { + return this.getAttribute("direction") + } + + get value(): Array { + let values = [] + for (const litCheckBoxElement of this.querySelectorAll('lit-check-box[checked]')) { + values.push(litCheckBoxElement.value) + } + return values; + } + + initElements(): void { + } + + initHtml(): string { + return ` + + `; + } +} \ No newline at end of file diff --git a/host/ide/src/base-ui/icon.svg b/host/ide/src/base-ui/icon.svg new file mode 100644 index 0000000..168c113 --- /dev/null +++ b/host/ide/src/base-ui/icon.svg @@ -0,0 +1 @@ + diff --git a/host/ide/src/base-ui/icon/LitIcon.ts b/host/ide/src/base-ui/icon/LitIcon.ts new file mode 100644 index 0000000..6fa3d03 --- /dev/null +++ b/host/ide/src/base-ui/icon/LitIcon.ts @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import {BaseElement, element} from "../BaseElement.js"; + +@element('lit-icon') +export class LitIcon extends BaseElement { + private view?: number + private icon: HTMLElement | undefined | null + private use: SVGUseElement | undefined | null + private d: SVGPathElement | undefined | null + private _name?: string + private _size?: number + private _color?: string + private _path?: string + + static get observedAttributes() { + return ["name", "size", "color", "path"] + } + + get name(): string { + return this.getAttribute("name") || ""; + } + + set name(value: string) { + this._name = value; + this.setAttribute("name", value) + } + + get size(): number { + return parseInt(this.getAttribute("size") || '0', 10) + } + + set size(value: number) { + this._size = value; + this.setAttribute("size", `${value}`) + } + + set color(value: string) { + this._color = value; + this.setAttribute('color', value) + } + + set path(value: string) { + this._path = value + this.setAttribute('path', value); + } + + initHtml(): string { + return ` + + + `; + } + + initElements() { + if (this.shadowRoot) { + this.icon = this.shadowRoot.getElementById("icon"); + this.use = this.shadowRoot.querySelector("use"); + this.d = this.shadowRoot.querySelector("path"); + } + } + + attributeChangedCallback(name: string, oldValue: string, value: string) { + switch (name) { + case "name": + if (this.use) this.use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', `./base-ui/icon.svg#icon-${value}`); + break; + case "path": + if (this.d) this.d.setAttribute("d", value); + break; + case "color": + if (this.icon) this.icon.style.color = value as string; + break; + case "size": + if (this.icon) this.icon.style.fontSize = `${value}px`; + break; + } + } +} + diff --git a/host/ide/src/base-ui/menu/LitMainMenu.ts b/host/ide/src/base-ui/menu/LitMainMenu.ts new file mode 100644 index 0000000..008f584 --- /dev/null +++ b/host/ide/src/base-ui/menu/LitMainMenu.ts @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import {BaseElement, element} from "../BaseElement.js"; +import './LitMainMenuItem.js' +import './LitMainMenuGroup.js' +import {LitMainMenuGroup} from "./LitMainMenuGroup.js"; +import {LitMainMenuItem} from "./LitMainMenuItem.js"; + +@element('lit-main-menu') +export class LitMainMenu extends BaseElement { + private slotElements: Element[] | undefined; + private _menus: Array | undefined + + static get observedAttributes() { + return [] + } + + get menus(): Array | undefined { + return this._menus; + } + + set menus(value: Array | undefined) { + this._menus = value; + this.shadowRoot?.querySelectorAll('lit-main-menu-group').forEach(a => a.remove()); + let menuBody = this.shadowRoot?.querySelector('.menu-body'); + value?.forEach(it => { + let group = new LitMainMenuGroup(); + group.setAttribute('title', it.title || ""); + group.setAttribute('describe', it.describe || ""); + if (it.collapsed) { + group.setAttribute('collapsed', ''); + } else { + group.removeAttribute('collapsed'); + } + menuBody?.appendChild(group); + it.children?.forEach((item: any) => { + let th = new LitMainMenuItem(); + th.setAttribute('icon', item.icon || ""); + th.setAttribute('title', item.title || ""); + if (item.fileChoose) { + th.setAttribute('file', ""); + th.addEventListener('file-change', e => { + if (item.fileHandler) { + item.fileHandler(e) + } + }) + } else { + th.removeAttribute('file'); + th.addEventListener('click', e => { + if (item.clickHandler) { + item.clickHandler(item) + } + }) + } + if (item.disabled != undefined) { + th.disabled = item.disabled + } + group?.appendChild(th); + }) + }) + } + + initElements(): void { + let st: HTMLSlotElement | null | undefined = this.shadowRoot?.querySelector('#st'); + st?.addEventListener('slotchange', e => { + this.slotElements = st?.assignedElements(); + this.slotElements?.forEach(it => { + it.querySelectorAll("lit-main-menu-item").forEach(cell => { + }) + }) + }) + let versionDiv: HTMLElement | null | undefined = this.shadowRoot?.querySelector('.version'); + versionDiv!.innerText = (window as any).version || "" + } + + initHtml(): string { + return ` + +

      + +
      + `; + } +} + +export interface MenuGroup { + title: string + describe: string + collapsed: boolean + children: Array +} + +export interface MenuItem { + icon: string + title: string + fileChoose?: boolean + clickHandler?: Function + fileHandler?: Function +} diff --git a/host/ide/src/base-ui/menu/LitMainMenuGroup.ts b/host/ide/src/base-ui/menu/LitMainMenuGroup.ts new file mode 100644 index 0000000..57c51e0 --- /dev/null +++ b/host/ide/src/base-ui/menu/LitMainMenuGroup.ts @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import {BaseElement, element} from "../BaseElement.js"; + +@element('lit-main-menu-group') +export class LitMainMenuGroup extends BaseElement { + protected _collapsed: boolean | undefined; + private groupNameEl: HTMLElement | null | undefined; + private groupDescEl: HTMLElement | null | undefined; + + static get observedAttributes() { + return ['title', 'describe', 'collapsed', 'nocollapse', "radius"] + } + + get collapsed(): boolean { + return this.hasAttribute('collapsed') + } + + set collapsed(value: boolean) { + if (value) { + this.setAttribute('collapsed', '') + } else { + this.removeAttribute('collapsed') + } + } + + get nocollapsed() { + return this.hasAttribute('nocollapsed') + } + + set nocollapsed(value: boolean) { + if (value) { + this.setAttribute('nocollapsed', '') + } else { + this.removeAttribute('nocollapsed') + } + } + + get radius() { + return this.hasAttribute("radius") + } + + initElements(): void { + this.groupNameEl = this.shadowRoot?.querySelector('.group-name'); + this.groupDescEl = this.shadowRoot?.querySelector('.group-describe'); + this.addEventListener('click', (e) => { + if (this.nocollapsed) { + return; + } + this.collapsed = !this.collapsed + }) + } + + initHtml(): string { + return ` + +
      +
      + + `; + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + switch (name) { + case "title": + if (this.groupNameEl) this.groupNameEl.textContent = newValue + break; + case "describe": + if (this.groupDescEl) this.groupDescEl.textContent = newValue + break; + } + } +} diff --git a/host/ide/src/base-ui/menu/LitMainMenuItem.ts b/host/ide/src/base-ui/menu/LitMainMenuItem.ts new file mode 100644 index 0000000..4af57f1 --- /dev/null +++ b/host/ide/src/base-ui/menu/LitMainMenuItem.ts @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import {BaseElement, element} from "../BaseElement.js"; + +@element('lit-main-menu-item') +export class LitMainMenuItem extends BaseElement { + private titleEl: HTMLElement | null | undefined; + private rootEL: HTMLElement | null | undefined; + private iconEl: HTMLElement | null | undefined; + private fileEL: HTMLInputElement | undefined | null; + + static get observedAttributes() { + return ['title', 'icon', 'file', 'disabled'] + } + + get title(): string { + return this.getAttribute("title") || "" + } + + set title(val: string) { + this.setAttribute("title", val); + } + + get disabled(): boolean { + return this.hasAttribute("disabled") + } + + set disabled(val: boolean) { + if (val) { + this.setAttribute("disabled", val.toString()); + this.fileEL?.setAttribute("disabled", val.toString()); + } else { + this.removeAttribute("disabled"); + this.fileEL?.removeAttribute("disabled"); + } + } + + initElements(): void { + this.rootEL = this.shadowRoot?.querySelector('.root'); + this.titleEl = this.shadowRoot?.querySelector('.name'); + this.iconEl = this.shadowRoot?.querySelector('.icon'); + this.fileEL = this.shadowRoot?.querySelector('.file'); + } + + isFile(): boolean { + if (this.hasAttribute("file")) { + if (this.fileEL) { + return true + } + } + return false + } + + connectedCallback() { + if (this.hasAttribute("file")) { + if (this.fileEL) { + this.fileEL.addEventListener('change', () => { + let files = this.fileEL!.files; + if (files && files.length > 0) { + // @ts-ignore + this.dispatchEvent(new CustomEvent('file-change', {target: this, detail: files[0]})) + if (this.fileEL) this.fileEL.value = '' + } + }); + } + } + this.addEventListener('click', e => { + e.stopPropagation(); + }) + } + + initHtml(): string { + return ` + + + +`; + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + switch (name) { + case "title": + if (this.titleEl) this.titleEl.textContent = newValue; + break; + case "icon": + if (this.iconEl) this.iconEl.setAttribute("name", newValue) + break; + } + } +} diff --git a/host/ide/src/base-ui/popover/LitPopContent.ts b/host/ide/src/base-ui/popover/LitPopContent.ts new file mode 100644 index 0000000..2515862 --- /dev/null +++ b/host/ide/src/base-ui/popover/LitPopContent.ts @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../BaseElement.js"; + +@element("lit-pop-content") +export class LitPopContent extends BaseElement { + static get observedAttributes() { + return ["open"] + } + + get open() { + return this.hasAttribute('open'); + } + + set open(value: boolean) { + if (value === null || !value) { + this.removeAttribute('open'); + let parentElement = this.parentNode as Element; + parentElement?.removeAttribute('open'); + } else { + this.setAttribute('open', ''); + let parentElement = this.parentNode as Element; + parentElement?.setAttribute('open', ''); + } + } + + initElements(): void { + } + + initHtml(): string { + return ` + +
      + +
      + `; + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + switch (name) { + case "open": + if (newValue === null || newValue === "false") { + let parentElement = this.parentNode as Element; + parentElement?.removeAttribute('open'); + } else { + let parentElement = this.parentNode as Element; + parentElement?.setAttribute('open', ''); + } + break; + default: + break; + } + } +} \ No newline at end of file diff --git a/host/ide/src/base-ui/popover/LitPopover.ts b/host/ide/src/base-ui/popover/LitPopover.ts new file mode 100644 index 0000000..1087d9e --- /dev/null +++ b/host/ide/src/base-ui/popover/LitPopover.ts @@ -0,0 +1,458 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../BaseElement.js"; +import {LitPopContent} from "./LitPopContent.js"; +import {LitPopoverTitle} from "./LitPopoverTitle.js"; +import {LitRadioGroup} from "../radiobox/LitRadioGroup.js"; +import {LitRadioBox} from "../radiobox/LitRadioBox.js"; +import {LitCheckBox} from "../checkbox/LitCheckBox.js"; +import {LitCheckGroup} from "../checkbox/LitCheckGroup.js"; +import {LitCheckBoxWithText} from "../checkbox/LitCheckBoxWithText.js"; + +@element("lit-popover") +export class LitPopover extends BaseElement { + private popContent: LitPopContent | null | undefined; + private litGroup: LitRadioGroup | LitCheckGroup | undefined + private _texBox: LitCheckBoxWithText | undefined + + static get observedAttributes() { + return [] + } + + get type() { + return this.getAttribute("type") || '' + } + + set type(type: string) { + this.setAttribute("type", type) + } + + get title() { + return this.getAttribute("title") || '' + } + + set title(title: string) { + this.setAttribute("title", title) + } + + get limit(): LimitText { + if (this._texBox?.checked) { + return {textLowerLimit: this._texBox.lowerLimit, textUpperLimit: this._texBox.upLimit} + } + return {textLowerLimit: "", textUpperLimit: ""} + } + + set dataSource(dataSource: Array) { + this.popContent = this.querySelector('lit-pop-content'); + if (!this.popContent) { + this.popContent = new LitPopContent(); + this.appendChild(this.popContent); + } + switch (this.type) { + case "multiple": + this.litGroup = new LitCheckGroup(); + this.litGroup.setAttribute("layout", "dispersion") + this.popContent!.appendChild(this.litGroup); + dataSource.forEach(data => { + let litCheckBox = new LitCheckBox(); + this.litGroup?.appendChild(litCheckBox) + if (data.isSelected) { + litCheckBox.setAttribute('checked', "true") + } + litCheckBox.setAttribute("value", data.text) + }) + break; + case "radio": + this.litGroup = new LitRadioGroup(); + if (this.title !== '') { + let title = new LitPopoverTitle(); + title.setAttribute('title', this.title || ""); + this.popContent!.appendChild(title); + this.litGroup.setAttribute("layout", "compact") + } else { + this.litGroup.setAttribute("layout", "dispersion") + } + this.popContent!.appendChild(this.litGroup); + dataSource.forEach(data => { + let litRadioBox = new LitRadioBox(); + if (this.title == '') { + litRadioBox.setAttribute('dis', 'round') + } else { + litRadioBox.setAttribute('dis', 'check') + } + if (data.isSelected) { + litRadioBox.setAttribute('checked', "true") + } + this.litGroup?.appendChild(litRadioBox) + litRadioBox.setAttribute("value", data.text) + }) + break; + case "multiple-text": + dataSource.forEach(data => { + this._texBox = new LitCheckBoxWithText(); + this._texBox.setAttribute("text", data.text) + this._texBox.setAttribute("checked", "") + this.popContent!.appendChild(this._texBox); + }) + break; + case "data-ming": + break; + } + } + + get select(): Array | undefined { + if (this._texBox?.checked) { + return [this._texBox!.text] + } + return this.litGroup?.value; + } + + get trigger() { + return this.getAttribute('trigger'); + } + + get direction() { + return this.getAttribute('direction') || 'topright' + } + + set direction(value: string) { + this.setAttribute('direction', value); + } + + get open() { + return this.getAttribute('open') !== null; + } + + set open(value: boolean) { + if (value === null || value === false) { + this.removeAttribute('open'); + } else { + this.setAttribute('open', ''); + } + } + + initElements(): void { + } + + initHtml(): string { + return ` + + + `; + } + + connectedCallback() { + if (!(this.trigger && this.trigger !== 'click')) { + this.addEventListener('click', () => { + this.popContent = this.querySelector('lit-pop-content'); + if (!this.popContent) { + this.popContent = new LitPopContent(); + this.appendChild(this.popContent); + } + this.popContent?.setAttribute("open", 'true') + }); + } + document.addEventListener('mousedown', ev => { + const path = ev.composedPath && ev.composedPath(); + if (this.popContent && !path.includes(this.popContent) && !path.includes(this.children[0]) && !path.includes(this.popContent)) { + this.popContent!.open = false; + } + }); + } +} + +export interface SelectBean { + text: string; + isSelected: boolean; + limitText?: LimitText; +} + +export interface LimitText { + textUpperLimit: string; + textLowerLimit: string; +} + +export interface Charge { + text: string; + isSelected: boolean; +} diff --git a/host/ide/src/base-ui/popover/LitPopoverTitle.ts b/host/ide/src/base-ui/popover/LitPopoverTitle.ts new file mode 100644 index 0000000..2761ec9 --- /dev/null +++ b/host/ide/src/base-ui/popover/LitPopoverTitle.ts @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../BaseElement.js"; + +@element("lit-popover-title") +export class LitPopoverTitle extends BaseElement { + private titleText: HTMLElement | null | undefined; + + static get observedAttributes() { + return ['title'] + } + + initElements(): void { + this.titleText = this.shadowRoot?.querySelector('.pop-title'); + } + + initHtml(): string { + return ` + +
      + `; + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + switch (name) { + case "title": + if (this.titleText) this.titleText.textContent = newValue + break; + default: + break; + } + } + +} \ No newline at end of file diff --git a/host/ide/src/base-ui/popover/LitPopoverV.ts b/host/ide/src/base-ui/popover/LitPopoverV.ts new file mode 100644 index 0000000..6fd0a08 --- /dev/null +++ b/host/ide/src/base-ui/popover/LitPopoverV.ts @@ -0,0 +1,494 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {element,BaseElement} from "../BaseElement.js"; + +@element("lit-popover") +export class LitPopover extends BaseElement { + static get observedAttributes() { + return [ + 'title',/*标题*/ + 'trigger',/*触发条件 hover | click | focus[未实现]*/ + 'width',/*自定义高度*/ + 'placement',/*设置方向 topLeft top topRight leftTop left leftBottom rightTop right rightBottom bottomLeft bottom bottomRight*/ + 'visible'/*控制popover是否显示*/ + ] + } + initElements(): void { + + } + get visible(){ + return this.getAttribute('visible') || 'false'; + } + set visible(value){ + if (value) { + this.setAttribute('visible', 'true'); + }else{ + this.setAttribute('visible', 'false'); + } + } + get trigger() { + return this.getAttribute('trigger') || 'hover' + } + + set trigger(value) { + this.setAttribute('trigger', value); + } + + get title() { + return this.getAttribute('title'); + } + + set title(value:any) { + this.setAttribute('title', value); + } + + get width() { + return this.getAttribute('width') || 'max-content'; + } + + set width(value) { + this.setAttribute('width', value); + } + + get haveRadio(){ + return this.getAttribute("haveRadio") + } + + initHtml() { + // super(); + // const shadowRoot = this.attachShadow({mode: 'open'}); + return ` + + +
      +
      ${this.title}
      +
      +
      + + ` + } + + //当 custom element首次被插入文档DOM时,被调用。 + connectedCallback() { + let popover:any = this.shadowRoot!.querySelector('.popover'); + let checkbox:any = this.shadowRoot!.querySelector('.trigger-click'); + this.setAttribute('tabindex', '1'); + popover.onclick = (e:any) => { + e.stopPropagation(); + } + popover.addEventListener('mousemove',(e:any)=>{ + e.stopPropagation(); + }) + this.onclick = (e) => { + e.stopPropagation(); + // e.preventDefault() + this.focus(); + checkbox.checked = !checkbox.checked; + } + this.onblur = (ev:any) => { + if (ev.relatedTarget&&this.haveRadio) { + if (ev.relatedTarget.type === "radio") { + this.focus(); + }else { + checkbox.checked = false; + } + }else { + checkbox.checked = false; + } + } + } + + //当 custom element从文档DOM中删除时,被调用。 + disconnectedCallback() { + + } + + //当 custom element被移动到新的文档时,被调用。 + adoptedCallback() { + } + + //当 custom element增加、删除、修改自身属性时,被调用。 + attributeChangedCallback(name:any, oldValue:any, newValue:any) { + if(name==='visible'){ + if(newValue==='false'){ + // @ts-ignore + this.shadowRoot!.querySelector('.trigger-click')!.checked=false; + }else{ + // @ts-ignore + this.shadowRoot!.querySelector('.trigger-click')!.checked=true; + } + } + } +} diff --git a/host/ide/src/base-ui/progress-bar/LitProgressBar.ts b/host/ide/src/base-ui/progress-bar/LitProgressBar.ts new file mode 100644 index 0000000..befbb3f --- /dev/null +++ b/host/ide/src/base-ui/progress-bar/LitProgressBar.ts @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import {BaseElement, element} from "../BaseElement.js"; + +@element("lit-progress-bar") +export class LitProgressBar extends BaseElement { + static get observedAttributes() { + return ['loading'] + } + + get loading(): boolean { + return this.hasAttribute("loading"); + } + + set loading(value: boolean) { + if (value) { + this.setAttribute('loading', ''); + } else { + this.removeAttribute('loading'); + } + } + + initElements(): void { + } + + initHtml(): string { + return ` + +
      +
      +
      +
      + `; + } + +} \ No newline at end of file diff --git a/host/ide/src/base-ui/radiobox/LitRadioBox.ts b/host/ide/src/base-ui/radiobox/LitRadioBox.ts new file mode 100644 index 0000000..daef135 --- /dev/null +++ b/host/ide/src/base-ui/radiobox/LitRadioBox.ts @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../BaseElement.js"; +import {LitRadioGroup} from "./LitRadioGroup.js"; + +@element('lit-radio') +export class LitRadioBox extends BaseElement { + + private group: LitRadioGroup | undefined | null + private parent: LitRadioGroup | undefined | null + private radio: HTMLInputElement | undefined | null + + static get observedAttributes() { + return ['checked', 'value'] + } + + get checked() { + return this.getAttribute('checked') !== null; + } + + set checked(value: boolean) { + if (value === null || !value) { + this.removeAttribute('checked'); + } else { + this.setAttribute('checked', ''); + } + } + + get name() { + return this.getAttribute('name'); + } + + get value() { + let slot = this.shadowRoot?.getElementById("slot") + return slot!.textContent || this.textContent || ""; + } + + set value(value: string) { + this.setAttribute('value', value); + } + + set dis(dis: string) { + this.setAttribute('dis', dis) + } + + initHtml(): string { + return ` + + + + `; + } + + initElements(): void { + this.radio = this.shadowRoot?.getElementById('radio') as HTMLInputElement; + } + + connectedCallback() { + this.group = this.closest('lit-radio-group') as LitRadioGroup; + this.parent = this.group || this.getRootNode(); + this.radio = this.shadowRoot?.getElementById('radio') as HTMLInputElement; + this.checked = this.checked; + this.radio.addEventListener('change', () => { + const selector = this.group ? `lit-radio[checked]` : `lit-radio[name="${this.name}"][checked]`; + const siblingNode = this.parent?.querySelector(selector) as LitRadioBox; + if (siblingNode) { + siblingNode.checked = false; + } + this.checked = true; + }) + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + if (name == 'checked' && this.radio) { + this.radio.checked = newValue !== null; + } + if (name == 'value') { + let slot = this.shadowRoot?.getElementById("slot") + slot!.textContent = newValue + } + } +} diff --git a/host/ide/src/base-ui/radiobox/LitRadioGroup.ts b/host/ide/src/base-ui/radiobox/LitRadioGroup.ts new file mode 100644 index 0000000..1c4d284 --- /dev/null +++ b/host/ide/src/base-ui/radiobox/LitRadioGroup.ts @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../BaseElement.js"; +import {LitRadioBox} from "./LitRadioBox.js"; + +@element("lit-radio-group") +export class LitRadioGroup extends BaseElement { + static get observedAttributes() { + return ["direction"] + } + + set layout(vale: string) { + this.setAttribute("layout", vale) + } + + get direction() { + return this.getAttribute("direction") + } + + get value(): Array { + const radio = this.querySelector('lit-radio[checked]') as LitRadioBox; + return radio ? [radio.value] : []; + } + + initElements(): void { + } + + //方向 + initHtml(): string { + return ` + + `; + } +} \ No newline at end of file diff --git a/host/ide/src/base-ui/select/LitSelect.ts b/host/ide/src/base-ui/select/LitSelect.ts new file mode 100644 index 0000000..460cac9 --- /dev/null +++ b/host/ide/src/base-ui/select/LitSelect.ts @@ -0,0 +1,602 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../BaseElement.js"; + +@element('lit-select') +export class LitSelect extends BaseElement { + private focused:any; + private inputElement:any; + private clearElement:any; + private iconElement:any; + private searchElement:any; + private multipleRootElement:any; + static get observedAttributes() { + return [ + 'value',//默认值 + 'default-value',//默认值 + 'placeholder',//placeholder + 'disabled', + 'loading',//是否处于加载状态 + 'allow-clear',//是否允许清除 + 'show-search',//是否允许搜索 + 'list-height',//设置弹窗滚动高度 默认256px + 'border',//是否显示边框 + 'mode',// mode='multiple'多选 + ]; + } + initElements(): void { + + } + + get value() { + return this.getAttribute('value') || this.defaultValue; + } + + set value(value) { + this.setAttribute('value', value); + } + + get border() { + return this.getAttribute('border') || 'true'; + } + + set border(value) { + if (value) { + this.setAttribute('border', 'true'); + } else { + this.setAttribute('border', 'false'); + } + } + + get listHeight() { + return this.getAttribute('list-height') || '256px'; + } + + set listHeight(value) { + this.setAttribute('list-height', value); + } + + get defaultPlaceholder() { + return this.getAttribute('placeholder') || '请选择'; + } + + get showSearch() { + return this.hasAttribute('show-search'); + } + + set defaultValue(value) { + this.setAttribute('default-value', value); + } + + get defaultValue() { + return this.getAttribute('default-value') || ''; + } + + set placeholder(value) { + this.setAttribute('placeholder', value); + } + + get placeholder() { + return this.getAttribute('placeholder') || this.defaultPlaceholder; + } + + get loading() { + return this.hasAttribute('loading'); + } + + set loading(value) { + if (value) { + this.setAttribute('loading', ''); + } else { + this.removeAttribute('loading') + } + } + + initHtml() { + // super(); + // const shadowRoot = this.attachShadow({mode: 'open'}); + return` + +
      +
      + + + + +
      +
      + + +
      + ` + } + + isMultiple() { + return this.hasAttribute('mode') && this.getAttribute('mode') === 'multiple' + } + + newTag(value:any, text:any) { + let tag:any = document.createElement('div'); + let icon:any = document.createElement('lit-icon'); + icon.classList.add('tag-close') + icon.name = 'close' + let span = document.createElement('span'); + tag.classList.add('tag'); + span.dataset['value'] = value; + span.textContent = text; + tag.append(span); + tag.append(icon); + icon.onclick = (ev:any) => { + tag.parentElement.removeChild(tag); + this.querySelector(`lit-select-option[value=${value}]`)!.removeAttribute('selected') + if (this.shadowRoot!.querySelectorAll('.tag').length == 0) { + this.inputElement.style.width = 'auto'; + this.inputElement.placeholder = this.defaultPlaceholder; + } + ev.stopPropagation(); + } + tag.value = value; + tag.dataset['value'] = value; + tag.text = text; + tag.dataset['text'] = text; + return tag; + } + + //当 custom element首次被插入文档DOM时,被调用。 + connectedCallback() { + this.tabIndex = 0;//设置当前组件为可以获取焦点 + this.focused = false; + this.inputElement = this.shadowRoot!.querySelector('input'); + this.clearElement = this.shadowRoot!.querySelector('.clear'); + this.iconElement = this.shadowRoot!.querySelector('.icon'); + this.searchElement = this.shadowRoot!.querySelector('.search'); + this.multipleRootElement = this.shadowRoot!.querySelector('.multipleRoot'); + //点击清理 清空input值,展示placeholder, + this.clearElement.onclick = (ev:any) => { + if (this.isMultiple()) { + let delNodes:Array = [] + this.multipleRootElement.childNodes.forEach((a:any) => { + if (a.tagName === 'DIV') { + delNodes.push(a); + } + }) + for (let i = 0; i < delNodes.length; i++) { + delNodes[i].remove(); + } + if (this.shadowRoot!.querySelectorAll('.tag').length == 0) { + this.inputElement.style.width = 'auto'; + this.inputElement.placeholder = this.defaultPlaceholder; + } + } + this.querySelectorAll('lit-select-option').forEach(a => a.removeAttribute('selected')); + this.inputElement.value = '' + this.clearElement.style.display = 'none'; + this.iconElement.style.display = 'flex'; + this.blur(); + ev.stopPropagation();//这里不会因为点击清理而触发 选择栏目显示或者隐藏 + this.dispatchEvent(new CustomEvent('onClear', {detail: ev}))//向外派发清理事件 + } + //初始化时遍历所有的option节点 + this.initOptions(); + //当前控件点击时 如果时select本身 需要显示 或 隐藏选择栏目,通过this.focused变量控制(默认为false) + this.onclick = (ev:any) => { + if (ev.target.tagName === 'LIT-SELECT') { + if (this.focused === false) { + this.inputElement.focus(); + this.focused = true; + } else { + this.blur(); + this.focused = false; + } + } + } + this.onmouseover = this.onfocus = ev => { + if (this.hasAttribute('allow-clear')) { + if (this.inputElement.value.length > 0 || this.inputElement.placeholder !== this.defaultPlaceholder) { + this.clearElement.style.display = 'flex' + this.iconElement.style.display = 'none'; + } else { + this.clearElement.style.display = 'none' + this.iconElement.style.display = 'flex'; + } + } + } + this.onmouseout = this.onblur = ev => { + if (this.hasAttribute('allow-clear')) { + this.clearElement.style.display = 'none'; + this.iconElement.style.display = 'flex'; + } + this.focused = false; + } + //输入框获取焦点时,value值 暂存于 placeholder 然后value值清空,这样值会以placeholder形式灰色展示,鼠标位于第一个字符 + this.inputElement.onfocus = (ev:any) => { + if (this.hasAttribute('disabled')) return;//如果控件处于disabled状态 直接忽略 + if (this.inputElement.value.length > 0) { + this.inputElement.placeholder = this.inputElement.value; + this.inputElement.value = '' + } + if (this.hasAttribute('show-search')) {//如果有show-search属性 需要显示放大镜,隐藏向下的箭头 + this.searchElement.style.display = 'flex'; + this.iconElement.style.display = 'none'; + } + this.querySelectorAll('lit-select-option').forEach(a => {//input获取焦点时显示所有可选项,相当于清理了搜索结果 + // @ts-ignore + a.style.display = 'flex'; + }) + } + //当输入框失去焦点的时候 placeholder 的值 保存到value上,input显示值 + this.inputElement.onblur = (ev:any) => { + if (this.hasAttribute('disabled')) return;//如果控件处于disabled状态 直接忽略 + if (this.isMultiple()) { + if (this.hasAttribute('show-search')) {//如果有show-search属性 失去焦点需要 隐藏放大镜图标,显示默认的向下箭头图标 + this.searchElement.style.display = 'none'; + this.iconElement.style.display = 'flex'; + } + } else { + if (this.inputElement.placeholder !== this.defaultPlaceholder) {//如果placeholder为 请输入(默认值)不做处理 + this.inputElement.value = this.inputElement.placeholder; //placeholder 保存的值放入 value中 + this.inputElement.placeholder = this.defaultPlaceholder;//placeholder 值为 默认值(请输入) + } + if (this.hasAttribute('show-search')) {//如果有show-search属性 失去焦点需要 隐藏放大镜图标,显示默认的向下箭头图标 + this.searchElement.style.display = 'none'; + this.iconElement.style.display = 'flex'; + } + } + } + //输入框每次文本变化 会匹配搜索的option 显示或者隐藏,达到搜索的效果 + this.inputElement.oninput = (ev:any) => { + let els = [...this.querySelectorAll('lit-select-option')]; + if (!ev.target.value) { + els.forEach((a:any) => a.style.display = 'flex'); + } else { + els.forEach((a:any) => { + let value = a.getAttribute('value'); + if (value.toLowerCase().indexOf(ev.target.value.toLowerCase()) !== -1 || + a.textContent.toLowerCase().indexOf(ev.target.value.toLowerCase()) !== -1) { + a.style.display = 'flex'; + } else { + a.style.display = 'none'; + } + }) + } + } + //输入框按下回车键,自动输入当前搜索出来的第一行,及display!='none'的第一个,搜索会隐藏其他行 + this.inputElement.onkeydown = (ev:any) => { + if (ev.key === 'Backspace') { + if (this.isMultiple()) { + let tag = this.multipleRootElement.lastElementChild.previousElementSibling; + if (tag) { + this.querySelector(`lit-select-option[value=${tag.value}]`)?.removeAttribute('selected'); + tag.remove() + if (this.shadowRoot!.querySelectorAll('.tag').length == 0) { + this.inputElement.style.width = 'auto'; + this.inputElement.placeholder = this.defaultPlaceholder; + } + } + } else { + this.clear(); + this.dispatchEvent(new CustomEvent('onClear', {detail: ev}))//向外派发清理事件 + } + } else if (ev.key === 'Enter') { + let filter = [...this.querySelectorAll('lit-select-option')].filter((a:any) => a.style.display !== 'none'); + if (filter.length > 0) { + this.inputElement.value = filter[0].textContent; + this.inputElement.placeholder = filter[0].textContent; + this.blur(); + // @ts-ignore + this.value=filter[0].getAttribute('value') + this.dispatchEvent(new CustomEvent('change', { + detail: { + selected: true, + value: filter[0].getAttribute('value'), + text: filter[0].textContent + } + }));//向外层派发change事件,返回当前选中项 + } + } + } + } + + initOptions(){ + this.querySelectorAll('lit-select-option').forEach(a => { + //如果节点的值为 当前控件的默认值 defalut-value则 显示该值对应的option文本 + if (this.isMultiple()) { + a.setAttribute('check', ''); + if (a.getAttribute('value') === this.defaultValue) { + let tag = this.newTag(a.getAttribute('value'), a.textContent); + this.multipleRootElement.insertBefore(tag, this.inputElement); + this.inputElement.placeholder = ''; + this.inputElement.value = ''; + this.inputElement.style.width = '1px'; + a.setAttribute('selected', ''); + } + // this.inputElement.focus(); + } else { + if (a.getAttribute('value') === this.defaultValue) { + this.inputElement.value = a.textContent; + a.setAttribute('selected', ''); + } + } + //每个option设置onSelected事件 接受当前点击的option + a.addEventListener('onSelected', (e:any) => { + //所有option设置为未选中状态 + if (this.isMultiple()) {//多选 + if (a.hasAttribute('selected')) { + let tag = this.shadowRoot!.querySelector(`div[data-value=${e.detail.value}]`); + // @ts-ignore + tag.parentElement!.removeChild(tag); + e.detail.selected = false; + } else { + let tag = this.newTag(e.detail.value, e.detail.text); + this.multipleRootElement.insertBefore(tag, this.inputElement); + this.inputElement.placeholder = ''; + this.inputElement.value = ''; + this.inputElement.style.width = '1px'; + } + if (this.shadowRoot!.querySelectorAll('.tag').length == 0) { + this.inputElement.style.width = 'auto'; + this.inputElement.placeholder = this.defaultPlaceholder; + } + this.inputElement.focus(); + } else {//单选 + [...this.querySelectorAll('lit-select-option')].forEach(a => a.removeAttribute('selected')) + this.blur();//失去焦点,隐藏选择栏目列表 + // @ts-ignore + this.inputElement.value = e.detail.text; + } + //设置当前option为选择状态 + if (a.hasAttribute('selected')) { + a.removeAttribute('selected') + } else { + a.setAttribute('selected', '') + } + //设置input的值为当前选择的文本 + // @ts-ignore + this.value = e.detail.value; + this.dispatchEvent(new CustomEvent('change', {detail: e.detail}));//向外层派发change事件,返回当前选中项 + }) + }) + } + //js调用清理选项 + clear() { + this.inputElement.value = ''; + this.inputElement.placeholder = this.defaultPlaceholder; + } + + //重置为默认值 + reset() { + this.querySelectorAll('lit-select-option').forEach(a => { + //如果节点的值为 当前控件的默认值 defalut-value则 显示该值对应的option文本 + [...this.querySelectorAll('lit-select-option')].forEach(a => a.removeAttribute('selected')) + if (a.getAttribute('value') === this.defaultValue) { + this.inputElement.value = a.textContent; + a.setAttribute('selected', ''); + } + }) + } + + //当 custom element从文档DOM中删除时,被调用。 + disconnectedCallback() { + + } + + //当 custom element被移动到新的文档时,被调用。 + adoptedCallback() { + } + + //当 custom element增加、删除、修改自身属性时,被调用。 + attributeChangedCallback(name:any, oldValue:any, newValue:any) { + if (name === 'value' && this.inputElement) { + if(newValue){ + [...this.querySelectorAll('lit-select-option')].forEach(a => { + if (a.getAttribute('value') === newValue) { + a.setAttribute('selected', ''); + this.inputElement.value = a.textContent; + } else { + a.removeAttribute('selected') + } + }) + }else{ + this.clear(); + } + } + } + set dataSource(value:any){ + value.forEach((a:any)=>{ + let option = document.createElement('lit-select-option'); + option.setAttribute('value',a.key); + option.textContent = a.val; + this.append(option) + }) + this.initOptions(); + } + +} diff --git a/host/ide/src/base-ui/select/LitSelectOption.ts b/host/ide/src/base-ui/select/LitSelectOption.ts new file mode 100644 index 0000000..440097f --- /dev/null +++ b/host/ide/src/base-ui/select/LitSelectOption.ts @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement} from "../BaseElement.js"; +import "../icon/LitIcon.js" + +export class LitSelectOption extends BaseElement { + static get observedAttributes() { + return ['selected','disabled','check'] + } + + initHtml() { + // super(); + // const shadowRoot = this.attachShadow({mode: 'open'}); + return` + +
      + +
      + + + ` + } + + initElements(): void { + + } + + //当 custom element首次被插入文档DOM时,被调用。 + connectedCallback() { + if(!this.hasAttribute('disabled')){ + this.onclick=ev => { + this.dispatchEvent(new CustomEvent('onSelected',{detail:{ + selected:true, + value: this.getAttribute('value'), + text: this.textContent + }})) + } + } + + } + + //当 custom element从文档DOM中删除时,被调用。 + disconnectedCallback() { + + } + + //当 custom element被移动到新的文档时,被调用。 + adoptedCallback() { + } + + //当 custom element增加、删除、修改自身属性时,被调用。 + attributeChangedCallback(name:any, oldValue:any, newValue:any) { + + } +} + +if (!customElements.get('lit-select-option')) { + customElements.define('lit-select-option', LitSelectOption); +} diff --git a/host/ide/src/base-ui/slider/LitSlider.ts b/host/ide/src/base-ui/slider/LitSlider.ts new file mode 100644 index 0000000..fa70421 --- /dev/null +++ b/host/ide/src/base-ui/slider/LitSlider.ts @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../BaseElement.js"; + +@element('lit-slider') +export class LitSlider extends BaseElement { + private litSliderStyle: LitSliderStyle | undefined | null; + private litSlider: HTMLInputElement | undefined | null; + private litSliderCon: HTMLDivElement | undefined | null; + private litResult: HTMLInputElement | undefined | null; + private litSliderButton: HTMLDivElement | undefined | null; + private slotEl: HTMLSlotElement | undefined | null; + private currentValue: number = 0; + private sliderLineHeight: string | undefined; + private sliderButtonHeight: string | undefined; + private sliderButtonWidth: string | undefined; + private defaultTimeText: string | undefined | null; + + static get observedAttributes() { + return ['percent', 'disabled-X', 'custom-slider', 'custom-line', 'custom-button'] + } + + get sliderStyle() { + if (this.hasAttribute('custom-slider')) { + this.defaultTimeText = "64"; + return { + minRange: 4, + maxRange: 512, + defaultValue: this.defaultTimeText, + resultUnit: "MB", + stepSize: 2, + lineColor: "var(--dark-color3,#46B1E3)", + buttonColor: "#999999" + } + } else { + let defaultTime = "00:00:50"; + this.defaultTimeText = defaultTime.split(':')[2]; + return { + minRange: 10, + maxRange: 600, + defaultValue: defaultTime, + resultUnit: "h:m:s", + stepSize: 1, + lineColor: "var(--dark-color4,#61CFBE)", + buttonColor: "#999999" + } + } + + } + + set sliderStyle(value) { + this.litSliderStyle = value; + this.litSliderStyle = this.sliderStyle; + this.litSliderStyle.defaultValue = value.defaultValue + if (this.hasAttribute('custom-slider')) { + this.renderCustomSlider(); + } else { + this.renderDefaultSlider(); + } + } + + get disabledX() { + return this.getAttribute('disabled-X') || ''; + } + + set disabledX(value: string) { + if (value) { + this.setAttribute('disabled-X', ''); + } else { + this.removeAttribute('disabled-X'); + } + } + + get customSlider() { + return this.getAttribute('custom-slider') || ''; + } + + set customSlider(value: string) { + if (value) { + this.setAttribute('custom-slider', ''); + } else { + this.removeAttribute('custom-slider'); + } + } + + get customLine() { + return this.getAttribute('custom-line') || ''; + } + + set customLine(value: string) { + this.setAttribute('custom-line', value); + } + + get customButton() { + return this.getAttribute('custom-button') || ''; + } + + set customButton(value: string) { + this.setAttribute('custom-button', value); + } + + get percent() { + return this.getAttribute('percent') || ''; + } + + set percent(value: string) { + this.setAttribute('percent', value); + } + + get resultUnit() { + return this.getAttribute('resultUnit') || ''; + } + + set resultUnit(value: string) { + this.setAttribute('resultUnit', value); + } + + get sliderSize() { + return this.currentValue; + } + + initElements(): void { + } + + initHtml(): string { + this.litSliderStyle = this.sliderStyle; + this.currentValue = Number(this.sliderStyle.defaultValue); + let parentElement = this.parentNode as Element; + if (parentElement) { + parentElement.setAttribute('percent', this.defaultTimeText + ""); + } + return ` + + +
      + + ${this.litSliderStyle?.resultUnit} +
      + ` + } + + // It is called when the custom element is first inserted into the document DOM. + connectedCallback() { + this.slotEl = this.shadowRoot?.querySelector('#slot'); + this.litSlider = this.shadowRoot?.querySelector('#slider'); + this.litSliderCon = this.shadowRoot?.querySelector('#slider-con'); + this.litResult = this.shadowRoot?.querySelector('#result'); + // Add a slider for input event listeners + this.litSlider?.addEventListener('input', this.inputChangeEvent) + this.litSlider?.addEventListener('change', this.inputChangeEvent) + // Add slot slot to change event listeners + this.slotEl?.addEventListener('slotchange', this.slotChangeEvent); + // Add a slider for line click event listeners + this.litSlider?.addEventListener('click', this.sliderClickEvent); + // Add a slider button to start touching the event listener + this.litSliderButton?.addEventListener('TouchEvent', this.sliderStartTouchEvent); + this.litSliderStyle = this.sliderStyle; + + } + + slotChangeEvent = (event: any) => { + } + + sliderClickEvent = (event: any) => { + } + + inputChangeEvent = (event: any) => { + if (this.litSlider) { + this.currentValue = parseInt(this.litSlider?.value) + let resultNumber = (this.currentValue - this.litSliderStyle!.minRange) * 100 / (this.litSliderStyle!.maxRange - this.litSliderStyle!.minRange); + this.percent = Math.floor(resultNumber) + "%"; + this.litSliderCon?.style.setProperty('percent', this.currentValue + "%") + let parentElement = this.parentNode as Element; + parentElement.setAttribute('percent', this.currentValue + ""); + if (this.sliderStyle.resultUnit === 'MB') { + this.litSlider!.style.backgroundSize = this.percent; + this.litResult!.value = " " + this.currentValue; + } else if (this.sliderStyle.resultUnit === 'h:m:s') { + this.litSlider!.style.backgroundSize = this.percent; + let time = this.formatSeconds(this.litSlider?.value); + this.litResult!.value = " " + time; + } + } + } + + sliderStartTouchEvent = (event: any) => { + + } + + sliderMoveTouchEvent = (event: any) => { + + } + + sliderEndTouchEvent = (event: any) => { + + } + + disconnectedCallback() { + this.litSlider?.removeEventListener('input', this.inputChangeEvent); + this.litSlider?.removeEventListener('change', this.inputChangeEvent) + this.litSlider?.removeEventListener('click', this.sliderClickEvent); + this.litSliderButton?.removeEventListener('TouchEvent', this.sliderStartTouchEvent); + } + + adoptedCallback() { + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + switch (name) { + case "percent": + if (newValue === null || newValue === "0%") { + let parentElement = this.parentNode as Element; + parentElement?.removeAttribute('percent'); + } else { + let parentElement = this.parentNode as Element; + } + break; + default: + break; + } + } + + renderCustomSlider() { + } + + renderDefaultSlider() { + if (!this.litSliderStyle) return; + } + + formatSeconds(value: string) { + let result = parseInt(value) + let hours = Math.floor(result / 3600) < 10 ? '0' + Math.floor(result / 3600) : Math.floor(result / 3600); + let minute = Math.floor((result / 60 % 60)) < 10 ? '0' + Math.floor((result / 60 % 60)) : Math.floor((result / 60 % 60)); + let second = Math.floor((result % 60)) < 10 ? '0' + Math.floor((result % 60)) : Math.floor((result % 60)); + let resultTime = ''; + if (hours === '00') { + resultTime += `00:`; + } else { + resultTime += `${hours}:`; + } + if (minute === '00') { + resultTime += `00:`; + } else { + resultTime += `${minute}:`; + } + resultTime += `${second}`; + return resultTime; + } +} + +export interface LitSliderStyle { + minRange: number + maxRange: number + defaultValue: string + resultUnit: string + stepSize?: number + lineColor?: string + buttonColor?: string +} + +export interface LitSliderLineStyle { + lineWith: number + lineHeight: number + border?: string + borderRadiusValue?: number + lineChangeColor?: string +} + +export interface LitSliderButtonStyle { + buttonWith: number + buttonHeight: number + border?: string + borderRadiusValue?: number + buttonChangeColor?: string +} diff --git a/host/ide/src/base-ui/switch/lit-switch.ts b/host/ide/src/base-ui/switch/lit-switch.ts new file mode 100644 index 0000000..2dc25ea --- /dev/null +++ b/host/ide/src/base-ui/switch/lit-switch.ts @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import {BaseElement, element} from "../BaseElement.js"; + +@element('lit-switch') +export default class LitSwitch extends BaseElement { + private switch: HTMLInputElement | null | undefined + private isfocus: boolean | undefined + + static get observedAttributes() { + return ['disabled', 'checked'] + } + + get disabled() { + return this.getAttribute("disabled") !== null; + } + + set disabled(value) { + if (value === null || value === false) { + this.removeAttribute("disabled"); + } else { + this.setAttribute("disabled", ""); + } + } + + get checked() { + return this.getAttribute("checked") !== null; + } + + set checked(value) { + if (value === null || value === false) { + this.removeAttribute("checked"); + } else { + this.setAttribute("checked", ""); + } + } + + get name() { + return this.getAttribute("name"); + } + + initElements(): void { + + } + + initHtml(): string { + return ` + + + `; + } + + connectedCallback() { + this.switch = this.shadowRoot?.getElementById("switch") as HTMLInputElement; + this.disabled = this.disabled; + this.checked = this.checked; + this.switch!.onchange = (ev) => { + this.checked = this.switch!.checked; + this.dispatchEvent(new CustomEvent("change", {detail: {checked: this.checked}})); + } + this.switch.onkeydown = (ev) => { + switch (ev.keyCode) { + case 13://enter + this.checked = !this.checked; + this.dispatchEvent(new CustomEvent("change", {detail: {checked: this.checked}})); + break; + default: + break; + } + } + this.switch.onfocus = (ev) => { + ev.stopPropagation(); + if (!this.isfocus) { + this.dispatchEvent(new CustomEvent("focus", {detail: {value: this.switch!.value}})) + } + } + this.switch.onblur = ev => { + ev.stopPropagation(); + if (getComputedStyle(this.switch!).zIndex == '2') { + this.isfocus = true; + } else { + this.isfocus = false; + this.dispatchEvent(new CustomEvent("blur", {detail: {value: this.switch!.value}})); + } + } + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + if (name === "disabled" && this.switch) { + if (newValue !== null) { + this.switch.setAttribute("disabled", ""); + } else { + this.switch.removeAttribute("disabled"); + } + } + if (name === "checked" && this.switch) { + if (newValue !== null) { + this.switch.checked = true; + } else { + this.switch.checked = false; + } + } + } +} + diff --git a/host/ide/src/base-ui/table/TableRowObject.ts b/host/ide/src/base-ui/table/TableRowObject.ts new file mode 100644 index 0000000..93556e1 --- /dev/null +++ b/host/ide/src/base-ui/table/TableRowObject.ts @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class TableRowObject { + public top: number = 0; + public height: number = 0 + public rowIndex: number = 0; + public data: any | undefined + public expanded: boolean = true + public rowHidden: boolean = false; + public children: any[] = [] + public depth: number = -1 +} \ No newline at end of file diff --git a/host/ide/src/base-ui/table/lit-table-column.ts b/host/ide/src/base-ui/table/lit-table-column.ts new file mode 100644 index 0000000..b66d300 --- /dev/null +++ b/host/ide/src/base-ui/table/lit-table-column.ts @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import {element} from "../BaseElement.js"; + +@element('lit-table-column') +export class LitTableColumn extends HTMLElement { + template: Element | undefined | null + private st: HTMLSlotElement | undefined | null + + constructor() { + super(); + const shadowRoot = this.attachShadow({mode: 'open'}); + shadowRoot.innerHTML = ` + + + ` + } + + static get observedAttributes() { + return ['name', 'order'] + } + + connectedCallback() { + this.template = null; + this.st = this.shadowRoot?.querySelector('#slot') + this.st?.addEventListener('slotchange', () => { + const elements = this.st!.assignedElements({flatten: false}); + if (elements!.length > 0) { + this.template = elements[0]; + } + }) + } + + disconnectedCallback() { + + } + + adoptedCallback() { + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + } + +} \ No newline at end of file diff --git a/host/ide/src/base-ui/table/lit-table-group.ts b/host/ide/src/base-ui/table/lit-table-group.ts new file mode 100644 index 0000000..78355ef --- /dev/null +++ b/host/ide/src/base-ui/table/lit-table-group.ts @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {element} from "../BaseElement.js"; + +@element('lit-table-group') +export class LitTableGroup extends HTMLElement { + constructor() { + super(); + const shadowRoot = this.attachShadow({mode: 'open'}); + shadowRoot.innerHTML = ` + + + ` + } + + static get observedAttributes() { + return ['title'] + } + + get title() { + return this.getAttribute('title') || ''; + } + + set title(value: string) { + this.setAttribute('title', value); + } + + connectedCallback() { + + } + + disconnectedCallback() { + + } + + adoptedCallback() { + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + } +} \ No newline at end of file diff --git a/host/ide/src/base-ui/table/lit-table.ts b/host/ide/src/base-ui/table/lit-table.ts new file mode 100644 index 0000000..7a6dab1 --- /dev/null +++ b/host/ide/src/base-ui/table/lit-table.ts @@ -0,0 +1,1161 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import {LitTableColumn} from "./lit-table-column.js"; +import {element} from "../BaseElement.js"; +import "../utils/Template.js" +import {TableRowObject} from "./TableRowObject.js"; + +@element('lit-table') +export class LitTable extends HTMLElement { + meauseRowElement: HTMLDivElement | undefined + currentRecycleList: HTMLDivElement[] = [] + currentTreeDivList: HTMLDivElement[] = [] + private ds: Array = [] + private recycleDs: Array = [] + private gridTemplateColumns: any + /*Grid css layout descriptions are obtained according to the clustern[] nested structure*/ + private st: HTMLSlotElement | null | undefined + private tableElement: HTMLDivElement | null | undefined + private theadElement: HTMLDivElement | null | undefined + private columns: Array | null | undefined + private tbodyElement: HTMLDivElement | undefined | null + private treeElement: HTMLDivElement | undefined | null + private tableColumns: NodeListOf | undefined + private colCount: number = 0 + + constructor() { + super(); + const shadowRoot = this.attachShadow({mode: 'open'}); + shadowRoot.innerHTML = ` + + + +
      +
      +
      +
      +
      +
      +
      + ` + + } + + static get observedAttributes() { + return ['scroll-y', 'selectable', 'no-head', 'grid-line', 'defaultOrderColumn'] + } + + get selectable() { + return this.hasAttribute('selectable'); + } + + set selectable(value) { + if (value) { + this.setAttribute('selectable', ''); + } else { + this.removeAttribute('selectable'); + } + } + + get scrollY() { + return this.getAttribute('scroll-y') || 'auto'; + } + + set scrollY(value) { + this.setAttribute('scroll-y', value); + } + + get dataSource() { + return this.ds || []; + } + + set dataSource(value) { + this.ds = value; + if (this.hasAttribute('tree')) { + this.renderTreeTable(); + } else { + this.renderTable(); + } + } + + get recycleDataSource() { + return this.recycleDs || []; + } + + set recycleDataSource(value) { + this.tableElement!.scrollTop = 0 + if (this.hasAttribute('tree')) { + this.recycleDs = this.meauseTreeRowElement(value) + } else { + this.recycleDs = this.meauseAllRowHeight(value) + } + } + + // It is called when the custom element is first inserted into the document DOM. + connectedCallback() { + this.st = this.shadowRoot?.querySelector('#slot'); + this.tableElement = this.shadowRoot?.querySelector('.table'); + this.theadElement = this.shadowRoot?.querySelector('.thead'); + this.treeElement = this.shadowRoot?.querySelector('.tree'); + this.tbodyElement = this.shadowRoot?.querySelector('.body'); + this.tableColumns = this.querySelectorAll('lit-table-column'); + this.colCount = this.tableColumns!.length; + this.st?.addEventListener('slotchange', () => { + this.theadElement!.innerHTML = ''; + setTimeout(() => { + this.columns = this.st!.assignedElements(); + let rowElement = document.createElement('div'); + rowElement.classList.add('th'); + if (this.selectable) { + let box = document.createElement('div'); + box.style.display = 'flex'; + box.style.justifyContent = 'center'; + box.style.alignItems = 'center'; + box.style.gridArea = "_checkbox_"; + box.classList.add('td'); + box.style.backgroundColor = "#ffffff66"; + let checkbox = document.createElement('lit-checkbox'); + checkbox.classList.add('row-checkbox-all'); + checkbox.onchange = (e: any) => { + this.shadowRoot!.querySelectorAll('.row-checkbox').forEach((a: any) => a.checked = e.detail.checked); + if (e.detail.checked) { + this.shadowRoot!.querySelectorAll('.tr').forEach(a => a.setAttribute('checked', '')); + } else { + this.shadowRoot!.querySelectorAll('.tr').forEach(a => a.removeAttribute('checked')); + } + } + + box.appendChild(checkbox); + rowElement.appendChild(box); + } + let area: Array = [], gridTemplateColumns: Array = []; + let resolvingArea = (columns: any, x: any, y: any) => { + columns.forEach((a: any, i: any) => { + if (!area[y]) area[y] = [] + let key = a.getAttribute('key') || a.getAttribute('title') + if (a.tagName === 'LIT-TABLE-GROUP') { + let len = a.querySelectorAll('lit-table-column').length; + let children = [...a.children].filter(a => a.tagName !== 'TEMPLATE'); + if (children.length > 0) { + resolvingArea(children, x, y + 1); + } + for (let j = 0; j < len; j++) { + area[y][x] = {x, y, t: key}; + x++; + } + let h = document.createElement('div'); + h.classList.add('td'); + h.style.justifyContent = a.getAttribute('align') + h.style.borderBottom = '1px solid #f0f0f0' + h.style.gridArea = key; + h.innerText = a.title; + if (a.hasAttribute('fixed')) { + this.fixed(h, a.getAttribute('fixed'), "#42b983") + } + rowElement.append(h); + } else if (a.tagName === 'LIT-TABLE-COLUMN') { + area[y][x] = {x, y, t: key}; + x++; + let h: any = document.createElement('div'); + h.classList.add('td'); + if (a.hasAttribute('order')) { + h.sortType = 0; + h.classList.add('td-order'); + h.style.position = "relative" + let NS = "http://www.w3.org/2000/svg"; + let upSvg: any = document.createElementNS(NS, "svg"); + let upPath: any = document.createElementNS(NS, "path"); + upSvg.setAttribute('fill', '#efefef'); + upSvg.setAttribute('viewBox', '0 0 1024 1024'); + upSvg.setAttribute('stroke', '#000000'); + upSvg.classList.add('up-svg'); + upPath.setAttribute("d", "M858.9 689L530.5 308.2c-9.4-10.9-27.5-10.9-37 0L165.1 689c-12.2 14.2-1.2 35 18.5 35h656.8c19.7 0 30.7-20.8 18.5-35z"); + upSvg.appendChild(upPath); + let downSvg: any = document.createElementNS(NS, "svg"); + let downPath: any = document.createElementNS(NS, "path"); + downSvg.setAttribute('fill', '#efefef'); + downSvg.setAttribute('viewBox', '0 0 1024 1024'); + downSvg.setAttribute('stroke', '#efefef'); + downSvg.classList.add('down-svg'); + downPath.setAttribute("d", "M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z"); + downSvg.appendChild(downPath) + if (i == 0) { + h.sortType = 0; // 默认以第一列 降序排序 作为默认排序 + upSvg.setAttribute('fill', '#fff'); + downSvg.setAttribute('fill', '#fff'); + } + upSvg.style.display = 'none'; + downSvg.style.display = 'none'; + h.appendChild(upSvg); + h.appendChild(downSvg); + h.onclick = () => { + this?.shadowRoot?.querySelectorAll('.td-order svg').forEach((it: any) => { + it.setAttribute('fill', '#fff'); + it.setAttribute('fill', '#fff'); + it.sortType = 0; + }) + if (h.sortType == undefined || h.sortType == null) { + h.sortType = 0; + } else if (h.sortType === 2) { + h.sortType = 0; + } else { + h.sortType += 1; + } + switch (h.sortType) { + case 1: + upSvg.setAttribute('fill', '#333'); + downSvg.setAttribute('fill', '#fff'); + upSvg.style.display = 'block'; + downSvg.style.display = 'none'; + break; + case 2: + upSvg.setAttribute('fill', '#fff'); + downSvg.setAttribute('fill', '#333'); + upSvg.style.display = 'none'; + downSvg.style.display = 'block'; + break; + default: + upSvg.setAttribute('fill', "#fff"); + downSvg.setAttribute('fill', "#fff"); + upSvg.style.display = 'none'; + downSvg.style.display = 'none'; + break; + } + this.dispatchEvent(new CustomEvent("column-click", { + detail: { + sort: h.sortType, key: key + }, composed: true + })) + } + } + h.style.justifyContent = a.getAttribute('align') + gridTemplateColumns.push(a.getAttribute('width') || '1fr'); + h.style.gridArea = key; + let titleLabel = document.createElement("label"); + titleLabel.textContent = a.title; + h.appendChild(titleLabel); + if (a.hasAttribute('fixed')) { + this.fixed(h, a.getAttribute('fixed'), "#42b983") + } + rowElement.append(h); + } + }) + } + resolvingArea(this.columns, 0, 0); + area.forEach((rows, j, array) => { + for (let i = 0; i < this.colCount; i++) { + if (!rows[i]) rows[i] = array[j - 1][i]; + } + }) + this.gridTemplateColumns = gridTemplateColumns.join(' '); + if (this.selectable) { + let s = area.map(a => '"_checkbox_ ' + (a.map((aa: any) => aa.t).join(' ')) + '"').join(' '); + rowElement.style.gridTemplateColumns = "60px " + gridTemplateColumns.join(' ');//`repeat(${this.colCount},1fr)` + rowElement.style.gridTemplateRows = `repeat(${area.length},1fr)` + rowElement.style.gridTemplateAreas = s + } else { + let s = area.map(a => '"' + (a.map((aa: any) => aa.t).join(' ')) + '"').join(' '); + rowElement.style.gridTemplateColumns = gridTemplateColumns.join(' ');//`repeat(${this.colCount},1fr)` + rowElement.style.gridTemplateRows = `repeat(${area.length},1fr)` + rowElement.style.gridTemplateAreas = s + } + this.theadElement!.append(rowElement); + this.treeElement!.style.top = this.theadElement?.clientHeight + "px" + }); + + }); + + this.shadowRoot!.addEventListener("load", function (event) { + }); + } + + // Is called when the custom element is removed from the document DOM. + disconnectedCallback() { + + } + + // It is called when the custom element is moved to a new document. + adoptedCallback() { + } + + // It is called when a custom element adds, deletes, or modifies its own properties. + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + + } + + fixed(td: HTMLElement, placement: string, bgColor: string) { + td.style.position = 'sticky'; + if (placement === "left") { + td.style.left = '0px'; + td.style.boxShadow = '3px 0px 5px #33333333' + } else if (placement === "right") { + td.style.right = '0px'; + td.style.boxShadow = '-3px 0px 5px #33333333' + } + } + + renderTable() { + if (!this.columns) return; + if (!this.ds) return; // If no data source is set, it is returned directly + this.tbodyElement!.innerHTML = '';// Clear the table contents + this.ds.forEach((rowData: any) => { + let rowElement = document.createElement('div'); + rowElement.classList.add('tr'); + // @ts-ignore + rowElement.data = rowData; + let gridTemplateColumns: Array = [] + // If the table is configured with selectable (select row mode) add a checkbox at the head of the line alone + if (this.selectable) { + let box = document.createElement('div'); + box.style.display = 'flex'; + box.style.justifyContent = 'center'; + box.style.alignItems = 'center'; + box.classList.add('td'); + let checkbox = document.createElement('lit-checkbox'); + checkbox.classList.add('row-checkbox'); + checkbox.onchange = (e: any) => {// Checkbox checking affects whether the div corresponding to the row has a checked attribute for marking + if (e.detail.checked) { + rowElement.setAttribute('checked', ""); + } else { + rowElement.removeAttribute('checked'); + } + } + box.appendChild(checkbox); + rowElement.appendChild(box); + } + this.tableColumns!.forEach(cl => { + let dataIndex = cl.getAttribute('data-index') || '1'; + gridTemplateColumns.push(cl.getAttribute('width') || '1fr') + if (cl.template) {// If you customize the rendering, you get the nodes from the template + // @ts-ignore + let cloneNode = cl.template.render(rowData).content.cloneNode(true); + let d = document.createElement('div'); + d.classList.add('td'); + d.style.wordBreak = 'break-all' + d.style.whiteSpace = 'pre-wrap' + d.style.justifyContent = cl.getAttribute('align') || '' + if (cl.hasAttribute('fixed')) { + this.fixed(d, cl.getAttribute('fixed') || '', "#ffffff") + } + d.append(cloneNode); + rowElement.append(d); + } else { + let td = document.createElement('div'); + td.classList.add('td'); + td.style.wordBreak = 'break-all' + td.style.whiteSpace = 'pre-wrap' + td.title = rowData[dataIndex] + td.style.justifyContent = cl.getAttribute('align') || '' + if (cl.hasAttribute('fixed')) { + this.fixed(td, cl.getAttribute('fixed') || '', "#ffffff") + } + td.innerHTML = rowData[dataIndex]; + rowElement.append(td); + } + + }) + if (this.selectable) { // If the table with selection is preceded by a 60px column + rowElement.style.gridTemplateColumns = '60px ' + gridTemplateColumns.join(' '); + } else { + rowElement.style.gridTemplateColumns = gridTemplateColumns.join(' ');// + } + rowElement.onclick = e => { + this.dispatchEvent(new CustomEvent('row-click', {detail: rowData, composed: true})); + } + this.tbodyElement!.append(rowElement); + }) + } + + + renderTreeTable() { + if (!this.columns) return; + if (!this.ds) return; + this.tbodyElement!.innerHTML = ''; + this.treeElement!.innerHTML = ''; + let ids = JSON.parse(this.getAttribute('tree') || `["id","pid"]`); + let toTreeData = (data: any, id: any, pid: any) => { + let cloneData = JSON.parse(JSON.stringify(data)); + return cloneData.filter((father: any) => { + let branchArr = cloneData.filter((child: any) => father[id] == child[pid]); + branchArr.length > 0 ? father['children'] = branchArr : ''; + return !father[pid]; + }); + } + let treeData = toTreeData(this.ds, ids[0], ids[1]);// + let offset = 30; + let offsetVal = offset; + const drawRow = (arr: any, parentNode: any) => { + arr.forEach((rowData: any) => { + let rowElement = document.createElement('div'); + rowElement.classList.add('tr'); + // @ts-ignore + rowElement.data = rowData; + let gridTemplateColumns: Array = []; + if (this.selectable) { + let box = document.createElement('div'); + box.style.display = 'flex'; + box.style.justifyContent = 'center'; + box.style.alignItems = 'center'; + box.classList.add('td'); + let checkbox = document.createElement('lit-checkbox'); + checkbox.classList.add('row-checkbox'); + checkbox.onchange = (e: any) => { + if (e.detail.checked) { + rowElement.setAttribute('checked', ""); + } else { + rowElement.removeAttribute('checked'); + } + const changeChildNode = (rowElement: any, checked: any) => { + let id = rowElement.getAttribute('id'); + let pid = rowElement.getAttribute('pid'); + this.shadowRoot!.querySelectorAll(`div[pid=${id}]`).forEach(a => { + // @ts-ignore + a.querySelector('.row-checkbox')!.checked = checked; + if (checked) { + a.setAttribute('checked', ''); + } else { + a.removeAttribute('checked'); + } + changeChildNode(a, checked); + }); + }; + changeChildNode(rowElement, e.detail.checked); + } + box.appendChild(checkbox); + rowElement.appendChild(box); + } + this.tableColumns!.forEach((cl, index) => { + let dataIndex = cl.getAttribute('data-index'); + let td; + if (index !== 0) { + gridTemplateColumns.push(cl.getAttribute('width') || '1fr') + if (cl.template) { + // @ts-ignore + let cloneNode = cl.template.render(rowData).content.cloneNode(true); + // cloneNode.classList.add('td'); + td = document.createElement('div'); + td.classList.add('td'); + td.style.wordBreak = 'break-all' + // td.style.whiteSpace = 'pre-wrap' + td.style.justifyContent = cl.getAttribute('align') || '' + if (cl.hasAttribute('fixed')) { + this.fixed(td, cl.getAttribute('fixed') || '', "#ffffff") + } + td.append(cloneNode); + } else { + td = document.createElement('div'); + td.classList.add('td'); + td.style.wordBreak = 'break-all' + // td.style.whiteSpace = 'pre-wrap' + td.style.justifyContent = cl.getAttribute('align') || '' + if (cl.hasAttribute('fixed')) { + this.fixed(td, cl.getAttribute('fixed') || '', "#ffffff") + } + // td.style.position='sticky'; + // @ts-ignore + td.innerHTML = rowData[dataIndex]; + } + rowElement.append(td) + } else { + this.treeElement!.style.width = cl.getAttribute('width') || "260px" + let treeElement = document.createElement('div'); + treeElement.classList.add("tree-first-body") + if (cl.template) { + // @ts-ignore + let cloneNode = cl.template.render(rowData).content.cloneNode(true); + // cloneNode.classList.add('td'); + td = document.createElement('div'); + td.classList.add('td'); + // td.style.wordBreak = 'break-all' + // td.style.whiteSpace = 'pre-wrap' + td.style.justifyContent = cl.getAttribute('align') || '' + if (cl.hasAttribute('fixed')) { + this.fixed(td, cl.getAttribute('fixed') || '', "#ffffff") + } + td.append(cloneNode); + } else { + td = document.createElement('div'); + td.classList.add('td'); + // td.style.wordBreak = 'break-all' + // td.style.whiteSpace = 'pre-wrap' + td.style.justifyContent = cl.getAttribute('align') || '' + if (cl.hasAttribute('fixed')) { + this.fixed(td, cl.getAttribute('fixed') || '', "#ffffff") + } + // td.style.position='sticky'; + // @ts-ignore + td.innerHTML = rowData[dataIndex]; + } + if (rowData.children && rowData.children.length > 0) { + let btn = document.createElement('lit-icon'); + btn.classList.add('tree-icon'); + // @ts-ignore + btn.name = 'minus-square'; + treeElement.append(btn); + treeElement.append(td) + treeElement.style.paddingLeft = (offsetVal - 30) + 'px'; + } else { + treeElement.append(td) + treeElement.style.paddingLeft = offsetVal + 'px'; + } + this.treeElement!.append(treeElement); + } + + }) + if (this.selectable) { + rowElement.style.gridTemplateColumns = '60px ' + gridTemplateColumns.join(' ');//`repeat(${this.colCount},1fr)` + } else { + rowElement.style.gridTemplateColumns = gridTemplateColumns.join(' ');//`repeat(${this.colCount},1fr)` + } + rowElement.onclick = e => { + this.dispatchEvent(new CustomEvent('row-click', {detail: rowData, composed: true})); + } + rowElement.style.cursor = 'pointer' + parentNode.append(rowElement); + rowElement.setAttribute('id', rowData[ids[0]]); + rowElement.setAttribute('pid', rowData[ids[1]]); + rowElement.setAttribute('expend', ''); + if (rowData.children && rowData.children.length > 0) { + offsetVal = offsetVal + offset; + drawRow(rowData.children, parentNode); + offsetVal = offsetVal - offset; + } + }); + }; + drawRow(treeData, this.tbodyElement); + } + + getCheckRows() { + // @ts-ignore + return [...this.shadowRoot!.querySelectorAll('div[class=tr][checked]')].map(a => a.data).map(a => { + delete a['children']; + return a; + }); + } + + deleteRowsCondition(fn: any) { + this.shadowRoot!.querySelectorAll("div[class=tr]").forEach(tr => { + // @ts-ignore + if (fn(tr.data)) { + tr.remove(); + } + }) + } + + meauseElementHeight(rowData: any) { + return 27; + } + + meauseTreeElementHeight(rowData: any, depth: number) { + return 27; + } + + meauseAllRowHeight(list: any[]): TableRowObject[] { + this.tbodyElement!.innerHTML = ''; + this.meauseRowElement = undefined + this.tbodyElement && (this.tbodyElement.style.width = this.tableElement?.clientWidth + "px") + this.currentRecycleList = [] + let headHeight = 0 + let totalHeight = headHeight + let visibleObjects: TableRowObject[] = []; + list.forEach((rowData, index) => { + let height = this.meauseElementHeight(rowData); + let tableRowObject = new TableRowObject(); + tableRowObject.height = height + tableRowObject.top = totalHeight + tableRowObject.data = rowData + tableRowObject.rowIndex = index + if (Math.max(totalHeight, this.tableElement!.scrollTop + headHeight) <= Math.min(totalHeight + height, this.tableElement!.scrollTop + this.tableElement!.clientHeight + headHeight)) { + let newTableElement = this.createNewTableElement(rowData); + newTableElement.style.transform = `translateY(${totalHeight}px)` + this.tbodyElement?.append(newTableElement) + this.currentRecycleList.push(newTableElement) + } + totalHeight += height + visibleObjects.push(tableRowObject) + }) + this.tbodyElement && (this.tbodyElement.style.height = totalHeight + "px") + this.tableElement && (this.tableElement.onscroll = (event) => { + let top = this.tableElement!.scrollTop; + let skip = 0; + for (let i = 0; i < visibleObjects.length; i++) { + if (visibleObjects[i].top <= top && visibleObjects[i].top + visibleObjects[i].height >= top) { + skip = i + break; + } + } + let reduce = this.currentRecycleList.map((item) => item.clientHeight).reduce((a, b) => a + b,0); + if(reduce == 0){ + return + } + while (reduce <= this.tableElement!.clientHeight) { + let newTableElement = this.createNewTableElement(visibleObjects[skip].data); + this.tbodyElement?.append(newTableElement) + this.currentRecycleList.push(newTableElement) + reduce += newTableElement.clientHeight + } + for (let i = 0; i < this.currentRecycleList.length; i++) { + this.freshCurrentLine(this.currentRecycleList[i], visibleObjects[i + skip]) + } + }) + return visibleObjects + } + + meauseTreeRowElement(list: any[]): TableRowObject[] { + this.meauseRowElement = undefined + this.tbodyElement!.innerHTML = ''; + this.treeElement!.innerHTML = ''; + let headHeight = this.theadElement?.clientHeight || 0 + let totalHeight = 0 + let visibleObjects: TableRowObject[] = [] + this.currentRecycleList = [] + this.currentTreeDivList = [] + let resetAllHeight = (list: any[], depth: number, parentNode?: TableRowObject) => { + list.forEach((item) => { + let tableRowObject = new TableRowObject(); + tableRowObject.depth = depth + tableRowObject.data = item + tableRowObject.top = totalHeight + tableRowObject.height = this.meauseTreeElementHeight(tableRowObject, depth) + if (parentNode != undefined) { + parentNode.children.push(tableRowObject) + } + if (Math.max(totalHeight, this.tableElement!.scrollTop) <= Math.min(totalHeight + tableRowObject.height, this.tableElement!.scrollTop + this.tableElement!.clientHeight - headHeight)) { + let newTableElement = this.createNewTreeTableElement(tableRowObject); + newTableElement.style.transform = `translateY(${totalHeight}px)` + this.tbodyElement?.append(newTableElement) + if (this.treeElement?.lastChild) { + (this.treeElement?.lastChild as HTMLElement).style.height = tableRowObject.height + "px"; + } + this.currentRecycleList.push(newTableElement) + } + totalHeight += tableRowObject.height + visibleObjects.push(tableRowObject) + if (item.children != undefined && item.children.length > 0) { + resetAllHeight(item.children, depth + 1, tableRowObject) + } + }) + } + resetAllHeight(list, 0) + this.tbodyElement && (this.tbodyElement.style.height = totalHeight + "px") + this.treeElement!.style.height = (this.tableElement!.clientHeight - this.theadElement!.clientHeight) + "px" + this.tableElement && (this.tableElement.onscroll = (event) => { + let visibleObjects = this.recycleDs.filter((item) => { + return !item.rowHidden + }) + let top = this.tableElement!.scrollTop; + this.treeElement!.style.transform = `translateY(${top}px)` + let skip = 0; + for (let i = 0; i < visibleObjects.length; i++) { + if (visibleObjects[i].top <= top && visibleObjects[i].top + visibleObjects[i].height >= top) { + skip = i + break; + } + } + let reduce = this.currentRecycleList.map((item) => item.clientHeight).reduce((a, b) => a + b,0); + if(reduce == 0){ + return + } + while (reduce <= this.tableElement!.clientHeight) { + let newTableElement = this.createNewTreeTableElement(visibleObjects[skip]); + this.tbodyElement?.append(newTableElement) + if (this.treeElement?.lastChild) { + (this.treeElement?.lastChild as HTMLElement).style.height = visibleObjects[skip].height + "px"; + } + this.currentRecycleList.push(newTableElement) + reduce += newTableElement.clientHeight + } + for (let i = 0; i < this.currentRecycleList.length; i++) { + this.freshCurrentLine(this.currentRecycleList[i], visibleObjects[i + skip], (this.treeElement?.children[i] as HTMLElement)); + } + }) + return visibleObjects + } + + + createNewTreeTableElement(rowData: TableRowObject): any { + let newTableElement = document.createElement('div'); + newTableElement.classList.add('tr'); + let gridTemplateColumns: Array = []; + let treeTop = 0; + if (this.treeElement!.children?.length > 0) { + let transX = Number((this.treeElement?.lastChild as HTMLElement).style.transform.replace(/[^0-9]/ig, "")); + treeTop += (transX + rowData.height) + } + this?.columns?.forEach((column: any, index) => { + let dataIndex = column.getAttribute('data-index') || '1'; + let td: any + if (index === 0) { + if (column.template) { + td = column.template.render(rowData.data).content.cloneNode(true); + td.template = column.template + } else { + td = document.createElement('div') + td.innerHTML = rowData.data[dataIndex]; + td.dataIndex = dataIndex + } + if (rowData.data.children && rowData.data.children.length > 0) { + let btn = this.createExpandBtn(rowData) + td.insertBefore(btn, td.firstChild); + td.style.paddingLeft = rowData.depth * 15 + 'px'; + } else { + td.style.paddingLeft = rowData.depth * 15 + 20 + 'px'; + } + td.classList.add('tree-first-body'); + td.style.position = 'absolute'; + td.style.top = '0px' + td.style.left = '0px' + td.onmouseover = () => { + let indexOf = this.currentTreeDivList.indexOf(td); + if (indexOf >= 0 && indexOf < this.currentRecycleList.length && td.innerHTML != "") { + this.setSelectedRow(true,[(this.treeElement?.children[indexOf] as HTMLElement),newTableElement]); + } + } + td.onmouseout = () => { + let indexOf = this.currentTreeDivList.indexOf(td); + if (indexOf >= 0 && indexOf < this.currentRecycleList.length) { + this.setSelectedRow(false,[(this.treeElement?.children[indexOf] as HTMLElement),newTableElement]); + } + } + td.onclick = () => { + this.dispatchEvent(new CustomEvent('row-click', {detail: {...rowData.data,callBack:(isSelected:boolean)=>{ + let indexOf = this.currentTreeDivList.indexOf(td); + this.setSelectedRow(isSelected,[(this.currentRecycleList[indexOf] as HTMLElement),td]) + }}, composed: true,})); + } + this.treeElement!.style.width = column.getAttribute('width') + this.treeElement?.append(td) + this.currentTreeDivList.push(td) + } else { + gridTemplateColumns.push(column.getAttribute('width') || '1fr') + td = document.createElement('div') + td.classList.add('td'); + // td.style.wordBreak = 'break-all' + td.style.overflow = 'hidden' + td.style.textOverflow = 'ellipsis' + td.style.whiteSpace = "nowrap" + td.title = rowData.data[dataIndex] + // td.innerHTML = rowData.data[dataIndex]; + td.dataIndex = dataIndex + td.style.justifyContent = column.getAttribute('align')||'flex-start' + if (column.template) { + td.appendChild(column.template.render(rowData.data).content.cloneNode(true)); + td.template = column.template + } else { + td.innerHTML = rowData.data[dataIndex]; + } + newTableElement.append(td) + } + }); + (this.treeElement?.lastChild as HTMLElement).style.transform = `translateY(${treeTop}px)`; + newTableElement.style.gridTemplateColumns = gridTemplateColumns.join(' '); + newTableElement.style.position = 'absolute'; + newTableElement.style.top = '0px' + newTableElement.style.left = '0px' + newTableElement.style.cursor = 'pointer' + newTableElement.onmouseover = () => { + let indexOf = this.currentRecycleList.indexOf(newTableElement); + if (indexOf >= 0 && indexOf < this.treeElement!.children.length) { + this.setSelectedRow(true,[(this.treeElement?.children[indexOf] as HTMLElement),newTableElement]); + } + } + newTableElement.onmouseout = () => { + let indexOf = this.currentRecycleList.indexOf(newTableElement); + if (indexOf >= 0 && indexOf < this.treeElement!.children.length) { + this.setSelectedRow(false,[(this.treeElement?.children[indexOf] as HTMLElement),newTableElement]); + } + } + newTableElement.onclick = e => { + this.dispatchEvent(new CustomEvent('row-click', {detail: rowData.data, composed: true})); + } + return newTableElement + } + + createExpandBtn(rowData: any) { + let btn: any = document.createElement('lit-icon'); + btn.classList.add('tree-icon'); + // @ts-ignore + if (rowData.expanded) { + btn.name = 'minus-square'; + } else { + btn.name = 'plus-square'; + } + btn.onclick = (e: Event) => { + const resetNodeHidden = (hidden: boolean, rowData: any) => { + if (rowData.children.length > 0) { + if (hidden) { + rowData.children.forEach((child: any) => { + child.rowHidden = true + resetNodeHidden(hidden, child) + }) + } else { + rowData.children.forEach((child: any) => { + child.rowHidden = !rowData.expanded + if (rowData.expanded) { + resetNodeHidden(hidden, child) + } + }) + } + } + } + const foldNode = () => { + rowData.expanded = false + resetNodeHidden(true, rowData) + }; + const expendNode = () => { + rowData.expanded = true + resetNodeHidden(false, rowData) + } + if (rowData.expanded) { + foldNode() + } else { + expendNode() + } + this.reMeauseHeight() + e.stopPropagation(); + }; + return btn + } + + reMeauseHeight() { + if (this.currentRecycleList.length == 0) { + return + } + let totalHeight = 0 + this.recycleDs.forEach((it) => { + if (!it.rowHidden) { + it.top = totalHeight + totalHeight += it.height + } + }) + this.tbodyElement && (this.tbodyElement.style.height = totalHeight + "px") + this.treeElement!.style.height = (this.tableElement!.clientHeight - this.theadElement!.clientHeight) + "px" + let visibleObjects = this.recycleDs.filter((item) => { + return !item.rowHidden + }) + let top = this.tableElement!.scrollTop; + let skip = 0; + for (let i = 0; i < visibleObjects.length; i++) { + if (visibleObjects[i].top <= top && visibleObjects[i].top + visibleObjects[i].height >= top) { + skip = i + break; + } + } + let reduce = this.currentRecycleList.map((item) => item.clientHeight).reduce((a, b) => a + b,0); + if(reduce == 0){ + return + } + while (reduce <= this.tableElement!.clientHeight) { + let newTableElement + if (this.hasAttribute('tree')) { + newTableElement = this.createNewTreeTableElement(visibleObjects[skip]); + } else { + newTableElement = this.createNewTableElement(visibleObjects[skip].data) + } + this.tbodyElement?.append(newTableElement) + if (this.hasAttribute('tree')) { + if (this.treeElement?.lastChild) { + (this.treeElement?.lastChild as HTMLElement).style.height = visibleObjects[skip].height + "px"; + } + } + this.currentRecycleList.push(newTableElement) + reduce += newTableElement.clientHeight + } + for (let i = 0; i < this.currentRecycleList.length; i++) { + if (this.hasAttribute('tree')) { + this.freshCurrentLine(this.currentRecycleList[i], visibleObjects[i + skip], (this.treeElement?.children[i] as HTMLElement)) + } else { + this.freshCurrentLine(this.currentRecycleList[i], visibleObjects[i + skip]) + } + } + } + + createNewTableElement(rowData: any): any { + let newTableElement = document.createElement('div'); + newTableElement.classList.add('tr'); + let gridTemplateColumns: Array = []; + this?.columns?.forEach((column: any) => { + let dataIndex = column.getAttribute('data-index') || '1'; + gridTemplateColumns.push(column.getAttribute('width') || '1fr') + let td: any + td = document.createElement('div') + td.classList.add('td'); + td.style.overflow = 'hidden' + td.style.textOverflow = 'ellipsis' + td.style.whiteSpace = "nowrap" + td.dataIndex = dataIndex + td.style.justifyContent = column.getAttribute('align')||'flex-start' + td.title = rowData[dataIndex] + if (column.template) { + td.appendChild(column.template.render(rowData).content.cloneNode(true)); + td.template = column.template + } else { + td.innerHTML = rowData[dataIndex]; + } + newTableElement.append(td) + }) + newTableElement.onclick = ()=>{ + let detail = {...rowData,selectedCallback:()=>{ + if (detail.isSelected != undefined) { + if(detail.isSelected){ + newTableElement.setAttribute("selected","") + }else { + newTableElement.removeAttribute("selected") + } + rowData.isSelected = !rowData.isSelected + } + }} + this.dispatchEvent(new CustomEvent('row-click', {detail: detail, composed: true})); + } + if (rowData.isSelected != undefined) { + if(rowData.isSelected){ + newTableElement.setAttribute("selected","") + }else { + newTableElement.removeAttribute("selected") + } + } + newTableElement.style.cursor = "pointer" + newTableElement.style.gridTemplateColumns = gridTemplateColumns.join(' '); + newTableElement.style.position = 'absolute'; + newTableElement.style.top = '0px' + newTableElement.style.left = '0px' + return newTableElement + } + + freshCurrentLine(element: HTMLElement, rowObject: TableRowObject, firstElement?: HTMLElement) { + if (!rowObject) { + if (firstElement) { + firstElement.style.display = 'none' + } + element.style.display = 'none' + return + } + let childIndex = -1 + element.childNodes.forEach((child) => { + if(child.nodeType!=1)return + childIndex++; + let idx = firstElement != undefined ? childIndex + 1 : childIndex; + if (firstElement != undefined && childIndex == 0) { + if ((this.columns![0] as any).template) { + firstElement.innerHTML = (this.columns![0] as any).template.render(rowObject.data).content.cloneNode(true).innerHTML + } else { + let dataIndex = this.columns![0].getAttribute('data-index') || '1'; + firstElement.innerHTML = rowObject.data[dataIndex] + firstElement.title = rowObject.data[dataIndex] + } + if (rowObject.children && rowObject.children.length > 0) { + let btn = this.createExpandBtn(rowObject) + firstElement.insertBefore(btn, firstElement.firstChild); + firstElement.style.paddingLeft = 15 * rowObject.depth + "px" + } else { + firstElement.style.paddingLeft = 15 + 20 * rowObject.depth + "px" + } + firstElement.onclick = () => { + this.dispatchEvent(new CustomEvent('row-click', {detail: rowObject.data, composed: true})); + } + firstElement.style.transform = `translateY(${rowObject.top - this.tableElement!.scrollTop}px)` + } + if ((this.columns![idx] as any).template) { + (child as HTMLElement).innerHTML = ""; + (child as HTMLElement).appendChild((this.columns![idx] as any).template.render(rowObject.data).content.cloneNode(true)) + } else { + let dataIndex = this.columns![idx].getAttribute('data-index') || '1'; + (child as HTMLElement).innerHTML = rowObject.data[dataIndex]; + (child as HTMLElement).title = rowObject.data[dataIndex]; + } + }) + if(element.style.display == 'none'){ + element.style.display = 'grid' + } + element.style.transform = `translateY(${rowObject.top}px)` + if (firstElement && firstElement.style.display == 'none') { + firstElement.style.display = 'flex' + } + element.onclick = e => { + let datail = {...rowObject.data,selectedCallback:()=>{ + if (datail.isSelected != undefined) { + if(datail.isSelected){ + element.setAttribute("selected","") + }else { + element.removeAttribute("selected") + } + rowObject.data.isSelected = !rowObject.data.isSelected + } + }} + this.dispatchEvent(new CustomEvent('row-click', {detail: datail, composed: true})); + } + if (rowObject.data.isSelected != undefined) { + if(rowObject.data.isSelected){ + element.setAttribute("selected","") + }else { + element.removeAttribute("selected") + } + }else { + element.removeAttribute("selected") + } + } + + setSelectedRow(isSelected:boolean,rows:any[]){ + if(isSelected){ + rows.forEach((row)=>{ + row.style.backgroundColor = "var(--dark-background6,#DEEDFF)" + }) + }else { + rows.forEach((row)=>{ + row.style.backgroundColor = "var(--dark-background,#FFFFFF)" + }) + } + } +} diff --git a/host/ide/src/base-ui/tabs/lit-tabpane.ts b/host/ide/src/base-ui/tabs/lit-tabpane.ts new file mode 100644 index 0000000..1f8c681 --- /dev/null +++ b/host/ide/src/base-ui/tabs/lit-tabpane.ts @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import {BaseElement, element} from "../BaseElement.js"; +import {LitTabs} from "./lit-tabs.js"; + +@element('lit-tabpane') +export class LitTabpane extends BaseElement { + static get observedAttributes() { + return ['tab', 'key', 'disabled', 'icon', 'closeable', 'hidden']; + } + + get tab() { + return this.getAttribute('tab'); + } + + set tab(value) { + this.setAttribute("tab", value || ""); + } + + get icon() { + return this.getAttribute("icon"); + } + + get disabled() { + return this.getAttribute('disabled') !== null; + } + + set disabled(value) { + if (value === null || value === false) { + this.removeAttribute("disabled"); + } else { + this.setAttribute("disabled", value + ''); + } + } + + get hidden() { + return this.getAttribute('hidden') !== null; + } + + set hidden(value) { + this.setAttribute("hidden", `${value}`); + } + + get closeable() { + return this.getAttribute('closeable') !== null; + } + + set closeable(value) { + if (value === null || value === false) { + this.removeAttribute("closeable"); + } else { + this.setAttribute("closeable", value + ''); + } + } + + get key() { + return this.getAttribute("key") || ''; + } + + set key(value) { + this.setAttribute("key", value); + } + + initElements(): void { + } + + initHtml(): string { + return ` + + +`; + } + + connectedCallback() { + } + + disconnectedCallback() { + } + + adoptedCallback() { + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + if (oldValue !== newValue && newValue !== undefined) { + if (name === 'tab' && this.parentNode && this.parentNode instanceof LitTabs) { + this.parentNode.updateLabel && this.parentNode.updateLabel(this.key, newValue); + } + if (name === 'disabled' && this.parentNode && this.parentNode instanceof LitTabs) { + this.parentNode.updateDisabled && this.parentNode.updateDisabled(this.key, newValue); + } + if (name === 'closeable' && this.parentNode && this.parentNode instanceof LitTabs) { + this.parentNode.updateCloseable && this.parentNode.updateCloseable(this.key, newValue); + } + if (name === 'hidden' && this.parentNode && this.parentNode instanceof LitTabs) { + this.parentNode.updateHidden && this.parentNode.updateHidden(this.key, newValue); + } + } + } +} diff --git a/host/ide/src/base-ui/tabs/lit-tabs.ts b/host/ide/src/base-ui/tabs/lit-tabs.ts new file mode 100644 index 0000000..25d23ce --- /dev/null +++ b/host/ide/src/base-ui/tabs/lit-tabs.ts @@ -0,0 +1,646 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {element} from "../BaseElement.js"; +import {LitTabpane} from "./lit-tabpane.js"; + +@element('lit-tabs') +export class LitTabs extends HTMLElement { + private tabPos: any + private nav: HTMLDivElement | undefined | null + private line: HTMLDivElement | undefined | null + private slots: HTMLSlotElement | undefined | null + + constructor() { + super(); + const shadowRoot = this.attachShadow({mode: 'open'}); + shadowRoot.innerHTML = ` + + +
      + +
      + NEED CONTENT +
      +
      + ` + } + + static get observedAttributes() { + return ['activekey', 'mode', 'position'] + } + + get position() { + return this.getAttribute('position') || 'top'; + } + + set position(value) { + this.setAttribute('position', value); + } + + get mode() { + return this.getAttribute('mode') || 'flat'; + } + + set mode(value) { + this.setAttribute('mode', value); + } + + get activekey() { + return this.getAttribute("activekey") || ''; + } + + set activekey(value: string) { + this.setAttribute('activekey', value); + } + + set onTabClick(fn: any) { + this.addEventListener('onTabClick', fn); + } + + updateLabel(key: string, value: string) { + if (this.nav) { + let item = this.nav.querySelector(`.nav-item[data-key='${key}']`); + if (item) { + item.querySelector("span")!.innerHTML = value; + this.initTabPos() + } + } + } + + updateDisabled(key: string, value: string) { + if (this.nav) { + let item = this.nav.querySelector(`.nav-item[data-key='${key}']`); + if (item) { + if (value) { + item.setAttribute('data-disabled', '') + } else { + item.removeAttribute('data-disabled'); + } + this.initTabPos() + } + } + } + + updateCloseable(key: string, value: string) { + if (this.nav) { + let item = this.nav.querySelector(`.nav-item[data-key='${key}']`); + if (item) { + if (value) { + item.setAttribute('data-closeable', '') + } else { + item.removeAttribute('data-closeable'); + } + this.initTabPos() + } + } + } + + updateHidden(key: string, value: string) { + if (this.nav) { + let item = this.nav.querySelector(`.nav-item[data-key='${key}']`); + if (item) { + if (value === "true") { + item.setAttribute('data-hidden', '') + } else { + item.removeAttribute('data-hidden'); + } + this.initTabPos() + } + } + } + + initTabPos() { + const items = this.nav!.querySelectorAll(".nav-item"); + Array.from(items).forEach((a, index) => { + // @ts-ignore + this.tabPos[a.dataset.key] = { + index: index, + width: a.offsetWidth, + height: a.offsetHeight, + left: a.offsetLeft, + top: a.offsetTop, + label: a.textContent + } + }) + if (this.activekey) { + if (this.position.startsWith('left')) { + this.line?.setAttribute('style', `height:${this.tabPos[this.activekey].height}px;transform:translate(100%,${this.tabPos[this.activekey].top}px)`); + } else if (this.position.startsWith('top')) { + if (this.tabPos[this.activekey]) { + this.line?.setAttribute('style', `width:${this.tabPos[this.activekey].width}px;transform:translate(${this.tabPos[this.activekey].left}px,100%)`); + } + } else if (this.position.startsWith('right')) { + this.line?.setAttribute('style', `height:${this.tabPos[this.activekey].height}px;transform:translate(-100%,${this.tabPos[this.activekey].top}px)`); + } else if (this.position.startsWith('bottom')) { + this.line?.setAttribute('style', `width:${this.tabPos[this.activekey].width}px;transform:translate(${this.tabPos[this.activekey].left}px,100%)`); + } + } + } + + connectedCallback() { + let that = this; + this.tabPos = {} + this.nav = this.shadowRoot?.querySelector('#nav') + this.line = this.shadowRoot?.querySelector('#tab-line') + this.slots = this.shadowRoot?.querySelector('#slot'); + this.slots?.addEventListener('slotchange', () => { + const elements: Element[] | undefined = this.slots?.assignedElements(); + let panes = this.querySelectorAll('lit-tabpane'); + if (this.activekey) { + panes.forEach(a => { + if (a.key === this.activekey) { + a.style.display = 'block' + } else { + a.style.display = 'none'; + } + }) + } else { + panes.forEach((a, index) => { + if (index === 0) { + a.style.display = 'block' + this.activekey = a.key || '' + } else { + a.style.display = 'none'; + } + }) + } + let navHtml = ""; + elements?.map(it => it as LitTabpane).forEach(a => { + if (a.disabled) { + navHtml += ``; + } else if (a.hidden) { + navHtml += ``; + } else { + if (a.key === this.activekey) { + navHtml += ``; + } else { + navHtml += ``; + } + + } + }) + this.nav!.innerHTML = navHtml; + this.initTabPos() + this.nav!.querySelectorAll('.close-icon').forEach(a => { + a.onclick = (e) => { + e.stopPropagation(); + const closeKey = (e.target! as HTMLElement).parentElement!.dataset.key; + this.dispatchEvent(new CustomEvent('close-handler', {detail: {key: closeKey}, composed: true})); + } + }); + }) + this.nav!.onclick = (e) => { + if ((e.target! as HTMLElement).closest('div')!.hasAttribute('data-disabled')) return; + let key = (e.target! as HTMLElement).closest('div')!.dataset.key; + if (key) { + this.activeByKey(key) + } + let label = (e.target! as HTMLElement).closest('div')!.querySelector('span')!.textContent; + this.dispatchEvent(new CustomEvent('onTabClick', {detail: {key: key, tab: label}})) + }; + } + + activeByKey(key: string) { + if (key === null || key === undefined) return; //如果没有key 不做相应 + this.nav!.querySelectorAll('.nav-item').forEach(a => { + if (a.getAttribute('data-key') === key) { + a.setAttribute('data-selected', 'true'); + } else { + a.removeAttribute('data-selected'); + } + }) + let tbp = this.querySelector(`lit-tabpane[key='${key}']`); + let panes = this.querySelectorAll('lit-tabpane'); + panes.forEach(a => { + if (a.key === key) { + a.style.display = 'block'; + this.activekey = a.key; + this.initTabPos() + } else { + a.style.display = 'none'; + } + }) + } + + activePane(key: string) { + if (key === null || key === undefined) return false; + let tbp = this.querySelector(`lit-tabpane[key='${key}']`); + if (tbp) { + this.activeByKey(key) + return true; + } else { + return false; + } + } + + disconnectedCallback() { + + } + + adoptedCallback() { + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + if (name === 'activekey' && this.nav && oldValue !== newValue && newValue != '') { + this.activeByKey(newValue) + } + } +} + diff --git a/host/ide/src/base-ui/utils/Template.ts b/host/ide/src/base-ui/utils/Template.ts new file mode 100644 index 0000000..18925a4 --- /dev/null +++ b/host/ide/src/base-ui/utils/Template.ts @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const propsMap: string[] = ['disabled', 'hidden', 'checked', 'selected', 'required', 'open', 'readonly']; + +declare interface HTMLTemplateElement { + render(data: any): any +} + +(HTMLTemplateElement as any).prototype.render = function (data: any) { + if (!this.$fragment) { + const rule = this.getAttribute("rule") || 'v-'; + this.$fragment = this.cloneNode(true); + this.fragment = document.createElement('TEMPLATE'); + + // v-for Loop rendering + //
      => ${ list.map(function(item,index){ return '
      ' }).join('') } + const repeatEls = this.$fragment.content.querySelectorAll(`[\\${rule}for]`); + repeatEls.forEach((el: any) => { + const strFor = el.getAttribute(`${rule}for`); + const {isArray, items, params} = parseFor(strFor); + el.before('${Object.entries(' + items + ').map(function([' + `${(isArray ? '$index$' : (params[1] || 'name'))},${params[0] || (isArray ? 'item' : 'value')}],${params[2] || 'index'}` + '){ return `'); + el.removeAttribute(`${rule}for`); + el.after('`}).join("")}'); + }) + + // v-if Conditional rendering + //
      => ${ if ? '
      ' : '' } + const ifEls = this.$fragment.content.querySelectorAll(`[\\${rule}if]`); + ifEls.forEach((el: any) => { + const ifs = el.getAttribute(`${rule}if`); + el.before('${' + ifs + '?`'); + el.removeAttribute(`${rule}if`); + el.after('`:``}'); + }) + + // fragment aa => aa + const fragments = this.$fragment.content.querySelectorAll('fragment,block'); + fragments.forEach((el: any) => { + el.after(el.innerHTML); + el.parentNode.removeChild(el); + }) + } + this.fragment.innerHTML = this.$fragment.innerHTML.interpolate(data); + + // props + const propsEls = this.fragment.content.querySelectorAll(`[${propsMap.join('],[')}]`); + propsEls.forEach((el: any) => { + propsMap.forEach((props: any) => { + // If these attribute values are false, they are removed directly + if (el.getAttribute(props) === 'false') { + el.removeAttribute(props); + } + }) + }) + return this.fragment; +} + +function parseFor(strFor: String) { + // Whether it is an object + const isObject = strFor.includes(' of '); + const reg = /\s(?:in|of)\s/g; + const [keys, obj] = strFor.match(reg) ? strFor.split(reg) : ["item", strFor]; + const items = Number(obj) > 0 ? `[${'null,'.repeat(Number(obj) - 1)}null]` : obj; + const params = keys.split(/[\(|\)|,\s?]/g).filter(Boolean); + return {isArray: !isObject, items, params} +} + +// String to template string +(String as any).prototype.interpolate = function (params: any) { + const names = Object.keys(params); + const vals = Object.values(params); + const str = this.replace(/\{\{([^\}]+)\}\}/g, (all: any, s: any) => `\${${s}}`); + return new Function(...names, `return \`${escape2Html(str)}\`;`)(...vals); +}; + +// HTML Character inversion meaning < => < +function escape2Html(str: string) { + let arrEntities: any = {'lt': '<', 'gt': '>', 'nbsp': ' ', 'amp': '&', 'quot': '"'}; + return str.replace(/&(lt|gt|nbsp|amp|quot);/ig, function (all, t) { + return arrEntities[t]; + }); +} diff --git a/host/ide/src/icon.svg b/host/ide/src/icon.svg new file mode 100644 index 0000000..168c113 --- /dev/null +++ b/host/ide/src/icon.svg @@ -0,0 +1 @@ + diff --git a/host/ide/src/img/copy.png b/host/ide/src/img/copy.png new file mode 100644 index 0000000000000000000000000000000000000000..be910ccc40175ea1452faa36d44725662aaea3d5 GIT binary patch literal 869 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 i>U1H7?fC z8tZaA>O;FysX7bly3VT)&p>(sNGL~~+sP>JPg8Sz(<*BAgH6ZDGl(i`H*IACv7LdA z*2sfAs6&VCM498H#%enGR8IN^umck?Y*^G7#;y8_z8S$}J0$yGHQFG#iF&Z{&0ZoG z`|XQ4RtO!5m`aaynOwitFx!2T-Uf2l`thxYhd=n%uetwE-FWi{5nD}TBvE(KVq%44Ou0yIql^Z z7gUp^qdz>vMz=f74==*l58r-C`uOC0cIhpf%h~DW{;jiv?|t<9zw3tz^hV}rz6!&@ zo&AfW#a}(SIJ*C9AN|A~=N~%1!I_u`ut!o2v>J3h5XS*mvLbj`N<1=EK-~kC`?dO_ zK@DOLP}mGqacf21U)D0=BHcQzG15kv@J3YLe!=YumKB+!L>^=66wFoq z(GF50j|(6?nP5jm&P9S;0rd+Vndy`UeF{uWmAd^Mi{VQF8~TSoonWk9afu(H07t25 zoaOW3wcjhTZM>50i3E>YKQ@D9|C)*4ZO-}Ff3a)Ov8}0UtFtx^a)z3eEaOz`Ez3!QF8CKbj;h^rFVHn}z4TD_#u%I)zr7;ZpvCi|AJTqu~ zZJay88K~IE4h^U6kAoQMw$|phHubX~6K&gcecWj|65!O;eizTC${raD_I~>YKC8}z zaZ(pEI7KAIi}1RRE!gn*41NZ{^{H=rx?za{I+8KI>+A;9OF+(RR8n9T%ZvujtB#{wSNG1OwiXM&G6rvDGEhqT~nq zl`M>H>F9OjOl&$B0gnF2>$up4MD%0|?nXoT-aUHa;r@-qtw;M0oH=v%@Bip`f3tor z@YS2>%~wU*eBTG(p*Q(`;Esd66JPbvNAG>&@uRuU?3^-)D2{_PUPFtXSQd(2kgF}C zGf5Gf);GRm6=a($ea95ZNi1QJ=mFk`cBBb}SaOuuDM%f^NVBzWVY{xfU2$7PvCGTMV@ecX!7cY&VISH5Z`PK_f9N+n^XP2;k*9Bdq2GMrb}Ix7 zwAB$a476d#aimrUR>l^L(w_|Y-toX*=oF9B4tCsuj+S;h?o`iYB7%-85a0?T_l;qj zXFs`8x+OWD4aH`U?5x713z-DDb!1bgs8Av|%0`EJUb@J7my>L81h}dfOt+7=8SQdf z)EhUlIn}$UBT}Cxi^wyqegNF+iPI2MAc&b#!mly~(K=2mvX}W{MOth5_JA*hyjuJ6 zhxG9^b};CF$2j!SE-tlw`vZ9fT${Jw{ek9I(^okAsXrFbElJ8H`YB&`LSY#r`nq(wk z^_7^#M-ZYv*6nAYhwYUnu^x8;z^BAyu64uRH|iFhduE5XE|0$Ji9h|Fe`^4G6%?kC zd_^^P>o5Mux9diQzw*`3KDN2}D^H%du$N&)BgX-j65FJNqxUg(<8g;WV&rFM%c+tF zm)Rqqqpv;gL(>k;4#X*k^$U_CB6jr4Ckk*`ck6eUwb%HP3j{3S2djBJhQ%!t@UufS z2d7B!tINLfs#gm2O}4a=-`(8grd*}z#E{sm>aR|4s}l+B;q4x@1Aj7!#B#PwEM_}T z97jHr9Tyv36c>xgkBZ+Ae-Yv%0`YUeZ($}Ir;$JCi-&yJtNaOY3&aHS*1LkAf*9~5 zW>M!;A40Vp|Z~*IDY4E`s7D%d+MQ!b1uXUgGOblNosx%C()22Mn3+qO=>?o7rqwmxe>R0;#Zl*mo}n>ENvQh+UX>7Wjpbo`{_7k z=2Uukxg!d+QOUZlQ){0(e#L0nmUAvy2*~gy#u_dh@J^ zT@{KpfMQ&hrY0o9D};JLi{{ zTkGGl|GmHOA6e)Xyct(bUjdEW`O6=?_vs7I{ImP_Uigjg{MR3P&0`P(}B~5vH7e93^q-#y>$QL6<{lOpm z6+hZ1@MA|599Bxk>2zL^ePmw$Kv_v4GkV>VJwGO1> z512@brvH>`z#=LFSJy9H;-U5U;gKr3Wo19_M9V|toSt%ZlA{^^_+b;~LS$ptT2<$@ zJiH|Xe#)&Kqv&mp?C(Ra@|F<;!7c-)MWpV)6`~3kBf$wifrlDrFmU(>SfzrFr&8 zg$P>D6N}NCEPlzhm|>e(el%^Em`p%%OsoD})~@oU?f1B{8EmW?FJI-ORwE97y`IuL zIsc{kne}&G_#^LnYP9&uDZL_Q>&^GP|BtO79sbmBe)6H4*8kO`<7B3OqT_p?@y>9C zPG=0jm{2lOGYBIa(2`eX9-|2oJB;=qXdYm-12ukQ^CSR^=e|N&&sp+lQYN!t zapdJIp*kfT+M)F_cGM)5uv6aKym^#Jou`(qZY0n}25zbiN^EhIrfY;F&Du-~FHWnp zw5+#CFY9QJvSl%$&oCKH0HEj7LM021l|WJ1ZMkfx6^^uuM=5ELbyY(8XKj+)yy_FP z4N$g~#YYO_!Xxnb;R;zMRh7bXL8ub3xa++MCH{;wA4R#U_n{GY9m!f>;_h49!&Jo| zmB_3cL%Z$G?n{Lzi~z5i>i`U>07WiRWr{q7(B z^CylD|Ijyn{3AC%^~qg*1*thT{yZ>xEYivz1nK_>LLZU_Rh7wm((pKOq!KR6SRi#& z7t4YxoFB~BXWzH`eShEwBlpVKPFw!6YSHg};DPm@{N$y-dDGGUw|wn~KYZr# zC-P3RG=h%PM+0Fxu$vc}YBdmtI=wv%p58fnr1nr_fT0VTH?Dhv5S_V-34wdUIOQU3 zYx3c@DMvd65Oojg!^1-@q;rIn#PH9R&`eUH>n2;_?&_tIyd}eLTb10Qe)^lYXmH1W zxumT92d@kjc3WWW4j;KV&=WF8ZM-(TQ#@V8N>s5h(%WzD1>wHAU0p@xKCo@A75e@S z01Y=I(aj1!!?9w9rpU2ZK0*_rTb-s9M%ll-%}6hH zbm8VJ_1Nv^?5|z=lfVCa)ZVCq_cBh?ui}?gGyeV~mwxh&{Y&5aRsZ@|&OGs??%9&} z3_?Cm83|BBV}Pobu8&9t0z)CmdFy+KKxa4_-J^4Wg~dS9$0`NFO|cBnV_!i;D1OCA zJct6ihS2(#P6u?wlQl3~5@5d#7JrT5t=>Mb^(fZc{00vRbBf5-M09gAZn`vDR=UEb zc039BZhG0X`^t}Ts%+Bs%TwYPx_z^JBpoTS{OFN#ziUnVNuwCKWRP5?CSdJ_|%&(2}woEG#5Qo27Qa9W;&NtR3;fM@|7FmMe-J zwe`R%FhjLPXslsz%LWVcTb%lFxK{>8&uvymReCCb39vw!c5Jf$n9xl zyPkGmx&NSkO8o&&QnGis{g^L5aLPWpka88F12w=wFT1O>F^(fWx~1Zbeml}tNpgS5 zr~$OW=DR?Jq`ZqQ;Au*1{BNd<_Nhwf1t7VW6xSYITi>|(_zefgi+i_!%eViZ`#=3- zyB2wwZeCVLXgtRB_~Da3eb2$gFaGj>`LDNlC3ejw>O&@$bX<=FcV3MF_mBA*D3PU{ zZ}RLQNKLwY7yva;3@j1MR5?2UV@P_Uv(WF?U{g0%PjD>!qF2d^jUt-W)pngUWqN;?oo2>M>bP$f=%Ir$w)U5Zxgcl%Jo zQ*$NSv2+N+yewuya4^zm$R&R5)r&FU5WvBc3MRflYnv(|sjaClvq9V#t_+*>i9}mJ z+QN)e2v(W^00f|f2h5?d5g)7ZWQ-ZA)XGx>@Fm1m>OMl2j(q~F3T{BzM4uwgQlPc? zVk~H-rp#gRM#PdGeNp`N4K)9Sjg6C!FMjv_;?h64>D>?9hT6+Dy=*45-}o~>v+?jJ zF8+hN_Ah+-TYmW?C!RgW`+n3(>dACudefZ@M?_OXuHGMay6!Lr?kLy7(FebI8SsoC zpcp+taIw{76A7MO>QaIx4%&@t%&%h!H#6+QCAqaX0k149O_kaD;iJNo`DNSL&1-D+s0O3?36Y zzQJ{BG4Y5?w;3MgNmFsoI32&nh|Xuxe`$@)Q*1l*g|P~l{)11|HdV(TmrKSV2qH_u zXL4=g7Zs{tn>=aL80Sc}PQc?!%0A-u0t8=vqJ^xye8#?LZ4-9NE!cHb#<5#UHXk3m zA;P6k^mq87&COdLKlr@;<)xoJ^Ot}4HSO!mEq_@|Xg{Ev0sq-AJp2=%xBL88y!AsL zJMqkU-C9tco6c6)tD`*-RP9b51|b7PLhedrT~fhiGWZQT>RY~)z>Wk;DlurfXdr03 zhpS(09Yg_ZT|Dwpi;}j|&!9qvtCZZv!K(nrNnX^-R>ilR6r!Ju> zv&ZC}XaUsZv>QE6bq3$WXD!f=MwN7IM|0d#f>W3R0FQg*jK%pUDQw59`vty(NBdQF z98!o%>9i`c{Iyy@g|C0pC{zi6ZlCqN;Vm3`;NL1J$yZ~#C=L98mU{aM){q7hB`XFP zSc@OgALLzVPhLz#lQua3DigPSxd@L~*l#fcZyPNO8&%t1^}vLrJh7g z={GHT{POnZt&bhtzrVlyCok7EwU@<&_7~p$!|&1ip1$FYANly{v(M{DhmP$2aL=aR zo#{T4kUPo)PQsR><2;0F+aS~2Bf$I>eb9u_a9G5Lm>&mBkU0I*)j~b+&FLKF?Hwkn ziM|_vkASdAi_#(SGt{A_RF$ZQ)<2ua6;fVL7;elr|Ad z)&X1FF4}U(uiXd-xt8{kW*?==kv=`h+V=xe1w_Pel9}INec`6wL&2Td(;vJQNG=g^9CM+&C+K1xV4?&xz4fT8` zN{!eqO#HUK`0+7OL##!o-s0)OFK=z$diL;*XV1=_V2Nkd%JpBn?kBXO~E>fNgrE+IO^n#D&Zol9w~MarGtm^}m{Wa<7E z_n?s+EbGW2cpF-9=avmV@mB(!Bn%Dt3_26a3WGX^ddpx-8Gw}h{HBiTxQ{LOvgHB^ zS1ONrzyFX%&5#P*EKU3g4vm=#+M9BvrR)Djeby_`_qU8sWDkoLw|}5zcIUPY%NN2$2wP8qr=6A zb>j~ci#XwSyg)#^eWb3`F;S5pB<7`(oE6XOhhK1W8Xw2s!0{jb<`3}?u(qRrCSkqW z9bcfX`WH%VTU~ifmOcgx=W_onC`|sRX+?kH7h>=_r94%}KWpQC{GZsjA7Wd=u~4ae z8)=0uddF|HkkTMqw$Lz5Hp!Fh!C1Gd&M+RHuWjFT`1*hJ3m>{;@zX#4(=C0y=hy9o z#?Q*F9UuN*-~9N;PCotMIR#viR;MT4+EU=-o}p8#Ord&fWoHn57(~cA4Fy3B0L{!m zp=L4Z*PyekXg!eN54J2D2-6x(bks?qUpA0pq_$JJGA~a1AIj)4M;>dMELem7{(Ofm*cgj_KN*Cu<0PX(vd`Mh4QtR0CgfWd z-^PIyU)(;!q@6`A7^LliCE%GNP}b?Vf6VJd@{8L5WXW=os-iT8n3&XZ`Y~QjfZ3s< zcdQ90w}vFfL5MzD(|TJnZHU<*q6s%|;NYfXeretp9qOnO@x-)S7qrj{nOO5D!-glm z)jlLrj#u!zRT0LIPtkv21+*G9;0G9qHd??ju+o24l3Za#2dfibOdLF$xQ5guHI_&# zzMx9GMI;UZPaGyH#fWmp1%6yBeX)~Df2d(I{v039XE*=z#jV-_|Lo>p{J`5=<9g1o z%L(lle*VF~^1A(tZ#?sFA6eV?6+4B{F;15d&}j$Kxt?sQv)y$eN)jd-N}a3N9o_6q zRS4B!5yRphInXBwh8)PwFId&P96uvFsEXh24}PO>P3=V$vxCj8^3LEpi@#KCy3cuK~p_3rChei+wc&-u-i8aSiUIWPSvOwXY9Y2 zE4jj_R;ptmA|S}jr|Q43dm9zDtNmv)BHPQs5U}0AMu@xAx{XGg`UwXteFlAm3YVhO zVCTyHhm0rbK~K+_p4*(?_2AwKY5X1DH5j1lcYEDUXzTC!k>8?lz3WRKesJr#^BL?6 z9tIux@a?3!2Yk_*hoJkTWr;nV4-wihuwW>O2OD}y7_u?jQYGXG4X&%O(D$bbV&WHm zdT<4*&7&D#0MP$108!T;;?`FlpPD`$gqE`G=r?jCZfVJSm<^Z~PLL>P*8pWzU$@r7g_!ppjQtSKlc;$D$6OJRc+*23d zVr;~9!#+j-VH2&f9oQee#SaoRdntwV3o_-iG1xH4NtXWO4{$?~tDwvu$}k`yLsEv0 zIcz2=^tKNp9wG)0yCj7;r$!lRln@1~MYW;W+DwZ}h`*`h37^Z5&q(*Z| ztxq{rX;uGpjx6`$(GDI|XU&Yqx<+N%$0J_)Edw2~LEqESTK`0wjg_J9`jt){oJgFg zCQ8k~gOr;h!a(5y@S%O&`)?|KS^t$o*yxxlfL7?XHYauQ zRIn_?hDP7b1V4-+{snB=anqWU+yL%B#9Dt42(0Zd{>Ao9J4eUAef`}(@+|}ZIxJjQ z6WYNuXMgDP_MShp|M0W=rKIW=_nIO{RUd*5sXL#TnxzecB+MZ*tz{V~bbdpkOR~PU zJ&;VZPz@vo3|t#_sT@Gltnh4Kb^91z!PsU=M;1TOZRe=vKR$*VaLXcX4d>%-;t?yF zP8o&8WW+5P92N091EY>bYLhrniFR<^ei69AcV}B`aym7n&BJr46Nu3+&QQ0OP_ z!DAxhhEeX3<41|R|6(`~+w*Jdc(QwN;3pm6Rj3oIG8X*H`VZNO2iPVJvaSIjN`d+Z zh!vyZ=3xV917H8~k%HAAgl9|8(vCnQL;~OHXx^;bd^n>^Pt)JPMs9#sb z>t;gRc;5%U`glJ79dG{lBOB*;IW?)&gNdG~PHa*l9x}9aa`m3oHC)7CkdVwrE>$Ql zvCU`XKxldcpg%|eicxee7+Vo&g8{Z^$;F2ZaMUtj4Pb*o6l0PYXjtw)1VdwxVk=ts z!EbDmSP%2<66TdZ=EPR?F4Hd@o+@DF-}Nb{CCELIDWCSAOzMOKmOEn_khm3h{8(`;{I-9E>L)v}TN zKwBAyWnBOr3v$a^Q~Z_qiKvc)_yj{>EoNvGv50^`n{9$sSD8vZ6SQFS>%js{^5><0#N?VRUCY_!crdeNmkzaqnN` z0Z-?K4p_u{60mU51{GmjzHAITBI>QLvi*^a2b}2eO4CjlLzEtn;CoOiZyu%QCl3c% z(zw<lWmNjS6}WV8MBK3sc*N*b-<%s{vB8ebR%Ghnn<(+j{^x2IZf z8oqL~E$!m;b3C$YB=Rv9$Wae|+B(;(bXkMTkFl8T+c&tEY%1*XSCfm!nH-vpXL-*gW1}{Ml6xuaDX_pU^lT z*QwE;|MI6F*?#JUEYuDmtj-<=p@Q9=>70I{1Jubq20TF0Umh$JO%oP6?j);O+4Vu! zeUcj63^uS`i|v>L!M4H7xXL>&5VcDp4!%U)^q7cHJQ(rOMKnH^5`LI!i&pOq`bo%k zsoKf=*F<76$j;gl$wysEt)C%|eeP?+f65()TyTi&pig55gle-XLY1h|3!ax4g3E-~ z{nzn}3a=5@+Ln$gIg(@T4n7wc)-}mm2Y)2oW&79moa1v^)N$*YbwN~*Jt-(Y&s>jGLy88eauPM^3^Eum6rC`O>%ulN`9^8TZ( zjWFhkC$P4H(Ms_<;~P@qH=kYj@QE!wa{AkL-t)mXw(RwiU-Jp=sk0CN>3a??oY?#1 z^NGpWQ$1Re6Xyyxf$m_Z*AwiKCfV-1?m5s~-^Fwsorp*VHbPNw*Efdc;Z>s({+^ACUM&ocMVzR^;3IxbwmW1wUJMES2&wOjAdWtCi&V;m4v{RC!~?J4;Qt zDf%@=gZnA?Mq90cwSIes#xSuAI@m5)ay##h;vfVEgPn!8#!mvoQ6(1%n9R8CgG5WB zp1y(>{j>%6EB42UA_>&w&iet=B zO?xb2Uk2fJpkiXAfVlxUTly3Y41jEyUN%Fr-Eku#7$S_mqzp{FbfG{*&A)T2N)y@T z;c|A%qsN+jHqppq0@d9}MNV7uY`;YnLG1eleb_)m_1@vijLo-i zga4B2G8nd2{!I1766`SKTq@6siiFJ$b#_uPv3bbbHzp}aLFh8(@5 z=|FGwh&5`_NUpN}+sm{E8Ko8zmv(iVVNphQNHVl8=Qni}$7wHXd3;Yq=d_f(c$)N_IpnL;NQNh z#jc0^noejR|HQ>V_&Eocw$49($%EH-mOH2l4RP(b_n=WQE3(Ou?O>Z#gA&!&X$*AL zW3D3Wk_s-9!G}ve>RY~)+?kd@DYm$B=U1H>NP-2^_N@w_=JROj+IO3T;CS}Fa9w0#kc zKEtei4^iAnvasslAnRfg(bFILt7+`XDSf5?$`%p(!x`<>x>9 z_|`M$552C!JG}^=gIygkju27l3w1bs-s~aRif4Cb%ekX^gt=4b3=ikpUa1q()kv-A zQ=>Bn!>pNcb?nHK@$ig~A(4}y{Vf}Df7CY)Hk7;xu!|zZOWHXYEFkJlo9Yz1;UvRa z6~7P4kyfsDwr=Z>I@3~o;40-fJRCj=;SU!JXnWjwRqS}VS0(zX`p7<- zln4{#6!Q%uG1mczj)5&CQZzj|Q6`ntq!uaY!$^o%tpY=*Mp-=PRsxjUA`aP?2{0mM z0MwFa;==!83V;$@w@;;bh+Iv6<~KEYhNv0d1dRbGzW$lme7AqVC#bE{jK&Kc_08%R zyI5W!Wl03=S$wn4t5Ds5p^f+yx1>u!^0ePHHYCw_%g2^35C8yV|448H8Yf}QGK z0(G;?pN?OM5e{ug=2cYsJ0HT&{P}k4v{=)smna%h8=-0`_b&;;w zgm&`q;Jd%@!ZW9yxu9PN(yts5Fpdb8T&E|{bV_xib0Ta~h#rLWQey0Qw|M#~L)iV% z4CkR+zYKW57~3Yiv6S#_w)Nq3Sv)Yd^zuf(cZ|B&me*#A55sgL*T(YG9a%z+E$Q;o z!jv;CR{$)Y0So$|Y+GH+(V1?0&>9izU+oF}$OIwf)`}f!-1Hh>ppB(b;?w90!0iU8 z0+6@}N$q#1iQYRbVosthb#Ci7-|~A0i1Or!1tK+#FZv`nDN&{tS$It!NW@e)vy3rb*S1q({Myj=1I-Hu|YgFwh4N z=#*5MmS7q{BJs`KsU5LhjTj1235I=AdE?RbjgyDF-xub!-(Hgm?e2F!aAI$5x7~GcY4O6ddMcm>f?xip@bS&O^fQRW zMd4%+IiQsj=Anny zw_f=jF9$=m863c%lRicNnWbl7$?a78ocNcCCPoHK$*%NY+bIm-&~2~UivkF{iLGlY zdd;X}#mN4-oLB~&@vpy8C5FKgkw0w0k86~t9*AGz;Ws5Y)#PfH1$^+N!}Ks&$ZxkrwchV}{p z{r;&s+JPh=$F$>v;i_cTgl7E$5v1K~9}vSY>ZS$C zJ|XL52N6WKh~VfCaqBCOPfec=LQAzrmIN21kpm?OJnoa@?X$eFk>#k!wT<|YEj+lR zU8;VZiZtpoZ6wMeYz=cMJz2&o%(H1NISy+a(_4#6P~aFJ=H>6X+s{eWRKVhB<5|DfG4vBOW5w zcA)~*_NR5ntZdnyg(6Em7|}O2d|_Enw#+X;%Ac2BjG#tEwqYF3A`XoWw8*IkrnswpVd(u{D^qcxmGfDg1 z`Jmqbm*)T>grD@EKZS!VywY)eRpr%4tfw97ywmLv&4QBM+dr7?>#2gidw{p)?(Ws2 zQH?29z4UqU7D*A(fs!_j;jsU6{lKsu+^k&=5_1mw9-}1cb6WVfl z_)Yijo!fljl77gRE1De`I!J%L5ZpUQ54^|`KXzEhaftf}p^}+HrYe(5M8T)?5t%0I z+xp1Jq|j3;01P1URRkxXC9ljp_>k;lc(#R3v9P;>-Oiy{GWO9xmIhEY@c59@j+Co$ zrfQPW3}c%US>&dtjK@04>7YbAxY*$DavF7B0mSQeI<-}P03}*@HToDIL=(xf%3ZGh zD-LqC`H|V=rqQrFB&3jr1}Ce#S44sYt*PHyM`oLmZS%b3Y?)p{Ycyu+1lR zYpd9F+b{h=BE1_?3*vhzrOO9^iTfp!0gjpnTw7{&qmCN}8iTz501l18$nXLwTVj`L zXsKieU{aC;KILS`C#~v7C!EF{cSeqy(T|=p-#6%^wDPWumq!tI{H5mjNX-y-TSVm! z+rSTg&?}~K$Vcxy;GUa=kq}=z?gu1s(SV2A{e*++z5!VL?C7!I=-JV;b6S0H0;DoX z5rweEv#38ogX*-<)R4w46!ZtKuGkozVZ2Nza4jsmd;L}=ErHUj2pn^c`fy+4*w z-2=gNaR#~4BrBRqQ4zK=%7*QmfGk`)s>%z9=0HL4vbo1M8otWP_1>ZwosHDa*4?L>g~k;(yA+nM4u&17w;=YuYl z*uu`V|0r*2TyTqpPcIFSqk2rD%_O$3E3mW;OrHga&e58O6M{+(1-U-RWkUvb>>ChX z*-2dl2{`VeC!_`t-TxMA{HPni^p;3p0BsL!SC+y?vH#y6s zu;(TdSnfF^Ok5Q3?@Fppc(dCO%+=^{^DXOaD7j-D@QBgzJx~a8I-+ReVF#N7q-3Ub z)4THqxf(cV*=E7w7W(>Fr9iQd9nt{hib>g+fvzEhjIuuYw@hQSR>4;_HDvJLipHQM zo)*b@1rHYixJ{#2L)>sI_jQf>LF~4+^;N41efik-C8tI(fv{k2?UhEiymBQ_hj>|a z54|iAwk~_!Ic}AU(vC;uup-J}(|nG!_J9|O=vFeFNDOjn1^;EQ-Ii|=+oOly-{5W2yv z-@1^9S4q2^dd!&K!Ywy#6Z*2*Ca`TEL|8koHxh4bydg-}ZhKWHw7>V*{{1HxN4xwW zhC84-mZLNVq^B~U3TXjb!D7&QSkeaa-r2s@HY`U#A17&z86B+iLo8gyjb(f;ES-;Xb(9aUCihSD zjgAlg1RvvN9v?*<`%lP#m+!8q3{>D?M#f^QO;eAf!L*n|*#)$cVpBxm^oc9g1B3mE zD-jslKIjXMvir{sL?=FDsqLqK%XS-0HSL#VsawBeFzr7C)-UP^ZENjJsa~_}s!nM7 z&49aaK0aJryre5q-DCxW9fF;2p^44AnePW+0hkL1^s0nWsK_$$A-f#jk(18Kz{y}B z@b0wkIqa2wpm?JLi^<#u5Pd|ttF7G_qDB=>D#ONfiKVm6%*Gua4@`*7i2#7oE5t!R z?y~dRD=Sc9NVn`*YP@K54P0Jl%_CV%0vX8M4x`A4N1S|vt-m}#y-dG=MrxOM97!*J z9DIQuujn$?uclCL0$77s$;y8Bm3KPv-ChSdw$gs+amJc5l1Jk(%Ud{(XBYPlX1lvg zronCp=s$i3)UN=y{YlgUX5cnC*b6oOwDF=%I>7d6S6dw%we09O{@DBwGgRY?n`Qu) zqFF^u$9xrVl#i;`r-*DdfU=9AfQcIAVN?1dO#6>ABDJ@ad?`*af?Z!0s&ot7k21~; zwnyvp4bH;QzEhDG9l@ z8!XWW++f&a2?0ITr_O+@rnywDbD0Y(eKZ>>xJ>&-a_$9vmH2|dK2KyY3ADUemL9D@ z>6C#?jkEne%4-dB9)M>OZGB>bb*D5yMeN3YamtsjS;UIn{>HG+>iGk{{Nw}cu6~oP z970BylH|jP!xd3J0VDS9S<3C4`$Pa>*k<*o%WWK31cwetAE7k#r7h96#zKjdo*J#U zPQa~S1Bfhg(wPRZV}|{SO%P%~SXgU+&^NU=ZfLw|8EV*zqy#OSM%q4rB-^Jj=|9Vs zz4CSaU^)A#Ck|eZ!H9`uBI>E1x z?tw($s$*D3%bEz?k#B28dUwRoX;WRvxeAQ_*a5b5Z&SLI9)e4bnJ9W&N?=E#{7}7J z@}NMcU{>@26n&7b0;0jR0b$pG!M-h_IF^u)kD*fM2-C0E5oXLo1uvLm&SOOC0cHcZ z63+T%%SY1fu*7sjgevKXj~6gDIt>Ghx6Jyr-6e@HgG?9fV=C|+5YdRizTPXB2E>y2 z5@}UrIbx^J`Q3p&o!+8Nl4HrAzQi9=UVT)tGf3>%EHW~HU8G|x?GVZJ?mvU|#=@#d z2tHLX>?L~obG7B2kNr0#TUG>nP>m%U@Tk~t2|-8Mrb4ue>Xx^n#E>fo@XwOHF+h9- zCny1JU()6X8Xq>PDqwfJ%NPcWuYNhuQQFb`n!fPys!nL?yxUG3A8h1kLV+5BtO-q% zm~W=l4#&|MceFETDRV_sd!l=Rpt{er;gv%KmdAShCJA2_r32Ej?kEd)r!WXTgtA@f zSjjl0tC3pKrzTqshx53PYcXi$+8`J3L7pavJJj8FY$$nSLWRd-rAlnILD=*YEb~e#Zx&3Gwb^5~|c$2vJO7&n)udMcf9mmlCvbjiHIE};rf4)GZ8e@v~^uz2-)}%on58QukIeFTJL5t&} zlI-9Ml@>RBQa3%!jDb-w7*U9qt=xIZc6*ylE@kT%&wfbUG;OHoGgT@#+XaLN6Dh<; z$oviL7%Zr8QbC43vwf3@pY-s{kR?3=&(?PJr88e#(+TYwJRv{dV5~Xj6%2yp`_UA* zEbj8`Aq%~F_E0-4>n7;%<_Gj9n*>(@_BS#Eok>k~f7^IaNW;cV6pwBKgU+H52-94K zhzvUq#gSv6t|C!ocd{x}*^T@tSK|*d1ywc?p34_kCw?BStDsx4YnzEs^VJ$vxPh(7HW^ZAzTro~=|#`PH1b(eO(yP1-7Mzq{2Y#2$()hXxu}@H!cHl zeZJ{$TzI^yW@l&1K^g`IfhUR2sT2u{gUx0Lw2QDKASs5nJIF)^c6Frd*|?j^Kbtae zVXWYf`h9O*;LAT(%C8Cj=pSHZUnBXkH@|-N{OfNSBzJE*G2`>p8(up*p$YBwkDs00 z^29k!hPkgQr+?BPTaQK80@Ae{$#&Q}Cw}~SBA#fW+wmN+t0c&@E)s2_sj;h?*n&@} z5mM@%FOz@>Wq&2c)`^8dU!tttaZv6f{IGr6?5m#wVB9c{xUqJhb=nLh%sM76pl64> zEvm>!cI0wWB7=VjuQ&%2q=~*{Ebp19;1Q|R@hg{2Z5pwU32|v`CsmOj#%A$M6WuN$ zD>_=#83jX9(Xw{mC8R=g3CYl{PnCMP0LNA9t6X#p`HZO@DEp=}i1Zsbs9vj-ncyl} zSLpYT=9}|e`d5S$3Z#Ur0UZk&q1k@NK@nAp^gnKUU%@b@YN#`gS;W&)sp; zpmKSEiS3hLc+c#^Uv>ZNh1cEUBSDT<#$g)NZgTV|wMh|c{F*rjM(uX;_5|pMw zV<})v0fl1y32#yqx8m1|C?HUs<(N=54QYyrk5vqoR~d1e1``NxWbseysoed?5j|^p zz743bW3ucEc%}cubo|;bhAX9?x}v=C><5vnc8hspCpY%odL}q=~&9*i+wN}DZFByF!^YK+Yb)3&Qq)8ukEcN7puH5O$7j$W{ z7RPBT-`WT!-1K%2dKV>)JlyDEue)`?7iBOeeh-%ZHYazdOLf>O29@{;gp*`ERJ1wX z+?+l7&aa=Hf5Yd_@PAux9y$H+CucW(_@UX6ZZ-H!Y5(;0?Bj30XSVZ(J7#x&^vT(o zv${=tC5>$f*_QeC^eNf?fqouN0ptxD@hb8qC0gr`@q?12c6I-mkT~)q#9PJ_u>)Ae zG@MwWkhrA(gsKAKu^DeTjdcHnVV&;34ZW6|N$Zj>A}}sEKZ|c{gLSBjPTM+4!>5h) z*Z}|2^`Gs>!T2-yg>OpsMGc@0)jsz>HebYtpd?1TN*lBfC+r#cu=MjrQwozdl`HnBm8SGAQY5(OZ+M|!ImsnJ_O~LWAzU$i9Ppm7I zj|fk#B+`+=zmAJz(hILBpid$Lx2siHTh6%*eNB(h7!Fr0=`N**&*=zk%detI7~R1N zjsvWm(+OYQISED7ZFar_ia0?%3!H+yoFcBHpRFd3TPvb3%{0~%P<76WUM zY|~rrG25~ztn^~+cU8F{>r;jD3qL(L17)9(C7rwf-W0C1J$Y)jcJKYO^*4XfZ0+XT zt|ajy+V0IKW)HpNzS+mV>;|Pa|jPiJ!MI0;l2K~f}!Jn9s z^T$7F=s`7%_0PNoW3*0t?FsUZgS167tv9L4=?N2emIKgI<2y>TSYy>FZR zn2LA2($@sX)$xsbJBTfc#iWi2q%ufShX5{2%rLHO3E}c39XRo&{B$3KR~U$U{6VV( z7EzIiVE07P~)fLFo`QA~HSXv&nJ zTc+bsvS?YA4Z864InYa4)DIV6U#oOgkI-I!^W7hR)bs&p8Vo~dBcT`Bf!DHt(07ymgT!RUDW&hhW2Ty*;DK-9@Ir*1G1OyOIv|aN zPR)E|s=nnG57rUUHn$w19T|USTyf*J3#;8Uf_*Jl2Q6e`!K8_eA#ms^RVL}QCw1}k zZv=`BB_BOCNys#d$&&aE6?g{KX61AgVyCGc(S+6TmM|vjMrx(x7Ssm&#aV9KmuFT5 zuWs$C9-%$_2fkr%YpKgrXS72TBnA%ctsclg(qPoL>$+}V;5RR|P*N8LkV7qDLZF(u#x0uKrLs`l2>LPd`R{&JU311%`(1r z#+RH<=!or&Z=P*@-sjKO&fNTx)Gpq3diKzl-{;fOOE+KX41k?k$IBep7_oe<7+g-f z+*{ssu}Vwij&!t~0HnP@SIA6u*|8C00^ryXx-N*vTWAmTgT@zi+X|0VG5N+y`j4YD zu6Ftdi6yV$f;Bct#^>s7CO+0>xi^h3K9)PUxtr%tt&dvURykvg0(5=oYxs??GBQq` z&f%8R_t%(|RR~ zK4KyRKTP!1d?UcnXSt<#&)3eHc8xYybwZw2rkTmq&`vGpM_o zd^j3Im?M&4MIQ`oZ1-C?MBdctW+q-HkM^Zoh`ibWwx3$Ttc3hQR*X*Qjtv;(&<{(p zHb7idY`sa|F%FL{lXB^I0HB67wJus@4Qv2yrZjxBBAEs3ez%l76P@^gNT#I9!}tL$ zoZ@yivDNry5hCohedIPZrj~0@7w|R7uKI+wb9Be-*_-Yd0!wf_d=!|@i3D#P<#dyE zvF@-8tjYLt2L*t*X8la3A-al3tU>P1q-K4|*$#Efqz=t=fXq?{nWpU^7oHhKL)I$g zzwM#o&Nje``z!vS{MjZ=X6u^N*5Aa$cKRi0U%37B?7^?p#P%gjY}?Xd5RZS<)3R#c z1QpJ>3&7W))oEHTC=i4M0*)ir5FN8Hf>^P2CcZ_qp`H)IRK%mG4PR8)g3oJ!E?qkC z*zgMt5B_o(b(6C;A6k7J}9`mrtw=)KSG7zQhYn$#UkP^}LUUDbiv(k&5QGtp^9 z9tVOx=^SZwXSpL#ug=55xL(k5aHBuSOVC|QN7{VxyNoX`dyQ>*=`*jzUby4*jL+$_7iV`ocy@O3`Q37b7Lt-zt6Z;^n#%?rH}i4@`jB_` zBmmauo-ks~Gr2<&a5vqdP(a)ts(=-B#ZQIqClj@y|NP@90@L@Kmm)Y_q<;b3(T_Yc zF)@kF_0vtPsd1FZm2nD7XksC*A+^*vOkS;gh@~iMt!El^(F}^f8{a5@} zMqXvVWYP^)orIVa(T!fgPUx1CATmw}3J*w9VgoJp-j+#&5Ahp}NFP9Olz9?j-K>;a zH#U1*16A=af6zuPspJG~OmJbyX4?-!d|uqD+jb7OXCM7j-@G6FYqP!TM`$OHZke6e zVB0yvDNqI7ZBKCf+XoWA@WZdF^UdpE(qZ(Dl5>jgtqQiP>}VW8@Ix7#uDUbm`09vs zdFhMmPLv#}JG{&BrMoR131H+@D1SVzLNwDZiD-R05b$yx_#rKS>~GAL4}W;5f3}7B znOkP-zwQfW>sn`dlO^>6k@Xmj0+cIM#D*$el+DXn(U2wR1i06B5RGe-cv zqNQ(t4=VBKpmb6NQ(1NlI#4em)B}#}4EZsTf+H(ibW?XSorpzu3^L`1{iy+fOjOZS za+Wf5Tpsl!k~+vqNSFC0C4cnev)NC4V7C0=f4@rqENkCxr|Z9M-HV1lmJZ|6(1V(jF@Zp_`f< zMTw;K5*QjAjj@_&cnlSo$1f_>4G^nlS8+5i0u}7FP30~kOM!@&h%rJ6J~A$qzRII7 zDI?N-7Nyb=qt$Ijn$+WejCeo7_-k|o{ahYTMEhE$t3IL8&|CKInmuv*-EOl7PNL<` zaRA{!(?3UA9Cfis?Bg`i(c#q4hsWf^HX}p4x`HPlewdbTcqf9c?x5yksKKH1*3J{^tENChkQRiS_xW$tJMDq(YKx9LOv^kYNu|4(6l(`SFgcIj5#?(r4( z%^rKp>t+jPix;H~zdV!4 zB(%#@0S3#ByVE#YA64_cF%Uh+HO_VuIlCaPkK(IlEbIYM{b%1-8XzWu4WM$Ho3_O^ zB@-;W0jlex^|U!sRmK*Q$|5J7seP4g2m8p_=^&&WeJ}=|bWH8dpfim>F`_L9>0{Y6 za%KG5zR}12vux}wH}nY5nZw!U`B&i)8ezHl@Xp!A6PhIS?U@+X0AZlektE`(4?j4} zWWr67EcWxL5Q)q13!REG3fxyxqchP*HR~oU6l*PxWZdvD+5;zL`VzZn#(obBC_Gon zj==?rNZ6P$fyKV{Q!va_Ul4|ES=V3c=70H{v)LmLy;SKzZa8AomC^a>m+)p}wc)wX zy=C^%uf0!$^YWv$>K~&M0mWBeGD*ZMG}^qcc=M(dLJ{h~cD#nTz$760IBd>Egoa8f z`#o#2wY#f}0qSq>fEF=;$zTe^eyfx= zrvb({-wc-=KQSy}n|5G!e5dZj+CQy(Ku6S8z1n$?%ByOZ(@x#reSDbDIMT&q+audFeFSOa`_HkejYD$V0tQK2x^TWVU2dS)l70LgchB}ujc+$W2j5AwnkI2dm^Oz44Gw+6V^zJn-0gL*N|d-j>#(F> zKn~9Z%V9HdJ>$w-SN0Ei*MP zg?8L|Dj5UK7-NMFUXGkbDaznW)0N8s`%palA#u~RN&FJSiB@d1A%q7LDa6OopB9~Z zJM|QP$3J#dEBkHC7$Pn{N9&j8&dkp5s^711x+W8v+pHz79?b4Ehff@JWCE4Qco1rI z9_Z+7YjaCix^i=*o*Prdxo3^eO|bI~1BQ;O>S`C=!Yv>MrK8JEWK9@YW34*V;-PX! z9B+H&lr{;QrNQ7+nVPw(n@0trJ|3SDkNT(BE`L;OX}|Q(IoDp1U}C#s`tcv0&CWh? zMb--Ow62PB@!)gr(~BOjbXqw|A8P_(QajY-Myqh!?W!_$-e0!iFC_Yl$)9FwD?uhT z*4dn3C=Go~oJ6xM1&(}@lV%)hK=)dv zYd4|MY6h~hbZmz25DYrDI+5HDvn#-x+Sv}YvmfbH6uskxP6IW`AXDPi8+nZk&-~>F zRazi+vV%p#0|s{n!n^*v_M$oPp^#~Dx8*7|mz z_-H5ENcA7`*2kAgiv6OQ*T@or-1;KNzKA7`WEG9rBja)!b$gb9W!YgPW?UkUs+9T0 zFwX!&kbdgT6^e?kNpIR~Es>n<|Hs~&fLV52b)M(tzE#$~t2Wi5UDj%?#+Hn1j5oZ| z1{*M7e;8)TdzWM*oiMa2+SD95=Qb}pkj?8x>?um0w#65BDxv@vchLGcx(-F{b11++qgdCRy z^_$+wZZXSMJB#~3TDt3!8Z<=d!qM{+cIO)GfTSn^260OBk9_@IYD&`T31!3=9zu5I zpm3t|7kt1pL4fJI9k?FOrHA2i27)FSAru8X2gxu}=}_2l@rif7qEG3T$O8sFPWa$1 z+BMYB&PoU4#~%um#njleK|u3PjfS?LeNE}<&C9)(24fdN$p^-?2|gG&t~?%cVE!1L z`G}kjtNv{P(*Tt-eVoavyxE4mCxGE=?$~iJG3&o zf1K0OUY69Lg{_wWB&ZN;5*C7UCwLMf7}Uz>7-sS;@F|Uch0sH>AaU6X7k@@mJ$SL0 z_Q5vZ?4QL{kkDtrQuC42BE>|l8$u2%a3hUyaSdvi;)`vl^K1bOx|UFV(v`{4RWp_f6=p!6C>uQTcxr_Cs;-`p_K$| zJC(8)`3>^O`WfX%K7;@c4<)}Whad_RUBd}2QI+K53_L!GDEMU>{30R+b6z0zaL6*4 zM|qS;U;q~<&T>gv77)Fxm7m`WKT)K|i#vxFj8pkJJVrd*p_HQ{csZ+ zRG=S@dq&R2CMVsWP!ook8u$z_xGIn!N7JJ;96OzG{$;Cb zPAoCvC1_%$K#eF6vjDiua!5GH30dlrUbYB33}pSg!%qn_M~!^&fdZWK<_M0jiwcqQ zVeT1|a%rx1sCL(>6$a#+0Unl9&8D&>4Mc}?p3aAx6v+1=Km%407m=AB6kI^Ly5_hK zJd2}_2k=G)f&~x!61Q?biBS~4f*#bAx1g%s!zqyq7<@Ah>1f-dBUMo6v@W z7|>~^f#_?`)CAc2E)Y}`+yat+9wGAV`H6?*vB<&;cjwv1c1D7l#B16-GP}y^wP&rR zu{$m@i^m{Tdx$70YD^3eXlA}fqPtWk>=ujTd7*O>XCP_;9hNVYOZtU?6j8#3Jm>`k zPk%)FTr#CQsTx<*(#RJ-a^(yZjJ$FO%s)<~uk4Xu3>`JSX_YzU4u8tY%a4?FAFS{d z0Xu$#i2wu+PF~KNM+Y=Tk>_MY96fRf7idTq=gGk~G068Wvy@ik2_*2KfrBTW*oe1B zL|mjB{6XMPWXTZ{#Ni&)r55XHaqk&{Byho)+@oEA2YT$(3Be?1&Uy8nOF&yfx1F-$e%b$uYsQFe|vwp$sOlgTxmIX-VXOJ2@B>Kx_)FgAlURK0pgoq@lEU%Dj28%!uo?C*MkrMYuP@JdWXc6mh z*gEgxk||4m$ccP|oAX~Fz>}Mtu@o_<6?U^5Dz;`s158>%7C8tW1?>_Amein8a4xV^ z0`W!2u#iY7m%_+iKZFiCwJb(HF)CD!=o@i*BE=MsQ8AjPxhWLsC3^M0_<(_!3e~R> zs-+7f;+!EEi8sa#)sG3JDoD?hFo}mC-zx|x=ZD}+?dF}wMo;-eUoEmy(PokhI%!B8 zNL-KMW68 zd)8&Le$(acaWx5hS$if`hHj^7<>%#Glo1K5S*mFv?7BPW+Xa= zFLUxh+Tq~utjdq3C;bhMS8$; z77OwqDz3oemqVU}KZnzUsVLZ!?!={4TR*(UmTSPRqI2tr^ykZ>0Dp@^d<^cp*CBcu~AtP^>Vqa{KR&?=K5(O zod0}2Tn-bbCTlhM)};Qu7Hucqs$WZ|emN|^l2(|AJI!561c_rDlH_Z0RdkeZe0Yce zCtmcdf4Wz?DoSIMN~K&!FJ#@TUsgUfP$m_}i!ORETqPnDSC2C?Kvd92PPvxT&xAn_ zW|>93{zEBxk}J|p^zNnn5C+My6U-q&OC>=6$zd=97pelDK;`ton_efZt|nVQu-5v< z)&HoX&h*8Urp$T?{3SJL$c�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 z�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 iPtL7f|FkJhCBE5@0R}8mMv_#VNl2g%OJC;9 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 Yk?f?`a&5MUT8PgjC#5V|BW@D>Ut#hORR910 literal 0 HcmV?d00001 diff --git a/host/ide/src/img/function.png b/host/ide/src/img/function.png new file mode 100644 index 0000000000000000000000000000000000000000..f68381ff4ad756c6a3ddd60b2c219deb80aeb0cc GIT binary patch literal 398 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K$eet5b#hFAzD zCrD%*=s3%`F#P}K+CATGrhNQ=-)6#}dbekuf4nBG4$n6J=M>(b)NuV;xKRDSzkca| z{_8*fU(d#waigSw?{vu@A%R_!PrO6t?M4#e;=R5 zbkup`Qwd4QFhipc%enn_d|-5ynfZBNz|nvI|JQf_|NmdVTZb>%*vP;xDJ7v#bWX)@ z0VkdlzwSRc@af;*$8K!L>P7$mH-7L%%tQEs>VFA|8MjzjQ=}!N`Tis_a_+1CZ!x3u zqdj{-6;lAC-~;>TyZil{8o0t2YScxqi1@S3VI_x}M+bipdl{n-^MyiN2mZz#wF!n! zH4BvX{9~1NYLKX4K9I=ArPDJ1pq#eiiT~$+#8({JYuCZ}v+KbGK p;K&IM&Qc!L3YG@F1IpfeL>O{w61Uex$L4~<$kWx&Wt~$(69CUMlyCq5 literal 0 HcmV?d00001 diff --git a/host/ide/src/img/logo.png b/host/ide/src/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..725db32f13c43af3ede6c3e5fe2fa51e2c0ad263 GIT binary patch literal 3118 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 CK;eb} literal 0 HcmV?d00001 diff --git a/host/ide/src/img/pic.png b/host/ide/src/img/pic.png new file mode 100644 index 0000000000000000000000000000000000000000..5ed51ecbf5c94788e37a11485045558f8c64dd2e GIT binary patch literal 89971 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 T+CSvKcwPWGHQ6d@i-`XLdxT + + + Trace Example + + + + + + + + + diff --git a/host/ide/src/log/Log.ts b/host/ide/src/log/Log.ts new file mode 100644 index 0000000..d74cbc3 --- /dev/null +++ b/host/ide/src/log/Log.ts @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export enum LogLevel { + OFF = Number.MAX_VALUE, + ERROR = 4000, + WARN = 3000, + INFO = 2000, + DEBUG = 1000, + TRACE = 500, + ALL = Number.MIN_VALUE +} + +export const error = (message?: any, ...optionalParams: any[]) => { + SpLog.logger(LogLevel.ERROR, message, ...optionalParams); +}; +export const warn = (message?: any, ...optionalParams: any[]) => { + SpLog.logger(LogLevel.WARN, message, ...optionalParams); +}; +export const info = (message?: any, ...optionalParams: any[]) => { + SpLog.logger(LogLevel.INFO, message, ...optionalParams); +}; +export const debug = (message?: any, ...optionalParams: any[]) => { + SpLog.logger(LogLevel.DEBUG, message, ...optionalParams); +}; +export const trace = (message?: any, ...optionalParams: any[]) => { + SpLog.logger(LogLevel.TRACE, message, ...optionalParams); +}; +export const log = (message?: any) => { + SpLog.logger(LogLevel.TRACE, message) +} + +class SpLog { + private static nowLogLevel: LogLevel = LogLevel.ALL; + + public static getNowLogLevel(): LogLevel { + return this.nowLogLevel; + } + + public static setLogLevel(logLevel: LogLevel) { + SpLog.nowLogLevel = logLevel; + } + + public static logger(logLevel: LogLevel, message?: any, ...optionalParams: any[]) { + if (logLevel >= SpLog.nowLogLevel) { + switch (logLevel) { + case LogLevel.ERROR: + console.error(message, ...optionalParams) + break + case LogLevel.WARN: + console.warn(message, ...optionalParams) + break + case LogLevel.INFO: + console.info(message, ...optionalParams) + break + case LogLevel.DEBUG: + console.debug(message, ...optionalParams) + break + case LogLevel.TRACE: + console.trace(message, ...optionalParams) + break + default: + console.log(message) + } + } + } +} + + diff --git a/host/ide/src/trace/SpApplication.ts b/host/ide/src/trace/SpApplication.ts new file mode 100644 index 0000000..b5e8eff --- /dev/null +++ b/host/ide/src/trace/SpApplication.ts @@ -0,0 +1,619 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../base-ui/BaseElement.js"; +import "../base-ui/menu/LitMainMenu.js"; +import "../base-ui/icon/LitIcon.js"; +import {SpMetrics} from "./component/SpMetrics.js"; +import {SpQuerySQL} from "./component/SpQuerySQL.js"; +import "./component/SpQuerySQL.js"; +import {SpSystemTrace} from "./component/SpSystemTrace.js"; +import {LitMainMenu, MenuItem} from "../base-ui/menu/LitMainMenu.js"; +import {SpInfoAndStats} from "./component/SpInfoAndStas.js"; +import "../base-ui/progress-bar/LitProgressBar.js"; +import {LitProgressBar} from "../base-ui/progress-bar/LitProgressBar.js"; +import {SpRecordTrace} from "./component/SpRecordTrace.js"; +import {SpWelcomePage} from "./component/SpWelcomePage.js"; +import {LitSearch} from "./component/trace/search/Search.js"; +import {threadPool} from "./database/SqlLite.js"; +import "./component/trace/search/Search.js"; +import "./component/SpWelcomePage.js"; +import "./component/SpSystemTrace.js"; +import "./component/SpRecordTrace.js"; +import {TraceRow} from "./component/trace/base/TraceRow.js"; + +@element('sp-application') +export class SpApplication extends BaseElement { + static skinChange: Function | null | undefined = null; + static skinChange2: Function | null | undefined = null; + skinChangeArray: Array = []; + private icon: HTMLDivElement | undefined | null + private rootEL: HTMLDivElement | undefined | null + + static get observedAttributes() { + return ["server", "sqlite", "wasm", "dark", "vs", "query-sql"] + } + + get dark() { + return this.hasAttribute('dark'); + } + + set dark(value) { + if (value) { + this.rootEL!.classList.add('dark'); + this.setAttribute('dark', ''); + } else { + this.rootEL!.classList.remove('dark'); + this.removeAttribute('dark'); + } + if (this.skinChangeArray.length > 0) { + this.skinChangeArray.forEach((item) => item(value)); + } + if (SpApplication.skinChange) { + SpApplication.skinChange(value); + } + if (SpApplication.skinChange2) { + SpApplication.skinChange2(value); + } + } + + get vs(): boolean { + return this.hasAttribute("vs") + } + + set vs(isVs: boolean) { + if (isVs) { + this.setAttribute("vs", "") + } + } + + get sqlite(): boolean { + return this.hasAttribute("sqlite") + } + + get wasm(): boolean { + return this.hasAttribute("wasm") + } + + get server(): boolean { + return this.hasAttribute("server") + } + + get querySql(): boolean { + return this.hasAttribute("query-sql") + } + + set server(s: boolean) { + if (s) { + this.setAttribute('server', '') + } else { + this.removeAttribute('server') + } + } + + set search(search: boolean) { + if (search) { + this.setAttribute('search', '') + } else { + this.removeAttribute('search') + } + } + + addSkinListener(handler: Function) { + this.skinChangeArray.push(handler) + }; + + removeSkinListener(handler: Function) { + this.skinChangeArray.splice(this.skinChangeArray.indexOf(handler), 1); + }; + + initHtml(): string { + return ` + +
      + +
      + + +
      +
      + + + + +
      +
      + `; + } + + initElements() { + let that = this; + this.rootEL = this.shadowRoot!.querySelector(".root") + let spWelcomePage = this.shadowRoot!.querySelector("#sp-welcome") as SpWelcomePage + let spMetrics = new SpMetrics(); + let spSystemTrace = this.shadowRoot!.querySelector("#sp-system-trace") + let spInfoAndStats = new SpInfoAndStats(); + let spRecordTrace = this.shadowRoot!.querySelector("#sp-record-trace") + let spQuerySQL = this.shadowRoot!.querySelector("#sp-query-sql") + let appContent = this.shadowRoot?.querySelector('#app-content') as HTMLDivElement; + let mainMenu = this.shadowRoot?.querySelector('#main-menu') as LitMainMenu + let progressEL = this.shadowRoot?.querySelector('.progress') as LitProgressBar + let litSearch = this.shadowRoot?.querySelector('#lit-search') as LitSearch + let sidebarButton: HTMLDivElement | undefined | null = this.shadowRoot?.querySelector('.sidebar-button') + let childNodes = [spSystemTrace, spRecordTrace, spWelcomePage, spQuerySQL] + litSearch.addEventListener("focus", () => { + spSystemTrace!.keyboardEnable = false + }) + litSearch.addEventListener('blur', () => { + spSystemTrace!.keyboardEnable = true + }) + litSearch.addEventListener("previous-data", (ev: any) => { + litSearch.index = spSystemTrace!.showPreCpuStruct(litSearch.index,litSearch.list); + litSearch.blur(); + }) + litSearch.addEventListener("next-data", (ev: any) => { + litSearch.index = spSystemTrace!.showNextCpuStruct(litSearch.index,litSearch.list); + litSearch.blur(); + // spSystemTrace!.search(e.detail.value) + }) + litSearch.valueChangeHandler = (value: string) => { + if (value.length > 0) { + litSearch.list = spSystemTrace!.searchCPU(value); + } else { + litSearch.list = []; + spSystemTrace?.visibleRows.forEach(it => { + it.highlight = false; + it.draw(); + }); + spSystemTrace?.timerShaftEL?.removeTriangle("inverted"); + } + } + spSystemTrace?.addEventListener("previous-data", (ev: any) => { + litSearch.index = spSystemTrace!.showPreCpuStruct(litSearch.index, litSearch.list); + }) + spSystemTrace?.addEventListener("next-data", (ev: any) => { + litSearch.index = spSystemTrace!.showNextCpuStruct(litSearch.index, litSearch.list); + }) + //打开侧边栏 + sidebarButton!.onclick = (e) => { + let menu: HTMLDivElement | undefined | null = this.shadowRoot?.querySelector('#main-menu') + let menuButton: HTMLElement | undefined | null = this.shadowRoot?.querySelector('.sidebar-button') + if (menu) { + menu.style.width = `248px` + // @ts-ignore + menu.style.zIndex = 2000; + menu.style.display = `flex` + } + if (menuButton) { + menuButton.style.width = `0px` + } + } + let icon: HTMLDivElement | undefined | null = this.shadowRoot?.querySelector("#main-menu")?.shadowRoot?.querySelector("div.header > div") + icon!.onclick = (e) => { + let menu: HTMLElement | undefined | null = this.shadowRoot?.querySelector("#main-menu") + let menuButton: HTMLElement | undefined | null = this.shadowRoot?.querySelector('.sidebar-button') + if (menu) { + menu.style.width = `0px` + menu.style.display = `flex` + // @ts-ignore + menu.style.zIndex = 0 + } + if (menuButton) { + menuButton.style.width = `48px` + } + } + + function showContent(showNode: HTMLElement) { + childNodes.forEach((node) => { + if (node === showNode) { + showNode.style.visibility = 'visible' + } else { + node!.style.visibility = 'hidden' + } + }) + } + + function openTraceFile(ev: any) { + litSearch.clear(); + showContent(spSystemTrace!) + that.search = true + progressEL.loading = true + let fileName = (ev as any).name + let fileSize = ((ev as any).size / 1000000).toFixed(1) + document.title = `${fileName.substring(0, fileName.lastIndexOf('.'))} (${fileSize}M)` + if (that.server) { + threadPool.init("server").then(() => { + litSearch.setPercent("parse trace", 1); + // Load the trace file and send it to the background parse to return the db file path + const fd = new FormData() + that.freshMenuDisable(true) + fd.append('file', ev as any) + let uploadPath = `https://${window.location.host.split(':')[0]}:9001/upload` + if (that.vs) { + uploadPath = `http://${window.location.host.split(':')[0]}:${window.location.port}/upload` + } + fetch(uploadPath, { + method: 'POST', + body: fd, + }).then(res => { + litSearch.setPercent("load database", 5); + if (res.ok) { + let menus = [ + { + title: `${fileName.substring(0, fileName.lastIndexOf('.'))} (${fileSize}M)`, + icon: "file-fill", + clickHandler: function () { + that.search = true + showContent(spSystemTrace!) + } + } + ]; + if (that.querySql) { + menus.push({ + title: "Query (SQL)", icon: "file", clickHandler: () => { + showContent(spQuerySQL!) + } + }); + } + mainMenu.menus!.splice(1, 1, { + collapsed: false, + title: "Current Trace", + describe: "Actions on the current trace", + children: menus + }) + + that.freshMenuDisable(true) + return res.text(); + } else { + if (res.status == 404) { + litSearch.setPercent("This File is not supported!", -1) + progressEL.loading = false; + that.freshMenuDisable(false) + return Promise.reject(); + } + } + }).then(res => { + if (res != undefined) { + let loadPath = `https://${window.location.host.split(':')[0]}:9001` + if (that.vs) { + loadPath = `http://${window.location.host.split(':')[0]}:${window.location.port}` + } + spSystemTrace!.loadDatabaseUrl(loadPath + res, (command: string, percent: number) => { + litSearch.setPercent(command + ' ', percent); + }, (res) => { + litSearch.setPercent("", 101); + progressEL.loading = false; + that.freshMenuDisable(false) + }) + } else { + litSearch.setPercent("", 101) + progressEL.loading = false; + that.freshMenuDisable(false) + } + + }) + }) + return; + } + if (that.sqlite) { + litSearch.setPercent("", 0); + threadPool.init("sqlite").then(res => { + let reader = new FileReader(); + reader.readAsArrayBuffer(ev as any) + reader.onloadend = function (ev) { + spSystemTrace!.loadDatabaseArrayBuffer(this.result as ArrayBuffer, (command: string, percent: number) => { + litSearch.setPercent(command + ' ', percent); + }, () => { + litSearch.setPercent("", 101); + progressEL.loading = false; + that.freshMenuDisable(false) + }) + } + }) + return; + } + if (that.wasm) { + litSearch.setPercent("", 1); + threadPool.init("wasm").then(res => { + let reader = new FileReader(); + reader.readAsArrayBuffer(ev as any) + reader.onloadend = function (ev) { + litSearch.setPercent("ArrayBuffer loaded ", 2); + that.freshMenuDisable(true) + let menus = [ + { + title: `${fileName.substring(0, fileName.lastIndexOf('.'))} (${fileSize}M)`, + icon: "file-fill", + clickHandler: function () { + that.search = true + showContent(spSystemTrace!) + } + } + ]; + if (that.querySql) { + menus.push({ + title: "Query (SQL)", icon: "file", clickHandler: () => { + showContent(spQuerySQL!) + } + }); + } + mainMenu.menus!.splice(1, 1, { + collapsed: false, + title: "Current Trace", + describe: "Actions on the current trace", + children: menus + }) + spSystemTrace!.loadDatabaseArrayBuffer(this.result as ArrayBuffer, (command: string, percent: number) => { + litSearch.setPercent(command + ' ', percent); + }, (res) => { + if (res.status) { + litSearch.setPercent("", 101); + progressEL.loading = false; + that.freshMenuDisable(false) + } else { + litSearch.setPercent("This File is not supported!", -1) + progressEL.loading = false; + that.freshMenuDisable(false) + mainMenu.menus!.splice(1, 1); + mainMenu.menus = mainMenu.menus!; + } + }) + } + }) + return; + } + } + + mainMenu.menus = [ + { + collapsed: false, + title: 'Navigation', + describe: 'Open or record a new trace', + children: [ + { + title: "Open trace file", + icon: "folder", + fileChoose: true, + fileHandler: function (ev: InputEvent) { + openTraceFile(ev.detail as any); + } + }, + { + title: "Record new trace", icon: "copyhovered", clickHandler: function (item: MenuItem) { + that.search = false + showContent(spRecordTrace!) + } + } + ] + }, + ] + + let body = document.querySelector("body"); + body!.addEventListener('dragover', (e: any) => { + e.preventDefault(); + e.stopPropagation(); + if (e.dataTransfer.items.length > 0 && e.dataTransfer.items[0].kind === "file") { + e.dataTransfer.dropEffect = 'copy'; + if (!this.rootEL!.classList.contains('filedrag')) { + this.rootEL!.classList.add("filedrag") + } + } + }, false); + body!.addEventListener("dragleave", (e) => { + e.stopPropagation(); + e.preventDefault(); + if (this.rootEL!.classList.contains('filedrag')) { + this.rootEL!.classList.remove("filedrag") + } + }, false); + body!.addEventListener('drop', (e: any) => { + e.preventDefault(); + e.stopPropagation(); + if (this.rootEL!.classList.contains('filedrag')) { + this.rootEL!.classList.remove("filedrag") + } + if (e.dataTransfer.items !== undefined && e.dataTransfer.items.length > 0) { + let item = e.dataTransfer.items[0]; + if (item.webkitGetAsEntry()?.isFile) { + openTraceFile(item.getAsFile()); + } else if (item.webkitGetAsEntry()?.isDirectory) { + litSearch.setPercent("This File is not supported!", -1) + progressEL.loading = false; + that.freshMenuDisable(false) + mainMenu.menus!.splice(1, 1); + mainMenu.menus = mainMenu.menus!; + spSystemTrace!.reset(null); + } + } + }, false); + } + + freshMenuDisable(disable: boolean) { + let mainMenu = this.shadowRoot?.querySelector('#main-menu') as LitMainMenu + // @ts-ignore + mainMenu.menus[0].children[0].disabled = disable + mainMenu.menus = mainMenu.menus; + } +} diff --git a/host/ide/src/trace/bean/BaseStruct.ts b/host/ide/src/trace/bean/BaseStruct.ts new file mode 100644 index 0000000..eccd75c --- /dev/null +++ b/host/ide/src/trace/bean/BaseStruct.ts @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Rect} from "../component/trace/timer-shaft/Rect.js"; + +export class BaseStruct { + frame: Rect | undefined + isHover: boolean = false; +} diff --git a/host/ide/src/trace/bean/BinderArgBean.ts b/host/ide/src/trace/bean/BinderArgBean.ts new file mode 100644 index 0000000..b119d21 --- /dev/null +++ b/host/ide/src/trace/bean/BinderArgBean.ts @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class BinderArgBean { + argset: number | undefined + keyName: string | undefined + id: number | undefined + desc: string | undefined + strValue: string | undefined +} \ No newline at end of file diff --git a/host/ide/src/trace/bean/BoxSelection.ts b/host/ide/src/trace/bean/BoxSelection.ts new file mode 100644 index 0000000..788e3c1 --- /dev/null +++ b/host/ide/src/trace/bean/BoxSelection.ts @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class SelectionParam { + cpus: Array = []; + threadIds: Array = []; + trackIds: Array = []; + funTids: Array = []; + heapIds: Array = []; + nativeMemory:Array = []; + leftNs: number = 0; + rightNs: number = 0; + hasFps: boolean = false; + statisticsSelectData:any = undefined +} + +export class BoxJumpParam { + leftNs: number = 0; + rightNs: number = 0; + state: string = ""; + processId: number = 0; + threadId: number = 0; +} + +export class SelectionData { + name: string = "" + process: string = "" + pid: string = "" + thread: string = "" + tid: string = "" + wallDuration: number = 0 + avgDuration: string = "" + occurrences: number = 0 + state: string = "" + trackId: number = 0 + delta: string = "" + rate: string = "" + avgWeight: string = "" + count: string = "" + first: string = "" + last: string = "" + min: string = "" + max: string = "" + stateJX: string = "" +} + +export class Counter { + id: number = 0 + trackId: number = 0 + name: string = "" + value: number = 0 + startTime: number = 0 +} + +export class Fps { + startNS: number = 0 + timeStr: string = "" + fps: number = 0 +} \ No newline at end of file diff --git a/host/ide/src/trace/bean/CpuFreqStruct.ts b/host/ide/src/trace/bean/CpuFreqStruct.ts new file mode 100644 index 0000000..19d3ed7 --- /dev/null +++ b/host/ide/src/trace/bean/CpuFreqStruct.ts @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseStruct} from "./BaseStruct.js"; +import {ColorUtils} from "../database/ProcedureWorkerCommon.js"; + +export class CpuFreqStruct extends BaseStruct { + static maxFreq: number = 0 + static maxFreqName: string = "0 GHz" + static hoverCpuFreqStruct: CpuFreqStruct | undefined; + static selectCpuFreqStruct: CpuFreqStruct | undefined; + cpu: number | undefined + value: number | undefined + startNS: number | undefined + dur: number | undefined // Self-supplementing, the database is not returned + + static draw(ctx: any, data: CpuFreqStruct) { + if (data.frame) { + let width = data.frame.width || 0; + let index = data.cpu || 0 + index += 2 + ctx.fillStyle = ColorUtils.colorForTid(index) + ctx.strokeStyle = ColorUtils.colorForTid(index) + if (data.startNS === CpuFreqStruct.hoverCpuFreqStruct?.startNS) { + ctx.lineWidth = 1; + ctx.globalAlpha = 0.6; + let drawHeight: number = Math.floor(((data.value || 0) * (data.frame.height || 0) * 1.0) / CpuFreqStruct.maxFreq); + ctx.fillRect(data.frame.x, data.frame.y + data.frame.height - drawHeight, width, drawHeight) + ctx.beginPath() + ctx.arc(data.frame.x, data.frame.y + data.frame.height - drawHeight, 3, 0, 2 * Math.PI, true) + ctx.fill() + ctx.globalAlpha = 1.0; + ctx.stroke(); + ctx.beginPath() + ctx.moveTo(data.frame.x + 3, data.frame.y + data.frame.height - drawHeight); + ctx.lineWidth = 3; + ctx.lineTo(data.frame.x + width, data.frame.y + data.frame.height - drawHeight) + ctx.stroke(); + } else { + ctx.globalAlpha = 0.6; + ctx.lineWidth = 1; + let drawHeight: number = Math.floor(((data.value || 0) * (data.frame.height || 0)) / CpuFreqStruct.maxFreq); + ctx.fillRect(data.frame.x, data.frame.y + data.frame.height - drawHeight, width, drawHeight) + } + } + ctx.globalAlpha = 1.0; + ctx.lineWidth = 1; + } +} + +const textPadding = 2; + diff --git a/host/ide/src/trace/bean/CpuStruct.ts b/host/ide/src/trace/bean/CpuStruct.ts new file mode 100644 index 0000000..3a86a4d --- /dev/null +++ b/host/ide/src/trace/bean/CpuStruct.ts @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {ColorUtils} from "../component/trace/base/ColorUtils.js"; +import {BaseStruct} from "./BaseStruct.js"; +import {WakeupBean} from "./WakeupBean.js"; + +export class CpuStruct extends BaseStruct { + static cpuCount: number //最大cpu数量 + static hoverCpuStruct: CpuStruct | undefined; + static selectCpuStruct: CpuStruct | undefined; + static wakeupBean: WakeupBean | null | undefined = null; + cpu: number | undefined + dur: number | undefined + end_state: string | undefined + id: number | undefined + name: string | undefined + priority: number | undefined + processCmdLine: string | undefined + processId: number | undefined + processName: string | undefined + schedId: number | undefined + startTime: number | undefined + tid: number | undefined + type: string | undefined + + static draw(ctx: CanvasRenderingContext2D, data: CpuStruct) { + if (data.frame) { + let width = data.frame.width || 0; + if (data.processId === CpuStruct.hoverCpuStruct?.processId || !CpuStruct.hoverCpuStruct) { + ctx.fillStyle = ColorUtils.colorForTid((data.processId || 0) > 0 ? (data.processId || 0) : (data.tid || 0)) + } else { + ctx.fillStyle = "#e0e0e0" + } + ctx.fillRect(data.frame.x, data.frame.y, width, data.frame.height) + if (width > textPadding * 2) { + let process = `${(data.processName || "Process")} [${data.processId}]` + let thread = `${data.name || "Thread"} [${data.tid}]` + let processMeasure = ctx.measureText(process); + let threadMeasure = ctx.measureText(thread); + let processCharWidth = Math.round(processMeasure.width / process.length) + let threadCharWidth = Math.round(threadMeasure.width / thread.length) + ctx.fillStyle = "#ffffff" + let y = data.frame.height / 2 + data.frame.y; + if (processMeasure.width < width - textPadding * 2) { + let x1 = Math.floor(width / 2 - processMeasure.width / 2 + data.frame.x + textPadding) + ctx.textBaseline = "bottom"; + ctx.fillText(process, x1, y, width - textPadding * 2) + } else if (width - textPadding * 2 > processCharWidth * 4) { + let chatNum = (width - textPadding * 2) / processCharWidth; + let x1 = data.frame.x + textPadding + ctx.textBaseline = "bottom"; + ctx.fillText(process.substring(0, chatNum - 4) + '...', x1, y, width - textPadding * 2) + } + if (threadMeasure.width < width - textPadding * 2) { + ctx.textBaseline = "top"; + let x2 = Math.floor(width / 2 - threadMeasure.width / 2 + data.frame.x + textPadding) + ctx.fillText(thread, x2, y + 2, width - textPadding * 2) + } else if (width - textPadding * 2 > threadCharWidth * 4) { + let chatNum = (width - textPadding * 2) / threadCharWidth; + let x1 = data.frame.x + textPadding + ctx.textBaseline = "top"; + ctx.fillText(thread.substring(0, chatNum - 4) + '...', x1, y + 2, width - textPadding * 2) + } + } + if (CpuStruct.selectCpuStruct && CpuStruct.equals(CpuStruct.selectCpuStruct, data)) { + ctx.strokeStyle = '#232c5d' + ctx.lineWidth = 2 + ctx.strokeRect(data.frame.x, data.frame.y, width - 2, data.frame.height) + } + } + } + + static equals(d1: CpuStruct, d2: CpuStruct): boolean { + if (d1 && d2 && d1.cpu == d2.cpu && + d1.tid == d2.tid && + d1.processId == d2.processId && + d1.startTime == d2.startTime && + d1.dur == d2.dur) { + return true; + } else { + return false; + } + } +} + +const textPadding = 2; + diff --git a/host/ide/src/trace/bean/CpuUsage.ts b/host/ide/src/trace/bean/CpuUsage.ts new file mode 100644 index 0000000..63a6e8b --- /dev/null +++ b/host/ide/src/trace/bean/CpuUsage.ts @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class CpuUsage { + cpu: number = 0 + usage: number = 0 + usageStr: string = "" + top1: number = 0 + top2: number = 0 + top3: number = 0 + top1Percent: number = 0 + top1PercentStr: string = "" + top2Percent: number = 0 + top2PercentStr: string = "" + top3Percent: number = 0 + top3PercentStr: string = "" +} + +export class Freq { + cpu: number = 0 + value: number = 0 + startNs: number = 0 + dur: number = 0 +} \ No newline at end of file diff --git a/host/ide/src/trace/bean/FpsStruct.ts b/host/ide/src/trace/bean/FpsStruct.ts new file mode 100644 index 0000000..f4e97dc --- /dev/null +++ b/host/ide/src/trace/bean/FpsStruct.ts @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Rect} from "../component/trace/timer-shaft/Rect.js"; +import {BaseStruct} from "./BaseStruct.js"; + +import {ns2x} from "../component/trace/TimerShaftElement.js"; + +export class FpsStruct extends BaseStruct { + static maxFps: number = 0 + static maxFpsName: string = "0 FPS" + static hoverFpsStruct: FpsStruct | undefined; + static selectFpsStruct: FpsStruct | undefined; + fps: number | undefined + startNS: number | undefined = 0 + dur: number | undefined //自补充,数据库没有返回 + + static draw(ctx: CanvasRenderingContext2D, data: FpsStruct) { + if (data.frame) { + let width = data.frame.width || 0; + ctx.fillStyle = '#535da6' + ctx.strokeStyle = '#535da6' + if (data.startNS === FpsStruct.hoverFpsStruct?.startNS) { + ctx.lineWidth = 1; + ctx.globalAlpha = 0.6; + let drawHeight: number = ((data.fps || 0) * (data.frame.height || 0) * 1.0) / FpsStruct.maxFps; + ctx.fillRect(data.frame.x, data.frame.y + data.frame.height - drawHeight, width, drawHeight) + ctx.beginPath() + ctx.arc(data.frame.x, data.frame.y + data.frame.height - drawHeight, 3, 0, 2 * Math.PI, true) + ctx.fill() + ctx.globalAlpha = 1.0; + ctx.stroke(); + ctx.beginPath() + ctx.moveTo(data.frame.x + 3, data.frame.y + data.frame.height - drawHeight); + ctx.lineWidth = 3; + ctx.lineTo(data.frame.x + width, data.frame.y + data.frame.height - drawHeight) + ctx.stroke(); + } else { + ctx.globalAlpha = 0.6; + ctx.lineWidth = 1; + let drawHeight: number = ((data.fps || 0) * (data.frame.height || 0) * 1.0) / FpsStruct.maxFps; + ctx.fillRect(data.frame.x, data.frame.y + data.frame.height - drawHeight, width, drawHeight) + } + } + ctx.globalAlpha = 1.0; + ctx.lineWidth = 1; + } + + static setFrame(node: FpsStruct, padding: number, startNS: number, endNS: number, totalNS: number, frame: Rect) { + let x1: number, x2: number; + if ((node.startNS || 0) < startNS) { + x1 = 0; + } else { + x1 = ns2x((node.startNS || 0), startNS, endNS, totalNS, frame); + } + if ((node.startNS || 0) + (node.dur || 0) > endNS) { + x2 = frame.width; + } else { + x2 = ns2x((node.startNS || 0) + (node.dur || 0), startNS, endNS, totalNS, frame); + } + let getV: number = x2 - x1 <= 1 ? 1 : x2 - x1; + let rectangle: Rect = new Rect(Math.floor(x1), Math.ceil(frame.y + padding), Math.ceil(getV), Math.floor(frame.height - padding * 2)); + node.frame = rectangle; + } +} + +const textPadding = 2; + diff --git a/host/ide/src/trace/bean/FuncStruct.ts b/host/ide/src/trace/bean/FuncStruct.ts new file mode 100644 index 0000000..d4b2ca9 --- /dev/null +++ b/host/ide/src/trace/bean/FuncStruct.ts @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import {BaseStruct} from "./BaseStruct.js"; +import {Rect} from "../component/trace/timer-shaft/Rect.js"; +import {ColorUtils} from "../component/trace/base/ColorUtils.js"; + +export class FuncStruct extends BaseStruct { + static hoverFuncStruct: FuncStruct | undefined; + static selectFuncStruct: FuncStruct | undefined; + argsetid: number | undefined + depth: number | undefined + dur: number | undefined + funName: string | undefined + id: number | undefined + is_main_thread: number | undefined + parent_id: number | undefined + startTs: number | undefined + threadName: string | undefined + tid: number | undefined + track_id: number | undefined + + static draw(ctx: CanvasRenderingContext2D, data: FuncStruct) { + if (data.frame) { + if (data.dur == undefined || data.dur == null || data.dur == 0 || FuncStruct.isBinder(data)) { + } else { + let width = data.frame.width || 0; + ctx.fillStyle = ColorUtils.FUNC_COLOR[data.depth || 0 % ColorUtils.FUNC_COLOR.length] + let miniHeight = 20 + ctx.fillRect(data.frame.x, data.frame.y, data.frame.width, miniHeight - padding * 2) + ctx.fillStyle = "#fff" + FuncStruct.drawString(ctx, data.funName || '', 5, data.frame) + if (FuncStruct.isSelected(data)) { + ctx.strokeStyle = "#000" + ctx.lineWidth = 1 + ctx.strokeRect(data.frame.x, data.frame.y, data.frame.width, miniHeight - padding * 2) + } + } + } + } + + static drawString(ctx: CanvasRenderingContext2D, str: string, textPadding: number, frame: Rect) { + let textMetrics = ctx.measureText(str); + let charWidth = Math.round(textMetrics.width / str.length) + if (textMetrics.width < frame.width - textPadding * 2) { + let x2 = Math.floor(frame.width / 2 - textMetrics.width / 2 + frame.x + textPadding) + ctx.fillText(str, x2, Math.floor(frame.y + frame.height / 2 + 2), frame.width - textPadding * 2) + return; + } + if (frame.width - textPadding * 2 > charWidth * 4) { + let chatNum = (frame.width - textPadding * 2) / charWidth; + let x1 = frame.x + textPadding + ctx.fillText(str.substring(0, chatNum - 4) + '...', x1, Math.floor(frame.y + frame.height / 2 + 2), frame.width - textPadding * 2) + return; + } + } + + static isSelected(data: FuncStruct): boolean { + return (FuncStruct.selectFuncStruct != undefined && + FuncStruct.selectFuncStruct.startTs == data.startTs && + FuncStruct.selectFuncStruct.dur == data.dur && + FuncStruct.selectFuncStruct.funName == data.funName) + } + + static isBinder(data: FuncStruct): boolean { + if (data.funName != null && + ( + data.funName.toLowerCase().startsWith("binder transaction") + || data.funName.toLowerCase().startsWith("binder async") + || data.funName.toLowerCase().startsWith("binder reply") + ) + ) { + return true; + } else { + return false; + } + } +} + +const padding = 1; diff --git a/host/ide/src/trace/bean/HeapBean.ts b/host/ide/src/trace/bean/HeapBean.ts new file mode 100644 index 0000000..ec7541f --- /dev/null +++ b/host/ide/src/trace/bean/HeapBean.ts @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class HeapBean{ + MoudleName:string|undefined + AllocationFunction:string|undefined + Allocations:number|string = 0 + Deallocations:number|string= 0 + AllocationSize:number|string= 0 + DeAllocationSize:number|string= 0 + Total:number|string= 0 + RemainingSize:number|string= 0 + children:HeapBean[] = [] + depth:number = 0 +} \ No newline at end of file diff --git a/host/ide/src/trace/bean/HeapStruct.ts b/host/ide/src/trace/bean/HeapStruct.ts new file mode 100644 index 0000000..123ed31 --- /dev/null +++ b/host/ide/src/trace/bean/HeapStruct.ts @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseStruct} from "./BaseStruct.js"; +import {ns2x} from "../component/trace/TimerShaftElement.js"; +import {Rect} from "../component/trace/timer-shaft/Rect.js"; + +export class HeapStruct extends BaseStruct { + static hoverHeapStruct: HeapStruct | undefined; + startTime: number | undefined + endTime: number | undefined + dur: number | undefined + eventType:string | undefined + heapsize: number | undefined + maxHeapSize: number = 0 + minHeapSize: number = 0 + + static setFrame(node: HeapStruct, padding: number, startNS: number, endNS: number, totalNS: number, frame: Rect) { + let x1: number, x2: number; + if ((node.startTime || 0) < startNS) { + x1 = 0; + } else { + x1 = ns2x((node.startTime || 0), startNS, endNS, totalNS, frame); + } + if ((node.startTime || 0) + (node.dur || 0) > endNS) { + x2 = frame.width; + } else { + // @ts-ignore + x2 = ns2x(node.startTime + node.dur, startNS, endNS, totalNS, frame); + } + let getV: number = x2 - x1 <= 1 ? 1 : x2 - x1; + let rectangle: Rect = new Rect(Math.floor(x1), Math.ceil(frame.y + padding), Math.ceil(getV), Math.floor(frame.height - padding * 2)); + node.frame = rectangle; + } + + static draw(ctx: CanvasRenderingContext2D, data: HeapStruct) { + if (data.frame) { + let width = data.frame.width || 0; + ctx.fillStyle = "#2db3aa" + ctx.strokeStyle = "#2db3aa" + if (data.startTime === HeapStruct.hoverHeapStruct?.startTime) { + ctx.lineWidth = 1; + ctx.globalAlpha = 0.6; + let drawHeight: number = Math.ceil(((data.heapsize || 0) * (data.frame.height || 0)) / data.maxHeapSize); + ctx.fillRect(data.frame.x, data.frame.y + data.frame.height - drawHeight, width, drawHeight) + ctx.beginPath() + ctx.arc(data.frame.x, data.frame.y + data.frame.height - drawHeight, 3, 0, 2 * Math.PI, true) + ctx.fill() + ctx.globalAlpha = 1.0; + ctx.stroke(); + ctx.beginPath() + ctx.moveTo(data.frame.x + 3, data.frame.y + data.frame.height - drawHeight); + ctx.lineWidth = 3; + ctx.lineTo(data.frame.x + width, data.frame.y + data.frame.height - drawHeight) + ctx.stroke(); + } else { + ctx.globalAlpha = 0.6; + ctx.lineWidth = 1; + let drawHeight: number = Math.ceil(((data.heapsize || 0) * (data.frame.height || 0)) / data.maxHeapSize); + ctx.fillRect(data.frame.x, data.frame.y + data.frame.height - drawHeight, width, drawHeight) + } + } + ctx.globalAlpha = 1.0; + ctx.lineWidth = 1; + } +} \ No newline at end of file diff --git a/host/ide/src/trace/bean/HeapTreeDataBean.ts b/host/ide/src/trace/bean/HeapTreeDataBean.ts new file mode 100644 index 0000000..a155a58 --- /dev/null +++ b/host/ide/src/trace/bean/HeapTreeDataBean.ts @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class HeapTreeDataBean{ + MoudleName:string|undefined + AllocationFunction:string|undefined + startTs:number = 0 + endTs:number = 0 + eventType:string|undefined + depth:number = 0 + heapSize:number = 0 + eventId:string="" +} \ No newline at end of file diff --git a/host/ide/src/trace/bean/NativeHook.ts b/host/ide/src/trace/bean/NativeHook.ts new file mode 100644 index 0000000..99168e0 --- /dev/null +++ b/host/ide/src/trace/bean/NativeHook.ts @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Utils} from "../component/trace/base/Utils.js"; + +export class NativeHookStatistics{ + eventId:number = 0; + eventType:string = ""; + subType:string = ""; + heapSize:number = 0; + addr:string = ""; + startTs:number = 0; + endTs:number = 0; + sumHeapSize:number = 0; + max:number = 0; + count:number = 0; + tid:number = 0; + isSelected:boolean = false; +} + +export class NativeHookMalloc{ + eventType:string = ""; + subType:string = ""; + heapSize:number = 0; + allocByte:number = 0; + allocCount:number = 0; + freeByte:number = 0; + freeCount:number = 0; +} + +export class NativeEventHeap{ + eventType:string = ""; + sumHeapSize:number = 0 +} + +export class NativeHookProcess{ + ipid:number = 0; + pid:number = 0; + name:String = "" +} + +export class NativeHookStatisticsTableData{ + memoryTap:string = ""; + existing:number = 0; + existingString:string = ""; + allocCount:number = 0; + freeCount:number = 0; + totalBytes:number = 0 + totalBytesString:string = ""; + maxStr:string = ""; + max:number = 0 + totalCount:number = 0; + existingValue:Array = []; +} + +export class NativeMemory{ + index:number = 0; + eventId:number = 0; + eventType:string = ""; + subType:string = ""; + addr:string = ""; + startTs:number = 0; + timestamp:string = "" + heapSize:number = 0; + heapSizeUnit:string = ""; + symbol:string = ""; + library:string = ""; +} + +export class NativeHookCallInfo{ + id:string = ""; + pid:string | undefined; + threadId:number = 0; + symbol:string = ""; + library:string = ""; + title:string = ""; + count:number = 0; + type:number = 0; + heapSize:number = 0; + heapSizeStr:string = ""; + eventId:number = 0 + depth:number = 0; + children:Array = []; +} + +export class NativeHookSamplerInfo { + current:string = "" + currentSize:number = 0 + startTs:number = 0; + heapSize:number = 0; + snapshot:string = ""; + growth:string = ""; + total:number = 0; + totalGrowth:string = "" + existing:number = 0; + children:Array = []; + tempList:Array = []; + timestamp:string = "" + eventId:number = -1 + merageObj(merageObj:NativeHookSamplerInfo){ + this.currentSize+=merageObj.currentSize + this.heapSize += merageObj.heapSize + this.existing += merageObj.existing + this.total += merageObj.total + this.growth = Utils.getByteWithUnit(this.heapSize) + this.current = Utils.getByteWithUnit(this.currentSize) + this.totalGrowth = Utils.getByteWithUnit(this.total) + } +} + +export class NativeHookSampleQueryInfo { + eventId:number = -1 + current:number = 0 + eventType:string = ""; + subType:string = ""; + growth:number = 0; + existing:number = 0; + addr:string = ""; + startTs:number = 0; + endTs:number = 0; + total:number = 0; + children:Array = []; +} \ No newline at end of file diff --git a/host/ide/src/trace/bean/ProcessMemStruct.ts b/host/ide/src/trace/bean/ProcessMemStruct.ts new file mode 100644 index 0000000..636722b --- /dev/null +++ b/host/ide/src/trace/bean/ProcessMemStruct.ts @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseStruct} from "./BaseStruct.js"; +import {ColorUtils} from "../component/trace/base/ColorUtils.js"; + +export class ProcessMemStruct extends BaseStruct { + trackId: number | undefined + processName: string | undefined + pid: number | undefined + upid: number | undefined + trackName: string | undefined + type: string | undefined + track_id: string | undefined + value: number | undefined + startTime: number | undefined + duration: number | undefined + maxValue: number | undefined + delta: number | undefined; + + static draw(ctx: CanvasRenderingContext2D, data: ProcessMemStruct) { + if (data.frame) { + let width = data.frame.width || 0; + ctx.fillStyle = ColorUtils.colorForTid(data.maxValue || 0) + ctx.strokeStyle = ColorUtils.colorForTid(data.maxValue || 0) + ctx.globalAlpha = 0.6; + ctx.lineWidth = 1; + let drawHeight: number = ((data.value || 0) * (data.frame.height || 0) * 1.0) / (data.maxValue || 1); + ctx.fillRect(data.frame.x, data.frame.y + data.frame.height - drawHeight, width, drawHeight) + } + ctx.globalAlpha = 1.0; + ctx.lineWidth = 1; + } +} \ No newline at end of file diff --git a/host/ide/src/trace/bean/ProcessStruct.ts b/host/ide/src/trace/bean/ProcessStruct.ts new file mode 100644 index 0000000..f9844f7 --- /dev/null +++ b/host/ide/src/trace/bean/ProcessStruct.ts @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {ColorUtils} from "../component/trace/base/ColorUtils.js"; +import {CpuStruct} from "./CpuStruct.js"; +import {BaseStruct} from "./BaseStruct.js"; + +const padding = 1; + +export class ProcessStruct extends BaseStruct { + cpu: number | undefined + dur: number | undefined + id: number | undefined + pid: number | undefined + process: string | undefined + startTime: number | undefined + state: string | undefined + thread: string | undefined + tid: number | undefined + ts: number | undefined + type: string | undefined + utid: number | undefined + + static draw(ctx: CanvasRenderingContext2D, data: ProcessStruct) { + if (data.frame) { + let width = data.frame.width || 0; + ctx.fillStyle = ColorUtils.colorForTid(data.pid || 0) + let miniHeight = Math.round(data.frame.height / CpuStruct.cpuCount) + ctx.fillRect(data.frame.x, data.frame.y + (data.cpu || 0) * miniHeight + padding, data.frame.width, miniHeight - padding * 2) + } + } +} \ No newline at end of file diff --git a/host/ide/src/trace/bean/StateProcessThread.ts b/host/ide/src/trace/bean/StateProcessThread.ts new file mode 100644 index 0000000..642d399 --- /dev/null +++ b/host/ide/src/trace/bean/StateProcessThread.ts @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class StateProcessThread { + id: string = "" + pid: string = "" + title: string | null | undefined = "" + children: Array = [] + process: string = "" + processId: number = -1 + thread: string = "" + threadId: number = -1 + state: string = "" + wallDuration: number = 0 + avgDuration: string = "" + count: number = 0 + minDuration: number = 0 + maxDuration: number = 0 + stdDuration: string = "" +} + +export class SPTChild { + process: string = "" + processId: number = 0 + processName: string = "" + thread: string = "" + threadId: number = 0 + threadName: string = "" + state: string = "" + startNs: number = 0 + startTime: string = "" + duration: number = 0 + cpu: number | undefined = undefined + core: string = "" + priority: number = 0 + prior: string = "" + note: string = "-" +} + +export class SPT { + process: string = "" + processId: number = 0 + thread: string = "" + threadId: number = 0 + state: string = "" + dur: number = 0 + start_ts: number = 0 + end_ts: number = 0 + cpu: number = 0; + priority: string = "-" + note: string = "-" +} \ No newline at end of file diff --git a/host/ide/src/trace/bean/ThreadStruct.ts b/host/ide/src/trace/bean/ThreadStruct.ts new file mode 100644 index 0000000..ade6a0a --- /dev/null +++ b/host/ide/src/trace/bean/ThreadStruct.ts @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseStruct} from "./BaseStruct.js"; +import {Rect} from "../component/trace/timer-shaft/Rect.js"; + +const padding = 1; + +export class ThreadStruct extends BaseStruct { + static runningColor: string = "#467b3b"; + static rColor = "#a0b84d"; + static uninterruptibleSleepColor = "#f19d38"; + static sColor = "#FBFBFB"; + static hoverThreadStruct: ThreadStruct | undefined; + static selectThreadStruct: ThreadStruct | undefined; + static statusMap: any = { + "D": "Uninterruptible Sleep", + "S": "Sleeping", + "R": "Runnable", + "Running": "Running", + "R+": "Runnable (Preempted)", + "DK": "Uninterruptible Sleep + Wake Kill", + "I": "Task Dead", + "T": "Stopped", + "t": "Traced", + "X": "Exit (Dead)", + "Z": "Exit (Zombie)", + "K": "Wake Kill", + "W": "Waking", + "P": "Parked", + "N": "No Load" + } + hasSched: number | undefined; + pid: number | undefined; + processName: string | undefined; + threadName: string | undefined; + tid: number | undefined; + upid: number | undefined; + utid: number | undefined; + cpu: number | undefined; + dur: number | undefined; + end_ts: number | undefined; + id: number | undefined; + is_main_thread: number | undefined; + name: string | undefined; + startTime: number | undefined; + start_ts: number | undefined; + state: string | undefined; + type: string | undefined; + + static draw(ctx: CanvasRenderingContext2D, data: ThreadStruct) { + if (data.frame) { + ctx.globalAlpha = 1 + let stateText = data.state || ''; + if ("S" == data.state) { + ctx.fillStyle = ThreadStruct.sColor; + ctx.globalAlpha = 0.2; // transparency + ctx.fillRect(data.frame.x, data.frame.y + padding, data.frame.width, data.frame.height - padding * 2) + ctx.globalAlpha = 1; // transparency + } else if ("R" == data.state) { + ctx.fillStyle = ThreadStruct.rColor; + ctx.fillRect(data.frame.x, data.frame.y + padding, data.frame.width, data.frame.height - padding * 2) + ctx.fillStyle = "#fff"; + ThreadStruct.drawString(ctx, ThreadStruct.getEndState(data.state || ''), 2, data.frame); + } else if ("D" == data.state) { + ctx.fillStyle = ThreadStruct.uninterruptibleSleepColor; + ctx.fillRect(data.frame.x, data.frame.y + padding, data.frame.width, data.frame.height - padding * 2) + ctx.fillStyle = "#fff"; + ThreadStruct.drawString(ctx, ThreadStruct.getEndState(data.state || ''), 2, data.frame); + } else if ("Running" == data.state) { + ctx.fillStyle = ThreadStruct.runningColor; + ctx.fillRect(data.frame.x, data.frame.y + padding, data.frame.width, data.frame.height - padding * 2) + ctx.fillStyle = "#fff"; + ThreadStruct.drawString(ctx, ThreadStruct.getEndState(data.state || ''), 2, data.frame); + } else { + ctx.fillStyle = ThreadStruct.rColor; + ctx.fillRect(data.frame.x, data.frame.y + padding, data.frame.width, data.frame.height - padding * 2) + ctx.fillStyle = "#fff"; + ThreadStruct.drawString(ctx, ThreadStruct.getEndState(data.state || ''), 2, data.frame); + } + if (ThreadStruct.selectThreadStruct && ThreadStruct.equals(ThreadStruct.selectThreadStruct, data) && ThreadStruct.selectThreadStruct.state != "S") { + ctx.strokeStyle = '#232c5d' + ctx.lineWidth = 2 + ctx.strokeRect(data.frame.x, data.frame.y + padding, data.frame.width - 2, data.frame.height - padding * 2) + } + } + } + + static drawString(ctx: CanvasRenderingContext2D, str: string, textPadding: number, frame: Rect) { + let textMetrics = ctx.measureText(str); + let charWidth = Math.round(textMetrics.width / str.length) + if (textMetrics.width < frame.width - textPadding * 2) { + let x2 = Math.floor(frame.width / 2 - textMetrics.width / 2 + frame.x + textPadding) + ctx.textBaseline = "middle" + ctx.fillText(str, x2, Math.floor(frame.y + frame.height / 2), frame.width - textPadding * 2) + return; + } + if (frame.width - textPadding * 2 > charWidth * 4) { + let chatNum = (frame.width - textPadding * 2) / charWidth; + let x1 = frame.x + textPadding + ctx.textBaseline = "middle" + ctx.fillText(str.substring(0, chatNum - 4) + '...', x1, Math.floor(frame.y + frame.height / 2), frame.width - textPadding * 2) + return; + } + } + + static getEndState(state: string): string { + let statusMapElement = ThreadStruct.statusMap[state]; + if (statusMapElement) { + return statusMapElement + } else { + if ("" == statusMapElement || statusMapElement == null) { + return ""; + } + return "Unknown State"; + } + } + + static equals(d1: ThreadStruct, d2: ThreadStruct): boolean { + if (d1 && d2 && d1.cpu == d2.cpu && + d1.tid == d2.tid && + d1.state == d2.state && + d1.startTime == d2.startTime && + d1.dur == d2.dur) { + return true; + } else { + return false; + } + } +} diff --git a/host/ide/src/trace/bean/WakeUpTimeBean.ts b/host/ide/src/trace/bean/WakeUpTimeBean.ts new file mode 100644 index 0000000..1a20fbb --- /dev/null +++ b/host/ide/src/trace/bean/WakeUpTimeBean.ts @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class WakeUpTimeBean { + wakeTs: number | undefined + startTs: number | undefined + preRow: number | undefined +} \ No newline at end of file diff --git a/host/ide/src/trace/bean/WakeupBean.ts b/host/ide/src/trace/bean/WakeupBean.ts new file mode 100644 index 0000000..f8d6fbc --- /dev/null +++ b/host/ide/src/trace/bean/WakeupBean.ts @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class WakeupBean { + wakeupTime: number | undefined + cpu: number | undefined + process: string | undefined + pid: number | undefined + thread: string | undefined + tid: number | undefined + schedulingLatency: number | undefined + schedulingDesc: string | undefined +} \ No newline at end of file diff --git a/host/ide/src/trace/component/SpInfoAndStas.ts b/host/ide/src/trace/component/SpInfoAndStas.ts new file mode 100644 index 0000000..a450ef9 --- /dev/null +++ b/host/ide/src/trace/component/SpInfoAndStas.ts @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../base-ui/BaseElement.js"; + +@element('sp-info-and-stats') +export class SpInfoAndStats extends BaseElement { + initElements(): void { + } + + initHtml(): string { + return ` +
      System info and metadata
      + `; + } +} diff --git a/host/ide/src/trace/component/SpMetrics.ts b/host/ide/src/trace/component/SpMetrics.ts new file mode 100644 index 0000000..3e06d04 --- /dev/null +++ b/host/ide/src/trace/component/SpMetrics.ts @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../base-ui/BaseElement.js"; + +@element('sp-metrics') +export class SpMetrics extends BaseElement { + initElements(): void { + } + + initHtml(): string { + return ` + +
      + + trace_metadata: { + trace_duration_ns: 14726175738 + trace_uuid: "00000000-0000-0000-c0bd-eb5c5728bf40" + statsd_triggering_subscription_id: 0 + unique_session_name: "" + trace_size_bytes: 57202082 + trace_config_pbtxt: "buffers: { size_kb: 63488\\n fill_policy: DISCARD\\n}\\nbuffers: {\\n size_kb: 2048\\n fill_policy: DISCARD\\n}\\ndata_sources: {\\n config: {\\n name: \\"linux.process_stats\\"\\n target_buffer: 1\\n trace_duration_ms: 0\\n tracing_session_id: 0\\n enable_extra_guardrails: false\\n ftrace_config: {\\n buffer_size_kb: 0\\n drain_period_ms: 0\\n }\\n chrome_config: {\\n trace_config: \\"\\"\\n privacy_filtering_enabled: false\\n }\\n inode_file_config: {\\n scan_interval_ms: 0\\n scan_delay_ms: 0\\n scan_batch_size: 0\\n do_not_scan: false\\n }\\n process_stats_config: {\\n scan_all_processes_on_start: true\\n record_thread_names: false\\n proc_stats_poll_ms: 1000\\n proc_stats_cache_ttl_ms: 0\\n }\\n sys_stats_config: {\\n meminfo_period_ms: 0\\n vmstat_period_ms: 0\\n stat_period_ms: 0\\n }\\n heapprofd_config: {\\n sampling_interval_bytes: 0\\n all: false\\n continuous_dump_config: {\\n dump_phase_ms: 0\\n dump_interval_ms: 0\\n }\\n shmem_size_bytes: 0\\n block_client: false\\n }\\n android_power_config: {\\n battery_poll_ms: 0\\n collect_power_rails: false\\n }\\n android_log_config: {\\n min_prio: PRIO_UNSPECIFIED\\n }\\n packages_list_config: {\\n }\\n legacy_config: \\"\\"\\n }\\n}\\ndata_sources: {\\n config: {\\n name: \\"linux.ftrace\\"\\n target_buffer: 0\\n trace_duration_ms: 0\\n tracing_session_id: 0\\n enable_extra_guardrails: false\\n ftrace_config: {\\n ftrace_events: \\"sched/sched_switch\\"\\n ftrace_events: \\"power/suspend_resume\\"\\n ftrace_events: \\"sched/sched_wakeup\\"\\n ftrace_events: \\"sched/sched_wakeup_new\\"\\n ftrace_events: \\"sched/sched_waking\\"\\n ftrace_events: \\"power/cpu_frequency\\"\\n ftrace_events: \\"power/cpu_idle\\"\\n ftrace_events: \\"sched/sched_process_exit\\"\\n ftrace_events: \\"sched/sched_process_free\\"\\n ftrace_events: \\"task/task_newtask\\"\\n ftrace_events: \\"task/task_rename\\"\\n ftrace_events: \\"lowmemorykiller/lowmemory_kill\\"\\n ftrace_events: \\"oom/oom_score_adj_update\\"\\n ftrace_events: \\"ftrace/print\\"\\n atrace_categories: \\"gfx\\"\\n atrace_apps: \\"lmkd\\"\\n buffer_size_kb: 0\\n drain_period_ms: 0\\n }\\n chrome_config: {\\n trace_config: \\"\\"\\n privacy_filtering_enabled: false\\n }\\n inode_file_config: {\\n scan_interval_ms: 0\\n scan_delay_ms: 0\\n scan_batch_size: 0\\n do_not_scan: false\\n }\\n process_stats_config: {\\n scan_all_processes_on_start: false\\n record_thread_names: false\\n proc_stats_poll_ms: 0\\n proc_stats_cache_ttl_ms: 0\\n }\\n sys_stats_config: {\\n meminfo_period_ms: 0\\n vmstat_period_ms: 0\\n stat_period_ms: 0\\n }\\n heapprofd_config: {\\n sampling_interval_bytes: 0\\n all: false\\n continuous_dump_config: {\\n dump_phase_ms: 0\\n dump_interval_ms: 0\\n }\\n shmem_size_bytes: 0\\n block_client: false\\n }\\n android_power_config: {\\n battery_poll_ms: 0\\n collect_power_rails: false\\n }\\n android_log_config: {\\n min_prio: PRIO_UNSPECIFIED\\n }\\n packages_list_config: {\\n }\\n legacy_config: \\"\\"\\n }\\n}\\nduration_ms: 15000\\nenable_extra_guardrails: false\\nlockdown_mode: LOCKDOWN_UNCHANGED\\nstatsd_metadata: {\\n triggering_alert_id: 0\\n triggering_config_uid: 0\\n triggering_config_id: 0\\n triggering_subscription_id: 0\\n}\\nwrite_into_file: false\\nfile_write_period_ms: 0\\nmax_file_size_bytes: 0\\nguardrail_overrides: {\\n max_upload_per_day_bytes: 0\\n}\\ndeferred_start: false\\nflush_period_ms: 0\\nflush_timeout_ms: 0\\nnotify_traceur: false\\ntrigger_config: {\\n trigger_mode: UNSPECIFIED\\n trigger_timeout_ms: 0\\n}\\nallow_user_build_tracing: false\\nbuiltin_data_sources: {\\n disable_clock_snapshotting: false\\n disable_trace_config: false\\n disable_system_info: false\\n}\\nincremental_state_config: {\\n clear_period_ms: 0\\n}\\nunique_session_name: \\"\\"\\ncompression_type: COMPRESSION_TYPE_UNSPECIFIED\\nincident_report_config: {\\n destination_package: \\"\\"\\n destination_class: \\"\\"\\n privacy_level: 0\\n skip_dropbox: false\\n}" + sched_duration_ns: 14726119124 +} + +
      + `; + } +} \ No newline at end of file diff --git a/host/ide/src/trace/component/SpQuerySQL.ts b/host/ide/src/trace/component/SpQuerySQL.ts new file mode 100644 index 0000000..291793d --- /dev/null +++ b/host/ide/src/trace/component/SpQuerySQL.ts @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../base-ui/BaseElement.js"; +import {querySql, queryThreadsByPid} from "../database/SqlLite.js"; + +@element('sp-query-sql') +export class SpQuerySQL extends BaseElement { + initElements(): void { + let sqlInput: HTMLInputElement | undefined | null = this.shadowRoot?.querySelector('#sql-input'); + let contentEL: HTMLPreElement | undefined | null = this.shadowRoot?.querySelector('#content'); + if (sqlInput) { + sqlInput.addEventListener('change', e => { + let dateA = new Date().getTime(); + if(sqlInput&&sqlInput.value) { + querySql(sqlInput.value).then(res=>{ + let dur = new Date().getTime() - dateA; + contentEL!.innerHTML = `耗时:${dur}ms 记录:${res.length}条\n${JSON.stringify(res,null,4)}` + }) + } + }) + } + } + + connectedCallback() { + } + + initHtml(): string { + return ` + +
      + +
      
      +
      + `; + } + +} \ No newline at end of file diff --git a/host/ide/src/trace/component/SpRecordTrace.ts b/host/ide/src/trace/component/SpRecordTrace.ts new file mode 100644 index 0000000..2e7674f --- /dev/null +++ b/host/ide/src/trace/component/SpRecordTrace.ts @@ -0,0 +1,574 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../base-ui/BaseElement.js"; +import "../../base-ui/popover/LitPopover.js" +import {LitMainMenuGroup} from "../../base-ui/menu/LitMainMenuGroup.js"; +import {LitMainMenuItem} from "../../base-ui/menu/LitMainMenuItem.js"; +import {SpRecordSetting} from "./setting/SpRecordSetting.js"; +import {MenuItem} from "../../base-ui/menu/LitMainMenu.js"; +import {SpProbesConfig} from "./setting/SpProbesConfig.js"; +import {SpTraceCommand} from "./setting/SpTraceCommand.js"; +import { + CreateSessionRequest, + FpsConfig, + HilogConfig, + levelFromJSON, + MemoryConfig, + NativeHookConfig, + ProfilerPluginConfig, + ProfilerSessionConfig, + ProfilerSessionConfigBufferConfig, + ProfilerSessionConfigBufferConfigPolicy, + ProfilerSessionConfigMode, + sysMeminfoTypeFromJSON, + sysVMeminfoTypeFromJSON, + TracePluginConfig, + Type +} from "./setting/bean/ProfilerServiceTypes.js"; +import {PluginConvertUtils} from "./setting/utils/PluginConvertUtils.js"; +import {SpAllocations} from "./setting/SpAllocations.js"; + +@element('sp-record-trace') +export class SpRecordTrace extends BaseElement { + static MEM_INFO = ["MEMINFO_ACTIVE", "MEMINFO_ACTIVE_ANON", "MEMINFO_ACTIVE_FILE", "MEMINFO_ANON_PAGES", "MEMINFO_BUFFERS", + "MEMINFO_CACHED", "MEMINFO_CMA_FREE", "MEMINFO_CMA_TOTAL", "MEMINFO_COMMIT_LIMIT", "MEMINFO_COMMITED_AS", + "MEMINFO_DIRTY", "MEMINFO_INACTIVE", "MEMINFO_INACTIVE_ANON", "MEMINFO_INACTIVE_FILE", + "MEMINFO_KERNEL_STACK", "MEMINFO_MAPPED", "MEMINFO_MEM_AVAILABLE", "MEMINFO_MEM_FREE", "MEMINFO_MEM_TOTAL", + "MEMINFO_MLOCKED", "MEMINFO_PAGE_TABLES", "MEMINFO_SHMEM", "MEMINFO_SLAB", "MEMINFO_SLAB_RECLAIMABLE", + "MEMINFO_SLAB_UNRECLAIMABLE", "MEMINFO_SWAP_CACHED", "MEMINFO_SWAP_FREE", "MEMINFO_SWAP_TOTAL", + "MEMINFO_UNEVICTABLE", "MEMINFO_VMALLOC_CHUNK", "MEMINFO_VMALLOC_TOTAL", "MEMINFO_VMALLOC_USED", + "MEMINFO_WRITEBACK"] + static VMEM_INFO = ["VMEMINFO_UNSPECIFIED", "VMEMINFO_NR_FREE_PAGES", "VMEMINFO_NR_ALLOC_BATCH", + "VMEMINFO_NR_INACTIVE_ANON", "VMEMINFO_NR_ACTIVE_ANON", "VMEMINFO_NR_INACTIVE_FILE", + "VMEMINFO_NR_ACTIVE_FILE", "VMEMINFO_NR_UNEVICTABLE", "VMEMINFO_NR_MLOCK", "VMEMINFO_NR_ANON_PAGES", + "VMEMINFO_NR_MAPPED", "VMEMINFO_NR_FILE_PAGES", "VMEMINFO_NR_DIRTY", "VMEMINFO_NR_WRITEBACK", + "VMEMINFO_NR_SLAB_RECLAIMABLE", "VMEMINFO_NR_SLAB_UNRECLAIMABLE", "VMEMINFO_NR_PAGE_TABLE_PAGES", + "VMEMINFO_NR_KERNEL_STACK", "VMEMINFO_NR_OVERHEAD", "VMEMINFO_NR_UNSTABLE", "VMEMINFO_NR_BOUNCE", + "VMEMINFO_NR_VMSCAN_WRITE", "VMEMINFO_NR_VMSCAN_IMMEDIATE_RECLAIM", "VMEMINFO_NR_WRITEBACK_TEMP", + "VMEMINFO_NR_ISOLATED_ANON", "VMEMINFO_NR_ISOLATED_FILE", "VMEMINFO_NR_SHMEM", "VMEMINFO_NR_DIRTIED", + "VMEMINFO_NR_WRITTEN", "VMEMINFO_NR_PAGES_SCANNED", "VMEMINFO_WORKINGSET_REFAULT", + "VMEMINFO_WORKINGSET_ACTIVATE", "VMEMINFO_WORKINGSET_NODERECLAIM", "VMEMINFO_NR_ANON_TRANSPARENT_HUGEPAGES", + "VMEMINFO_NR_FREE_CMA", "VMEMINFO_NR_SWAPCACHE", "VMEMINFO_NR_DIRTY_THRESHOLD", + "VMEMINFO_NR_DIRTY_BACKGROUND_THRESHOLD", "VMEMINFO_PGPGIN", "VMEMINFO_PGPGOUT", "VMEMINFO_PGPGOUTCLEAN", + "VMEMINFO_PSWPIN", "VMEMINFO_PSWPOUT", "VMEMINFO_PGALLOC_DMA"] + static VMEM_INFO_SECOND = ["VMEMINFO_PGALLOC_NORMAL", "VMEMINFO_PGALLOC_MOVABLE", "VMEMINFO_PGFREE", "VMEMINFO_PGACTIVATE", + "VMEMINFO_PGDEACTIVATE", "VMEMINFO_PGFAULT", "VMEMINFO_PGMAJFAULT", "VMEMINFO_PGREFILL_DMA", + "VMEMINFO_PGREFILL_NORMAL", "VMEMINFO_PGREFILL_MOVABLE", "VMEMINFO_PGSTEAL_KSWAPD_DMA", + "VMEMINFO_PGSTEAL_KSWAPD_NORMAL", "VMEMINFO_PGSTEAL_KSWAPD_MOVABLE", "VMEMINFO_PGSTEAL_DIRECT_DMA", + "VMEMINFO_PGSTEAL_DIRECT_NORMAL", "VMEMINFO_PGSTEAL_DIRECT_MOVABLE", "VMEMINFO_PGSCAN_KSWAPD_DMA", + "VMEMINFO_PGSCAN_KSWAPD_NORMAL", "VMEMINFO_PGSCAN_KSWAPD_MOVABLE", "VMEMINFO_PGSCAN_DIRECT_DMA", + "VMEMINFO_PGSCAN_DIRECT_NORMAL", "VMEMINFO_PGSCAN_DIRECT_MOVABLE", "VMEMINFO_PGSCAN_DIRECT_THROTTLE", + "VMEMINFO_PGINODESTEAL", "VMEMINFO_SLABS_SCANNED", "VMEMINFO_KSWAPD_INODESTEAL", + "VMEMINFO_KSWAPD_LOW_WMARK_HIT_QUICKLY", "VMEMINFO_KSWAPD_HIGH_WMARK_HIT_QUICKLY", "VMEMINFO_PAGEOUTRUN", + "VMEMINFO_ALLOCSTALL", "VMEMINFO_PGROTATED", "VMEMINFO_DROP_PAGECACHE", "VMEMINFO_DROP_SLAB", + "VMEMINFO_PGMIGRATE_SUCCESS", "VMEMINFO_PGMIGRATE_FAIL", "VMEMINFO_COMPACT_MIGRATE_SCANNED", + "VMEMINFO_COMPACT_FREE_SCANNED", "VMEMINFO_COMPACT_ISOLATED", "VMEMINFO_COMPACT_STALL", + "VMEMINFO_COMPACT_FAIL", "VMEMINFO_COMPACT_SUCCESS", "VMEMINFO_COMPACT_DAEMON_WAKE", + "VMEMINFO_UNEVICTABLE_PGS_CULLED", "VMEMINFO_UNEVICTABLE_PGS_SCANNED", "VMEMINFO_UNEVICTABLE_PGS_RESCUED", + "VMEMINFO_UNEVICTABLE_PGS_MLOCKED", "VMEMINFO_UNEVICTABLE_PGS_MUNLOCKED"] + static VMEM_INFO_THIRD = [ + "VMEMINFO_UNEVICTABLE_PGS_CLEARED", "VMEMINFO_UNEVICTABLE_PGS_STRANDED", "VMEMINFO_NR_ZSPAGES", + "VMEMINFO_NR_ION_HEAP", "VMEMINFO_NR_GPU_HEAP", "VMEMINFO_ALLOCSTALL_DMA", "VMEMINFO_ALLOCSTALL_MOVABLE", + "VMEMINFO_ALLOCSTALL_NORMAL", "VMEMINFO_COMPACT_DAEMON_FREE_SCANNED", + "VMEMINFO_COMPACT_DAEMON_MIGRATE_SCANNED", "VMEMINFO_NR_FASTRPC", "VMEMINFO_NR_INDIRECTLY_RECLAIMABLE", + "VMEMINFO_NR_ION_HEAP_POOL", "VMEMINFO_NR_KERNEL_MISC_RECLAIMABLE", "VMEMINFO_NR_SHADOW_CALL_STACK_BYTES", + "VMEMINFO_NR_SHMEM_HUGEPAGES", "VMEMINFO_NR_SHMEM_PMDMAPPED", "VMEMINFO_NR_UNRECLAIMABLE_PAGES", + "VMEMINFO_NR_ZONE_ACTIVE_ANON", "VMEMINFO_NR_ZONE_ACTIVE_FILE", "VMEMINFO_NR_ZONE_INACTIVE_ANON", + "VMEMINFO_NR_ZONE_INACTIVE_FILE", "VMEMINFO_NR_ZONE_UNEVICTABLE", "VMEMINFO_NR_ZONE_WRITE_PENDING", + "VMEMINFO_OOM_KILL", "VMEMINFO_PGLAZYFREE", "VMEMINFO_PGLAZYFREED", "VMEMINFO_PGREFILL", + "VMEMINFO_PGSCAN_DIRECT", "VMEMINFO_PGSCAN_KSWAPD", "VMEMINFO_PGSKIP_DMA", "VMEMINFO_PGSKIP_MOVABLE", + "VMEMINFO_PGSKIP_NORMAL", "VMEMINFO_PGSTEAL_DIRECT", "VMEMINFO_PGSTEAL_KSWAPD", "VMEMINFO_SWAP_RA", + "VMEMINFO_SWAP_RA_HIT", "VMEMINFO_WORKINGSET_RESTORE" + ] + schedulingEvents = [ + "sched/sched_switch", + "power/suspend_resume", + "sched/sched_wakeup", + "sched/sched_wakeup_new", + "sched/sched_waking", + "sched/sched_process_exit", + "sched/sched_process_free", + "task/task_newtask", + "task/task_rename" + ] + powerEvents = [ + "regulator/regulator_set_voltage", + "regulator/regulator_set_voltage_complete", + "power/clock_enable", + "power/clock_disable", + "power/clock_set_rate", + "power/suspend_resume" + ] + cpuFreqEvents = [ + "power/cpu_frequency", + "power/cpu_idle", + "power/suspend_resume" + ] + sysCallsEvents = [ + "raw_syscalls/sys_enter", + "raw_syscalls/sys_exit" + ] + highFrequencyEvents = [ + "mm_event/mm_event_record", + "kmem/rss_stat", + "ion/ion_stat", + "dmabuf_heap/dma_heap_stat", + "kmem/ion_heap_grow", + "kmem/ion_heap_shrink" + ] + advancedConfigEvents = ["sched/sched_switch", + "sched/sched_wakeup", + "sched/sched_wakeup_new", + "sched/sched_waking", + "sched/sched_process_exit", + "sched/sched_process_free", + "irq/irq_handler_entry", + "irq/irq_handler_exit", + "irq/softirq_entry", + "irq/softirq_exit", + "irq/softirq_raise", + "power/clock_disable", + "power/clock_enable", + "power/clock_set_rate", + "power/cpu_frequency", + "power/cpu_idle", + "clk/clk_disable", + "clk/clk_disable_complete", + "clk/clk_enable", + "clk/clk_enable_complete", + "clk/clk_set_rate", + "clk/clk_set_rate_complete", + "binder/binder_transaction", + "binder/binder_transaction_alloc_buf", + "binder/binder_transaction_received", + "binder/binder_lock", + "binder/binder_locked", + "binder/binder_unlock", + "workqueue/workqueue_execute_start", + "workqueue/workqueue_execute_end", + "oom/oom_score_adj_update", + "ftrace/print" + ] + private _menuItems: Array | undefined + + initElements(): void { + let that = this + let parentElement = this.parentNode as HTMLElement; + parentElement.style.overflow = 'hidden' + let recordSetting = new SpRecordSetting(); + let probesConfig = new SpProbesConfig(); + let traceCommand = new SpTraceCommand(); + let spAllocations = new SpAllocations(); + let menuGroup = this.shadowRoot?.querySelector('#menu-group') as LitMainMenuGroup + let appContent = this.shadowRoot?.querySelector('#app-content') as HTMLElement + appContent.append(recordSetting) + this._menuItems = [ + { + title: "Record setting", + icon: "properties", + fileChoose: false, + clickHandler: function (ev: InputEvent) { + appContent!.innerHTML = "" + appContent.append(recordSetting) + } + }, + { + title: "Trace command", + icon: "dbsetbreakpoint", + fileChoose: false, + clickHandler: function (ev: InputEvent) { + let maxDur = recordSetting.maxDur; + let bufferSize = recordSetting.bufferSize; + let bufferConfig: ProfilerSessionConfigBufferConfig = { + pages: bufferSize * 256, + policy: ProfilerSessionConfigBufferConfigPolicy.RECYCLE + } + let sessionConfig: ProfilerSessionConfig = { + buffers: [bufferConfig], + sessionMode: ProfilerSessionConfigMode.OFFLINE, + resultFile: "/data/local/tmp/hiprofiler_data.htrace", + resultMaxSize: 0, + sampleDuration: maxDur * 1000, + keepAliveTime: 0 + } + let request: CreateSessionRequest = { + requestId: 1, + sessionConfig: sessionConfig, + pluginConfigs: [] + } + if (probesConfig.traceConfig.length > 0) { + request.pluginConfigs.push(that.createHtracePluginConfig(that, probesConfig, recordSetting)) + if (probesConfig.traceConfig.indexOf("FPS") != -1) { + request.pluginConfigs.push(that.createFpsPluginConfig()) + } + } + let reportingFrequency: number; + if (maxDur > 20) { + reportingFrequency = 5 + } else { + reportingFrequency = 2 + } + if (probesConfig.memoryConfig.length > 0) { + request.pluginConfigs.push(that.createMemoryPluginConfig(probesConfig, that, reportingFrequency)) + } + if (spAllocations.appProcess != "") { + request.pluginConfigs.push(that.createNativePluginConfig(spAllocations, reportingFrequency)) + } + appContent!.innerHTML = "" + appContent.append(traceCommand) + traceCommand.hdcCommon = + PluginConvertUtils.createHdcCmd( + PluginConvertUtils.BeanToCmdTxt(request, false), maxDur) + } + }, + { + title: "Probes config", icon: "realIntentionBulb", fileChoose: false, + clickHandler: function (ev: InputEvent) { + appContent!.innerHTML = "" + appContent.append(probesConfig) + } + }, + { + title: "Allocations", + icon: "externaltools", + fileChoose: false, + clickHandler: function (ev: InputEvent) { + appContent!.innerHTML = "" + appContent.append(spAllocations) + } + } + ] + this._menuItems?.forEach(item => { + let th = new LitMainMenuItem(); + th.setAttribute('icon', item.icon || ""); + th.setAttribute('title', item.title || ""); + th.style.height = "60px" + th.style.fontFamily = "Helvetica-Bold" + th.style.fontSize = "16px" + th.style.lineHeight = "28px" + th.style.fontWeight = "700" + th.style.opacity = "0.9" + th.removeAttribute('file'); + th.addEventListener('click', e => { + if (item.clickHandler) { + item.clickHandler(item) + } + }) + menuGroup.appendChild(th); + }) + } + + createTraceEvents(traceConfig: Array): Array { + let traceEvents = new Set(); + traceConfig.forEach(config => { + switch (config) { + case "Scheduling details": + this.schedulingEvents.forEach((eve: string) => { + traceEvents.add(eve) + }); + break; + case "CPU Frequency and idle states": + this.cpuFreqEvents.forEach((eve: string) => { + traceEvents.add(eve) + }); + break; + case "High frequency memory": + this.highFrequencyEvents.forEach((eve: string) => { + traceEvents.add(eve) + }); + break; + case "Advanced ftrace config": + this.advancedConfigEvents.forEach((eve: string) => { + traceEvents.add(eve) + }); + break; + case "Syscalls": + this.sysCallsEvents.forEach((eve: string) => { + traceEvents.add(eve) + }); + break; + case "Board voltages & frequency": + this.powerEvents.forEach((eve: string) => { + traceEvents.add(eve) + }); + break; + } + } + ) + let ftraceEventsArray: string[] = []; + for (const ftraceEvent of traceEvents) { + ftraceEventsArray.push(ftraceEvent) + } + return ftraceEventsArray + } + + initHtml(): string { + return ` + +
      + +
      + +
      +
      +
      +
      +`; + } + + private createHilogConfig(probesConfig: SpProbesConfig, reportingFrequency: number) { + let hilogConfig: HilogConfig = { + deviceType: Type.HI3516, + logLevel: levelFromJSON(probesConfig.hilogConfig[0]), + needClear: true + } + let hilogConfigProfilerPluginConfig: ProfilerPluginConfig = { + pluginName: "hilog-plugin", + sampleInterval: reportingFrequency * 1000, + configData: hilogConfig, + } + return hilogConfigProfilerPluginConfig; + } + + private createNativePluginConfig(spAllocations: SpAllocations, reportingFrequency: number) { + let appProcess = spAllocations.appProcess; + let re = /^[0-9]+.?[0-9]*/; + let pid = 0; + let processName = ""; + if (re.test(appProcess)) { + pid = Number(appProcess); + } else { + processName = appProcess; + } + let nativeConfig: NativeHookConfig = { + pid: pid, + saveFile: false, + fileName: "", + filterSize: spAllocations.filter, + smbPages: spAllocations.shared, + maxStackDepth: spAllocations.unwind, + processName: processName + } + let nativePluginConfig: ProfilerPluginConfig = { + pluginName: "nativehook", + sampleInterval: reportingFrequency * 1000, + configData: nativeConfig, + } + return nativePluginConfig; + } + + private createMemoryPluginConfig(probesConfig: SpProbesConfig, that: this, reportingFrequency: number) { + let memoryconfig: MemoryConfig = { + reportProcessTree: true, + reportSysmemMemInfo: true, + sysMeminfoCounters: [], + reportSysmemVmemInfo: true, + sysVmeminfoCounters: [], + reportProcessMemInfo: true, + reportAppMemInfo: false, + reportAppMemByMemoryService: false, + pid: [] + } + probesConfig.memoryConfig.forEach(value => { + if (value.indexOf("Kernel meminfo") != -1) { + SpRecordTrace.MEM_INFO.forEach(va => { + memoryconfig.sysMeminfoCounters.push(sysMeminfoTypeFromJSON(va)); + }) + } + if (value.indexOf("Virtual memory stats") != -1) { + SpRecordTrace.VMEM_INFO.forEach((me => { + memoryconfig.sysVmeminfoCounters.push(sysVMeminfoTypeFromJSON(me)) + })) + SpRecordTrace.VMEM_INFO_SECOND.forEach((me => { + memoryconfig.sysVmeminfoCounters.push(sysVMeminfoTypeFromJSON(me)) + })) + SpRecordTrace.VMEM_INFO_THIRD.forEach((me => { + memoryconfig.sysVmeminfoCounters.push(sysVMeminfoTypeFromJSON(me)) + })) + } + }) + let profilerPluginConfig: ProfilerPluginConfig = { + pluginName: "memory-plugin", + sampleInterval: reportingFrequency * 1000, + configData: memoryconfig, + } + return profilerPluginConfig; + } + + private createFpsPluginConfig() { + let fpsConfig: FpsConfig = { + reportFps: true + } + let fpsPlugin: ProfilerPluginConfig = { + pluginName: "hidump-plugin", + sampleInterval: 1000, + configData: fpsConfig + } + return fpsPlugin; + } + + private createHtracePluginConfig(that: this, probesConfig: SpProbesConfig, recordSetting: SpRecordSetting) { + let tracePluginConfig: TracePluginConfig = { + ftraceEvents: that.createTraceEvents(probesConfig.traceConfig), + hitraceCategories: [], + hitraceApps: [], + bufferSizeKb: 2048, + flushIntervalMs: 1000, + flushThresholdKb: 4096, + parseKsyms: true, + clock: "mono", + tracePeriodMs: 200, + rawDataPrefix: "", + traceDurationMs: 0, + debugOn: false, + hitraceTime: recordSetting.maxDur + } + if (probesConfig.traceEvents.length > 0) { + tracePluginConfig.hitraceCategories = probesConfig.traceEvents + } + let htraceProfilerPluginConfig: ProfilerPluginConfig = { + pluginName: "ftrace-plugin", + sampleInterval: 1000, + configData: tracePluginConfig + } + return htraceProfilerPluginConfig; + } +} \ No newline at end of file diff --git a/host/ide/src/trace/component/SpRecyclerSystemTrace.ts b/host/ide/src/trace/component/SpRecyclerSystemTrace.ts new file mode 100644 index 0000000..a0a4ed1 --- /dev/null +++ b/host/ide/src/trace/component/SpRecyclerSystemTrace.ts @@ -0,0 +1,805 @@ +// @ts-nocheck +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../base-ui/BaseElement.js"; +import "./trace/TimerShaftElement.js"; +import "./trace/base/TraceRow.js"; +import "./trace/base/TraceRowRecyclerView.js" +import { + getAsyncEvents, + getCpuUtilizationRate, + getFps, + getFunDataByTid, + queryCpuData, + queryCpuFreq, + queryCpuFreqData, + queryCpuMax, + queryCpuMaxFreq, + queryHeapByPid, + queryHeapPid, + queryProcess, + queryProcessData, + queryProcessMem, + queryProcessMemData, + queryProcessThreads, + queryThreadData, + queryTotalTime, + threadPool +} from "../database/SqlLite.js"; +import {TraceRow} from "./trace/base/TraceRow.js"; +import {TimerShaftElement} from "./trace/TimerShaftElement.js"; +import {TimeRange} from "./trace/timer-shaft/RangeRuler.js"; +import {CpuStruct} from "../bean/CpuStruct.js"; +import {CpuFreqStruct} from "../bean/CpuFreqStruct.js"; +import {ProcessStruct} from "../bean/ProcessStruct.js"; +import {ColorUtils} from "./trace/base/ColorUtils.js"; +import "./trace/base/TraceSheet.js"; +import {TraceSheet} from "./trace/base/TraceSheet.js"; +import {ThreadStruct} from "../bean/ThreadStruct.js"; +import {ProcessMemStruct} from "../bean/ProcessMemStruct.js"; +import {FuncStruct} from "../bean/FuncStruct.js"; +import {FpsStruct} from "../bean/FpsStruct.js"; +import {RangeSelect} from "./trace/base/RangeSelect.js"; +import {SelectionParam} from "../bean/BoxSelection.js"; +import {procedurePool} from "../database/Procedure.js"; +import {SportRuler} from "./trace/timer-shaft/SportRuler.js"; +import {TraceRowRecyclerView} from "./trace/base/TraceRowRecyclerView.js"; +import {TraceRowObject} from "./trace/base/TraceRowObject.js"; +import {Rect} from "./trace/timer-shaft/Rect.js"; + +@element('sp-recycler-system-trace') +export class SpRecyclerSystemTrace extends BaseElement { + static scrollViewWidth = 0 + rowsEL: TraceRowRecyclerView | undefined | null; + private timerShaftEL: TimerShaftElement | null | undefined; + private range: TimeRange | undefined + private traceSheetEL: TraceSheet | undefined | null; + private rangeSelect!: RangeSelect; + private processThreads: Array = [] + private processAsyncEvent: Array = [] + private processMem: Array = [] + + initElements(): void { + this.rowsEL = this.shadowRoot?.querySelector('.rows') + this.timerShaftEL = this.shadowRoot?.querySelector('.timer-shaft') + this.traceSheetEL = this.shadowRoot?.querySelector('.trace-sheet') + this.rangeSelect = new RangeSelect(this.timerShaftEL); + this.rangeSelect.rowsEL = this.rowsEL; + document?.addEventListener("flag-change", (event: any) => { + let flag = event.detail; + this.timerShaftEL?.modifyFlagList(event.detail.type, event.detail.flagObj) + if (flag.hidden) { + this.traceSheetEL?.setAttribute("mode", 'hidden'); + } + }) + SpRecyclerSystemTrace.scrollViewWidth = this.getScrollWidth() + this.rangeSelect.selectHandler = (rows) => { + let selection = new SelectionParam(); + // 框选的 cpu ,无则不传 + selection.cpus = []; + // 框选的 线程 id,无则不传 + selection.threadIds = []; + // 款选的函数的 线程id ,无则不传 + selection.funTids = []; + // 框选的 内存 trackId ,无则不传 + selection.trackIds = []; + // 框选的起始时间 + selection.leftNs = 0; + // 框选的结束时间 + selection.rightNs = 0; + rows.forEach(it => { + if (it.rowType == TraceRow.ROW_TYPE_CPU) { + selection.cpus.push(parseInt(it.rowId!)) + } else if (it.rowType == TraceRow.ROW_TYPE_THREAD) { + selection.threadIds.push(parseInt(it.rowId!)) + } else if (it.rowType == TraceRow.ROW_TYPE_FUNC) { + selection.funTids.push(parseInt(it.rowId!)) + } else if (it.rowType == TraceRow.ROW_TYPE_MEM) { + selection.trackIds.push(parseInt(it.rowId!)) + } else if (it.rowType == TraceRow.ROW_TYPE_FPS) { + selection.hasFps = true; + } else if (it.rowType == TraceRow.ROW_TYPE_HEAP) { + selection.heapIds.push(parseInt(it.rowId!)) + } + if (it.rangeSelect && it.rangeSelect.startNS) { + selection.leftNs = it.rangeSelect.startNS; + } + if (it.rangeSelect && it.rangeSelect.endNS) { + selection.rightNs = it.rangeSelect.endNS; + } + }) + this.traceSheetEL?.boxSelection(selection) + } + // @ts-ignore + new ResizeObserver((entries) => { + let width = entries[0].contentRect.width - 1 - SpRecyclerSystemTrace.scrollViewWidth; + requestAnimationFrame(() => { + this.timerShaftEL?.updateWidth(width) + this.shadowRoot!.querySelectorAll>("trace-row").forEach(it => it.updateWidth(width)) + }) + }).observe(this) + // @ts-ignore + new ResizeObserver((entries) => { + let width = this.clientWidth - 1 - SpRecyclerSystemTrace.scrollViewWidth + requestAnimationFrame(() => { + this.timerShaftEL?.updateWidth(width) + this.shadowRoot!.querySelectorAll>("trace-row").forEach(it => it.updateWidth(width)) + }) + // this.shadowRoot!.querySelectorAll>("trace-row").forEach(it=>it.updateWidth(width)) + }).observe(window.document.body) + } + + getScrollWidth() { + let noScroll, scroll, oDiv = document.createElement('div'); + oDiv.style.cssText = 'position:absolute; top:-1000px; width:100px; height:100px; overflow:hidden;'; + noScroll = document.body.appendChild(oDiv).clientWidth; + oDiv.style.overflowY = 'scroll'; + scroll = oDiv.clientWidth; + document.body.removeChild(oDiv); + return noScroll - scroll + 1; + } + + getVisibleRows(): Array> { + return [...this.rowsEL!.shadowRoot!.querySelectorAll>("trace-row")]; + } + + timerShaftELRangeChange = (e: any) => { + this.range = e.detail; + TraceRow.range = this.range; + let scrollTop = this.rowsEL?.scrollTop || 0 + let scrollHeight = this.rowsEL?.clientHeight || 0 + //在rowsEL显示范围内的 trace-row组件将收到时间区间变化通知 + this.getVisibleRows().forEach(it => { + it.dataListCache.length = 0; + this.hoverStructNull(); + it.drawObject(); + }) + } + + rowsElOnScroll = (e: any) => { + this.hoverStructNull(); + let rows = this.getVisibleRows(); + rows.forEach((it, index) => { + if (index == 0 || index == rows.length - 1) { + it.dataListCache.length = 0; + it.drawObject(); + } + }) + } + + documentOnMouseDown = (ev: MouseEvent) => { + this.rangeSelect.mouseDown(ev) + } + + documentOnMouseUp = (ev: MouseEvent) => { + this.rangeSelect.mouseUp(ev); + } + + documentOnMouseMove = (ev: MouseEvent) => { + let rows = this.getVisibleRows(); + this.rangeSelect.mouseMove(rows, ev) + rows.forEach(tr => { + let x = ev.offsetX - (tr.canvasContainer?.offsetLeft || 0); + let y = ev.offsetY - (tr.canvasContainer?.offsetTop || 0) + (this.rowsEL?.scrollTop || 0); + //判断鼠标是否在当前 trace-row + if (x > tr.frame.x && x < tr.frame.x + tr.frame.width && y > tr.frame.y && y < tr.frame.y + tr.frame.height) { + this.hoverStructNull(); + if (tr.rowType === TraceRow.ROW_TYPE_CPU) { + CpuStruct.hoverCpuStruct = tr.onMouseHover(x, y); + if (CpuStruct.hoverCpuStruct) { + tr.tip = `P:${CpuStruct.hoverCpuStruct.processName || "Process"} [${CpuStruct.hoverCpuStruct.processId}]T:${CpuStruct.hoverCpuStruct.name} [${CpuStruct.hoverCpuStruct.tid}]`; + } + tr.setTipLeft(x, CpuStruct.hoverCpuStruct) + } else if (tr.rowType === TraceRow.ROW_TYPE_CPU_FREQ) { + CpuFreqStruct.hoverCpuFreqStruct = tr.onMouseHover(x, y); + if (CpuFreqStruct.hoverCpuFreqStruct) { + tr.tip = `${ColorUtils.formatNumberComma(CpuFreqStruct.hoverCpuFreqStruct.value!)} kHz` + } + tr.setTipLeft(x, CpuFreqStruct.hoverCpuFreqStruct) + } else if (tr.rowType === TraceRow.ROW_TYPE_THREAD) { + ThreadStruct.hoverThreadStruct = tr.onMouseHover(x, y, false); + } else if (tr.rowType === TraceRow.ROW_TYPE_FUNC) { + FuncStruct.hoverFuncStruct = tr.onMouseHover(x, y, false) + } else if (tr.rowType === TraceRow.ROW_TYPE_HEAP) { + HeapStruct.hoverHeapStruct = tr.onMouseHover(x, y, false) + if (HeapStruct.hoverHeapStruct) { + tr.tip = `${ColorUtils.formatNumberComma(HeapStruct.hoverHeapStruct.heapsize!)} byte` + } + tr.setTipLeft(x, HeapStruct.hoverHeapStruct) + } else { + this.hoverStructNull(); + } + } else { + tr.onMouseLeave(x, y); + } + tr.drawObject(); + }) + } + + hoverStructNull() { + CpuStruct.hoverCpuStruct = undefined; + CpuFreqStruct.hoverCpuFreqStruct = undefined; + ThreadStruct.hoverThreadStruct = undefined; + FuncStruct.hoverFuncStruct = undefined; + } + + selectStructNull() { + CpuStruct.selectCpuStruct = undefined; + CpuFreqStruct.selectCpuFreqStruct = undefined; + ThreadStruct.selectThreadStruct = undefined; + FuncStruct.selectFuncStruct = undefined; + } + + documentOnClick = (ev: MouseEvent) => { + if (this.rangeSelect.isDrag()) { + return; + } + this.rowsEL?.querySelectorAll>("trace-row").forEach(it => it.rangeSelect = undefined) + this.selectStructNull(); + if (CpuStruct.hoverCpuStruct) { + CpuStruct.selectCpuStruct = CpuStruct.hoverCpuStruct + this.traceSheetEL?.displayCpuData(CpuStruct.hoverCpuStruct); + } else if (ThreadStruct.hoverThreadStruct) { + ThreadStruct.selectThreadStruct = ThreadStruct.hoverThreadStruct; + this.traceSheetEL?.displayThreadData(ThreadStruct.hoverThreadStruct) + } else if (FuncStruct.hoverFuncStruct) { + FuncStruct.selectFuncStruct = FuncStruct.hoverFuncStruct; + this.traceSheetEL?.displayFuncData(FuncStruct.hoverFuncStruct) + } else if (SportRuler.rulerFlagObj) { + + } else { + this.traceSheetEL?.setAttribute("mode", 'hidden'); + } + this.documentOnMouseMove(ev) + } + + connectedCallback() { + /** + * 监听时间轴区间变化 + */ + this.timerShaftEL?.addEventListener('range-change', this.timerShaftELRangeChange) + /** + * 监听rowsEL的滚动时间,刷新可见区域的trace-row组件的时间区间(将触发trace-row组件重绘) + */ + this.rowsEL?.addEventListener('scroll', this.rowsElOnScroll) + /** + * 监听document的mousemove事件 坐标通过换算后找到当前鼠标所在的trace-row组件,将坐标传入 + */ + document.addEventListener('mousemove', this.documentOnMouseMove) + document.addEventListener('mousedown', this.documentOnMouseDown) + document.addEventListener('mouseup', this.documentOnMouseUp) + document.addEventListener('click', this.documentOnClick) + } + + disconnectedCallback() { + this.timerShaftEL?.removeEventListener('range-change', this.timerShaftELRangeChange); + this.rowsEL?.removeEventListener('scroll', this.rowsElOnScroll); + document.removeEventListener('mousemove', this.documentOnMouseMove); + document.removeEventListener('click', this.documentOnClick); + } + + loadDatabaseUrl(url: string, complete?: Function) { + this.init({url: url}).then(() => { + let scrollTop = this.rowsEL?.scrollTop || 0 + let scrollHeight = this.rowsEL?.clientHeight || 0 + this.rowsEL?.querySelectorAll("trace-row").forEach((it: any) => { + let top = it.offsetTop - (this.rowsEL?.offsetTop || 0); + if (top + it.clientHeight > scrollTop && top + it.clientHeight < scrollTop + scrollHeight + it.clientHeight) { + (it as TraceRow).dataListCache.length = 0; + } + }) + if (complete) { + complete(); + } + }) + } + + loadDatabaseArrayBuffer(buf: ArrayBuffer, complete?: Function) { + this.init({buf}).then(() => { + let scrollTop = this.rowsEL?.scrollTop || 0 + let scrollHeight = this.rowsEL?.clientHeight || 0 + this.rowsEL?.querySelectorAll("trace-row").forEach((it: any) => { + let top = it.offsetTop - (this.rowsEL?.offsetTop || 0); + if (top + it.clientHeight > scrollTop && top + it.clientHeight < scrollTop + scrollHeight + it.clientHeight) { + (it as TraceRow).dataListCache.length = 0; + } + }) + if (complete) { + complete(); + } + }) + } + + init = async (param: { buf?: ArrayBuffer, url?: string }) => { + if (this.rowsEL) this.rowsEL.innerHTML = '' + this.traceSheetEL?.setAttribute("mode", "hidden") + this.timerShaftEL?.reset(); + procedurePool.clearCache(); + param.buf && await threadPool.initSqlite(param.buf); + param.url && await threadPool.initServer(param.url); + this.processThreads = await queryProcessThreads(); + this.processMem = await queryProcessMem() + this.processAsyncEvent = await getAsyncEvents() + await this.initTotalTime(); + let cpuObjs = await this.initCpu(); + await this.initCpuRate(); + let freqObjs = await this.initCpuFreq(); + let fpsObjs = await this.initFPS(); + let processObjs = await this.initProcess(); + this.rowsEL.dataSource = [...cpuObjs, ...freqObjs, ...fpsObjs, ...processObjs] + this.getVisibleRows().forEach(it => it.drawObject()); + } + initCpuRate = async () => { + let rates = await getCpuUtilizationRate(0, this.timerShaftEL?.totalNS || 0); + if (this.timerShaftEL) this.timerShaftEL.cpuUsage = rates; + } + initTotalTime = async () => { + let res = await queryTotalTime(); + if (this.timerShaftEL) { + this.timerShaftEL.totalNS = res[0].total + this.timerShaftEL.loadComplete = true; + } + } + initCpu = async () => { + let objs = []; + let array = await queryCpuMax(); + if (array && array.length > 0 && array[0]) { + let cpuMax = array[0].cpu + CpuStruct.cpuCount = cpuMax + 1; + for (let i1 = 0; i1 < CpuStruct.cpuCount; i1++) { + const cpuId = i1; + let traceRow = new TraceRowObject(); + traceRow.rowId = `${cpuId}` + traceRow.rowType = TraceRow.ROW_TYPE_CPU + traceRow.rowParentId = '' + traceRow.rowHeight = 40 + traceRow.frame = new Rect(0, 0, this.rowsEL.clientWidth - 248, traceRow.rowHeight) + traceRow.name = `Cpu ${cpuId}` + traceRow.supplier = () => queryCpuData(cpuId, 0, this.timerShaftEL?.totalNS || 0) + traceRow.onThreadHandler = ((row, ctx) => { + procedurePool.submitWithName("cpu", `cpu${cpuId}`, { + list: traceRow.must ? traceRow.dataList : undefined, + startNS: TraceRow.range?.startNS || 0, + endNS: TraceRow.range?.endNS || 0, + totalNS: TraceRow.range?.totalNS || 0, + frame: traceRow.frame + }, (res: any) => { + traceRow.dataListCache = res; + traceRow.must = false; + row.clearCanvas(); + row.c!.beginPath(); + row.drawLines(); + for (let i = 0; i < res.length; i++) { + CpuStruct.draw(ctx, res[i]) + } + row.drawSelection(); + row.c!.closePath(); + }) + }) + objs.push(traceRow) + } + } + return objs; + } + + initCpuFreq = async () => { + let objs = []; + let freqList = await queryCpuFreq(); + let freqMaxList = await queryCpuMaxFreq(); + CpuFreqStruct.maxFreq = freqMaxList[0].maxFreq; + let math = () => { + let units: Array = ["", "K", "M", "G", "T", "E"]; + let sb = " "; + CpuFreqStruct.maxFreqName = " "; + if (CpuFreqStruct.maxFreq > 0) { + let log10: number = Math.ceil(Math.log10(CpuFreqStruct.maxFreq)); + let pow10: number = Math.pow(10, log10); + let afterCeil: number = Math.ceil(CpuFreqStruct.maxFreq / (pow10 / 4)) * (pow10 / 4); + CpuFreqStruct.maxFreq = afterCeil; + let unitIndex: number = Math.floor(log10 / 3); + sb = `${afterCeil / Math.pow(10, unitIndex * 3)}${units[unitIndex + 1]}hz` + } + CpuFreqStruct.maxFreqName = sb.toString(); + } + math(); + for (let i = 0; i < freqList.length; i++) { + const it = freqList[i]; + let traceRow = new TraceRowObject(); + traceRow.rowId = `${it.cpu}` + traceRow.rowType = TraceRow.ROW_TYPE_CPU_FREQ + traceRow.rowParentId = '' + traceRow.rowHeight = 40 + traceRow.frame = new Rect(0, 0, this.rowsEL.clientWidth - 248, traceRow.rowHeight) + traceRow.name = `Cpu ${it.cpu} Frequency`; + traceRow.supplier = () => queryCpuFreqData(it.cpu) + traceRow.onThreadHandler = (row, ctx) => { + procedurePool.submitWithName("freq", `freq${it.cpu}`, { + list: traceRow.must ? traceRow.dataList : undefined, + startNS: TraceRow.range?.startNS || 0, + endNS: TraceRow.range?.endNS || 0, + totalNS: TraceRow.range?.totalNS || 0, + frame: traceRow.frame + }, (res: any) => { + traceRow.dataListCache = res; + traceRow.must = false; + row.clearCanvas(); + row.drawLines(); + row.c!.beginPath(); + for (let i = 0; i < res.length; i++) { + CpuFreqStruct.draw(ctx, res[i]) + } + row.drawSelection(); + row.c!.closePath(); + let s = CpuFreqStruct.maxFreqName + let textMetrics = ctx.measureText(s); + row.c!.globalAlpha = 0.8 + row.c!.fillStyle = "#f0f0f0" + row.c!.fillRect(0, 5, textMetrics.width + 8, 18) + row.c!.globalAlpha = 1 + row.c!.fillStyle = "#333" + ctx.textBaseline = "middle" + ctx.fillText(maxFps, 4, 5 + 9) + }) + } + objs.push(traceRow) + } + return objs; + } + + initFPS = async () => { + let objs = []; + let fpsRow = new TraceRowObject(); + fpsRow.rowId = `fps` + fpsRow.rowType = TraceRow.ROW_TYPE_FPS + fpsRow.rowParentId = '' + FpsStruct.maxFps = 0 + fpsRow.rowHeight = 40 + fpsRow.frame = new Rect(0, 0, this.rowsEL.clientWidth - 248, fpsRow.rowHeight) + fpsRow.name = "FPS" + fpsRow.supplier = () => getFps() + fpsRow.onDrawHandler = (row, ctx) => { + if (fpsRow.dataListCache.length > 0) { + for (let i = 0; i < fpsRow.dataListCache.length; i++) { + FpsStruct.draw(ctx, fpsRow.dataListCache[i]) + } + } else { + if (fpsRow.dataList) { + for (let i = 0; i < fpsRow.dataList.length; i++) { + let it = fpsRow.dataList[i]; + if ((it.fps || 0) > FpsStruct.maxFps) { + FpsStruct.maxFps = it.fps || 0 + } + if (i === fpsRow.dataList.length - 1) { + it.dur = (TraceRow.range?.endNS || 0) - (it.startNS || 0) + } else { + it.dur = (fpsRow.dataList[i + 1].startNS || 0) - (it.startNS || 0) + } + if ((it.startNS || 0) + (it.dur || 0) > (TraceRow.range?.startNS || 0) && (it.startNS || 0) < (TraceRow.range?.endNS || 0)) { + FpsStruct.setFrame(fpsRow.dataList[i], 5, TraceRow.range?.startNS || 0, TraceRow.range?.endNS || 0, TraceRow.range?.totalNS || 0, fpsRow.frame) + if (i > 0 && ((fpsRow.dataList[i - 1].frame?.x || 0) == (fpsRow.dataList[i].frame?.x || 0) && (fpsRow.dataList[i - 1].frame?.width || 0) == (fpsRow.dataList[i].frame?.width || 0))) { + + } else { + fpsRow.dataListCache.push(fpsRow.dataList[i]) + FpsStruct.draw(ctx, fpsRow.dataList[i]) + } + } + } + } + } + if (ctx) { + let maxFps = FpsStruct.maxFps + "FPS" + let textMetrics = ctx.measureText(maxFps); + ctx.globalAlpha = 0.8 + ctx.fillStyle = "#f0f0f0" + ctx.fillRect(0, 5, textMetrics.width + 8, 18) + ctx.globalAlpha = 1 + ctx.fillStyle = "#333" + ctx.textBaseline = "middle" + ctx.fillText(maxFps, 4, 5 + 9) + } + } + objs.push(fpsRow) + return objs; + } + + /** + * 添加进程信息 + */ + initProcess = async () => { + let objs = []; + let processList = await queryProcess(); + let heapPidList = await queryHeapPid() + for (let i = 0; i < processList.length; i++) { + const it = processList[i]; + let processRow = new TraceRowObject(); + processRow.rowId = `${it.pid}` + processRow.rowType = TraceRow.ROW_TYPE_PROCESS + processRow.rowParentId = '' + processRow.frame = new Rect(0, 0, this.rowsEL.clientWidth - 248, processRow.rowHeight); + processRow.folder = true; + processRow.name = `${it.processName || "Process"} ${it.pid}`; + processRow.supplier = () => queryProcessData(it.pid || -1, 0, TraceRow.range?.totalNS || 0) + processRow.onThreadHandler = (row, ctx) => { + procedurePool.submitWithName("process", `process ${it.pid} ${it.processName}`, { + list: processRow.must ? processRow.dataList : undefined, + startNS: TraceRow.range?.startNS || 0, + endNS: TraceRow.range?.endNS || 0, + totalNS: TraceRow.range?.totalNS || 0, + frame: processRow.frame + }, (res: any) => { + processRow.dataListCache = res; + processRow.must = false; + row.clearCanvas(); + row.drawLines(); + row.c!.beginPath(); + for (let i = 0; i < res.length; i++) { + ProcessStruct.draw(ctx, res[i]) + } + row.drawSelection(); + row.c!.closePath(); + }) + } + objs.push(processRow); + if (heapPidList != undefined && Array.isArray(heapPidList) && heapPidList.filter((item) => { + return item.pid == it.pid + }).length > 0) { + /** + * 添加heap信息 + */ + let allHeapRow = new TraceRowObject(); + allHeapRow.rowParentId = `${it.pid}` + allHeapRow.rowHidden = !processRow.expansion + allHeapRow.rowHeight = 40 + allHeapRow.name = "All Heap Allocations"; + allHeapRow.folder = false; + allHeapRow.rowType = TraceRow.ROW_TYPE_HEAP + allHeapRow.frame = new Rect(0, 0, this.rowsEL.clientWidth - 248, allHeapRow.rowHeight) + allHeapRow.children = true + allHeapRow.supplier = () => queryHeapByPid(0, TraceRow.range?.totalNS || 0, it.pid || 0).then((res) => { + let heapList: HeapStruct[] = [] + let allocMap: Map = new Map() + let currentHeapSize = 0 + let maxHeapSize = 0; + for (let j = 0; j < res.length; j++) { + let struct = new HeapStruct(); + if (res[j].eventType == "AllocEvent") { + currentHeapSize += (res[j].heapsize || 0) + if (allocMap.has(res[j].addr || "")) { + allocMap.get(res[j].addr || "")?.push(res[j]) + } else { + allocMap.set(res[j].addr || "", [res[j]]) + } + } else if (res[j].eventType == "FreeEvent") { + if (allocMap.has(res[j].addr || "")) { + let allocList = allocMap.get(res[j].addr || ""); + if (allocList != undefined && allocList.length > 0) { + currentHeapSize -= allocList[allocList.length - 1].heapsize || 0 + } + } + } + if (currentHeapSize > maxHeapSize) { + maxHeapSize = currentHeapSize + } + struct.pid = it.pid + "" + struct.startTime = res[j].startTime + if (j != res.length - 1) { + struct.endTime = res[j + 1].startTime + } else { + struct.endTime = allHeapRow.range?.totalNS || 0 + } + struct.duration = (struct.endTime || 0) - (struct.startTime || 0) + struct.heapsize = currentHeapSize + heapList.push(struct) + } + for (let j = 0; j < heapList.length; j++) { + heapList[j].maxHeapSize = maxHeapSize + } + return heapList + }) + allHeapRow.onDrawHandler = ctx => { + if (allHeapRow.dataList) { + for (let i = 0; i < allHeapRow.dataList.length; i++) { + let it = allHeapRow.dataList[i]; + if ((it.startTime || 0) + (it.duration || 0) > (TraceRow.range?.startNS || 0) && (it.startTime || 0) < (TraceRow.range?.endNS || 0)) { + HeapStruct.setFrame(allHeapRow.dataList[i], 5, TraceRow.range?.startNS || 0, TraceRow.range?.endNS || 0, TraceRow.range?.totalNS || 0, allHeapRow.frame) + HeapStruct.draw(ctx, allHeapRow.dataList[i]) + } + } + } + } + objs.push(allHeapRow); + } + + /** + * 添加进程内存信息 + */ + let processMem = this.processMem.filter(mem => mem.pid === it.pid); + processMem.forEach(mem => { + let row = new TraceRowObject(); + row.rowId = `${mem.trackId}` + row.rowType = TraceRow.ROW_TYPE_MEM + row.rowParentId = `${it.pid}` + row.rowHidden = !processRow.expansion + row.rowHeight = 40 + row.frame = new Rect(0, 0, this.rowsEL.clientWidth - 248, row.rowHeight) + row.name = `${mem.trackName}`; + row.children = true; + row.supplier = () => queryProcessMemData(mem.trackId).then(res => { + let maxValue = Math.max(...res.map(it => it.value || 0)) + for (let j = 0; j < res.length; j++) { + res[j].maxValue = maxValue; + if (j == res.length - 1) { + res[j].duration = this.range?.totalNS || 0; + } else { + res[j].duration = (res[j + 1].startTime || 0) - (res[j].startTime || 0); + } + if (j > 0) { + res[j].delta = (res[j].value || 0) - (res[j - 1].value || 0); + } else { + res[j].delta = 0; + } + } + return res + }); + row.onThreadHandler = (r, ctx) => { + procedurePool.submitWithName("mem", `mem ${mem.trackId} ${mem.trackName}`, { + list: row.must ? row.dataList : undefined, + startNS: TraceRow.range?.startNS || 0, + endNS: TraceRow.range?.endNS || 0, + totalNS: TraceRow.range?.totalNS || 0, + frame: row.frame + }, (res: any) => { + row.dataListCache = res; + row.must = false; + r.clearCanvas(); + r.drawLines(); + r.c!.beginPath(); + for (let i = 0; i < res.length; i++) { + ProcessMemStruct.draw(ctx, res[i]) + } + r.drawSelection(); + r.c!.closePath(); + }) + } + objs.push(row); + }); + /** + * 添加进程线程信息 + */ + let threads = this.processThreads.filter(thread => thread.pid === it.pid && thread.tid != 0 && thread.threadName != null); + threads.forEach((thread, i) => { + let threadRow = new TraceRowObject(); + threadRow.rowId = `${thread.tid}` + threadRow.rowType = TraceRow.ROW_TYPE_THREAD + threadRow.rowParentId = `${it.pid}` + threadRow.rowHidden = !processRow.expansion + threadRow.rowHeight = 40 + threadRow.frame = new Rect(0, 0, this.rowsEL.clientWidth - 248, threadRow.rowHeight); + threadRow.name = `${thread.threadName} ${thread.tid}`; + threadRow.children = true; + threadRow.supplier = () => queryThreadData(thread.tid || 0).then(res => { + getFunDataByTid(thread.tid || 0).then(funs => { + if (funs.length > 0) { + let maxHeight = (Math.max(...funs.map(it => it.depth || 0)) + 1) * 20 + 20; + let funcRow = new TraceRowObject(); + funcRow.rowId = `${thread.tid}` + funcRow.rowType = TraceRow.ROW_TYPE_FUNC + funcRow.rowParentId = `${it.pid}` + funcRow.rowHidden = !processRow.expansion + funcRow.rowHeight = maxHeight; + funcRow.frame = new Rect(0, 0, this.rowsEL.clientWidth - 248, funcRow.rowHeight); + funcRow.name = `${thread.threadName} ${thread.tid}`; + funcRow.children = true; + funcRow.supplier = () => new Promise((resolve, reject) => resolve(funs)) + funcRow.onThreadHandler = (r, ctx) => { + procedurePool.submitWithName("func", `func ${thread.tid} ${thread.threadName}`, { + list: funcRow.must ? funcRow.dataList : undefined, + startNS: TraceRow.range?.startNS || 0, + endNS: TraceRow.range?.endNS || 0, + totalNS: TraceRow.range?.totalNS || 0, + frame: threadRow.frame + }, (res: any) => { + funcRow.must = false; + funcRow.dataListCache = res; + r.clearCanvas(); + r.drawLines(); + r.c!.beginPath(); + for (let i = 0; i < res.length; i++) { + FuncStruct.draw(ctx, res[i]) + } + r.drawSelection(); + r.c!.closePath(); + }) + } + } + }) + return res; + }) + threadRow.onThreadHandler = (r, ctx) => { + procedurePool.submitWithName("thread", `thread ${thread.tid} ${thread.threadName}`, { + list: threadRow.must ? threadRow.dataList : undefined, + startNS: TraceRow.range?.startNS || 0, + endNS: TraceRow.range?.endNS || 0, + totalNS: TraceRow.range?.totalNS || 0, + frame: threadRow.frame + }, (res: any) => { + threadRow.dataListCache = res; + threadRow.must = false; + r.clearCanvas(); + r.drawLines(); + r.c!.beginPath(); + for (let i = 0; i < res.length; i++) { + ThreadStruct.draw(ctx, res[i]) + } + r.drawSelection(); + r.c!.closePath(); + }) + } + objs.push(threadRow); + }) + } + return objs; + } + + insertAfter(newEl: HTMLElement, targetEl: HTMLElement) { + let parentEl = targetEl.parentNode; + if (parentEl!.lastChild == targetEl) { + parentEl!.appendChild(newEl); + } else { + parentEl!.insertBefore(newEl, targetEl.nextSibling); + } + } + + initHtml(): string { + return ` + +
      + + + +
      + `; + } + +} diff --git a/host/ide/src/trace/component/SpSystemTrace.ts b/host/ide/src/trace/component/SpSystemTrace.ts new file mode 100644 index 0000000..9b8d629 --- /dev/null +++ b/host/ide/src/trace/component/SpSystemTrace.ts @@ -0,0 +1,1517 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../base-ui/BaseElement.js"; +import "./trace/TimerShaftElement.js"; +import "./trace/base/TraceRow.js"; +import { + getAsyncEvents, + getCpuUtilizationRate, + getFps, + getFunDataByTid, + getStatesProcessThreadData, + getStatesProcessThreadDataCount, + queryCpuData, + queryCpuFreq, + queryCpuFreqData, + queryCpuMax, + queryCpuMaxFreq, + queryHeapAllTable, + queryHeapByEventType, + queryHeapByPid, + queryHeapFrameCount, + queryHeapGroupByEvent, + queryHeapPid, + queryNativeHookProcess, + queryProcess, + queryProcessData, + queryProcessMem, + queryProcessMemData, + queryProcessThreads, + queryThreadData, + queryTotalTime, + threadPool +} from "../database/SqlLite.js"; +import {TraceRow} from "./trace/base/TraceRow.js"; +import {TimerShaftElement} from "./trace/TimerShaftElement.js"; +import {CpuStruct} from "../bean/CpuStruct.js"; +import {CpuFreqStruct} from "../bean/CpuFreqStruct.js"; +import {ProcessStruct} from "../bean/ProcessStruct.js"; +import {ColorUtils} from "./trace/base/ColorUtils.js"; +import "./trace/base/TraceSheet.js"; +import {TraceSheet} from "./trace/base/TraceSheet.js"; +import {ThreadStruct} from "../bean/ThreadStruct.js"; +import {ProcessMemStruct} from "../bean/ProcessMemStruct.js"; +import {FuncStruct} from "../bean/FuncStruct.js"; +import {FpsStruct} from "../bean/FpsStruct.js"; +import {RangeSelect} from "./trace/base/RangeSelect.js"; +import {SelectionParam} from "../bean/BoxSelection.js"; +import {HeapStruct} from "../bean/HeapStruct.js"; +import {procedurePool} from "../database/Procedure.js"; +import {Utils} from "./trace/base/Utils.js"; +import {SpApplication} from "../SpApplication.js"; +import {SPT} from "../bean/StateProcessThread.js"; +import {HeapTreeDataBean} from "../bean/HeapTreeDataBean.js"; +import {Flag} from "./trace/timer-shaft/Flag.js"; +import {SportRuler} from "./trace/timer-shaft/SportRuler.js"; +import {NativeEventHeap} from "../bean/NativeHook.js"; + +@element('sp-system-trace') +export class SpSystemTrace extends BaseElement { + static scrollViewWidth = 0 + static isCanvasOffScreen = true; + static SPT_DATA: Array = []; + static EVENT_HEAP: Array = []; + static HEAP_FRAME_DATA: Array = []; + rowsEL: HTMLDivElement | undefined | null; + spacerEL: HTMLDivElement | undefined | null; + visibleRows: Array> = []; + keyboardEnable = true; + currentRowType = "";/*保存当前鼠标所在行的类型*/ + observerScrollHeightEnable: boolean = false; + observerScrollHeightCallback: Function | undefined; + // @ts-ignore + observer = new ResizeObserver((entries) => { + if (this.observerScrollHeightEnable && this.observerScrollHeightCallback) { + this.observerScrollHeightCallback(); + } + }); + isMousePointInSheet = false; + hoverFlag: Flag | undefined | null = undefined + selectFlag: Flag | undefined | null = undefined + public timerShaftEL: TimerShaftElement | null | undefined; + private traceSheetEL: TraceSheet | undefined | null; + private rangeSelect!: RangeSelect; + private processThreads: Array = [] + private processAsyncEvent: Array = [] + private processMem: Array = [] + + initElements(): void { + this.rowsEL = this.shadowRoot?.querySelector('.rows'); + this.spacerEL = this.shadowRoot?.querySelector('.spacer'); + this.timerShaftEL = this.shadowRoot?.querySelector('.timer-shaft'); + this.traceSheetEL = this.shadowRoot?.querySelector('.trace-sheet'); + this.rangeSelect = new RangeSelect(this.timerShaftEL); + this.rangeSelect.rowsEL = this.rowsEL; + document?.addEventListener("triangle-flag", (event: any) => { + this.timerShaftEL?.drawTriangle(event.detail.time, event.detail.type); + }) + + document?.addEventListener("flag-change", (event: any) => { + this.timerShaftEL?.modifyFlagList(event.detail) + if (event.detail.hidden) { + this.selectFlag = undefined; + this.traceSheetEL?.setAttribute("mode", 'hidden'); + this.visibleRows.forEach(it => it.draw(true)); + } + }) + + SpSystemTrace.scrollViewWidth = this.getScrollWidth(); + this.rangeSelect.selectHandler = (rows, refreshCheckBox) => { + if (rows.length == 0) { + this.rowsEL!.querySelectorAll>("trace-row").forEach(it => { + it.checkType = "-1" + }) + this.getVisibleRows().forEach(it => { + it.draw(true); + }); + this.traceSheetEL?.setAttribute("mode", 'hidden'); + return; + } + if (refreshCheckBox) { + if (rows.length > 0) { + this.rowsEL?.querySelectorAll>("trace-row").forEach(row => row.checkType = "0") + rows.forEach(it => it.checkType = "2") + } else { + this.rowsEL?.querySelectorAll>("trace-row").forEach(row => row.checkType = "-1") + return + } + } + let selection = new SelectionParam(); + selection.cpus = []; + selection.threadIds = []; + selection.funTids = []; + selection.trackIds = []; + selection.leftNs = 0; + selection.rightNs = 0; + let native_memory = ["All Heap & Anonymous VM", "All Heap", "All Anonymous VM"]; + rows.forEach(it => { + if (it.rowType == TraceRow.ROW_TYPE_CPU) { + selection.cpus.push(parseInt(it.rowId!)) + } else if (it.rowType == TraceRow.ROW_TYPE_PROCESS) { + this.rowsEL?.querySelectorAll>(`trace-row[row-parent-id='${it.rowId}']`).forEach(th => { + th.rangeSelect = true; + th.checkType = "2" + selection.threadIds.push(parseInt(th.rowId!)) + }) + } else if (it.rowType == TraceRow.ROW_TYPE_NATIVE_MEMORY) { + this.rowsEL?.querySelectorAll>(`trace-row[row-parent-id='${it.rowId}']`).forEach(th => { + th.rangeSelect = true; + th.checkType = "2" + selection.nativeMemory.push(th.rowId!); + }) + }else if (it.rowType == TraceRow.ROW_TYPE_THREAD) { + selection.threadIds.push(parseInt(it.rowId!)) + } else if (it.rowType == TraceRow.ROW_TYPE_FUNC) { + selection.funTids.push(parseInt(it.rowId!)) + } else if (it.rowType == TraceRow.ROW_TYPE_MEM) { + selection.trackIds.push(parseInt(it.rowId!)) + } else if (it.rowType == TraceRow.ROW_TYPE_FPS) { + selection.hasFps = true; + } else if (it.rowType == TraceRow.ROW_TYPE_HEAP) { + if (native_memory.indexOf(it.rowId ?? "") != -1) { + selection.nativeMemory.push(it.rowId!); + } else { + selection.heapIds.push(parseInt(it.rowId!)) + } + } + }) + selection.leftNs = TraceRow.rangeSelectObject?.startNS || 0; + selection.rightNs = TraceRow.rangeSelectObject?.endNS || 0; + this.traceSheetEL?.boxSelection(selection); + } + // @ts-ignore + new ResizeObserver((entries) => { + let width = entries[0].contentRect.width - 1 - SpSystemTrace.scrollViewWidth; + requestAnimationFrame(() => { + this.timerShaftEL?.updateWidth(width) + this.shadowRoot!.querySelectorAll>("trace-row").forEach(it => it.updateWidth(width)) + }) + }).observe(this); + + new ResizeObserver((entries) => { + this.getVisibleRows().forEach(it => { + it.draw(true); + }); + if (this.traceSheetEL!.getAttribute("mode") == "hidden") { + this.timerShaftEL?.removeTriangle("triangle") + } + }).observe(this.rowsEL!); + } + + getScrollWidth() { + let totalScrollDiv, scrollDiv, overflowDiv = document.createElement('div'); + overflowDiv.style.cssText = 'position:absolute; top:-2000px;width:200px; height:200px; overflow:hidden;'; + totalScrollDiv = document.body.appendChild(overflowDiv).clientWidth; + overflowDiv.style.overflowY = 'scroll'; + scrollDiv = overflowDiv.clientWidth; + document.body.removeChild(overflowDiv); + return totalScrollDiv - scrollDiv; + } + + getVisibleRows(): Array> { + let scrollTop = this.rowsEL?.scrollTop || 0; + let scrollHeight = this.rowsEL?.clientHeight || 0; + let res = [...this.rowsEL!.querySelectorAll>("trace-row")].filter((it) => { + let tr = (it as TraceRow); + let top = it.offsetTop - (this.rowsEL?.offsetTop || 0); + if ((top + it.clientHeight > scrollTop && top + it.clientHeight < scrollTop + scrollHeight + it.clientHeight) || it.collect) { + it.sleeping = false; + return true + } else { + if (!it.hasAttribute("collect-type")) { + it.sleeping = true; + } + return false; + } + }) + this.visibleRows = res; + return res; + } + + timerShaftELFlagClickHandler = (flag: Flag | undefined | null) => { + if (flag) { + setTimeout(() => { + this.traceSheetEL?.displayFlagData(flag); + }, 100) + } + } + + timerShaftELFlagChange = (hoverFlag: Flag | undefined | null, selectFlag: Flag | undefined | null) => { + this.hoverFlag = hoverFlag; + this.selectFlag = selectFlag; + this.visibleRows.forEach(it => it.draw(true)); + } + + timerShaftELRangeChange = (e: any) => { + TraceRow.range = e; + //在rowsEL显示范围内的 trace-row组件将收到时间区间变化通知 + for (let i = 0; i < this.visibleRows.length; i++) { + this.visibleRows[i].draw(); + } + } + + rowsElOnScroll = (e: any) => { + this.hoverStructNull(); + this.visibleRows = this.getVisibleRows(); + for (let index = 0; index < this.visibleRows.length; index++) { + if (index == 0 || index == this.visibleRows.length - 1) { + this.visibleRows[index].isHover = false; + } + } + } + + documentOnMouseDown = (ev: MouseEvent) => { + if (this.isMouseInSheet(ev)) return; + this.observerScrollHeightEnable = false; + if (ev.offsetX > this.timerShaftEL!.canvas!.offsetLeft) { + this.rangeSelect.mouseDown(ev) + this.timerShaftEL?.documentOnMouseDown(ev) + this.visibleRows.forEach(it => { + it.draw(); + }) + } + } + + documentOnMouseUp = (ev: MouseEvent) => { + this.rangeSelect.isMouseDown = false; + if (this.isMouseInSheet(ev)) return; + this.rangeSelect.mouseUp(ev); + this.timerShaftEL?.documentOnMouseUp(ev) + } + + documentOnMouseOut = (ev: MouseEvent) => { + if (this.isMouseInSheet(ev)) return; + if (ev.offsetX > this.timerShaftEL!.canvas!.offsetLeft) { + this.timerShaftEL?.documentOnMouseOut(ev) + } + } + + documentOnKeyPress = (ev: KeyboardEvent) => { + if (this.isMousePointInSheet) { + return; + } + this.observerScrollHeightEnable = false; + this.keyboardEnable && this.timerShaftEL!.documentOnKeyPress(ev); + } + + documentOnKeyUp = (ev: KeyboardEvent) => { + if (this.isMousePointInSheet) { + return; + } + this.observerScrollHeightEnable = false; + this.keyboardEnable && this.timerShaftEL!.documentOnKeyUp(ev); + if (ev.code == "Enter") { + if (ev.shiftKey) { + this.dispatchEvent(new CustomEvent("previous-data", { + detail: {} + })); + } else { + this.dispatchEvent(new CustomEvent("next-data", { + detail: {} + })); + } + } + } + + isMouseInSheet = (ev: MouseEvent) => { + this.isMousePointInSheet = this.traceSheetEL?.getAttribute("mode") != "hidden" && ev.offsetX > this.traceSheetEL!.offsetLeft && ev.offsetY > this.traceSheetEL!.offsetTop; + return this.isMousePointInSheet; + } + + favoriteChangeHandler = (row: TraceRow) => { + this.getVisibleRows(); + } + + selectChangeHandler = (rows: Array>) => { + this.rangeSelect.rangeTraceRow = rows; + this.rangeSelect.selectHandler?.(this.rangeSelect.rangeTraceRow, false); + } + + documentOnMouseMove = (ev: MouseEvent) => { + if (this.isMouseInSheet(ev)) return; + let rows = this.visibleRows; + if (this.timerShaftEL?.isScaling()) { + return; + } + this.timerShaftEL?.documentOnMouseMove(ev) + this.rangeSelect.mouseMove(rows, ev); + if (this.rangeSelect.isMouseDown) { + for (let i = 0; i < rows.length; i++) { + rows[i].tipEL!.style.display = "none"; + rows[i].draw(true); + } + } else { + for (let i = 0; i < rows.length; i++) { + let tr = rows[i]; + let rowsELScrollTop = this.rowsEL?.scrollTop || 0; + let x = ev.offsetX - (tr.canvasContainer?.offsetLeft || 0); + let y = ev.offsetY - (tr.canvasContainer?.offsetTop || 0) + rowsELScrollTop; + if ((!tr.collect && x > tr.frame.x && x < tr.frame.x + tr.frame.width && ev.offsetY + rowsELScrollTop > tr.offsetTop && ev.offsetY + rowsELScrollTop < tr.offsetTop + tr.frame.height) || + (tr.collect && x > tr.frame.x && x < tr.frame.x + tr.frame.width && ev.offsetY > tr.offsetTop - 48 && ev.offsetY < tr.offsetTop - 48 + tr.frame.height)) { + tr.isHover = true; + tr.hoverX = x; + tr.hoverY = tr.collect ? (ev.offsetY + 48 - tr.offsetTop) : y; + if (tr.rowType === TraceRow.ROW_TYPE_CPU) { + this.currentRowType = TraceRow.ROW_TYPE_CPU; + if (CpuStruct.hoverCpuStruct) { + tr.tip = `P:${CpuStruct.hoverCpuStruct.processName || "Process"} [${CpuStruct.hoverCpuStruct.processId}]T:${CpuStruct.hoverCpuStruct.name} [${CpuStruct.hoverCpuStruct.tid}]`; + } + tr.setTipLeft(x, CpuStruct.hoverCpuStruct) + } else if (tr.rowType === TraceRow.ROW_TYPE_CPU_FREQ) { + this.currentRowType = TraceRow.ROW_TYPE_CPU_FREQ; + if (CpuFreqStruct.hoverCpuFreqStruct) { + tr.tip = `${ColorUtils.formatNumberComma(CpuFreqStruct.hoverCpuFreqStruct.value!)} kHz` + } + tr.setTipLeft(x, CpuFreqStruct.hoverCpuFreqStruct) + } else if (tr.rowType === TraceRow.ROW_TYPE_THREAD) { + this.currentRowType = TraceRow.ROW_TYPE_THREAD; + } else if (tr.rowType === TraceRow.ROW_TYPE_FUNC) { + this.currentRowType = TraceRow.ROW_TYPE_FUNC; + } else if (tr.rowType === TraceRow.ROW_TYPE_HEAP) { + this.currentRowType = TraceRow.ROW_TYPE_HEAP; + if (HeapStruct.hoverHeapStruct) { + if (tr.drawType === 1) { + tr.tip = `${HeapStruct.hoverHeapStruct.heapsize}` + } else { + tr.tip = `${Utils.getByteWithUnit(HeapStruct.hoverHeapStruct.heapsize!)}` + } + } + tr.setTipLeft(x, HeapStruct.hoverHeapStruct) + } else { + this.hoverStructNull(); + } + tr.draw(true); + } else { + tr.onMouseLeave(x, y); + tr.isHover = false; + tr.hoverX = x; + tr.hoverY = y; + } + + } + if (ev.offsetX > this.timerShaftEL!.canvas!.offsetLeft! + && ev.offsetX < this.timerShaftEL!.canvas!.offsetLeft! + this.timerShaftEL!.canvas!.offsetWidth! + && ev.offsetY > this.rowsEL!.offsetTop + && ev.offsetY < this.rowsEL!.offsetTop + this.rowsEL!.offsetHeight + ) { + } else { + this.hoverStructNull(); + for (let i = 0, len = rows.length; i < len; i++) { + if (!(rows[i].rowType === TraceRow.ROW_TYPE_PROCESS) && this.currentRowType === rows[i].rowType) { // + rows[i].draw(true); + } + } + } + } + } + + hoverStructNull() { + CpuStruct.hoverCpuStruct = undefined; + CpuFreqStruct.hoverCpuFreqStruct = undefined; + ThreadStruct.hoverThreadStruct = undefined; + FuncStruct.hoverFuncStruct = undefined; + } + + selectStructNull() { + CpuStruct.selectCpuStruct = undefined; + CpuStruct.wakeupBean = null; + CpuFreqStruct.selectCpuFreqStruct = undefined; + ThreadStruct.selectThreadStruct = undefined; + FuncStruct.selectFuncStruct = undefined; + } + + documentOnClick = (ev: MouseEvent) => { + if (this.isMouseInSheet(ev)) return; + if (this.rangeSelect.isDrag()) { + return; + } + this.onClickHandler(); + this.documentOnMouseMove(ev) + } + + onClickHandler(){ + this.rowsEL?.querySelectorAll>("trace-row").forEach(it => it.rangeSelect = false) + this.selectStructNull(); + let threadClickHandler: any; + let cpuClickHandler: any; + threadClickHandler = (d: ThreadStruct) => { + this.observerScrollHeightEnable = false; + this.goProcess(`${d.cpu}`, "", "cpu", true); + let row = this.shadowRoot!.querySelector>(`trace-row[row-id='${d.pid}'][row-type='process'][folder]`); + if (row) { + row.expansion = false; + } + let cpuRow = this.rowsEL?.querySelectorAll>(`trace-row[row-id='${d.cpu}'][row-type='cpu']`)[0]; + let findEntry = cpuRow!.dataList!.find(it => it.startTime === d.startTime); + this.hoverStructNull(); + this.selectStructNull(); + CpuStruct.hoverCpuStruct = findEntry; + CpuStruct.selectCpuStruct = findEntry; + cpuRow!.draw(); + this.traceSheetEL?.displayCpuData(CpuStruct.selectCpuStruct!, (wakeUpBean) => { + CpuStruct.wakeupBean = wakeUpBean; + this.visibleRows.forEach(it => it.draw()); + }, cpuClickHandler); + } + let scrollTimer: any; + cpuClickHandler = (d: CpuStruct) => { + this.observerScrollHeightEnable = true; + let threadRow = this.rowsEL?.querySelectorAll>(`trace-row[row-id='${d.tid}'][row-type='thread']`)[0]; + this.goProcess(`${d.tid}`, `${d.processId}`, "thread", true) + this.observerScrollHeightCallback = () => { + if (threadRow!.isComplete) { + let findEntry = threadRow!.dataList!.find((dat) => dat.startTime === d.startTime); + this.hoverStructNull(); + this.selectStructNull(); + ThreadStruct.hoverThreadStruct = findEntry; + ThreadStruct.selectThreadStruct = findEntry; + threadRow!.draw(); + this.traceSheetEL?.displayThreadData(ThreadStruct.selectThreadStruct!, threadClickHandler); + // clearTimeout(scrollTimer); + this.goProcess(`${d.tid}`, `${d.processId}`, "thread", true) + } else { + threadRow!.onComplete = () => { + let findEntry = threadRow!.dataList!.find((dat) => dat.startTime === d.startTime); + this.hoverStructNull(); + this.selectStructNull(); + ThreadStruct.hoverThreadStruct = findEntry; + ThreadStruct.selectThreadStruct = findEntry; + threadRow!.draw(); + this.traceSheetEL?.displayThreadData(ThreadStruct.selectThreadStruct!, threadClickHandler); + clearTimeout(scrollTimer); + scrollTimer = setTimeout(() => this.goProcess(`${d.tid}`, `${d.processId}`, "thread", false), 100) + } + } + } + } + + if (CpuStruct.hoverCpuStruct) { + CpuStruct.selectCpuStruct = CpuStruct.hoverCpuStruct + this.traceSheetEL?.displayCpuData(CpuStruct.selectCpuStruct, (wakeUpBean) => { + CpuStruct.wakeupBean = wakeUpBean; + this.visibleRows.forEach(it => it.draw()); + }, cpuClickHandler); + this.timerShaftEL?.modifyFlagList(undefined); + } else if (ThreadStruct.hoverThreadStruct) { + ThreadStruct.selectThreadStruct = ThreadStruct.hoverThreadStruct; + this.traceSheetEL?.displayThreadData(ThreadStruct.selectThreadStruct, threadClickHandler); + this.timerShaftEL?.modifyFlagList(undefined); + } else if (FuncStruct.hoverFuncStruct) { + FuncStruct.selectFuncStruct = FuncStruct.hoverFuncStruct; + this.traceSheetEL?.displayFuncData(FuncStruct.hoverFuncStruct) + this.timerShaftEL?.modifyFlagList(undefined); + } else { + this.observerScrollHeightEnable = false; + this.selectFlag = null; + if (!SportRuler.isMouseInSportRuler) { + this.traceSheetEL?.setAttribute("mode", 'hidden'); + this.getVisibleRows().forEach(it => it.draw(true)); + } + } + } + + connectedCallback() { + /** + * 监听时间轴区间变化 + */ + this.timerShaftEL!.rangeChangeHandler = this.timerShaftELRangeChange; + this.timerShaftEL!.flagChangeHandler = this.timerShaftELFlagChange; + this.timerShaftEL!.flagClickHandler = this.timerShaftELFlagClickHandler; + /** + * 监听rowsEL的滚动时间,刷新可见区域的trace-row组件的时间区间(将触发trace-row组件重绘) + */ + this.rowsEL?.addEventListener('scroll', this.rowsElOnScroll) + /** + * 监听document的mousemove事件 坐标通过换算后找到当前鼠标所在的trace-row组件,将坐标传入 + */ + this.addEventListener('mousemove', this.documentOnMouseMove) + this.addEventListener('click', this.documentOnClick) + this.addEventListener('mousedown', this.documentOnMouseDown) + this.addEventListener('mouseup', this.documentOnMouseUp) + this.addEventListener('mouseout', this.documentOnMouseOut) + document.addEventListener('keypress', this.documentOnKeyPress) + document.addEventListener('keyup', this.documentOnKeyUp) + SpApplication.skinChange2 = (val: boolean) => { + this.timerShaftEL?.render() + this.rowsEL!.querySelectorAll>(`trace-row:not([sleeping])`).forEach(it => { + this.hoverStructNull(); + it.draw(); + }) + } + } + + goProcess(rowId: string, rowParentId: string, rowType: string, smooth: boolean = true) { + let row = this.shadowRoot!.querySelector>(`trace-row[row-id='${rowParentId}'][folder]`); + if (row) { + row.expansion = true + } + let rootRow = this.shadowRoot!.querySelector>(`trace-row[row-id='${rowId}'][row-type='${rowType}']`); + this.rowsEL!.scroll({ + top: rootRow!.offsetTop - this.rowsEL!.offsetTop - this.rowsEL!.offsetHeight + rootRow!.offsetHeight, + left: 0, + behavior: smooth ? "smooth" : undefined + }) + } + + rowScrollTo(offset: number, callback: Function) { + const fixedOffset = offset; + const onScroll = () => { + if (this.rowsEL!.scrollTop === fixedOffset) { + this.rowsEL!.removeEventListener('scroll', onScroll) + callback() + } + } + + this.rowsEL!.addEventListener('scroll', onScroll) + onScroll() + this.rowsEL!.scrollTo({ + top: offset, + behavior: 'smooth' + }) + } + + disconnectedCallback() { + this.timerShaftEL?.removeEventListener('range-change', this.timerShaftELRangeChange); + this.rowsEL?.removeEventListener('scroll', this.rowsElOnScroll); + this.removeEventListener('mousemove', this.documentOnMouseMove); + this.removeEventListener('click', this.documentOnClick); + this.removeEventListener('mousedown', this.documentOnMouseDown) + this.removeEventListener('mouseup', this.documentOnMouseUp) + this.removeEventListener('mouseout', this.documentOnMouseOut) + document.removeEventListener('keypress', this.documentOnKeyPress) + document.removeEventListener('keyup', this.documentOnKeyUp) + } + + loadDatabaseUrl(url: string, progress: Function, complete?: ((res: { status: boolean, msg: string }) => void) | undefined) { + this.init({url: url}, progress).then((res) => { + if (complete) { + complete(res); + } + }) + } + + loadDatabaseArrayBuffer(buf: ArrayBuffer, progress: ((name: string, percent: number) => void), complete?: ((res: { status: boolean, msg: string }) => void) | undefined) { + this.init({buf}, progress).then((res) => { + let scrollTop = this.rowsEL?.scrollTop || 0 + let scrollHeight = this.rowsEL?.clientHeight || 0 + this.rowsEL?.querySelectorAll("trace-row").forEach((it: any) => { + this.observer.observe(it); + }) + if (complete) { + complete(res); + } + }) + } + + search(query: string) { + this.shadowRoot?.querySelectorAll>('trace-row').forEach(item => { + if (query == null || query == undefined || query == '') { + if (item.rowType == TraceRow.ROW_TYPE_CPU || + item.rowType == TraceRow.ROW_TYPE_CPU_FREQ || + item.rowType == TraceRow.ROW_TYPE_NATIVE_MEMORY || + item.rowType == TraceRow.ROW_TYPE_FPS || + item.rowType == TraceRow.ROW_TYPE_PROCESS) { + item.expansion = false; + item.rowHidden = false; + } else { + item.rowHidden = true; + } + } else { + if (item.name.toLowerCase().indexOf(query.toLowerCase()) >= 0) { + item.rowHidden = false; + } else { + item.rowHidden = true; + } + } + }) + this.getVisibleRows().forEach(it => it.rowHidden = false && it.draw(true)) + } + + searchCPU(query: string): Array { + let searchResults: Array = [] + this.rowsEL!.querySelectorAll>(`trace-row[row-type='cpu']`).forEach(item => { + let res = item!.dataList!.filter(it => (it.name && it.name.search(query) >= 0) || it.tid == query + || it.processId == query + || (it.processName && it.processName.search(query) >= 0) + ) + searchResults.push(...res); + }) + searchResults.sort((a, b) => (a.startTime || 0) - (b.startTime || 0)); + return searchResults; + } + + showPreCpuStruct(currentIndex: number, cpuStructs: Array): number { + if (cpuStructs.length == 0) { + return 0; + } + let findIndex = -1; + for (let i = cpuStructs.length - 1; i >= 0; i--) { + let it = cpuStructs[i]; + if (i < currentIndex && (it.startTime!) >= (TraceRow.range!.startNS) && (it.startTime!) + (it.dur!) <= (TraceRow.range!.endNS)) { + findIndex = i; + break; + } + } + if (findIndex >= 0) { + let findEntry = cpuStructs[findIndex]; + CpuStruct.selectCpuStruct = findEntry; + this.rowsEL!.querySelectorAll>(`trace-row[row-type='cpu']`).forEach(item =>{ + item.highlight = item.rowId == `${findEntry.cpu}`; + item.draw(true); + }) + this.timerShaftEL?.drawTriangle(findEntry.startTime||0, "inverted"); + } else { + for (let i = cpuStructs.length - 1; i >= 0; i--) { + let it = cpuStructs[i]; + if ((it.startTime! + it.dur!) < (TraceRow.range!.startNS)) { + findIndex = i; + break; + } + } + let findEntry: CpuStruct; + if (findIndex == -1) { + findIndex = cpuStructs.length - 1; + } + findEntry = cpuStructs[findIndex]; + CpuStruct.selectCpuStruct = findEntry; + let startNS = this.timerShaftEL?.getRange()?.startNS || 0; + let endNS = this.timerShaftEL?.getRange()?.endNS || 0; + let harfDur = Math.trunc((endNS - startNS) / 2 - findEntry.dur! / 2); + this.timerShaftEL?.setRangeNS(findEntry.startTime! - harfDur, findEntry.startTime! + findEntry.dur! + harfDur); + this.rowsEL!.querySelectorAll>(`trace-row[row-type='cpu']`).forEach(item => { + item.highlight = item.rowId == `${findEntry.cpu}`; + item.draw(true) + }) + this.timerShaftEL?.drawTriangle(findEntry.startTime||0, "inverted"); + } + CpuStruct.hoverCpuStruct = CpuStruct.selectCpuStruct; + this.onClickHandler(); + return findIndex; + } + + showNextCpuStruct(currentIndex: number, cpuStructs: Array): number { + if (cpuStructs.length == 0) { + return 0; + } + let findIndex = cpuStructs.findIndex((it, idx) => { + return idx > currentIndex && (it.startTime!) >= (TraceRow.range!.startNS) && (it.startTime!) + (it.dur!) <= (TraceRow.range!.endNS) + }) + if (findIndex >= 0) { + let findEntry = cpuStructs[findIndex]; + CpuStruct.selectCpuStruct = findEntry; + this.rowsEL!.querySelectorAll>(`trace-row[row-type='cpu']`).forEach(item =>{ + item.highlight = item.rowId == `${findEntry.cpu}`; + item.draw(true); + }) + this.timerShaftEL?.drawTriangle(findEntry.startTime||0, "inverted"); + } else { + findIndex = cpuStructs.findIndex((it) => (it.startTime!) > (TraceRow.range!.endNS)) + let findEntry: CpuStruct; + if (findIndex == -1) { + findIndex = 0; + } + findEntry = cpuStructs[findIndex]; + CpuStruct.selectCpuStruct = findEntry; + let startNS = this.timerShaftEL?.getRange()?.startNS || 0; + let endNS = this.timerShaftEL?.getRange()?.endNS || 0; + let harfDur = Math.trunc((endNS - startNS) / 2 - findEntry.dur! / 2); + this.timerShaftEL?.setRangeNS(findEntry.startTime! - harfDur, findEntry.startTime! + findEntry.dur! + harfDur); + this.rowsEL!.querySelectorAll>(`trace-row[row-type='cpu']`).forEach(item => { + item.highlight = item.rowId == `${findEntry.cpu}`; + item.draw(true); + }) + this.timerShaftEL?.drawTriangle(findEntry.startTime||0, "inverted"); + } + CpuStruct.hoverCpuStruct = CpuStruct.selectCpuStruct; + this.onClickHandler(); + return findIndex; + } + + reset(progress: Function | undefined | null) { + if (this.rowsEL) this.rowsEL.innerHTML = '' + this.spacerEL!.style.height = '0px'; + this.rangeSelect.rangeTraceRow = []; + CpuStruct.wakeupBean = undefined; + this.selectStructNull(); + this.hoverStructNull(); + this.traceSheetEL?.setAttribute("mode", "hidden") + progress && progress("rest timershaft", 8); + this.timerShaftEL?.reset(); + progress && progress("clear cache", 10); + procedurePool.clearCache(); + } + + init = async (param: { buf?: ArrayBuffer, url?: string }, progress: Function) => { + progress("Load database", 6); + this.reset(progress); + if (param.buf) { + let {status, msg} = await threadPool.initSqlite(param.buf, progress); + if (!status) { + return {status: false, msg: msg} + } + } + if (param.url) { + let {status, msg} = await threadPool.initServer(param.url, progress); + if (!status) { + return {status: false, msg: msg} + } + } + + progress("load process threads", 50); + this.processThreads = await queryProcessThreads(); + progress("process memory", 60); + this.processMem = await queryProcessMem() + progress("async event", 63); + this.processAsyncEvent = await getAsyncEvents() + progress("time range", 65); + await this.initTotalTime(); + progress("cpu", 70); + await this.initCpu(); + progress("cpu rate", 75); + await this.initCpuRate(); + progress("cpu freq", 80); + await this.initCpuFreq(); + progress("fps", 85); + await this.initFPS(); + progress("native memory", 87); + await this.initNativeMemory(); + progress("process", 90); + await this.initProcess(); + progress("process", 93); + await this.initProcessThreadStateData(progress); + await this.initHeapStateData(progress) + progress("display", 95); + this.getVisibleRows().forEach(it => { + it.draw(); + }); + this.processThreads.length = 0; + this.processMem.length = 0; + this.processAsyncEvent.length = 0; + this.rowsEL?.querySelectorAll>("trace-row").forEach((it: any) => { + it.addEventListener('expansion-change', () => { + this.getVisibleRows().forEach(it2 => it2.draw()); + }) + }) + progress("completed", 100); + return {status: true, msg: "success"} + } + + initCpuRate = async () => { + let rates = await getCpuUtilizationRate(0, this.timerShaftEL?.totalNS || 0); + if (this.timerShaftEL) this.timerShaftEL.cpuUsage = rates; + } + + initTotalTime = async () => { + let res = await queryTotalTime(); + if (this.timerShaftEL) { + this.timerShaftEL.totalNS = res[0].total + this.timerShaftEL.loadComplete = true; + } + } + + initCpu = async () => { + let array = await queryCpuMax(); + if (array && array.length > 0 && array[0]) { + let cpuMax = array[0].cpu + CpuStruct.cpuCount = cpuMax + 1; + for (let i1 = 0; i1 < CpuStruct.cpuCount; i1++) { + const cpuId = i1; + let traceRow = new TraceRow({ + canvasNumber: 1, + alpha: true, + contextId: '2d', + isOffScreen: SpSystemTrace.isCanvasOffScreen + }); + traceRow.rowId = `${cpuId}` + traceRow.rowType = TraceRow.ROW_TYPE_CPU + traceRow.rowParentId = '' + traceRow.style.height = '40px' + traceRow.name = `Cpu ${cpuId}` + traceRow.favoriteChangeHandler = this.favoriteChangeHandler; + traceRow.selectChangeHandler = this.selectChangeHandler; + traceRow.supplier = () => queryCpuData(cpuId, 0, this.timerShaftEL?.totalNS || 0) + traceRow.onThreadHandler = ((useCache: boolean) => { + procedurePool.submitWithName(`cpu${cpuId % procedurePool.cpusLen.length}`, `cpu${cpuId}`, { + list: traceRow.must ? traceRow.dataList : undefined, + offscreen: traceRow.must ? traceRow.offscreen[0] : undefined,//是否离屏 + dpr: traceRow.dpr,//屏幕dpr值 + xs: TraceRow.range?.xs,//线条坐标信息 + isHover: traceRow.isHover, + flagMoveInfo: this.hoverFlag, + flagSelectedInfo: this.selectFlag, + hoverX: traceRow.hoverX, + hoverY: traceRow.hoverY, + canvasWidth: traceRow.canvasWidth, + canvasHeight: traceRow.canvasHeight, + hoverCpuStruct: CpuStruct.hoverCpuStruct, + selectCpuStruct: CpuStruct.selectCpuStruct, + wakeupBean: CpuStruct.wakeupBean, + isRangeSelect: traceRow.rangeSelect, + rangeSelectObject: TraceRow.rangeSelectObject, + useCache: useCache, + lineColor: traceRow.getLineColor(), + startNS: TraceRow.range?.startNS || 0, + endNS: TraceRow.range?.endNS || 0, + totalNS: TraceRow.range?.totalNS || 0, + frame: traceRow.frame + }, traceRow.must && traceRow.args.isOffScreen ? traceRow.offscreen[0] : undefined, (res: any, hover: any) => { + traceRow.must = false; + if (traceRow.args.isOffScreen == true) { + if (traceRow.isHover) { + CpuStruct.hoverCpuStruct = hover; + this.visibleRows.filter(it => it.rowType === TraceRow.ROW_TYPE_CPU && it.name !== traceRow.name).forEach(it => it.draw(true)); + } + return; + } + }) + }) + this.rowsEL?.appendChild(traceRow) + } + } + } + + initCpuFreq = async () => { + let freqList = await queryCpuFreq(); + let freqMaxList = await queryCpuMaxFreq(); + CpuFreqStruct.maxFreq = freqMaxList[0].maxFreq; + let math = () => { + let units: Array = ["", "K", "M", "G", "T", "E"]; + let sb = " "; + CpuFreqStruct.maxFreqName = " "; + if (CpuFreqStruct.maxFreq > 0) { + let log10: number = Math.ceil(Math.log10(CpuFreqStruct.maxFreq)); + let pow10: number = Math.pow(10, log10); + let afterCeil: number = Math.ceil(CpuFreqStruct.maxFreq / (pow10 / 4)) * (pow10 / 4); + CpuFreqStruct.maxFreq = afterCeil; + let unitIndex: number = Math.floor(log10 / 3); + sb = `${afterCeil / Math.pow(10, unitIndex * 3)}${units[unitIndex + 1]}hz` + } + CpuFreqStruct.maxFreqName = sb.toString(); + } + math(); + for (let i = 0; i < freqList.length; i++) { + const it = freqList[i]; + let traceRow = new TraceRow({ + canvasNumber: 1, + alpha: true, + contextId: '2d', + isOffScreen: SpSystemTrace.isCanvasOffScreen + }); + traceRow.rowId = `${it.cpu}` + traceRow.rowType = TraceRow.ROW_TYPE_CPU_FREQ + traceRow.rowParentId = '' + traceRow.style.height = '40px' + traceRow.name = `Cpu ${it.cpu} Frequency`; + traceRow.favoriteChangeHandler = this.favoriteChangeHandler; + traceRow.selectChangeHandler = this.selectChangeHandler; + traceRow.supplier = () => queryCpuFreqData(it.cpu) + traceRow.onThreadHandler = (useCache) => { + procedurePool.submitWithName(`freq${it.cpu % procedurePool.freqLen.length}`, `freq${it.cpu}`, { + list: traceRow.must ? traceRow.dataList : undefined, + offscreen: traceRow.must ? traceRow.offscreen[0] : undefined, + xs: TraceRow.range?.xs, + dpr: traceRow.dpr, + isHover: traceRow.isHover, + flagMoveInfo: this.hoverFlag, + flagSelectedInfo: this.selectFlag, + hoverX: traceRow.hoverX, + hoverY: traceRow.hoverY, + canvasWidth: traceRow.canvasWidth, + canvasHeight: traceRow.canvasHeight, + hoverCpuFreqStruct: CpuFreqStruct.hoverCpuFreqStruct, + selectCpuFreqStruct: CpuFreqStruct.selectCpuFreqStruct, + wakeupBean: CpuStruct.wakeupBean, + isRangeSelect: traceRow.rangeSelect, + rangeSelectObject: TraceRow.rangeSelectObject, + maxFreq: CpuFreqStruct.maxFreq, + maxFreqName: CpuFreqStruct.maxFreqName, + useCache: useCache, + lineColor: traceRow.getLineColor(), + startNS: TraceRow.range?.startNS || 0, + endNS: TraceRow.range?.endNS || 0, + totalNS: TraceRow.range?.totalNS || 0, + frame: traceRow.frame + }, traceRow.must && traceRow.args.isOffScreen ? traceRow.offscreen[0] : undefined, (res: any, hover: any) => { + traceRow.must = false; + if (traceRow.args.isOffScreen == true) { + if (traceRow.isHover) { + CpuFreqStruct.hoverCpuFreqStruct = hover; + this.visibleRows.filter(it => it.rowType === TraceRow.ROW_TYPE_CPU_FREQ && it.name !== traceRow.name).forEach(it => it.draw(true)); + } + return; + } + }) + + } + this.rowsEL?.appendChild(traceRow) + } + } + + initFPS = async () => { + let fpsRow = new TraceRow({canvasNumber: 1, alpha: true, contextId: '2d', isOffScreen: true}); + fpsRow.rowId = `fps` + fpsRow.rowType = TraceRow.ROW_TYPE_FPS + fpsRow.rowParentId = '' + FpsStruct.maxFps = 0 + fpsRow.style.height = '40px' + fpsRow.name = "FPS" + fpsRow.supplier = () => getFps() + fpsRow.favoriteChangeHandler = this.favoriteChangeHandler; + fpsRow.selectChangeHandler = this.selectChangeHandler; + fpsRow.onThreadHandler = (useCache) => { + procedurePool.submitWithName(`process0`, `fps0`, { + list: fpsRow.must ? fpsRow.dataList : undefined, + offscreen: fpsRow.must ? fpsRow.offscreen[0] : undefined, + xs: TraceRow.range?.xs, + dpr: fpsRow.dpr, + isHover: fpsRow.isHover, + flagMoveInfo: this.hoverFlag, + flagSelectedInfo: this.selectFlag, + hoverX: fpsRow.hoverX, + hoverY: fpsRow.hoverY, + canvasWidth: fpsRow.canvasWidth, + canvasHeight: fpsRow.canvasHeight, + wakeupBean: CpuStruct.wakeupBean, + isRangeSelect: fpsRow.rangeSelect, + rangeSelectObject: TraceRow.rangeSelectObject, + useCache: useCache, + lineColor: fpsRow.getLineColor(), + startNS: TraceRow.range?.startNS || 0, + endNS: TraceRow.range?.endNS || 0, + totalNS: TraceRow.range?.totalNS || 0, + frame: fpsRow.frame + }, fpsRow.must && fpsRow.args.isOffScreen ? fpsRow.offscreen[0] : undefined, (res: any, hover: any) => { + fpsRow.must = false; + }) + } + this.rowsEL?.appendChild(fpsRow) + } + + initNativeMemory = async () => { + let nativeProcess = await queryNativeHookProcess(); + if (nativeProcess.length == 0) { + return; + } + SpSystemTrace.EVENT_HEAP = await queryHeapGroupByEvent(); + let nativeRow = new TraceRow({ + canvasNumber: 1, + alpha: false, + contextId: '2d', + isOffScreen: SpSystemTrace.isCanvasOffScreen + }); + let process = ""; + if (nativeProcess.length > 0) { + process = ` ${nativeProcess[0].pid}` + } + nativeRow.rowId = `native-memory` + nativeRow.index = 0; + nativeRow.rowType = TraceRow.ROW_TYPE_NATIVE_MEMORY + nativeRow.drawType = 0; + nativeRow.rowParentId = ''; + nativeRow.folder = true; + nativeRow.name = `Native Memory` + process; + nativeRow.favoriteChangeHandler = this.favoriteChangeHandler; + nativeRow.selectChangeHandler = this.selectChangeHandler; + nativeRow.onDrawTypeChangeHandler = (type) => { + this.rowsEL?.querySelectorAll>(`trace-row[row-type='heap']`).forEach(it => { + it.drawType = type; + it.isComplete = false; + it.draw(); + }) + }; + nativeRow.supplier = () => new Promise>((resolve, reject) => resolve([])); + nativeRow.onThreadHandler = (useCache) => { + procedurePool.submitWithName(`process${nativeRow.index}`, `native-memory`, { + list: nativeRow.must ? nativeRow.dataList : undefined, + offscreen: nativeRow.must ? nativeRow.offscreen[0] : undefined, + xs: TraceRow.range?.xs, + dpr: nativeRow.dpr, + isHover: nativeRow.isHover, + flagMoveInfo: this.hoverFlag, + flagSelectedInfo: this.selectFlag, + hoverX: nativeRow.hoverX, + hoverY: nativeRow.hoverY, + canvasWidth: nativeRow.canvasWidth, + canvasHeight: nativeRow.canvasHeight, + isRangeSelect: nativeRow.rangeSelect, + rangeSelectObject: TraceRow.rangeSelectObject, + useCache: useCache, + lineColor: nativeRow.getLineColor(), + startNS: TraceRow.range?.startNS || 0, + endNS: TraceRow.range?.endNS || 0, + totalNS: TraceRow.range?.totalNS || 0, + frame: nativeRow.frame + }, nativeRow.must && nativeRow.args.isOffScreen ? nativeRow.offscreen[0] : undefined, (res: any) => { + nativeRow.must = false; + }) + } + + this.rowsEL?.appendChild(nativeRow) + /** + * 添加heap信息 + */ + let native_memory = ["All Heap & Anonymous VM", "All Heap", "All Anonymous VM"]; + for (let i = 0; i < native_memory.length; i++) { + let nm = native_memory[i]; + let allHeapRow = new TraceRow({ + canvasNumber: 1, + alpha: false, + contextId: '2d', + isOffScreen: true + }); + allHeapRow.index = i; + allHeapRow.rowParentId = `native-memory` + allHeapRow.rowHidden = !nativeRow.expansion + allHeapRow.style.height = '40px' + allHeapRow.name = nm; + allHeapRow.rowId = nm; + allHeapRow.drawType = 0; + allHeapRow.folder = false; + allHeapRow.rowType = TraceRow.ROW_TYPE_HEAP; + allHeapRow.favoriteChangeHandler = this.favoriteChangeHandler; + allHeapRow.selectChangeHandler = this.selectChangeHandler; + allHeapRow.setAttribute('children', '') + let arg1 = ""; + let arg2 = ""; + if (nm == "All Heap") { + arg1 = "where event_type = 'AllocEvent' or event_type = 'FreeEvent'" + arg2 = "and (event_type = 'AllocEvent' or event_type = 'FreeEvent')" + } else if (nm == "All Anonymous VM") { + arg1 = "where event_type = 'MmapEvent' or event_type = 'MunmapEvent'" + arg2 = "and (event_type = 'MmapEvent' or event_type = 'MunmapEvent')" + } else { + arg1 = "" + arg2 = "" + } + allHeapRow.supplier = () => { + if (allHeapRow.drawType === 0) { + return queryHeapByEventType(0, TraceRow.range?.totalNS || 0, arg1, arg2); + } else { + return queryHeapByEventType(0, TraceRow.range?.totalNS || 0, arg1, arg2).then(res => { + let arr: Array = []; + if (res.length > 0) { + let first = new HeapStruct(); + first.startTime = res[0].startTime; + first.endTime = res[0].endTime; + first.dur = res[0].dur; + first.eventType = res[0].eventType; + if (first.eventType == "AllocEvent" || first.eventType == "MmapEvent") { + first.heapsize = 1; + } else { + first.heapsize = -1; + } + arr.push(first); + let max = first.heapsize; + let min = first.heapsize; + for (let i = 1, len = res.length; i < len; i++) { + let heap = new HeapStruct(); + heap.startTime = res[i].startTime; + heap.endTime = res[i].endTime; + heap.eventType = res[i].eventType; + arr[i - 1].dur = heap.startTime! - arr[i - 1].startTime!; + if (i == len - 1) { + heap.dur = TraceRow.range?.totalNS! - heap.startTime!; + } + if (heap.eventType == "AllocEvent" || heap.eventType == "MmapEvent") { + heap.heapsize = arr[i - 1].heapsize! + 1; + } else { + heap.heapsize = arr[i - 1].heapsize! - 1; + } + if (heap.heapsize > max) { + max = heap.heapsize; + } + if (heap.heapsize < min) { + min = heap.heapsize; + } + arr.push(heap); + } + arr.map((heap) => { + heap.maxHeapSize = max; + heap.minHeapSize = min; + }) + } + return arr; + }) + } + } + allHeapRow.onThreadHandler = (useCache) => { + procedurePool.submitWithName(`process${allHeapRow.index}`, `heap-${nm}`, { + list: allHeapRow.must ? allHeapRow.dataList : undefined, + offscreen: !allHeapRow.isTransferCanvas ? allHeapRow.offscreen[0] : undefined, + xs: TraceRow.range?.xs, + dpr: allHeapRow.dpr, + isHover: allHeapRow.isHover, + flagMoveInfo: this.hoverFlag, + flagSelectedInfo: this.selectFlag, + hoverX: allHeapRow.hoverX, + hoverY: allHeapRow.hoverY, + canvasWidth: allHeapRow.canvasWidth, + canvasHeight: allHeapRow.canvasHeight, + isRangeSelect: allHeapRow.rangeSelect, + rangeSelectObject: TraceRow.rangeSelectObject, + useCache: useCache, + lineColor: allHeapRow.getLineColor(), + startNS: TraceRow.range?.startNS || 0, + endNS: TraceRow.range?.endNS || 0, + totalNS: TraceRow.range?.totalNS || 0, + frame: allHeapRow.frame + }, !allHeapRow.isTransferCanvas ? allHeapRow.offscreen[0] : undefined, (res: any, hover: any) => { + allHeapRow.must = false; + allHeapRow.isTransferCanvas = true; + if (allHeapRow.isHover) { + HeapStruct.hoverHeapStruct = hover; + } + }) + } + this.rowsEL?.appendChild(allHeapRow) + } + } + + initProcessThreadStateData = async (progress: Function) => { + SpSystemTrace.SPT_DATA = []; + let res = await getStatesProcessThreadDataCount(); + let count: number = (res[0] as any).count; + let pageSize = 500000; + let pages = Math.ceil(count / pageSize); + let percent = 93; + for (let i = 0; i < pages; i++) { + progress("StateProcessThread", percent + ((i + 1) / 100)); + let arr = await getStatesProcessThreadData(pageSize, i * pageSize); + SpSystemTrace.SPT_DATA = SpSystemTrace.SPT_DATA.concat(arr); + } + } + + initHeapStateData = async (progress: Function) => { + SpSystemTrace.HEAP_FRAME_DATA = []; + let res = await queryHeapFrameCount(); + let count = 0; + if (res != undefined && res.length > 0 && (res[0] as any).count != undefined) { + count = (res[0] as any).count; + } + if (count > 0) { + let pageSize = 500000; + let pages = Math.ceil(count / pageSize); + let percent = 94; + for (let i = 0; i < pages; i++) { + progress("StateHeap", percent + ((i + 1) / 100)); + let arr = await queryHeapAllTable(pageSize, i * pageSize); + SpSystemTrace.HEAP_FRAME_DATA = SpSystemTrace.HEAP_FRAME_DATA.concat(arr); + } + } + } + + /** + * 添加进程信息 + */ + initProcess = async () => { + let processList = await queryProcess(); + let heapPidList = await queryHeapPid() + for (let i = 0; i < processList.length; i++) { + const it = processList[i]; + let processRow = new TraceRow({ + canvasNumber: 1, + alpha: false, + contextId: '2d', + isOffScreen: SpSystemTrace.isCanvasOffScreen + }); + processRow.rowId = `${it.pid}` + processRow.index = i; + processRow.rowType = TraceRow.ROW_TYPE_PROCESS + processRow.rowParentId = ''; + processRow.folder = true; + processRow.name = `${it.processName || "Process"} ${it.pid}`; + processRow.supplier = () => queryProcessData(it.pid || -1, 0, TraceRow.range?.totalNS || 0); + processRow.favoriteChangeHandler = this.favoriteChangeHandler; + processRow.selectChangeHandler = this.selectChangeHandler; + processRow.onThreadHandler = (useCache) => { + procedurePool.submitWithName(`process${(processRow.index) % procedurePool.processLen.length}`, `process ${processRow.index} ${it.processName}`, { + list: processRow.must ? processRow.dataList : undefined, + offscreen: processRow.must ? processRow.offscreen[0] : undefined, + xs: TraceRow.range?.xs, + dpr: processRow.dpr, + isHover: processRow.isHover, + flagMoveInfo: this.hoverFlag, + flagSelectedInfo: this.selectFlag, + hoverX: processRow.hoverX, + hoverY: processRow.hoverY, + canvasWidth: processRow.canvasWidth, + canvasHeight: processRow.canvasHeight, + isRangeSelect: processRow.rangeSelect, + rangeSelectObject: TraceRow.rangeSelectObject, + wakeupBean: CpuStruct.wakeupBean, + cpuCount: CpuStruct.cpuCount, + useCache: useCache, + lineColor: processRow.getLineColor(), + startNS: TraceRow.range?.startNS || 0, + endNS: TraceRow.range?.endNS || 0, + totalNS: TraceRow.range?.totalNS || 0, + frame: processRow.frame + }, processRow.must && processRow.args.isOffScreen ? processRow.offscreen[0] : undefined, (res: any) => { + processRow.must = false; + }) + } + this.rowsEL?.appendChild(processRow) + /** + * 添加进程内存信息 + */ + let processMem = this.processMem.filter(mem => mem.pid === it.pid); + processMem.forEach(mem => { + let row = new TraceRow({ + canvasNumber: 1, + alpha: false, + contextId: '2d', + isOffScreen: SpSystemTrace.isCanvasOffScreen + }); + row.rowId = `${mem.trackId}` + row.rowType = TraceRow.ROW_TYPE_MEM + row.rowParentId = `${it.pid}` + row.rowHidden = !processRow.expansion + row.style.height = '40px' + row.style.width = `100%`; + row.name = `${mem.trackName}`; + row.setAttribute('children', ''); + row.favoriteChangeHandler = this.favoriteChangeHandler; + row.selectChangeHandler = this.selectChangeHandler; + row.supplier = () => queryProcessMemData(mem.trackId).then(res => { + let maxValue = Math.max(...res.map(it => it.value || 0)) + for (let j = 0; j < res.length; j++) { + res[j].maxValue = maxValue; + if (j == res.length - 1) { + res[j].duration = (TraceRow.range?.totalNS || 0) - (res[j].startTime || 0); + } else { + res[j].duration = (res[j + 1].startTime || 0) - (res[j].startTime || 0); + } + if (j > 0) { + res[j].delta = (res[j].value || 0) - (res[j - 1].value || 0); + } else { + res[j].delta = 0; + } + } + return res + }); + row.onThreadHandler = (useCache) => { + procedurePool.submitWithName(`cpu${mem.trackId % procedurePool.cpusLen.length}`, `mem ${mem.trackId} ${mem.trackName}`, { + list: row.must ? row.dataList : undefined, + offscreen: row.must ? row.offscreen[0] : undefined,//是否离屏 + dpr: row.dpr,//屏幕dpr值 + xs: TraceRow.range?.xs,//线条坐标信息 + isHover: row.isHover, + flagMoveInfo: this.hoverFlag, + flagSelectedInfo: this.selectFlag, + hoverX: row.hoverX, + hoverY: row.hoverY, + canvasWidth: row.canvasWidth, + canvasHeight: row.canvasHeight, + wakeupBean: CpuStruct.wakeupBean, + isRangeSelect: row.rangeSelect, + rangeSelectObject: TraceRow.rangeSelectObject, + useCache: useCache, + lineColor: row.getLineColor(), + startNS: TraceRow.range?.startNS || 0, + endNS: TraceRow.range?.endNS || 0, + totalNS: TraceRow.range?.totalNS || 0, + frame: row.frame + }, row.must && row.args.isOffScreen ? row.offscreen[0] : undefined, (res: any) => { + row.must = false; + }) + } + this.rowsEL?.appendChild(row) + }); + /** + * 添加进程线程信息 + */ + let threads = this.processThreads.filter(thread => thread.pid === it.pid && thread.tid != 0 && thread.threadName != null); + for (let j = 0; j < threads.length; j++) { + let thread = threads[j]; + let threadRow = new TraceRow({ + canvasNumber: 1, + alpha: false, + contextId: '2d', + isOffScreen: SpSystemTrace.isCanvasOffScreen + }); + threadRow.rowId = `${thread.tid}` + threadRow.rowType = TraceRow.ROW_TYPE_THREAD + threadRow.rowParentId = `${it.pid}` + threadRow.rowHidden = !processRow.expansion + threadRow.index = j + threadRow.style.height = '30px' + threadRow.setAttribute("height", `30`); + threadRow.style.width = `100%`; + threadRow.name = `${thread.threadName} ${thread.tid}`; + threadRow.setAttribute('children', '') + threadRow.favoriteChangeHandler = this.favoriteChangeHandler; + threadRow.selectChangeHandler = this.selectChangeHandler; + threadRow.supplier = () => queryThreadData(thread.tid || 0).then(res => { + getFunDataByTid(thread.tid || 0).then((funs: Array) => { + if (funs.length > 0) { + const groupedBy: Array = []; + for (let i = 0; i < funs.length; i++) { + if (groupedBy[funs[i].depth || 0]) { + groupedBy[funs[i].depth || 0].push(funs[i]); + } else { + groupedBy[funs[i].depth || 0] = [funs[i]]; + } + } + let max = Math.max(...funs.map(it => it.depth || 0)) + 1 + let maxHeight = max * 20; + let funcRow = new TraceRow({ + canvasNumber: max, + alpha: false, + contextId: '2d', + isOffScreen: SpSystemTrace.isCanvasOffScreen + }); + funcRow.rowId = `${thread.tid}` + funcRow.rowType = TraceRow.ROW_TYPE_FUNC + funcRow.rowParentId = `${it.pid}` + funcRow.rowHidden = !processRow.expansion + funcRow.checkType = threadRow.checkType; + funcRow.style.width = `100%`; + funcRow.setAttribute("height", `${maxHeight}`); + funcRow.name = `${thread.threadName} ${thread.tid}`; + funcRow.setAttribute('children', '') + funcRow.supplier = () => new Promise((resolve, reject) => resolve(funs)) + funcRow.favoriteChangeHandler = this.favoriteChangeHandler; + funcRow.selectChangeHandler = this.selectChangeHandler; + funcRow.onThreadHandler = (useCache) => { + for (let k = 0; k < groupedBy.length; k++) { + procedurePool.submitWithName(`cpu${k % procedurePool.cpusLen.length}`, `func${thread.tid}${k}${thread.threadName}`, { + list: funcRow.must ? groupedBy[k] : undefined, + offscreen: funcRow.must ? funcRow.offscreen[k] : undefined,//是否离屏 + dpr: funcRow.dpr,//屏幕dpr值 + xs: TraceRow.range?.xs,//线条坐标信息 + isHover: funcRow.isHover, + flagMoveInfo: this.hoverFlag, + flagSelectedInfo: this.selectFlag, + hoverX: funcRow.hoverX, + hoverY: funcRow.hoverY, + depth: k, + canvasWidth: funcRow.canvasWidth, + canvasHeight: funcRow.canvasHeight, + maxHeight: maxHeight, + hoverFuncStruct: FuncStruct.hoverFuncStruct, + selectFuncStruct: FuncStruct.selectFuncStruct, + wakeupBean: CpuStruct.wakeupBean, + isRangeSelect: funcRow.rangeSelect, + rangeSelectObject: TraceRow.rangeSelectObject, + useCache: useCache, + lineColor: funcRow.getLineColor(), + startNS: TraceRow.range?.startNS || 0, + endNS: TraceRow.range?.endNS || 0, + totalNS: TraceRow.range?.totalNS || 0, + frame: funcRow.frame + }, funcRow.must && funcRow.args.isOffScreen ? funcRow.offscreen[k] : undefined, (res: any, hover: any) => { + funcRow.must = false; + if (funcRow.args.isOffScreen == true) { + if (funcRow.isHover && hover) { + FuncStruct.hoverFuncStruct = hover; + // this.visibleRows.filter(it => it.rowType === TraceRow.ROW_TYPE_CPU && it.name !== traceRow.name).forEach(it => it.draw()); + } + return; + } + }) + } + } + this.insertAfter(funcRow, threadRow) + this.observer.observe(funcRow) + funcRow.draw(); + this.getVisibleRows();//function 由于后插入dom,所以需要重新获取可见行 + } + }) + return res; + }) + threadRow.onThreadHandler = (useCache) => { + procedurePool.submitWithName(`process${(threadRow.index) % procedurePool.processLen.length}`, `thread ${thread.tid} ${thread.threadName}`, { + list: threadRow.must ? threadRow.dataList : undefined, + offscreen: threadRow.must ? threadRow.offscreen[0] : undefined,//是否离屏 + dpr: threadRow.dpr,//屏幕dpr值 + xs: TraceRow.range?.xs,//线条坐标信息 + isHover: threadRow.isHover, + flagMoveInfo: this.hoverFlag, + flagSelectedInfo: this.selectFlag, + hoverX: threadRow.hoverX, + hoverY: threadRow.hoverY, + canvasWidth: threadRow.canvasWidth, + canvasHeight: threadRow.canvasHeight, + hoverThreadStruct: ThreadStruct.hoverThreadStruct, + selectThreadStruct: ThreadStruct.selectThreadStruct, + wakeupBean: CpuStruct.wakeupBean, + isRangeSelect: threadRow.rangeSelect, + rangeSelectObject: TraceRow.rangeSelectObject, + useCache: useCache, + lineColor: threadRow.getLineColor(), + startNS: TraceRow.range?.startNS || 0, + endNS: TraceRow.range?.endNS || 0, + totalNS: TraceRow.range?.totalNS || 0, + frame: threadRow.frame + }, threadRow.must && threadRow.args.isOffScreen ? threadRow.offscreen[0] : undefined, (res: any, hover: any) => { + threadRow.must = false; + if (threadRow.args.isOffScreen == true) { + if (threadRow.isHover) { + ThreadStruct.hoverThreadStruct = hover; + // this.visibleRows.filter(it => it.rowType === TraceRow.ROW_TYPE_CPU && it.name !== traceRow.name).forEach(it => it.draw()); + } + return; + } + }) + } + this.rowsEL?.appendChild(threadRow) + }; + } + } + + insertAfter(newEl: HTMLElement, targetEl: HTMLElement) { + let parentEl = targetEl.parentNode; + if (parentEl!.lastChild == targetEl) { + parentEl!.appendChild(newEl); + } else { + parentEl!.insertBefore(newEl, targetEl.nextSibling); + } + } + + initHtml(): string { + return ` + +
      + +
      +
      + +
      + `; + } +} diff --git a/host/ide/src/trace/component/SpWelcomePage.ts b/host/ide/src/trace/component/SpWelcomePage.ts new file mode 100644 index 0000000..8da17c7 --- /dev/null +++ b/host/ide/src/trace/component/SpWelcomePage.ts @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../base-ui/BaseElement.js"; + +@element('sp-welcome') +export class SpWelcomePage extends BaseElement { + initElements(): void { + } + + initHtml(): string { + return ` + +
      + +
      + `; + } + +} diff --git a/host/ide/src/trace/component/Sptext.ts b/host/ide/src/trace/component/Sptext.ts new file mode 100644 index 0000000..f0b089a --- /dev/null +++ b/host/ide/src/trace/component/Sptext.ts @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../base-ui/BaseElement.js"; +import "../../base-ui/popover/LitPopover.js" +import {LitPopover} from "../../base-ui/popover/LitPopover.js"; + +@element('sp-text') +export class Sptext extends BaseElement { + initElements(): void { + let litPopover = this.shadowRoot?.querySelector('lit-popover'); + litPopover!.dataSource = [{ + text: "# Samples", + isSelected: true + },] + } + + initHtml(): string { + return ` + +
      +
      + + + + +
      +
      `; + } + +} \ No newline at end of file diff --git a/host/ide/src/trace/component/StackBar.ts b/host/ide/src/trace/component/StackBar.ts new file mode 100644 index 0000000..9c9a0b2 --- /dev/null +++ b/host/ide/src/trace/component/StackBar.ts @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../base-ui/BaseElement.js"; +import {SelectionData} from "../bean/BoxSelection.js"; +import {Utils} from "./trace/base/Utils.js"; + +@element('stack-bar') +export class StackBar extends BaseElement { + private container: HTMLDivElement | undefined | null; + + static get observedAttributes() { + return ['mode'];// max min hidden show 三种状态 + } + + set data(val: Array) { + let map = new Map(); + for (let v of val) { + if (map.has(v.state)) { + let sv = map.get(v.state) + sv!.value = sv!.value + v.wallDuration; + sv!.state = v.state + " : " + sv!.value.toFixed(7) + "ms"; + } else { + let sv = new StackValue(); + sv.value = v.wallDuration; + sv.state = v.state + " : " + sv.value.toFixed(7) + "ms"; + sv.color = Utils.getStateColor(v.stateJX); + map.set(v.state, sv); + } + } + let totalDuration = 0 + let arr: Array = [] + for (let key of map.keys()) { + if (key == " ") { + totalDuration = map.get(key)!.value; + } else { + arr.push(map.get(key)!); + } + } + arr.sort((a, b) => a.value - b.value) + this.container!.innerHTML = '' + for (let stackValue of arr) { + this.container!.appendChild(this.createBarElement(stackValue, totalDuration)) + } + } + + initElements(): void { + this.container = this.shadowRoot?.querySelector('#container'); + } + + initHtml(): string { + return ` + +
      +
      `; + } + + getStateWidth(state: string): number { + let canvas = document.createElement("canvas"); + let context = canvas.getContext("2d"); + context!.font = "9pt"; + let metrics = context!.measureText(state); + return metrics.width; + } + + createBarElement(sv: StackValue, total: number): HTMLDivElement { + let bar = document.createElement('div'); + bar.setAttribute('class', 'state-text'); + bar.setAttribute('need-width', this.getStateWidth(sv.state) + ""); + bar.style.backgroundColor = sv.color; + bar.textContent = sv.state + if (sv.state.startsWith("Sleeping")) { + bar.style.color = '#555555'; + } else { + bar.style.color = '#ffffff'; + } + let weight = (sv.value * 1.0 / total) * 100.00 + if (weight < 1) { + weight = 1; + } + bar.style.width = weight + "%" + bar.addEventListener('mouseover', (event) => { + let needWidth = parseFloat(bar.getAttribute('need-width')!); + let trueWidth = parseFloat(window.getComputedStyle(bar).width); + if (trueWidth < needWidth) { + bar.style.width = (needWidth + 100) + "px" + } + }) + bar.addEventListener('mouseleave', (event) => { + let weight = (sv.value * 1.0 / total) * 100.00 + if (weight < 1) { + weight = 1; + } + bar.style.width = weight + "%" + }) + return bar; + } +} + +export class StackValue { + state: string = ""; + color: string = ""; + value: number = 0 +} \ No newline at end of file diff --git a/host/ide/src/trace/component/setting/SpAllocations.ts b/host/ide/src/trace/component/setting/SpAllocations.ts new file mode 100644 index 0000000..9763ea0 --- /dev/null +++ b/host/ide/src/trace/component/setting/SpAllocations.ts @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../base-ui/BaseElement.js"; + +@element('sp-allocations') +export class SpAllocations extends BaseElement { + private processId: HTMLInputElement | null | undefined; + private unwindEL: HTMLInputElement | null | undefined; + private shareMemory: HTMLInputElement | null | undefined; + private shareMemoryUnit: HTMLSelectElement | null | undefined; + private filterMemory: HTMLInputElement | null | undefined; + private filterMemoryUnit: HTMLSelectElement | null | undefined; + + get appProcess(): string { + return this.processId!.value; + } + + get unwind(): number { + return Number(this.unwindEL!.value); + } + + get shared(): number { + let value = this.shareMemory?.value || ""; + if (value != "") { + let unit = this.shareMemoryUnit?.value || ""; + return this.convertToValue(value, unit); + } + return 8192; + } + + get filter(): number { + let value = this.filterMemory?.value || ""; + if (value != "") { + return Number(value); + } + return 0; + } + + initElements(): void { + this.processId = this.shadowRoot?.getElementById("pid") as HTMLInputElement + this.unwindEL = this.shadowRoot?.getElementById("unwind") as HTMLInputElement + this.shareMemory = this.shadowRoot?.getElementById("shareMemory") as HTMLInputElement + this.shareMemoryUnit = this.shadowRoot?.getElementById("shareMemoryUnit") as HTMLSelectElement + this.filterMemory = this.shadowRoot?.getElementById("filterSized") as HTMLInputElement + this.filterMemoryUnit = this.shadowRoot?.getElementById("filterSizedUnit") as HTMLSelectElement + } + + initHtml(): string { + return ` + +
      +
      + Allocations +
      +
      + ProcessId or ProcessName : + +
      +
      + Max unwind level : + +
      +
      + Shared Memory Size (Must be a multiple of 4 KB) : +
      + + +
      +
      +
      + Filter Memory Size : +
      + + +
      +
      +
      `; + } + + private convertToValue(input: string, unit: string): number { + let value: number; + switch (unit) { + case "MB": + value = Number(input) * 1024 * 1024; + break; + case "KB": + value = Number(input) * 1024; + break; + default: + value = 0; + } + let number = value / 4096; + if (number > 0 && number < 1) { + return 8192; + } + return parseInt(String(number)); + } +} diff --git a/host/ide/src/trace/component/setting/SpCheckDesBox.ts b/host/ide/src/trace/component/setting/SpCheckDesBox.ts new file mode 100644 index 0000000..1f2d2d7 --- /dev/null +++ b/host/ide/src/trace/component/setting/SpCheckDesBox.ts @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../base-ui/BaseElement.js"; +import {LitCheckBox} from "../../../base-ui/checkbox/LitCheckBox.js"; + +@element('check-des-box') +export class SpCheckDesBox extends BaseElement { + private _checkBox: LitCheckBox | undefined + private _des: HTMLSpanElement | undefined; + + static get observedAttributes() { + return ['checked', 'value', 'des'] + } + + set des(des: string) { + this.setAttribute("des", des) + } + + get value(): string { + return this.getAttribute("value") || ''; + } + + set value(value: string) { + this.setAttribute("value", value) + this._checkBox!.value = value + } + + get checked() { + return this.getAttribute("checked") != null + } + + set checked(checked: boolean) { + if (checked) { + this.setAttribute("checked", 'true') + this._checkBox!.checked = true + } else { + this.removeAttribute("checked") + this._checkBox!.checked = false + } + } + + initElements(): void { + this._checkBox = this.shadowRoot?.getElementById('checkBox') as LitCheckBox + this._des = this.shadowRoot?.getElementById("des") as HTMLSpanElement; + } + + initHtml(): string { + return ` + + +
      + +
      `; + } + + public connectedCallback() { + this._checkBox?.addEventListener("change", (ev: any) => { + let detail = ev.detail; + this.checked = detail.checked + this.dispatchEvent(new CustomEvent("onchange", {detail})) + }) + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + if (name == 'checked') { + this._checkBox!.checked = newValue !== null; + } + if (name == 'value') { + this._checkBox!.value = newValue + } + if (name == 'des') { + this._des!.textContent = newValue + } + } +} + +export interface checkDesBean { + value: string; + isSelect: boolean; + des: string; +} diff --git a/host/ide/src/trace/component/setting/SpProbesConfig.ts b/host/ide/src/trace/component/setting/SpProbesConfig.ts new file mode 100644 index 0000000..a2af05c --- /dev/null +++ b/host/ide/src/trace/component/setting/SpProbesConfig.ts @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../base-ui/BaseElement.js"; +import {checkDesBean, SpCheckDesBox} from "./SpCheckDesBox.js"; +import {LitCheckBox} from "../../../base-ui/checkbox/LitCheckBox.js"; +import {LitRadioGroup} from "../../../base-ui/radiobox/LitRadioGroup.js"; + +@element('probes-config') +export class SpProbesConfig extends BaseElement { + private traceConfigList: Array | undefined + private memoryConfigList: Array | undefined + private hitraceConfigList: Array | undefined; + private hitrace: SpCheckDesBox | undefined + + private _traceConfig: HTMLElement | undefined; + private _memoryConfig: HTMLElement | undefined | null; + + get traceConfig() { + let selectedTrace = this._traceConfig?.querySelectorAll(`check-des-box[checked]`) || []; + let values = [] + for (const litCheckBoxElement of selectedTrace) { + values.push(litCheckBoxElement.value) + } + if (this.hitrace && this.hitrace.checked) { + values.push(this.hitrace.value) + } + return values; + } + + get memoryConfig() { + let values = [] + let selectedMemory = this._memoryConfig?.querySelectorAll(`check-des-box[checked]`) as NodeListOf + for (const litCheckBoxElement of selectedMemory) { + values.push(litCheckBoxElement.value) + } + return values + } + + get traceEvents() { + let values = [] + if (this.hitrace && this.hitrace.checked) { + let parent = this.shadowRoot?.querySelector('.user-events') as Element + const siblingNode = parent?.querySelectorAll(`lit-check-box[name=userEvents][checked]`); + for (const litCheckBoxElement of siblingNode) { + values.push(litCheckBoxElement.value) + } + } + return values; + } + + get hilogConfig() { + let logLevel = this.shadowRoot?.getElementById('logLevel') as LitCheckBox; + if (logLevel.checked) { + let logRadio = this.shadowRoot?.getElementById('log-radio') as LitRadioGroup; + return logRadio.value; + } else { + return [] + } + } + + initElements(): void { + this.traceConfigList = [ + {value: 'Scheduling details', isSelect: false, des: "enables high-detailed tracking of scheduling events"} + , { + value: "Board voltages & frequency", isSelect: false, + des: "Tracks voltage and frequency changes from board sensors" + } + , { + value: "CPU Frequency and idle states", isSelect: false, + des: "Records cpu frequency and idle state change viaftrace" + } + , { + value: "High frequency memory", isSelect: false, + des: "Allows to track short memory splikes and transitories through ftrace's mm_event." + + " rss_stat and ion events. " + + "Available only on recent Kernel version >= 4.19" + } + , { + value: "Advanced ftrace config", isSelect: false, + des: "Enable individual events and tune the kernel-tracng(ftrace) module." + + "The events enabled here are in addition to those from" + + " enabled by other probes." + } + , {value: "Syscalls", isSelect: false, des: "Tracks the enter and exit of all syscalls"} + , {value: "FPS", isSelect: false, des: "Tracks the FPS"}] + this._traceConfig = this.shadowRoot?.querySelector(".trace-config") as HTMLElement + this.traceConfigList.forEach(configBean => { + let checkDesBox = new SpCheckDesBox(); + checkDesBox.value = configBean.value; + checkDesBox.checked = configBean.isSelect; + checkDesBox.des = configBean.des; + this._traceConfig?.appendChild(checkDesBox) + }) + this.memoryConfigList = [ + {value: 'Kernel meminfo', isSelect: false, des: "polling of /proc/meminfo"}, + { + value: 'Virtual memory stats', + isSelect: false, + des: "Periodically polls virtual memory stats from /proc/vmstat." + + " Allows to gather statistics about swap," + + "eviction, compression and pagecache efficiency" + }] + this._memoryConfig = this.shadowRoot?.querySelector(".memory-config") + this.memoryConfigList.forEach(configBean => { + let checkDesBox = new SpCheckDesBox(); + checkDesBox.value = configBean.value; + checkDesBox.checked = configBean.isSelect; + checkDesBox.des = configBean.des; + this._memoryConfig?.appendChild(checkDesBox) + }) + this.hitraceConfigList = ["ability", "ace", "app", "ark", "binder", "disk", "distributeddatamgr" + , "dsoftbus", "freq", "graphic", "i2c", "idle", "irq", "mdfs", "memory", "memreclaim", "misc", "mmc", + "msdp", "multimodalinput", "notification", "ohos", "pagecache", "regulators", "rpc", "sched", "sensors", "sync" + , "window", "workq", "zaudio", "zcamera", "zimage", "zmedia"] + this.hitrace = this.shadowRoot?.getElementById("hitrace") as SpCheckDesBox + let parent = this.shadowRoot?.querySelector('.user-events') as Element + this.hitraceConfigList?.forEach(value => { + let litCheckBox = new LitCheckBox(); + litCheckBox.setAttribute("name", "userEvents") + litCheckBox.value = value; + litCheckBox.addEventListener("change", (ev: any) => { + let detail = ev.detail; + if (this.hitrace?.checked == false) { + this.hitrace.checked = detail.checked + } + if (detail.checked == false && this.hitrace?.checked == true) { + let hasChecked = false; + const nodes = parent?.querySelectorAll(`lit-check-box[name=userEvents]`); + nodes.forEach(vv => { + if (vv.checked) { + hasChecked = true; + } + }) + if (!hasChecked) { + this.hitrace.checked = hasChecked + } + } + }) + parent.append(litCheckBox) + }) + } + + initHtml(): string { + return ` + +
      +
      Record mode
      +
      +
      +
      +
      + +
      + +
      +
      +
      +
      +
      + Memory Config +
      +
      +
      +
      `; + } + + //当 custom element首次被插入文档DOM时,被调用。 + public connectedCallback() { + let parent = this.shadowRoot?.querySelector('.user-events') as Element + const siblingNode = parent?.querySelectorAll(`lit-check-box[name=userEvents]`); + this.hitrace!.addEventListener('onchange', (ev: any) => { + let detail = ev.detail; + siblingNode.forEach(node => { + node.checked = detail.checked + }) + }) + } +} \ No newline at end of file diff --git a/host/ide/src/trace/component/setting/SpRecordSetting.ts b/host/ide/src/trace/component/setting/SpRecordSetting.ts new file mode 100644 index 0000000..9db5328 --- /dev/null +++ b/host/ide/src/trace/component/setting/SpRecordSetting.ts @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../base-ui/BaseElement.js"; +import "../../../base-ui/radiobox/LitRadioBox.js"; +import {LitRadioBox} from "../../../base-ui/radiobox/LitRadioBox.js"; +import "../../../base-ui/slider/LitSlider.js"; +import {LitSlider} from "../../../base-ui/slider/LitSlider.js"; + +@element('record-setting') +export class SpRecordSetting extends BaseElement { + private memoryBufferSlider: LitSlider | undefined; + private maxDurationSliders: LitSlider | undefined; + private radioBox: LitRadioBox | undefined + + get recordMod(): boolean { + if (this.radioBox) { + return this.radioBox.checked + } + return false; + } + + get bufferSize(): number { + let bufferSize = this.shadowRoot?.querySelector(".buffer-size") as HTMLElement + return Number(bufferSize.getAttribute("percent")); + } + + get maxDur(): number { + let bufferSize = this.shadowRoot?.querySelector(".max-duration") as HTMLElement + return Number(bufferSize.getAttribute("percent")); + } + + initElements(): void { + this.radioBox = this.shadowRoot?.querySelector("#litradio") as LitRadioBox + this.memoryBufferSlider = this.shadowRoot?.querySelector('#memory-buffer') as LitSlider; + let sliderSize1 = this.memoryBufferSlider.sliderSize; + this.maxDurationSliders = this.shadowRoot?.querySelector('#max-duration') as LitSlider; + let sliderSize2 = this.maxDurationSliders.sliderSize; + } + + initHtml(): string { + return ` + +
      +
      + Record mode + Stop when full +
      +
      + In-memory buffer size + +
      +
      + Max duration + +
      +
      `; + } +} diff --git a/host/ide/src/trace/component/setting/SpTraceCommand.ts b/host/ide/src/trace/component/setting/SpTraceCommand.ts new file mode 100644 index 0000000..b3f2a54 --- /dev/null +++ b/host/ide/src/trace/component/setting/SpTraceCommand.ts @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../base-ui/BaseElement.js"; + +@element('trace-command') +export class SpTraceCommand extends BaseElement { + private codeHl: HTMLTextAreaElement | undefined | null; + private copyEl: HTMLElement | undefined | null; + private codeCopyText: HTMLInputElement | undefined; + + get hdcCommon(): string { + return this.codeHl!.textContent + ""; + } + + set hdcCommon(value: string) { + this.codeHl!.textContent = value; + } + + //当 custom element首次被插入文档DOM时,被调用。 + public connectedCallback() { + this.codeHl = this.shadowRoot?.querySelector('#code-text') as HTMLTextAreaElement; + this.copyEl = this.shadowRoot?.querySelector('#copy-image') as HTMLElement; + this.codeHl.textContent = "" + this.copyEl?.addEventListener('click', this.codeCopyEvent) + this.codeHl.addEventListener('selectionchange', this.textSelectEvent) + } + + public disconnectedCallback() { + this.copyEl?.removeEventListener('click', this.codeCopyEvent) + } + + codeCopyEvent = (event: any) => { + this.codeHl?.select(); + document.execCommand('copy'); + } + + textSelectEvent = (event: any) => { + this.copyEl!.style.backgroundColor = '#FFFFFF'; + } + + initElements(): void { + } + + initHtml(): string { + return ` + +
      + + +
      `; + } +} diff --git a/host/ide/src/trace/component/setting/bean/ProfilerServiceTypes.ts b/host/ide/src/trace/component/setting/bean/ProfilerServiceTypes.ts new file mode 100644 index 0000000..88464f8 --- /dev/null +++ b/host/ide/src/trace/component/setting/bean/ProfilerServiceTypes.ts @@ -0,0 +1,850 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface ProfilerSessionConfigBufferConfig { + pages: number; + policy: ProfilerSessionConfigBufferConfigPolicy; +} + +export enum ProfilerSessionConfigBufferConfigPolicy { + RECYCLE = 0, + FLATTEN = 1, + UNRECOGNIZED = 2, +} + +export interface ProfilerSessionConfig { + buffers: ProfilerSessionConfigBufferConfig[]; + sessionMode: ProfilerSessionConfigMode; + /** for OFFLINE mode, result file path */ + resultFile: string; + /** for OFFLINE mode, result file max size in KB */ + resultMaxSize: number; + /** for OFFLINE mode, sample duration in ms */ + sampleDuration: number; + /** if set to non-zero value, session will auto-destroyed after CreateSession in ms */ + keepAliveTime: number; +} + +export enum ProfilerSessionConfigMode { + /** OFFLINE - save all plugin results to result file. */ + OFFLINE = 0, + /** ONLINE - push all plugin results to host PC with streamed FetchDataResponse. */ + ONLINE = 1, + UNRECOGNIZED = -1, +} + +export interface TracePluginConfig { + /** kernel event set */ + ftraceEvents: string[]; + /** bytrace event set */ + hitraceCategories: string[]; + /** bytrace app set */ + hitraceApps: string[]; + /** kernel trace buffer size */ + bufferSizeKb: number; + /** time interval in milliseconds to notify service process */ + flushIntervalMs: number; + /** buffer water mark threshold to notify service process */ + flushThresholdKb: number; + /** parse /proc/kallsyms or not */ + parseKsyms: boolean; + /** value for trace_clock */ + clock: string; + /** time interval in milliseconds to read kernel trace buffer */ + tracePeriodMs: number; + /** raw data file prefix for debug */ + rawDataPrefix: string; + /** time duration in millisconds for trace actions */ + traceDurationMs: number; + /** enable debug options */ + debugOn: boolean; + hitraceTime: number; +} + +export interface CreateSessionRequest { + requestId: number; + sessionConfig: ProfilerSessionConfig | undefined; + pluginConfigs: ProfilerPluginConfig[]; +} + +export interface ProfilerPluginConfig { + pluginName: string; + sampleInterval: number; + configData: T; +} + + +export interface MemoryConfig { + /** set true to report process list */ + reportProcessTree: boolean; + /** set true to report memory counter from /proc/meminfo */ + reportSysmemMemInfo: boolean; + /** set required counter list of system meminfo, eg:MemTotal, MemFree, etc. */ + sysMeminfoCounters: SysMeminfoType[]; + /** set true to report memory counter from /proc/vmstat */ + reportSysmemVmemInfo: boolean; + /** set required counter list of virtual system meminfo, eg:nr_free_pages, nr_anon_pages, etc. */ + sysVmeminfoCounters: SysVMeminfoType[]; + /** set true to report process meminfo from /proc/${pid}/stat */ + reportProcessMemInfo: boolean; + /** set true to report application memory usage summary, eg:java heap memory, native heap, stack memory, etc. */ + reportAppMemInfo: boolean; + /** + * set true to report application memory by memory service, otherwise, + * application memory will count up by /proc/${pid}/smaps information + */ + reportAppMemByMemoryService: boolean; + /** set required pid list */ + pid: number[]; +} + + +export function sysVMeminfoTypeFromJSON(object: any): SysVMeminfoType { + switch (object) { + case 0: + case "VMEMINFO_UNSPECIFIED": + return SysVMeminfoType.VMEMINFO_UNSPECIFIED; + case 1: + case "VMEMINFO_NR_FREE_PAGES": + return SysVMeminfoType.VMEMINFO_NR_FREE_PAGES; + case 2: + case "VMEMINFO_NR_ALLOC_BATCH": + return SysVMeminfoType.VMEMINFO_NR_ALLOC_BATCH; + case 3: + case "VMEMINFO_NR_INACTIVE_ANON": + return SysVMeminfoType.VMEMINFO_NR_INACTIVE_ANON; + case 4: + case "VMEMINFO_NR_ACTIVE_ANON": + return SysVMeminfoType.VMEMINFO_NR_ACTIVE_ANON; + case 5: + case "VMEMINFO_NR_INACTIVE_FILE": + return SysVMeminfoType.VMEMINFO_NR_INACTIVE_FILE; + case 6: + case "VMEMINFO_NR_ACTIVE_FILE": + return SysVMeminfoType.VMEMINFO_NR_ACTIVE_FILE; + case 7: + case "VMEMINFO_NR_UNEVICTABLE": + return SysVMeminfoType.VMEMINFO_NR_UNEVICTABLE; + case 8: + case "VMEMINFO_NR_MLOCK": + return SysVMeminfoType.VMEMINFO_NR_MLOCK; + case 9: + case "VMEMINFO_NR_ANON_PAGES": + return SysVMeminfoType.VMEMINFO_NR_ANON_PAGES; + case 10: + case "VMEMINFO_NR_MAPPED": + return SysVMeminfoType.VMEMINFO_NR_MAPPED; + case 11: + case "VMEMINFO_NR_FILE_PAGES": + return SysVMeminfoType.VMEMINFO_NR_FILE_PAGES; + case 12: + case "VMEMINFO_NR_DIRTY": + return SysVMeminfoType.VMEMINFO_NR_DIRTY; + case 13: + case "VMEMINFO_NR_WRITEBACK": + return SysVMeminfoType.VMEMINFO_NR_WRITEBACK; + case 14: + case "VMEMINFO_NR_SLAB_RECLAIMABLE": + return SysVMeminfoType.VMEMINFO_NR_SLAB_RECLAIMABLE; + case 15: + case "VMEMINFO_NR_SLAB_UNRECLAIMABLE": + return SysVMeminfoType.VMEMINFO_NR_SLAB_UNRECLAIMABLE; + case 16: + case "VMEMINFO_NR_PAGE_TABLE_PAGES": + return SysVMeminfoType.VMEMINFO_NR_PAGE_TABLE_PAGES; + case 17: + case "VMEMINFO_NR_KERNEL_STACK": + return SysVMeminfoType.VMEMINFO_NR_KERNEL_STACK; + case 18: + case "VMEMINFO_NR_OVERHEAD": + return SysVMeminfoType.VMEMINFO_NR_OVERHEAD; + case 19: + case "VMEMINFO_NR_UNSTABLE": + return SysVMeminfoType.VMEMINFO_NR_UNSTABLE; + case 20: + case "VMEMINFO_NR_BOUNCE": + return SysVMeminfoType.VMEMINFO_NR_BOUNCE; + case 21: + case "VMEMINFO_NR_VMSCAN_WRITE": + return SysVMeminfoType.VMEMINFO_NR_VMSCAN_WRITE; + case 22: + case "VMEMINFO_NR_VMSCAN_IMMEDIATE_RECLAIM": + return SysVMeminfoType.VMEMINFO_NR_VMSCAN_IMMEDIATE_RECLAIM; + case 23: + case "VMEMINFO_NR_WRITEBACK_TEMP": + return SysVMeminfoType.VMEMINFO_NR_WRITEBACK_TEMP; + case 24: + case "VMEMINFO_NR_ISOLATED_ANON": + return SysVMeminfoType.VMEMINFO_NR_ISOLATED_ANON; + case 25: + case "VMEMINFO_NR_ISOLATED_FILE": + return SysVMeminfoType.VMEMINFO_NR_ISOLATED_FILE; + case 26: + case "VMEMINFO_NR_SHMEM": + return SysVMeminfoType.VMEMINFO_NR_SHMEM; + case 27: + case "VMEMINFO_NR_DIRTIED": + return SysVMeminfoType.VMEMINFO_NR_DIRTIED; + case 28: + case "VMEMINFO_NR_WRITTEN": + return SysVMeminfoType.VMEMINFO_NR_WRITTEN; + case 29: + case "VMEMINFO_NR_PAGES_SCANNED": + return SysVMeminfoType.VMEMINFO_NR_PAGES_SCANNED; + case 30: + case "VMEMINFO_WORKINGSET_REFAULT": + return SysVMeminfoType.VMEMINFO_WORKINGSET_REFAULT; + case 31: + case "VMEMINFO_WORKINGSET_ACTIVATE": + return SysVMeminfoType.VMEMINFO_WORKINGSET_ACTIVATE; + case 32: + case "VMEMINFO_WORKINGSET_NODERECLAIM": + return SysVMeminfoType.VMEMINFO_WORKINGSET_NODERECLAIM; + case 33: + case "VMEMINFO_NR_ANON_TRANSPARENT_HUGEPAGES": + return SysVMeminfoType.VMEMINFO_NR_ANON_TRANSPARENT_HUGEPAGES; + case 34: + case "VMEMINFO_NR_FREE_CMA": + return SysVMeminfoType.VMEMINFO_NR_FREE_CMA; + case 35: + case "VMEMINFO_NR_SWAPCACHE": + return SysVMeminfoType.VMEMINFO_NR_SWAPCACHE; + case 36: + case "VMEMINFO_NR_DIRTY_THRESHOLD": + return SysVMeminfoType.VMEMINFO_NR_DIRTY_THRESHOLD; + case 37: + case "VMEMINFO_NR_DIRTY_BACKGROUND_THRESHOLD": + return SysVMeminfoType.VMEMINFO_NR_DIRTY_BACKGROUND_THRESHOLD; + case 38: + case "VMEMINFO_PGPGIN": + return SysVMeminfoType.VMEMINFO_PGPGIN; + case 39: + case "VMEMINFO_PGPGOUT": + return SysVMeminfoType.VMEMINFO_PGPGOUT; + case 40: + case "VMEMINFO_PGPGOUTCLEAN": + return SysVMeminfoType.VMEMINFO_PGPGOUTCLEAN; + case 41: + case "VMEMINFO_PSWPIN": + return SysVMeminfoType.VMEMINFO_PSWPIN; + case 42: + case "VMEMINFO_PSWPOUT": + return SysVMeminfoType.VMEMINFO_PSWPOUT; + case 43: + case "VMEMINFO_PGALLOC_DMA": + return SysVMeminfoType.VMEMINFO_PGALLOC_DMA; + case 44: + case "VMEMINFO_PGALLOC_NORMAL": + return SysVMeminfoType.VMEMINFO_PGALLOC_NORMAL; + case 45: + case "VMEMINFO_PGALLOC_MOVABLE": + return SysVMeminfoType.VMEMINFO_PGALLOC_MOVABLE; + case 46: + case "VMEMINFO_PGFREE": + return SysVMeminfoType.VMEMINFO_PGFREE; + case 47: + case "VMEMINFO_PGACTIVATE": + return SysVMeminfoType.VMEMINFO_PGACTIVATE; + case 48: + case "VMEMINFO_PGDEACTIVATE": + return SysVMeminfoType.VMEMINFO_PGDEACTIVATE; + case 49: + case "VMEMINFO_PGFAULT": + return SysVMeminfoType.VMEMINFO_PGFAULT; + case 50: + case "VMEMINFO_PGMAJFAULT": + return SysVMeminfoType.VMEMINFO_PGMAJFAULT; + case 51: + case "VMEMINFO_PGREFILL_DMA": + return SysVMeminfoType.VMEMINFO_PGREFILL_DMA; + case 52: + case "VMEMINFO_PGREFILL_NORMAL": + return SysVMeminfoType.VMEMINFO_PGREFILL_NORMAL; + case 53: + case "VMEMINFO_PGREFILL_MOVABLE": + return SysVMeminfoType.VMEMINFO_PGREFILL_MOVABLE; + case 54: + case "VMEMINFO_PGSTEAL_KSWAPD_DMA": + return SysVMeminfoType.VMEMINFO_PGSTEAL_KSWAPD_DMA; + case 55: + case "VMEMINFO_PGSTEAL_KSWAPD_NORMAL": + return SysVMeminfoType.VMEMINFO_PGSTEAL_KSWAPD_NORMAL; + case 56: + case "VMEMINFO_PGSTEAL_KSWAPD_MOVABLE": + return SysVMeminfoType.VMEMINFO_PGSTEAL_KSWAPD_MOVABLE; + case 57: + case "VMEMINFO_PGSTEAL_DIRECT_DMA": + return SysVMeminfoType.VMEMINFO_PGSTEAL_DIRECT_DMA; + case 58: + case "VMEMINFO_PGSTEAL_DIRECT_NORMAL": + return SysVMeminfoType.VMEMINFO_PGSTEAL_DIRECT_NORMAL; + case 59: + case "VMEMINFO_PGSTEAL_DIRECT_MOVABLE": + return SysVMeminfoType.VMEMINFO_PGSTEAL_DIRECT_MOVABLE; + case 60: + case "VMEMINFO_PGSCAN_KSWAPD_DMA": + return SysVMeminfoType.VMEMINFO_PGSCAN_KSWAPD_DMA; + case 61: + case "VMEMINFO_PGSCAN_KSWAPD_NORMAL": + return SysVMeminfoType.VMEMINFO_PGSCAN_KSWAPD_NORMAL; + case 62: + case "VMEMINFO_PGSCAN_KSWAPD_MOVABLE": + return SysVMeminfoType.VMEMINFO_PGSCAN_KSWAPD_MOVABLE; + case 63: + case "VMEMINFO_PGSCAN_DIRECT_DMA": + return SysVMeminfoType.VMEMINFO_PGSCAN_DIRECT_DMA; + case 64: + case "VMEMINFO_PGSCAN_DIRECT_NORMAL": + return SysVMeminfoType.VMEMINFO_PGSCAN_DIRECT_NORMAL; + case 65: + case "VMEMINFO_PGSCAN_DIRECT_MOVABLE": + return SysVMeminfoType.VMEMINFO_PGSCAN_DIRECT_MOVABLE; + case 66: + case "VMEMINFO_PGSCAN_DIRECT_THROTTLE": + return SysVMeminfoType.VMEMINFO_PGSCAN_DIRECT_THROTTLE; + case 67: + case "VMEMINFO_PGINODESTEAL": + return SysVMeminfoType.VMEMINFO_PGINODESTEAL; + case 68: + case "VMEMINFO_SLABS_SCANNED": + return SysVMeminfoType.VMEMINFO_SLABS_SCANNED; + case 69: + case "VMEMINFO_KSWAPD_INODESTEAL": + return SysVMeminfoType.VMEMINFO_KSWAPD_INODESTEAL; + case 70: + case "VMEMINFO_KSWAPD_LOW_WMARK_HIT_QUICKLY": + return SysVMeminfoType.VMEMINFO_KSWAPD_LOW_WMARK_HIT_QUICKLY; + case 71: + case "VMEMINFO_KSWAPD_HIGH_WMARK_HIT_QUICKLY": + return SysVMeminfoType.VMEMINFO_KSWAPD_HIGH_WMARK_HIT_QUICKLY; + case 72: + case "VMEMINFO_PAGEOUTRUN": + return SysVMeminfoType.VMEMINFO_PAGEOUTRUN; + case 73: + case "VMEMINFO_ALLOCSTALL": + return SysVMeminfoType.VMEMINFO_ALLOCSTALL; + case 74: + case "VMEMINFO_PGROTATED": + return SysVMeminfoType.VMEMINFO_PGROTATED; + case 75: + case "VMEMINFO_DROP_PAGECACHE": + return SysVMeminfoType.VMEMINFO_DROP_PAGECACHE; + case 76: + case "VMEMINFO_DROP_SLAB": + return SysVMeminfoType.VMEMINFO_DROP_SLAB; + case 77: + case "VMEMINFO_PGMIGRATE_SUCCESS": + return SysVMeminfoType.VMEMINFO_PGMIGRATE_SUCCESS; + case 78: + case "VMEMINFO_PGMIGRATE_FAIL": + return SysVMeminfoType.VMEMINFO_PGMIGRATE_FAIL; + case 79: + case "VMEMINFO_COMPACT_MIGRATE_SCANNED": + return SysVMeminfoType.VMEMINFO_COMPACT_MIGRATE_SCANNED; + case 80: + case "VMEMINFO_COMPACT_FREE_SCANNED": + return SysVMeminfoType.VMEMINFO_COMPACT_FREE_SCANNED; + case 81: + case "VMEMINFO_COMPACT_ISOLATED": + return SysVMeminfoType.VMEMINFO_COMPACT_ISOLATED; + case 82: + case "VMEMINFO_COMPACT_STALL": + return SysVMeminfoType.VMEMINFO_COMPACT_STALL; + case 83: + case "VMEMINFO_COMPACT_FAIL": + return SysVMeminfoType.VMEMINFO_COMPACT_FAIL; + case 84: + case "VMEMINFO_COMPACT_SUCCESS": + return SysVMeminfoType.VMEMINFO_COMPACT_SUCCESS; + case 85: + case "VMEMINFO_COMPACT_DAEMON_WAKE": + return SysVMeminfoType.VMEMINFO_COMPACT_DAEMON_WAKE; + case 86: + case "VMEMINFO_UNEVICTABLE_PGS_CULLED": + return SysVMeminfoType.VMEMINFO_UNEVICTABLE_PGS_CULLED; + case 87: + case "VMEMINFO_UNEVICTABLE_PGS_SCANNED": + return SysVMeminfoType.VMEMINFO_UNEVICTABLE_PGS_SCANNED; + case 88: + case "VMEMINFO_UNEVICTABLE_PGS_RESCUED": + return SysVMeminfoType.VMEMINFO_UNEVICTABLE_PGS_RESCUED; + case 89: + case "VMEMINFO_UNEVICTABLE_PGS_MLOCKED": + return SysVMeminfoType.VMEMINFO_UNEVICTABLE_PGS_MLOCKED; + case 90: + case "VMEMINFO_UNEVICTABLE_PGS_MUNLOCKED": + return SysVMeminfoType.VMEMINFO_UNEVICTABLE_PGS_MUNLOCKED; + case 91: + case "VMEMINFO_UNEVICTABLE_PGS_CLEARED": + return SysVMeminfoType.VMEMINFO_UNEVICTABLE_PGS_CLEARED; + case 92: + case "VMEMINFO_UNEVICTABLE_PGS_STRANDED": + return SysVMeminfoType.VMEMINFO_UNEVICTABLE_PGS_STRANDED; + case 93: + case "VMEMINFO_NR_ZSPAGES": + return SysVMeminfoType.VMEMINFO_NR_ZSPAGES; + case 94: + case "VMEMINFO_NR_ION_HEAP": + return SysVMeminfoType.VMEMINFO_NR_ION_HEAP; + case 95: + case "VMEMINFO_NR_GPU_HEAP": + return SysVMeminfoType.VMEMINFO_NR_GPU_HEAP; + case 96: + case "VMEMINFO_ALLOCSTALL_DMA": + return SysVMeminfoType.VMEMINFO_ALLOCSTALL_DMA; + case 97: + case "VMEMINFO_ALLOCSTALL_MOVABLE": + return SysVMeminfoType.VMEMINFO_ALLOCSTALL_MOVABLE; + case 98: + case "VMEMINFO_ALLOCSTALL_NORMAL": + return SysVMeminfoType.VMEMINFO_ALLOCSTALL_NORMAL; + case 99: + case "VMEMINFO_COMPACT_DAEMON_FREE_SCANNED": + return SysVMeminfoType.VMEMINFO_COMPACT_DAEMON_FREE_SCANNED; + case 100: + case "VMEMINFO_COMPACT_DAEMON_MIGRATE_SCANNED": + return SysVMeminfoType.VMEMINFO_COMPACT_DAEMON_MIGRATE_SCANNED; + case 101: + case "VMEMINFO_NR_FASTRPC": + return SysVMeminfoType.VMEMINFO_NR_FASTRPC; + case 102: + case "VMEMINFO_NR_INDIRECTLY_RECLAIMABLE": + return SysVMeminfoType.VMEMINFO_NR_INDIRECTLY_RECLAIMABLE; + case 103: + case "VMEMINFO_NR_ION_HEAP_POOL": + return SysVMeminfoType.VMEMINFO_NR_ION_HEAP_POOL; + case 104: + case "VMEMINFO_NR_KERNEL_MISC_RECLAIMABLE": + return SysVMeminfoType.VMEMINFO_NR_KERNEL_MISC_RECLAIMABLE; + case 105: + case "VMEMINFO_NR_SHADOW_CALL_STACK_BYTES": + return SysVMeminfoType.VMEMINFO_NR_SHADOW_CALL_STACK_BYTES; + case 106: + case "VMEMINFO_NR_SHMEM_HUGEPAGES": + return SysVMeminfoType.VMEMINFO_NR_SHMEM_HUGEPAGES; + case 107: + case "VMEMINFO_NR_SHMEM_PMDMAPPED": + return SysVMeminfoType.VMEMINFO_NR_SHMEM_PMDMAPPED; + case 108: + case "VMEMINFO_NR_UNRECLAIMABLE_PAGES": + return SysVMeminfoType.VMEMINFO_NR_UNRECLAIMABLE_PAGES; + case 109: + case "VMEMINFO_NR_ZONE_ACTIVE_ANON": + return SysVMeminfoType.VMEMINFO_NR_ZONE_ACTIVE_ANON; + case 110: + case "VMEMINFO_NR_ZONE_ACTIVE_FILE": + return SysVMeminfoType.VMEMINFO_NR_ZONE_ACTIVE_FILE; + case 111: + case "VMEMINFO_NR_ZONE_INACTIVE_ANON": + return SysVMeminfoType.VMEMINFO_NR_ZONE_INACTIVE_ANON; + case 112: + case "VMEMINFO_NR_ZONE_INACTIVE_FILE": + return SysVMeminfoType.VMEMINFO_NR_ZONE_INACTIVE_FILE; + case 113: + case "VMEMINFO_NR_ZONE_UNEVICTABLE": + return SysVMeminfoType.VMEMINFO_NR_ZONE_UNEVICTABLE; + case 114: + case "VMEMINFO_NR_ZONE_WRITE_PENDING": + return SysVMeminfoType.VMEMINFO_NR_ZONE_WRITE_PENDING; + case 115: + case "VMEMINFO_OOM_KILL": + return SysVMeminfoType.VMEMINFO_OOM_KILL; + case 116: + case "VMEMINFO_PGLAZYFREE": + return SysVMeminfoType.VMEMINFO_PGLAZYFREE; + case 117: + case "VMEMINFO_PGLAZYFREED": + return SysVMeminfoType.VMEMINFO_PGLAZYFREED; + case 118: + case "VMEMINFO_PGREFILL": + return SysVMeminfoType.VMEMINFO_PGREFILL; + case 119: + case "VMEMINFO_PGSCAN_DIRECT": + return SysVMeminfoType.VMEMINFO_PGSCAN_DIRECT; + case 120: + case "VMEMINFO_PGSCAN_KSWAPD": + return SysVMeminfoType.VMEMINFO_PGSCAN_KSWAPD; + case 121: + case "VMEMINFO_PGSKIP_DMA": + return SysVMeminfoType.VMEMINFO_PGSKIP_DMA; + case 122: + case "VMEMINFO_PGSKIP_MOVABLE": + return SysVMeminfoType.VMEMINFO_PGSKIP_MOVABLE; + case 123: + case "VMEMINFO_PGSKIP_NORMAL": + return SysVMeminfoType.VMEMINFO_PGSKIP_NORMAL; + case 124: + case "VMEMINFO_PGSTEAL_DIRECT": + return SysVMeminfoType.VMEMINFO_PGSTEAL_DIRECT; + case 125: + case "VMEMINFO_PGSTEAL_KSWAPD": + return SysVMeminfoType.VMEMINFO_PGSTEAL_KSWAPD; + case 126: + case "VMEMINFO_SWAP_RA": + return SysVMeminfoType.VMEMINFO_SWAP_RA; + case 127: + case "VMEMINFO_SWAP_RA_HIT": + return SysVMeminfoType.VMEMINFO_SWAP_RA_HIT; + case 128: + case "VMEMINFO_WORKINGSET_RESTORE": + return SysVMeminfoType.VMEMINFO_WORKINGSET_RESTORE; + case -1: + case "UNRECOGNIZED": + default: + return SysVMeminfoType.UNRECOGNIZED; + } +} + +export enum SysVMeminfoType { + UNRECOGNIZED = "UNRECOGNIZED", + VMEMINFO_UNSPECIFIED = "VMEMINFO_UNSPECIFIED", + VMEMINFO_NR_FREE_PAGES = "VMEMINFO_NR_FREE_PAGES", + VMEMINFO_NR_ALLOC_BATCH = "VMEMINFO_NR_ALLOC_BATCH", + VMEMINFO_NR_INACTIVE_ANON = "VMEMINFO_NR_INACTIVE_ANON", + VMEMINFO_NR_ACTIVE_ANON = "VMEMINFO_NR_ACTIVE_ANON", + VMEMINFO_NR_INACTIVE_FILE = "VMEMINFO_NR_INACTIVE_FILE", + VMEMINFO_NR_ACTIVE_FILE = "VMEMINFO_NR_ACTIVE_FILE", + VMEMINFO_NR_UNEVICTABLE = "VMEMINFO_NR_UNEVICTABLE", + VMEMINFO_NR_MLOCK = "VMEMINFO_NR_MLOCK", + VMEMINFO_NR_ANON_PAGES = "VMEMINFO_NR_ANON_PAGES", + VMEMINFO_NR_MAPPED = "VMEMINFO_NR_MAPPED", + VMEMINFO_NR_FILE_PAGES = "VMEMINFO_NR_FILE_PAGES", + VMEMINFO_NR_DIRTY = "VMEMINFO_NR_DIRTY", + VMEMINFO_NR_WRITEBACK = "VMEMINFO_NR_WRITEBACK", + VMEMINFO_NR_SLAB_RECLAIMABLE = "VMEMINFO_NR_SLAB_RECLAIMABLE", + VMEMINFO_NR_SLAB_UNRECLAIMABLE = "VMEMINFO_NR_SLAB_UNRECLAIMABLE", + VMEMINFO_NR_PAGE_TABLE_PAGES = "VMEMINFO_NR_PAGE_TABLE_PAGES", + VMEMINFO_NR_KERNEL_STACK = "VMEMINFO_NR_KERNEL_STACK", + VMEMINFO_NR_OVERHEAD = "VMEMINFO_NR_OVERHEAD", + VMEMINFO_NR_UNSTABLE = "VMEMINFO_NR_UNSTABLE", + VMEMINFO_NR_BOUNCE = "VMEMINFO_NR_BOUNCE", + VMEMINFO_NR_VMSCAN_WRITE = "VMEMINFO_NR_VMSCAN_WRITE", + VMEMINFO_NR_VMSCAN_IMMEDIATE_RECLAIM = "VMEMINFO_NR_VMSCAN_IMMEDIATE_RECLAIM", + VMEMINFO_NR_WRITEBACK_TEMP = "VMEMINFO_NR_WRITEBACK_TEMP", + VMEMINFO_NR_ISOLATED_ANON = "VMEMINFO_NR_ISOLATED_ANON", + VMEMINFO_NR_ISOLATED_FILE = "VMEMINFO_NR_ISOLATED_FILE", + VMEMINFO_NR_SHMEM = "VMEMINFO_NR_SHMEM", + VMEMINFO_NR_DIRTIED = "VMEMINFO_NR_DIRTIED", + VMEMINFO_NR_WRITTEN = "VMEMINFO_NR_WRITTEN", + VMEMINFO_NR_PAGES_SCANNED = "VMEMINFO_NR_PAGES_SCANNED", + VMEMINFO_WORKINGSET_REFAULT = "VMEMINFO_WORKINGSET_REFAULT", + VMEMINFO_WORKINGSET_ACTIVATE = "VMEMINFO_WORKINGSET_ACTIVATE", + VMEMINFO_WORKINGSET_NODERECLAIM = "VMEMINFO_WORKINGSET_NODERECLAIM", + VMEMINFO_NR_ANON_TRANSPARENT_HUGEPAGES = "VMEMINFO_NR_ANON_TRANSPARENT_HUGEPAGES", + VMEMINFO_NR_FREE_CMA = "VMEMINFO_NR_FREE_CMA", + VMEMINFO_NR_SWAPCACHE = "VMEMINFO_NR_SWAPCACHE", + VMEMINFO_NR_DIRTY_THRESHOLD = "VMEMINFO_NR_DIRTY_THRESHOLD", + VMEMINFO_NR_DIRTY_BACKGROUND_THRESHOLD = "VMEMINFO_NR_DIRTY_BACKGROUND_THRESHOLD", + VMEMINFO_PGPGIN = "VMEMINFO_PGPGIN", + VMEMINFO_PGPGOUT = "VMEMINFO_PGPGOUT", + VMEMINFO_PGPGOUTCLEAN = "VMEMINFO_PGPGOUTCLEAN", + VMEMINFO_PSWPIN = "VMEMINFO_PSWPIN", + VMEMINFO_PSWPOUT = "VMEMINFO_PSWPOUT", + VMEMINFO_PGALLOC_DMA = "VMEMINFO_PGALLOC_DMA", + VMEMINFO_PGALLOC_NORMAL = "VMEMINFO_PGALLOC_NORMAL", + VMEMINFO_PGALLOC_MOVABLE = "VMEMINFO_PGALLOC_MOVABLE", + VMEMINFO_PGFREE = "VMEMINFO_PGFREE", + VMEMINFO_PGACTIVATE = "VMEMINFO_PGACTIVATE", + VMEMINFO_PGDEACTIVATE = "VMEMINFO_PGDEACTIVATE", + VMEMINFO_PGFAULT = "VMEMINFO_PGFAULT", + VMEMINFO_PGMAJFAULT = "VMEMINFO_PGMAJFAULT", + VMEMINFO_PGREFILL_DMA = "VMEMINFO_PGREFILL_DMA", + VMEMINFO_PGREFILL_NORMAL = "VMEMINFO_PGREFILL_NORMAL", + VMEMINFO_PGREFILL_MOVABLE = "VMEMINFO_PGREFILL_MOVABLE", + VMEMINFO_PGSTEAL_KSWAPD_DMA = "VMEMINFO_PGSTEAL_KSWAPD_DMA", + VMEMINFO_PGSTEAL_KSWAPD_NORMAL = "VMEMINFO_PGSTEAL_KSWAPD_NORMAL", + VMEMINFO_PGSTEAL_KSWAPD_MOVABLE = "VMEMINFO_PGSTEAL_KSWAPD_MOVABLE", + VMEMINFO_PGSTEAL_DIRECT_DMA = "VMEMINFO_PGSTEAL_DIRECT_DMA", + VMEMINFO_PGSTEAL_DIRECT_NORMAL = "VMEMINFO_PGSTEAL_DIRECT_NORMAL", + VMEMINFO_PGSTEAL_DIRECT_MOVABLE = "VMEMINFO_PGSTEAL_DIRECT_MOVABLE", + VMEMINFO_PGSCAN_KSWAPD_DMA = "VMEMINFO_PGSCAN_KSWAPD_DMA", + VMEMINFO_PGSCAN_KSWAPD_NORMAL = "VMEMINFO_PGSCAN_KSWAPD_NORMAL", + VMEMINFO_PGSCAN_KSWAPD_MOVABLE = "VMEMINFO_PGSCAN_KSWAPD_MOVABLE", + VMEMINFO_PGSCAN_DIRECT_DMA = "VMEMINFO_PGSCAN_DIRECT_DMA", + VMEMINFO_PGSCAN_DIRECT_NORMAL = "VMEMINFO_PGSCAN_DIRECT_NORMAL", + VMEMINFO_PGSCAN_DIRECT_MOVABLE = "VMEMINFO_PGSCAN_DIRECT_MOVABLE", + VMEMINFO_PGSCAN_DIRECT_THROTTLE = "VMEMINFO_PGSCAN_DIRECT_THROTTLE", + VMEMINFO_PGINODESTEAL = "VMEMINFO_PGINODESTEAL", + VMEMINFO_SLABS_SCANNED = "VMEMINFO_SLABS_SCANNED", + VMEMINFO_KSWAPD_INODESTEAL = "VMEMINFO_KSWAPD_INODESTEAL", + VMEMINFO_KSWAPD_LOW_WMARK_HIT_QUICKLY = "VMEMINFO_KSWAPD_LOW_WMARK_HIT_QUICKLY", + VMEMINFO_KSWAPD_HIGH_WMARK_HIT_QUICKLY = "VMEMINFO_KSWAPD_HIGH_WMARK_HIT_QUICKLY", + VMEMINFO_PAGEOUTRUN = "VMEMINFO_PAGEOUTRUN", + VMEMINFO_ALLOCSTALL = "VMEMINFO_ALLOCSTALL", + VMEMINFO_PGROTATED = "VMEMINFO_PGROTATED", + VMEMINFO_DROP_PAGECACHE = "VMEMINFO_DROP_PAGECACHE", + VMEMINFO_DROP_SLAB = "VMEMINFO_DROP_SLAB", + VMEMINFO_PGMIGRATE_SUCCESS = "VMEMINFO_PGMIGRATE_SUCCESS", + VMEMINFO_PGMIGRATE_FAIL = "VMEMINFO_PGMIGRATE_FAIL", + VMEMINFO_COMPACT_MIGRATE_SCANNED = "VMEMINFO_COMPACT_MIGRATE_SCANNED", + VMEMINFO_COMPACT_FREE_SCANNED = "VMEMINFO_COMPACT_FREE_SCANNED", + VMEMINFO_COMPACT_ISOLATED = "VMEMINFO_COMPACT_ISOLATED", + VMEMINFO_COMPACT_STALL = "VMEMINFO_COMPACT_STALL", + VMEMINFO_COMPACT_FAIL = "VMEMINFO_COMPACT_FAIL", + VMEMINFO_COMPACT_SUCCESS = "VMEMINFO_COMPACT_SUCCESS", + VMEMINFO_COMPACT_DAEMON_WAKE = "VMEMINFO_COMPACT_DAEMON_WAKE", + VMEMINFO_UNEVICTABLE_PGS_CULLED = "VMEMINFO_UNEVICTABLE_PGS_CULLED", + VMEMINFO_UNEVICTABLE_PGS_SCANNED = "VMEMINFO_UNEVICTABLE_PGS_SCANNED ", + VMEMINFO_UNEVICTABLE_PGS_RESCUED = "VMEMINFO_UNEVICTABLE_PGS_RESCUED", + VMEMINFO_UNEVICTABLE_PGS_MLOCKED = "VMEMINFO_UNEVICTABLE_PGS_MLOCKED", + VMEMINFO_UNEVICTABLE_PGS_MUNLOCKED = "VMEMINFO_UNEVICTABLE_PGS_MUNLOCKED", + VMEMINFO_UNEVICTABLE_PGS_CLEARED = "VMEMINFO_UNEVICTABLE_PGS_CLEARED", + VMEMINFO_UNEVICTABLE_PGS_STRANDED = "VMEMINFO_UNEVICTABLE_PGS_STRANDED", + VMEMINFO_NR_ZSPAGES = "VMEMINFO_NR_ZSPAGES", + VMEMINFO_NR_ION_HEAP = "VMEMINFO_NR_ION_HEAP", + VMEMINFO_NR_GPU_HEAP = "VMEMINFO_NR_GPU_HEAP", + VMEMINFO_ALLOCSTALL_DMA = "VMEMINFO_ALLOCSTALL_DMA", + VMEMINFO_ALLOCSTALL_MOVABLE = "VMEMINFO_ALLOCSTALL_MOVABLE", + VMEMINFO_ALLOCSTALL_NORMAL = "VMEMINFO_ALLOCSTALL_NORMAL", + VMEMINFO_COMPACT_DAEMON_FREE_SCANNED = "VMEMINFO_COMPACT_DAEMON_FREE_SCANNED", + VMEMINFO_COMPACT_DAEMON_MIGRATE_SCANNED = "VMEMINFO_COMPACT_DAEMON_MIGRATE_SCANNED", + VMEMINFO_NR_FASTRPC = "VMEMINFO_NR_FASTRPC", + VMEMINFO_NR_INDIRECTLY_RECLAIMABLE = "VMEMINFO_NR_INDIRECTLY_RECLAIMABLE", + VMEMINFO_NR_ION_HEAP_POOL = "VMEMINFO_NR_ION_HEAP_POOL", + VMEMINFO_NR_KERNEL_MISC_RECLAIMABLE = "VMEMINFO_NR_KERNEL_MISC_RECLAIMABLE", + VMEMINFO_NR_SHADOW_CALL_STACK_BYTES = "VMEMINFO_NR_SHADOW_CALL_STACK_BYTES", + VMEMINFO_NR_SHMEM_HUGEPAGES = "VMEMINFO_NR_SHMEM_HUGEPAGES", + VMEMINFO_NR_SHMEM_PMDMAPPED = "VMEMINFO_NR_SHMEM_PMDMAPPED", + VMEMINFO_NR_UNRECLAIMABLE_PAGES = "VMEMINFO_NR_UNRECLAIMABLE_PAGES", + VMEMINFO_NR_ZONE_ACTIVE_ANON = "VMEMINFO_NR_ZONE_ACTIVE_ANON", + VMEMINFO_NR_ZONE_ACTIVE_FILE = "VMEMINFO_NR_ZONE_ACTIVE_FILE", + VMEMINFO_NR_ZONE_INACTIVE_ANON = "VMEMINFO_NR_ZONE_INACTIVE_ANON", + VMEMINFO_NR_ZONE_INACTIVE_FILE = "VMEMINFO_NR_ZONE_INACTIVE_FILE", + VMEMINFO_NR_ZONE_UNEVICTABLE = "VMEMINFO_NR_ZONE_UNEVICTABLE", + VMEMINFO_NR_ZONE_WRITE_PENDING = "VMEMINFO_NR_ZONE_WRITE_PENDING", + VMEMINFO_OOM_KILL = "VMEMINFO_OOM_KILL ", + VMEMINFO_PGLAZYFREE = "VMEMINFO_PGLAZYFREE", + VMEMINFO_PGLAZYFREED = "VMEMINFO_PGLAZYFREED", + VMEMINFO_PGREFILL = "VMEMINFO_PGREFILL", + VMEMINFO_PGSCAN_DIRECT = "VMEMINFO_PGSCAN_DIRECT", + VMEMINFO_PGSCAN_KSWAPD = "VMEMINFO_PGSCAN_KSWAPD", + VMEMINFO_PGSKIP_DMA = "VMEMINFO_PGSKIP_DMA", + VMEMINFO_PGSKIP_MOVABLE = "VMEMINFO_PGSKIP_MOVABLE", + VMEMINFO_PGSKIP_NORMAL = "VMEMINFO_PGSKIP_NORMAL", + VMEMINFO_PGSTEAL_DIRECT = "VMEMINFO_PGSTEAL_DIRECT", + VMEMINFO_PGSTEAL_KSWAPD = "VMEMINFO_PGSTEAL_KSWAPD", + VMEMINFO_SWAP_RA = "VMEMINFO_SWAP_RA", + VMEMINFO_SWAP_RA_HIT = "VMEMINFO_SWAP_RA_HIT", + VMEMINFO_WORKINGSET_RESTORE = "VMEMINFO_WORKINGSET_RESTORE", +} + +export enum SysMeminfoType { + MEMINFO_UNSPECIFIED = "PMEM_UNSPECIFIED", + MEMINFO_MEM_TOTAL = "PMEM_MEM_TOTAL", + MEMINFO_MEM_FREE = "PMEM_MEM_FREE", + MEMINFO_MEM_AVAILABLE = "PMEM_MEM_AVAILABLE", + MEMINFO_BUFFERS = "PMEM_BUFFERS", + MEMINFO_CACHED = "PMEM_CACHED", + MEMINFO_SWAP_CACHED = "PMEM_SWAP_CACHED", + MEMINFO_ACTIVE = "PMEM_ACTIVE", + MEMINFO_INACTIVE = "PMEM_INACTIVE", + MEMINFO_ACTIVE_ANON = "PMEM_ACTIVE_ANON", + MEMINFO_INACTIVE_ANON = "PMEM_INACTIVE_ANON", + MEMINFO_ACTIVE_FILE = "PMEM_ACTIVE_FILE", + MEMINFO_INACTIVE_FILE = "PMEM_INACTIVE_FILE", + MEMINFO_UNEVICTABLE = "PMEM_UNEVICTABLE", + MEMINFO_MLOCKED = "PMEM_MLOCKED", + MEMINFO_SWAP_TOTAL = "PMEM_SWAP_TOTAL", + MEMINFO_SWAP_FREE = "PMEM_SWAP_FREE", + MEMINFO_DIRTY = "PMEM_DIRTY", + MEMINFO_WRITEBACK = "PMEM_WRITEBACK", + MEMINFO_ANON_PAGES = "PMEM_ANON_PAGES", + MEMINFO_MAPPED = "PMEM_MAPPED", + MEMINFO_SHMEM = "PMEM_SHMEM", + MEMINFO_SLAB = "PMEM_SLAB", + MEMINFO_SLAB_RECLAIMABLE = "PMEM_SLAB_RECLAIMABLE", + MEMINFO_SLAB_UNRECLAIMABLE = "PMEM_SLAB_UNRECLAIMABLE", + MEMINFO_KERNEL_STACK = "PMEM_KERNEL_STACK", + MEMINFO_PAGE_TABLES = "PMEM_PAGE_TABLES", + MEMINFO_COMMIT_LIMIT = "PMEM_COMMIT_LIMIT", + MEMINFO_COMMITED_AS = "PMEM_COMMITED_AS", + MEMINFO_VMALLOC_TOTAL = "PMEM_VMALLOC_TOTAL", + MEMINFO_VMALLOC_USED = "PMEM_VMALLOC_USED", + MEMINFO_VMALLOC_CHUNK = "PMEM_VMALLOC_CHUNK", + MEMINFO_CMA_TOTAL = "PMEM_CMA_TOTAL", + MEMINFO_CMA_FREE = "PMEM_CMA_FREE", + UNRECOGNIZED = "UNRECOGNIZED", +} + +export function sysMeminfoTypeFromJSON(object: any): SysMeminfoType { + switch (object) { + case 0: + case "MEMINFO_UNSPECIFIED": + return SysMeminfoType.MEMINFO_UNSPECIFIED; + case 1: + case "MEMINFO_MEM_TOTAL": + return SysMeminfoType.MEMINFO_MEM_TOTAL; + case 2: + case "MEMINFO_MEM_FREE": + return SysMeminfoType.MEMINFO_MEM_FREE; + case 3: + case "MEMINFO_MEM_AVAILABLE": + return SysMeminfoType.MEMINFO_MEM_AVAILABLE; + case 4: + case "MEMINFO_BUFFERS": + return SysMeminfoType.MEMINFO_BUFFERS; + case 5: + case "MEMINFO_CACHED": + return SysMeminfoType.MEMINFO_CACHED; + case 6: + case "MEMINFO_SWAP_CACHED": + return SysMeminfoType.MEMINFO_SWAP_CACHED; + case 7: + case "MEMINFO_ACTIVE": + return SysMeminfoType.MEMINFO_ACTIVE; + case 8: + case "MEMINFO_INACTIVE": + return SysMeminfoType.MEMINFO_INACTIVE; + case 9: + case "MEMINFO_ACTIVE_ANON": + return SysMeminfoType.MEMINFO_ACTIVE_ANON; + case 10: + case "MEMINFO_INACTIVE_ANON": + return SysMeminfoType.MEMINFO_INACTIVE_ANON; + case 11: + case "MEMINFO_ACTIVE_FILE": + return SysMeminfoType.MEMINFO_ACTIVE_FILE; + case 12: + case "MEMINFO_INACTIVE_FILE": + return SysMeminfoType.MEMINFO_INACTIVE_FILE; + case 13: + case "MEMINFO_UNEVICTABLE": + return SysMeminfoType.MEMINFO_UNEVICTABLE; + case 14: + case "MEMINFO_MLOCKED": + return SysMeminfoType.MEMINFO_MLOCKED; + case 15: + case "MEMINFO_SWAP_TOTAL": + return SysMeminfoType.MEMINFO_SWAP_TOTAL; + case 16: + case "MEMINFO_SWAP_FREE": + return SysMeminfoType.MEMINFO_SWAP_FREE; + case 17: + case "MEMINFO_DIRTY": + return SysMeminfoType.MEMINFO_DIRTY; + case 18: + case "MEMINFO_WRITEBACK": + return SysMeminfoType.MEMINFO_WRITEBACK; + case 19: + case "MEMINFO_ANON_PAGES": + return SysMeminfoType.MEMINFO_ANON_PAGES; + case 20: + case "MEMINFO_MAPPED": + return SysMeminfoType.MEMINFO_MAPPED; + case 21: + case "MEMINFO_SHMEM": + return SysMeminfoType.MEMINFO_SHMEM; + case 22: + case "MEMINFO_SLAB": + return SysMeminfoType.MEMINFO_SLAB; + case 23: + case "MEMINFO_SLAB_RECLAIMABLE": + return SysMeminfoType.MEMINFO_SLAB_RECLAIMABLE; + case 24: + case "MEMINFO_SLAB_UNRECLAIMABLE": + return SysMeminfoType.MEMINFO_SLAB_UNRECLAIMABLE; + case 25: + case "MEMINFO_KERNEL_STACK": + return SysMeminfoType.MEMINFO_KERNEL_STACK; + case 26: + case "MEMINFO_PAGE_TABLES": + return SysMeminfoType.MEMINFO_PAGE_TABLES; + case 27: + case "MEMINFO_COMMIT_LIMIT": + return SysMeminfoType.MEMINFO_COMMIT_LIMIT; + case 28: + case "MEMINFO_COMMITED_AS": + return SysMeminfoType.MEMINFO_COMMITED_AS; + case 29: + case "MEMINFO_VMALLOC_TOTAL": + return SysMeminfoType.MEMINFO_VMALLOC_TOTAL; + case 30: + case "MEMINFO_VMALLOC_USED": + return SysMeminfoType.MEMINFO_VMALLOC_USED; + case 31: + case "MEMINFO_VMALLOC_CHUNK": + return SysMeminfoType.MEMINFO_VMALLOC_CHUNK; + case 32: + case "MEMINFO_CMA_TOTAL": + return SysMeminfoType.MEMINFO_CMA_TOTAL; + case 33: + case "MEMINFO_CMA_FREE": + return SysMeminfoType.MEMINFO_CMA_FREE; + case -1: + case "UNRECOGNIZED": + default: + return SysMeminfoType.UNRECOGNIZED; + } +} + +export enum Type { + TYPE_UNSPECIFIED = 0, + HI3516 = 1, + P40 = 2, + UNRECOGNIZED = -1, +} + +export interface HilogConfig { + deviceType: Type; + logLevel: Level; + needClear: boolean; +} + +export function levelFromJSON(object: any): Level { + switch (object) { + case 0: + case "LEVEL_UNSPECIFIED": + return Level.LOG_UNSPECIFIED; + case 1: + case "Error": + return Level.LOG_ERROR; + case 2: + case "Info": + return Level.LOG_INFO; + case 3: + case "Debug": + return Level.LOG_DEBUG; + case 4: + case "Warns": + return Level.LOG_WARN; + case -1: + case "UNRECOGNIZED": + default: + return Level.UNRECOGNIZED; + } +} + +export enum Level { + LOG_UNSPECIFIED = "LOG_UNSPECIFIED", + LOG_ERROR = "LOG_ERROR", + LOG_INFO = "LOG_INFO", + LOG_DEBUG = "LOG_DEBUG", + LOG_WARN = "LOG_WARN", + UNRECOGNIZED = -1, +} + +export interface NativeHookConfig { + pid: number, + saveFile: boolean, + fileName: string, + filterSize: number, + smbPages: number, + maxStackDepth: number + processName: string +} + +export interface FpsConfig { + reportFps: boolean; +} diff --git a/host/ide/src/trace/component/setting/utils/PluginConvertUtils.ts b/host/ide/src/trace/component/setting/utils/PluginConvertUtils.ts new file mode 100644 index 0000000..895520d --- /dev/null +++ b/host/ide/src/trace/component/setting/utils/PluginConvertUtils.ts @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class PluginConvertUtils { + private static crlf: string = "\n"; + private static leftBrace: string = "{" + private static rightBrace: string = "}" + + public static createHdcCmd(requestString: string, time: number) { + return "hiprofiler_cmd \\" + this.crlf + + " -c - \\" + this.crlf + + " -o /data/local/tmp/hiprofiler_data.htrace \\" + this.crlf + + " -t " + time + " \\" + this.crlf + + "<, indentation: number, needColon: boolean, spacesNumber: number): string { + let text = ""; + arr.forEach(arrValue => { + switch (typeof arrValue) { + case "bigint": + text = text + ' '.repeat(spacesNumber).repeat(indentation + 1) + this.humpToSnake(key) + ": " + arrValue.toString() + this.crlf + break + case "boolean": + text = text + ' '.repeat(spacesNumber).repeat(indentation + 1) + this.humpToSnake(key) + ": " + arrValue.toString() + this.crlf + break + case "number": + text = text + ' '.repeat(spacesNumber).repeat(indentation + 1) + this.humpToSnake(key) + ": " + arrValue.toString() + this.crlf + break + case "string": + if (arrValue == '') { + break + } + if (arrValue.startsWith("VMEMINFO") || arrValue.startsWith("PMEM")) { + text = text + ' '.repeat(spacesNumber).repeat(indentation + 1) + this.humpToSnake(key) + ": " + arrValue.toString() + this.crlf + } else { + text = text + ' '.repeat(spacesNumber).repeat(indentation + 1) + this.humpToSnake(key) + ": \"" + arrValue.toString() + "\"" + this.crlf + } + break + case "object": + default: + if (needColon) { + text = text + ' '.repeat(spacesNumber).repeat(indentation + 1) + this.humpToSnake(key) + ": " + this.handleObj(arrValue, indentation + 1, needColon, spacesNumber) + "" + this.crlf + } else { + text = text + ' '.repeat(spacesNumber).repeat(indentation + 1) + this.humpToSnake(key) + this.handleObj(arrValue, indentation + 1, needColon, spacesNumber) + "" + this.crlf + } + } + }) + return text; + } + + // 驼峰转snake + private static humpToSnake(humpString: string): string { + return humpString.replace(/[A-Z]/g, (value) => '_' + value.toLowerCase()); + } + +} + diff --git a/host/ide/src/trace/component/trace/TimerShaftElement.ts b/host/ide/src/trace/component/trace/TimerShaftElement.ts new file mode 100644 index 0000000..04bb21e --- /dev/null +++ b/host/ide/src/trace/component/trace/TimerShaftElement.ts @@ -0,0 +1,521 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../base-ui/BaseElement.js"; +import {TimeRuler} from "./timer-shaft/TimeRuler.js"; +import {Rect} from "./timer-shaft/Rect.js"; +import {RangeRuler, TimeRange} from "./timer-shaft/RangeRuler.js"; +import {SportRuler} from "./timer-shaft/SportRuler.js"; +import {procedurePool} from "../../database/Procedure.js"; +import {Flag} from "./timer-shaft/Flag.js"; + +//随机生成十六位进制颜色 +export function randomRgbColor() { + const letters = '0123456789ABCDEF'; + let color = '#'; + for (let i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * 16)] + } + return color; +} + +export function ns2s(ns: number): string { + let second1 = 1_000_000_000; // 1 second + let millisecond1 = 1_000_000; // 1 millisecond + let microsecond1 = 1_000; // 1 microsecond + let nanosecond1 = 1000.0; + let res; + if (ns >= second1) { + res = (ns / 1000 / 1000 / 1000).toFixed(1) + " s"; + } else if (ns >= millisecond1) { + res = (ns / 1000 / 1000).toFixed(1) + " ms"; + } else if (ns >= microsecond1) { + res = (ns / 1000).toFixed(1) + " μs"; + } else if (ns > 0) { + res = ns.toFixed(1) + " ns"; + } else { + res = ns.toFixed(1) + " s"; + } + return res; +} + +export function ns2x(ns: number, startNS: number, endNS: number, duration: number, rect: Rect) { + if (endNS == 0) { + endNS = duration; + } + let xSize: number = (ns - startNS) * rect.width / (endNS - startNS); + if (xSize < 0) { + xSize = 0; + } + if (xSize > rect.width) { + xSize = rect.width; + } + return xSize; +} + +@element('timer-shaft-element') +export class TimerShaftElement extends BaseElement { + // @ts-ignore + offscreen: OffscreenCanvas | undefined; + isOffScreen: boolean = false; + public ctx: CanvasRenderingContext2D | undefined | null + public canvas: HTMLCanvasElement | null | undefined + public totalEL: HTMLDivElement | null | undefined + public timeTotalEL: HTMLSpanElement | null | undefined + public timeOffsetEL: HTMLSpanElement | null | undefined + public loadComplete: boolean = false + rangeChangeHandler: ((timeRange: TimeRange) => void) | undefined = undefined + flagChangeHandler: ((hoverFlag: Flag | undefined | null, selectFlag: Flag | undefined | null) => void) | undefined = undefined + flagClickHandler: ((flag: Flag | undefined | null) => void) | undefined = undefined + /** + * 离线渲染需要的变量 + */ + dpr = window.devicePixelRatio || 1; + frame: Rect = new Rect(0, 0, 0, 0); + must: boolean = true + hoverX: number = 0 + hoverY: number = 0 + canvasWidth: number = 0 + canvasHeight: number = 0 + _cpuUsage: Array<{ cpu: number, ro: number, rate: number }> = [] + protected timeRuler: TimeRuler | undefined; + protected rangeRuler: RangeRuler | undefined; + protected _sportRuler: SportRuler | undefined; + private root: HTMLDivElement | undefined | null + private _totalNS: number = 10_000_000_000; + private _startNS: number = 0; + private _endNS: number = 10_000_000_000; + + get sportRuler(): SportRuler | undefined { + return this._sportRuler; + } + + set cpuUsage(value: Array<{ cpu: number, ro: number, rate: number }>) { + this._cpuUsage = value; + if (this.rangeRuler) { + this.rangeRuler.cpuUsage = this._cpuUsage; + } + } + + get totalNS(): number { + return this._totalNS; + } + + set totalNS(value: number) { + this._totalNS = value; + if (this.timeRuler) this.timeRuler.totalNS = value; + if (this.rangeRuler) this.rangeRuler.range.totalNS = value; + if (this.timeTotalEL) this.timeTotalEL.textContent = `${ns2s(value)}` + requestAnimationFrame(() => this.render()) + } + + get startNS(): number { + return this._startNS; + } + + set startNS(value: number) { + this._startNS = value; + } + + get endNS(): number { + return this._endNS; + } + + set endNS(value: number) { + this._endNS = value; + } + + isScaling(): boolean { + return this.rangeRuler?.isPress || false; + } + + reset(): void { + this.loadComplete = false; + if (this.rangeRuler) { + this.rangeRuler.markA.frame.x = 0; + this.rangeRuler.markB.frame.x = this.rangeRuler.frame.width + this.rangeRuler.cpuUsage = [] + this.sportRuler!.flagList.length = 0 + this.sportRuler!.isRangeSelect = false + } + this.removeTriangle("inverted"); + this.totalNS = 10_000_000_000; + } + + initElements(): void { + this.root = this.shadowRoot?.querySelector('.root') + this.canvas = this.shadowRoot?.querySelector('.panel') + this.totalEL = this.shadowRoot?.querySelector('.total') + this.timeTotalEL = this.shadowRoot?.querySelector('.time-total') + this.timeOffsetEL = this.shadowRoot?.querySelector('.time-offset') + procedurePool.timelineChange = (a: any) => { + this.rangeChangeHandler?.(a); + } + } + + connectedCallback() { + if (this.canvas) { + if (this.isOffScreen) { + // @ts-ignore + this.offscreen = this.canvas.transferControlToOffscreen(); + return; + } else { + this.ctx = this.canvas?.getContext('2d', {alpha: true}); + } + } + if (this.timeTotalEL) this.timeTotalEL.textContent = ns2s(this._totalNS) + if (this.timeOffsetEL) this.timeOffsetEL.textContent = ns2s(this._startNS) + const width = this.canvas?.clientWidth || 0; + const height = this.canvas?.clientHeight || 0; + if (!this.timeRuler) { + this.timeRuler = new TimeRuler(this, new Rect(0, 0, width, 20), this._totalNS); + } + if (!this._sportRuler) { + this._sportRuler = new SportRuler(this, new Rect(0, 100, width, height - 100), + (hoverFlag, selectFlag) => { + this.flagChangeHandler?.(hoverFlag, selectFlag); + }, (flag) => { + this.flagClickHandler?.(flag); + }); + } + if (!this.rangeRuler) { + this.rangeRuler = new RangeRuler(this, new Rect(0, 25, width, 75), { + startX: 0, + endX: this.canvas?.clientWidth || 0, + startNS: 0, + endNS: this.totalNS, + totalNS: this.totalNS, + xs: [], + xsTxt: [] + }, (a) => { + if (this._sportRuler) { + this._sportRuler.range = a; + } + if (this.timeOffsetEL) { + this.timeOffsetEL.textContent = ns2s(a.startNS) + } + if (this.loadComplete) { + this.rangeChangeHandler?.(a) + } + }); + } + this.rangeRuler.frame.width = width; + this._sportRuler.frame.width = width; + this.timeRuler.frame.width = width; + } + + setRangeNS(startNS: number, endNS: number) { + this.rangeRuler?.setRangeNS(startNS, endNS); + } + + getRange(): TimeRange | undefined { + return this.rangeRuler?.getRange(); + } + + updateWidth(width: number) { + if (this.isOffScreen) { + this.frame.width = width - (this.totalEL?.clientWidth || 0); + this.frame.height = this.shadowRoot!.host.clientHeight || 0; + this.canvasWidth = Math.round((this.frame.width) * this.dpr); + this.canvasHeight = Math.round((this.frame.height) * this.dpr); + this.render(); + return; + } + this.canvas!.width = width - (this.totalEL?.clientWidth || 0); + this.canvas!.height = this.shadowRoot!.host.clientHeight || 0; + let oldWidth = this.canvas!.width; + let oldHeight = this.canvas!.height; + this.canvas!.width = Math.ceil((oldWidth) * this.dpr); + this.canvas!.height = Math.ceil(oldHeight * this.dpr); + this.canvas!.style.width = oldWidth + 'px'; + this.canvas!.style.height = oldHeight + 'px'; + this.ctx?.scale(this.dpr, this.dpr); + this.ctx?.translate(0, 0) + this.rangeRuler!.frame.width = oldWidth; + this._sportRuler!.frame.width = oldWidth; + this.timeRuler!.frame.width = oldWidth; + this.rangeRuler?.fillX() + this.render() + } + + documentOnMouseDown = (ev: MouseEvent) => { + if (this.isOffScreen) { + procedurePool.submitWithName(`timeline`, `timeline`, { + offscreen: this.must ? this.offscreen : undefined,//是否离屏 + dpr: this.dpr,//屏幕dpr值 + hoverX: this.hoverX, + hoverY: this.hoverY, + canvasWidth: this.canvasWidth, + canvasHeight: this.canvasHeight, + offsetLeft: this.canvas?.offsetLeft || 0, + offsetTop: this.canvas?.offsetTop || 0, + mouseDown: {offsetX: ev.offsetX, offsetY: ev.offsetY}, + mouseUp: null, + mouseMove: null, + mouseOut: null, + keyPressCode: null, + keyUpCode: null, + lineColor: "#dadada", + startNS: this.startNS, + endNS: this.endNS, + totalNS: this.totalNS, + frame: this.frame, + }, this.must ? this.offscreen : undefined, (res: any) => { + this.must = false; + }) + } else { + this.rangeRuler?.mouseDown(ev); + } + } + + documentOnMouseUp = (ev: MouseEvent) => { + if (this.isOffScreen) { + procedurePool.submitWithName(`timeline`, `timeline`, { + offscreen: this.must ? this.offscreen : undefined,//是否离屏 + dpr: this.dpr,//屏幕dpr值 + hoverX: this.hoverX, + hoverY: this.hoverY, + canvasWidth: this.canvasWidth, + canvasHeight: this.canvasHeight, + offsetLeft: this.canvas?.offsetLeft || 0, + offsetTop: this.canvas?.offsetTop || 0, + mouseUp: {offsetX: ev.offsetX, offsetY: ev.offsetY}, + mouseMove: null, + mouseOut: null, + keyPressCode: null, + keyUpCode: null, + lineColor: "#dadada", + startNS: this.startNS, + endNS: this.endNS, + totalNS: this.totalNS, + frame: this.frame, + }, this.must ? this.offscreen : undefined, (res: any) => { + this.must = false; + }) + } else { + this.rangeRuler?.mouseUp(ev); + this._sportRuler?.mouseUp(ev); + } + } + + documentOnMouseMove = (ev: MouseEvent) => { + if (this.isOffScreen) { + procedurePool.submitWithName(`timeline`, `timeline`, { + offscreen: this.must ? this.offscreen : undefined,//是否离屏 + dpr: this.dpr,//屏幕dpr值 + hoverX: this.hoverX, + hoverY: this.hoverY, + canvasWidth: this.canvasWidth, + canvasHeight: this.canvasHeight, + offsetLeft: this.canvas?.offsetLeft || 0, + offsetTop: this.canvas?.offsetTop || 0, + mouseMove: {offsetX: ev.offsetX, offsetY: ev.offsetY}, + mouseOut: null, + keyPressCode: null, + keyUpCode: null, + lineColor: "#dadada", + startNS: this.startNS, + endNS: this.endNS, + totalNS: this.totalNS, + frame: this.frame, + }, this.must ? this.offscreen : undefined, (res: any) => { + this.must = false; + }) + } else { + this.rangeRuler?.mouseMove(ev); + this._sportRuler?.mouseMove(ev); + } + } + + documentOnMouseOut = (ev: MouseEvent) => { + if (this.isOffScreen) { + procedurePool.submitWithName(`timeline`, `timeline`, { + offscreen: this.must ? this.offscreen : undefined,//是否离屏 + dpr: this.dpr,//屏幕dpr值 + hoverX: this.hoverX, + hoverY: this.hoverY, + canvasWidth: this.canvasWidth, + canvasHeight: this.canvasHeight, + offsetLeft: this.canvas?.offsetLeft || 0, + offsetTop: this.canvas?.offsetTop || 0, + mouseOut: {offsetX: ev.offsetX, offsetY: ev.offsetY}, + keyPressCode: null, + keyUpCode: null, + lineColor: "#dadada", + startNS: this.startNS, + endNS: this.endNS, + totalNS: this.totalNS, + frame: this.frame, + }, this.must ? this.offscreen : undefined, (res: any) => { + this.must = false; + }) + } else { + this.rangeRuler?.mouseOut(ev); + } + } + + documentOnKeyPress = (ev: KeyboardEvent) => { + if (this.isOffScreen) { + procedurePool.submitWithName(`timeline`, `timeline`, { + offscreen: this.must ? this.offscreen : undefined,//是否离屏 + dpr: this.dpr,//屏幕dpr值 + hoverX: this.hoverX, + hoverY: this.hoverY, + canvasWidth: this.canvasWidth, + canvasHeight: this.canvasHeight, + keyPressCode: {key: ev.key}, + keyUpCode: null, + lineColor: "#dadada", + startNS: this.startNS, + endNS: this.endNS, + totalNS: this.totalNS, + frame: this.frame, + }, this.must ? this.offscreen : undefined, (res: any) => { + this.must = false; + }) + } else { + this.rangeRuler?.keyPress(ev); + } + } + + documentOnKeyUp = (ev: KeyboardEvent) => { + if (this.isOffScreen) { + procedurePool.submitWithName(`timeline`, `timeline`, { + offscreen: this.must ? this.offscreen : undefined,//是否离屏 + dpr: this.dpr,//屏幕dpr值 + hoverX: this.hoverX, + hoverY: this.hoverY, + canvasWidth: this.canvasWidth, + canvasHeight: this.canvasHeight, + keyPressCode: null, + keyUpCode: {key: ev.key}, + lineColor: "#dadada", + startNS: this.startNS, + endNS: this.endNS, + totalNS: this.totalNS, + frame: this.frame, + }, this.must ? this.offscreen : undefined, (res: any) => { + this.must = false; + }) + } else { + this.rangeRuler?.keyUp(ev); + } + } + + disconnectedCallback() { + } + + render() { + if (this.ctx) { + this.ctx.fillStyle = 'transparent'; + this.ctx?.fillRect(0, 0, this.canvas?.width || 0, this.canvas?.height || 0) + this.timeRuler?.draw() + this.rangeRuler?.draw() + this._sportRuler?.draw() + } else { + procedurePool.submitWithName(`timeline`, `timeline`, { + offscreen: this.must ? this.offscreen : undefined,//是否离屏 + dpr: this.dpr,//屏幕dpr值 + hoverX: this.hoverX, + hoverY: this.hoverY, + canvasWidth: this.canvasWidth, + canvasHeight: this.canvasHeight, + keyPressCode: null, + keyUpCode: null, + lineColor: "#dadada", + startNS: this.startNS, + endNS: this.endNS, + totalNS: this.totalNS, + frame: this.frame, + }, this.must ? this.offscreen : undefined, (res: any) => { + this.must = false; + }) + } + } + + modifyFlagList(flag: Flag | null | undefined) { + this._sportRuler?.modifyFlagList(flag); + } + + drawTriangle(time: number, type: string) { + this._sportRuler?.drawTriangle(time, type); + } + + removeTriangle(type:string){ + this._sportRuler?.removeTriangle(type) + } + + initHtml(): string { + return ` + +
      +
      +
      +
      + 10 + 0 +
      +
      + +
      + `; + } +} diff --git a/host/ide/src/trace/component/trace/base/ColorUtils.ts b/host/ide/src/trace/component/trace/base/ColorUtils.ts new file mode 100644 index 0000000..e5798ef --- /dev/null +++ b/host/ide/src/trace/component/trace/base/ColorUtils.ts @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {CpuStruct} from "../../../bean/CpuStruct.js"; + +export class ColorUtils { + public static GREY_COLOR: string = "#f0f0f0" + + public static MD_PALETTE: Array = [ + "#3391ff", + "#0076ff", + "#66adff", + "#2db3aa", + "#008078", + "#73e6de", + "#535da6", + "#38428c", + "#7a84cc", + "#ff9201", + "#ff7500", + "#ffab40", + "#2db4e2", + "#0094c6", + "#7cdeff", + "#ffd44a", + "#fbbf00", + "#ffe593", + ]; + public static FUNC_COLOR: Array = [ + "#3391ff", + "#2db4e2", + "#2db3aa", + "#ffd44a", + "#535da6", + "#008078", + "#ff9201", + "#38428c"]; + + public static hash(str: string, max: number): number { + let colorA: number = 0x811c9dc5; + let colorB: number = 0xfffffff; + let colorC: number = 16777619; + let colorD: number = 0xffffffff; + let hash: number = colorA & colorB; + + for (let index: number = 0; index < str.length; index++) { + hash ^= str.charCodeAt(index); + hash = (hash * colorC) & colorD; + } + return Math.abs(hash) % max; + } + + public static colorForThread(thread: CpuStruct): string { + if (thread == null) { + return ColorUtils.GREY_COLOR; + } + let tid: number | undefined | null = (thread.processId || -1) >= 0 ? thread.processId : thread.tid; + return ColorUtils.colorForTid(tid || 0); + } + + public static colorForTid(tid: number): string { + let colorIdx: number = ColorUtils.hash(`${tid}`, ColorUtils.MD_PALETTE.length); + return ColorUtils.MD_PALETTE[colorIdx]; + } + + public static formatNumberComma(str: number): string { + let l = str.toString().split("").reverse(); + let t: string = ""; + for (let i = 0; i < l.length; i++) { + t += l[i] + ((i + 1) % 3 == 0 && (i + 1) != l.length ? "," : ""); + } + return t.split("").reverse().join("") + } +} diff --git a/host/ide/src/trace/component/trace/base/RangeSelect.ts b/host/ide/src/trace/component/trace/base/RangeSelect.ts new file mode 100644 index 0000000..3e2e8f5 --- /dev/null +++ b/host/ide/src/trace/component/trace/base/RangeSelect.ts @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {RangeSelectStruct, TraceRow} from "./TraceRow.js"; +import {Rect} from "../timer-shaft/Rect.js"; +import {ns2x, TimerShaftElement} from "../TimerShaftElement.js"; + +export class RangeSelect { + rowsEL: HTMLDivElement | undefined | null; + isMouseDown: boolean = false; + public rangeTraceRow: Array> | undefined + public selectHandler: ((ds: Array>, refreshCheckBox: boolean) => void) | undefined; + private startX: number = 0 + private endX: number = 0; + private startY: number = 0 + private endY: number = 0; + private startY2: number = 0 + private endY2: number = 0; + private timerShaftEL: TimerShaftElement | null | undefined; + private timerShaftDragEL: HTMLDivElement | null | undefined; + private isHover: boolean = false; + private movingMark: string = ""; + private mark: { startMark: number, endMark: number } = {startMark: 0, endMark: 0} + private spacerEL: HTMLDivElement; + + constructor(timerShaftEL: TimerShaftElement | null | undefined) { + this.timerShaftEL = timerShaftEL; + this.timerShaftDragEL = this.timerShaftEL?.shadowRoot?.querySelector(".total > div:nth-child(1)"); + this.spacerEL = this.timerShaftEL?.nextElementSibling! as HTMLDivElement; + } + + isInRowsEl(ev: MouseEvent): boolean { + return (ev.offsetY > this.timerShaftDragEL!.clientHeight! && + ev.offsetY < this.rowsEL!.offsetTop + this.rowsEL!.offsetHeight && + ev.offsetX > this.rowsEL!.offsetLeft! && + ev.offsetX < this.rowsEL!.offsetLeft + this.rowsEL!.offsetWidth + ) + } + + isInSpacerEL(ev: MouseEvent): boolean { + return (ev.offsetY > this.spacerEL.offsetTop && + ev.offsetY < this.spacerEL.offsetTop + this.spacerEL.offsetHeight && + ev.offsetX > this.spacerEL.offsetLeft! && + ev.offsetX < this.spacerEL.offsetLeft + this.spacerEL.offsetWidth + ) + } + + mouseDown(ev: MouseEvent) { + if (this.isHover) { + this.isMouseDown = true; + return; + } + if (this.isInRowsEl(ev)) { + this.rangeTraceRow = []; + this.isMouseDown = true; + this.startX = ev.offsetX - this.rowsEL!.offsetLeft!; + if (this.isInSpacerEL(ev)) { + this.startY = 0; + this.startY2 = ev.offsetY + 48; + } else { + this.startY = ev.offsetY - this.rowsEL!.offsetTop!; + this.startY2 = this.spacerEL.offsetTop + this.spacerEL.offsetHeight! + 48; + } + } + } + + mouseUp(ev: MouseEvent) { + if (this.isInRowsEl(ev) && this.isDrag()) { + this.endX = ev.offsetX - this.rowsEL!.offsetLeft!; + if (this.isInSpacerEL(ev)) { + this.endY = ev.offsetY - this.rowsEL!.clientTop! + this.rowsEL!.offsetTop!; + this.endY2 = ev.offsetY + 48; + } else { + this.endY = ev.offsetY - this.rowsEL!.clientTop! + this.rowsEL!.offsetTop!; + this.endY2 = this.spacerEL.offsetTop + this.spacerEL.offsetHeight! + 48; + } + if (this.selectHandler) { + this.selectHandler(this.rangeTraceRow || [], !this.isHover); + } + } + this.isMouseDown = false; + } + + isDrag(): boolean { + return this.startX != this.endX && (this.startY != this.endY || this.startY2 != this.endY2) + } + + isTouchMark(ev: MouseEvent): boolean { + let notTimeHeight: boolean = ev.offsetY > (this.timerShaftDragEL!.clientHeight || 0) + if (!notTimeHeight) { + this.isHover = false; + return false + } + if ((this.rangeTraceRow ? this.rangeTraceRow.length == 0 : false) && !this.isMouseDown) { + this.isHover = false; + } + return notTimeHeight && (this.rangeTraceRow ? this.rangeTraceRow.length > 0 : false) && !this.isMouseDown + } + + mouseMove(rows: Array>, ev: MouseEvent) { + if (this.isTouchMark(ev)) { + let markA = ns2x(TraceRow.rangeSelectObject!.startNS!, TraceRow.range!.startNS, TraceRow.range!.endNS, TraceRow.range!.totalNS!, {width: this.timerShaftEL?.canvas?.clientWidth || 0} as Rect); + let markB = ns2x(TraceRow.rangeSelectObject!.endNS!, TraceRow.range!.startNS, TraceRow.range!.endNS, TraceRow.range!.totalNS!, {width: this.timerShaftEL?.canvas?.clientWidth || 0} as Rect); + this.mark = {startMark: markA, endMark: markB}; + let mouseX = ev.offsetX - (this.timerShaftEL?.totalEL?.clientWidth || 0) + if ((mouseX > markA - 5 && mouseX < markA + 5)) { + this.isHover = true; + document.body.style.cursor = "ew-resize" + this.movingMark = markA < markB ? "markA" : "markB" + } else if (mouseX > markB - 5 && mouseX < markB + 5) { + this.isHover = true; + document.body.style.cursor = "ew-resize" + this.movingMark = markB < markA ? "markA" : "markB" + } else { + this.isHover = false; + } + } + if (this.isHover && this.isMouseDown) { + let rangeSelect: RangeSelectStruct | undefined; + this.rangeTraceRow = rows.filter(it => { + if (it.rangeSelect) { + if (!rangeSelect) { + rangeSelect = new RangeSelectStruct(); + let mouseX = ev.offsetX - this.rowsEL!.offsetLeft! - (it.canvasContainer?.offsetLeft || 248) + let markA = this.movingMark == "markA" ? mouseX : this.mark.startMark; + let markB = this.movingMark == "markB" ? mouseX : this.mark.endMark; + let startX = markA < markB ? markA : markB + let endX = markB < markA ? markA : markB + rangeSelect.startNS = Math.floor((TraceRow.range!.endNS - TraceRow.range!.startNS) * startX / it.frame.width + TraceRow.range!.startNS!); + rangeSelect.endNS = Math.floor((TraceRow.range!.endNS - TraceRow.range!.startNS) * endX / it.frame.width + TraceRow.range!.startNS!); + if (rangeSelect.startNS <= TraceRow.range!.startNS) { + rangeSelect.startNS = TraceRow.range!.startNS + } + if (rangeSelect.endNS >= TraceRow.range!.endNS) { + rangeSelect.endNS = TraceRow.range!.endNS + } + } + TraceRow.rangeSelectObject = rangeSelect; + return true + } + }) + this.timerShaftEL!.sportRuler!.isRangeSelect = (this.rangeTraceRow?.length || 0) > 0; + this.timerShaftEL!.sportRuler!.draw(); + return; + } + if (!this.isMouseDown) { + this.timerShaftEL!.sportRuler!.isRangeSelect = (this.rangeTraceRow?.length || 0) > 0; + this.timerShaftEL!.sportRuler!.draw(); + return; + } + this.endX = ev.offsetX - this.rowsEL!.offsetLeft!; + if (this.isInSpacerEL(ev)) { + this.endY = 0; + this.endY2 = ev.offsetY + 48; + } else { + this.endY = ev.offsetY - this.rowsEL!.offsetTop!; + this.endY2 = this.spacerEL.offsetTop + this.spacerEL.offsetHeight! + 48; + } + let scrollTop = this.rowsEL?.scrollTop || 0 + let xMin = this.startX < this.endX ? this.startX : this.endX; + let xMax = this.startX > this.endX ? this.startX : this.endX; + let yMin = this.startY < this.endY ? this.startY : this.endY; + let yMax = this.startY > this.endY ? this.startY : this.endY; + let rangeSelect: RangeSelectStruct | undefined; + this.rangeTraceRow = rows.filter(it => { + let rt: Rect; + let canvasOffsetLeft = (it.canvasContainer?.offsetLeft || 0); + let canvasOffsetTop = (it.canvasContainer?.offsetTop || 0); + if (it.collect) { + rt = new Rect(xMin - canvasOffsetLeft, Math.min(this.startY2, this.endY2) - it.offsetTop, xMax - xMin, Math.abs(this.startY2 - this.endY2)); + } else { + rt = new Rect(xMin - canvasOffsetLeft, yMin - canvasOffsetTop + scrollTop + this.rowsEL!.offsetTop, xMax - xMin, yMax - yMin); + } + if (Rect.intersect(it.frame, rt)) { + if (!rangeSelect) { + rangeSelect = new RangeSelectStruct(); + let startX = Math.floor(rt.x <= 0 ? 0 : rt.x); + let endX = Math.floor((rt.x + rt.width) > it.frame.width ? it.frame.width : (rt.x + rt.width)); + rangeSelect.startNS = Math.floor((TraceRow.range!.endNS - TraceRow.range!.startNS) * startX / it.frame.width + TraceRow.range!.startNS!); + rangeSelect.endNS = Math.floor((TraceRow.range!.endNS - TraceRow.range!.startNS) * endX / it.frame.width + TraceRow.range!.startNS!); + } + TraceRow.rangeSelectObject = rangeSelect; + it.rangeSelect = true; + return true + } else { + it.rangeSelect = false; + return false; + } + }) + this.timerShaftEL!.sportRuler!.isRangeSelect = (this.rangeTraceRow?.length || 0) > 0; + this.timerShaftEL!.sportRuler!.draw(); + } +} diff --git a/host/ide/src/trace/component/trace/base/TraceRow.ts b/host/ide/src/trace/component/trace/base/TraceRow.ts new file mode 100644 index 0000000..dc54efb --- /dev/null +++ b/host/ide/src/trace/component/trace/base/TraceRow.ts @@ -0,0 +1,823 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {element} from "../../../../base-ui/BaseElement.js"; +import {TimeRange} from "../timer-shaft/RangeRuler.js"; +import '../../../../base-ui/icon/LitIcon.js' +import {Rect} from "../timer-shaft/Rect.js"; +import {BaseStruct} from "../../../bean/BaseStruct.js"; +import {SpSystemTrace} from "../../SpSystemTrace.js"; +import {ns2x} from "../TimerShaftElement.js"; +import {TraceRowObject} from "./TraceRowObject.js"; +import {LitCheckBox} from "../../../../base-ui/checkbox/LitCheckBox.js"; +import {LitIcon} from "../../../../base-ui/icon/LitIcon"; +import "../../../../base-ui/popover/LitPopoverV.js" +import {LitPopover} from "../../../../base-ui/popover/LitPopoverV.js"; + +export class RangeSelectStruct { + startX: number | undefined + endX: number | undefined + startNS: number | undefined + endNS: number | undefined +} + +@element('trace-row') +export class TraceRow extends HTMLElement { + static ROW_TYPE_CPU = "cpu" + static ROW_TYPE_CPU_FREQ = "cpu-freq" + static ROW_TYPE_FPS = "fps" + static ROW_TYPE_NATIVE_MEMORY = "native-memory" + static ROW_TYPE_PROCESS = "process" + static ROW_TYPE_THREAD = "thread" + static ROW_TYPE_MEM = "mem" + static ROW_TYPE_HEAP = "heap" + static ROW_TYPE_FUNC = "func" + static range: TimeRange | undefined | null; + static rangeSelectObject: RangeSelectStruct | undefined + public obj: TraceRowObject | undefined | null; + isHover: boolean = false; + hoverX: number = 0; + hoverY: number = 0; + index: number = 0; + public must: boolean = false; + public isTransferCanvas = false; + onComplete: Function | undefined; + isComplete: boolean = false; + public dataList: undefined | Array; + public describeEl: Element | null | undefined; + public canvas: Array = []; + public canvasContainer: HTMLDivElement | null | undefined; + public tipEL: HTMLDivElement | null | undefined; + public checkBoxEL: LitCheckBox | null | undefined; + public collectEL: LitIcon | null | undefined; + public onDrawHandler: ((ctx: CanvasRenderingContext2D) => void) | undefined | null + public onThreadHandler: ((useCache: boolean) => void) | undefined | null + public onDrawTypeChangeHandler: ((type: number) => void) | undefined | null + public supplier: (() => Promise>) | undefined | null + public favoriteChangeHandler: ((fav: TraceRow) => void) | undefined | null + public selectChangeHandler: ((list: Array>) => void) | undefined | null + dpr = window.devicePixelRatio || 1; + // @ts-ignore + offscreen: Array = []; + canvasWidth = 0 + canvasHeight = 0 + public _frame: Rect | undefined; + private rootEL: HTMLDivElement | null | undefined; + private nameEL: HTMLLabelElement | null | undefined; + public isLoading: boolean = false + private _rangeSelect: boolean = false; + public readonly args: any; + private _drawType: number = 0 + + constructor(args: { canvasNumber: number, alpha: boolean, contextId: string, isOffScreen: boolean }) { + super(); + this.args = args; + this.attachShadow({mode: 'open'}).innerHTML = this.initHtml(); + this.initElements(); + } + + static get observedAttributes() { + return ["folder", "name", "expansion", "children", "height", "row-type", "row-id", "row-parent-id", "sleeping", + "check-type", + "collect-type" + ]; + } + + get collect() { + return this.hasAttribute("collect-type") + } + + set collect(value) { + if (value) { + this.setAttribute("collect-type", "") + } else { + this.removeAttribute("collect-type") + } + } + + get rangeSelect(): boolean { + return this._rangeSelect; + } + + set rangeSelect(value: boolean) { + this._rangeSelect = value; + } + + get sleeping(): boolean { + return this.hasAttribute("sleeping"); + } + + set sleeping(value: boolean) { + if (value) { + this.setAttribute("sleeping", "") + } else { + this.removeAttribute("sleeping") + this.draw(); + } + } + + get rowType(): string | undefined | null { + return this.getAttribute("row-type"); + } + + set rowType(val) { + this.setAttribute("row-type", val || "") + } + + get rowId(): string | undefined | null { + return this.getAttribute("row-id"); + } + + set rowId(val) { + this.setAttribute("row-id", val || "") + } + + get rowParentId(): string | undefined | null { + return this.getAttribute("row-parent-id"); + } + + set rowParentId(val) { + this.setAttribute("row-parent-id", val || "") + } + + set rowHidden(val: boolean) { + if (val) { + this.setAttribute("row-hidden", "") + } else { + this.removeAttribute("row-hidden") + } + } + + get name(): string { + return this.getAttribute("name") || "" + } + + set name(value: string) { + this.setAttribute("name", value) + } + + get folder(): boolean { + return this.hasAttribute("folder"); + } + + set folder(value: boolean) { + if (value) { + this.setAttribute("folder", '') + } else { + this.removeAttribute('folder') + } + } + + get expansion(): boolean { + return this.hasAttribute("expansion") + } + + set expansion(value) { + if (value) { + this.setAttribute("expansion", ''); + } else { + this.removeAttribute('expansion') + } + this.parentElement?.querySelectorAll>(`trace-row[row-parent-id='${this.rowId}']`).forEach(it => { + if (!it.collect) { + it.rowHidden = !this.expansion; + } + }) + this.dispatchEvent(new CustomEvent("expansion-change", { + detail: { + expansion: this.expansion, + rowType: this.rowType, + rowId: this.rowId, + rowParentId: this.rowParentId + } + })) + } + + set tip(value: string) { + if (this.tipEL) { + this.tipEL.innerHTML = value; + } + } + + get frame(): Rect { + let cHeight = 0; + this.canvas.forEach(it => { + cHeight += (it?.clientHeight || 40); + }) + if (this._frame) { + this._frame.width = (this.parentElement?.clientWidth || 0) - 248 - SpSystemTrace.scrollViewWidth; + this._frame.height = cHeight; + return this._frame; + } else { + this._frame = new Rect(0, 0, (this.parentElement?.clientWidth || 0) - 248 - SpSystemTrace.scrollViewWidth, cHeight); + return this._frame; + } + } + + set frame(f: Rect) { + this._frame = f; + } + + get checkType(): string { + return this.getAttribute("check-type") || ""; + } + + set checkType(value: string) { + this.setAttribute("check-type", value); + switch (value) { + case "-1": + this.checkBoxEL!.style.display = "none"; + this.rangeSelect = false; + break; + case "0": + this.checkBoxEL!.style.display = "flex"; + this.checkBoxEL!.checked = false; + this.checkBoxEL!.indeterminate = false; + this.rangeSelect = false; + break; + case "1": + this.checkBoxEL!.style.display = "flex"; + this.checkBoxEL!.checked = false + this.checkBoxEL!.indeterminate = true; + this.rangeSelect = false; + break; + case "2": + this.rangeSelect = true; + this.checkBoxEL!.style.display = "flex"; + this.checkBoxEL!.checked = true; + this.checkBoxEL!.indeterminate = false; + break; + } + } + + get drawType(): number { + return this._drawType; + } + + set drawType(value: number) { + this._drawType = value; + let radioList: NodeListOf = this.shadowRoot!.querySelectorAll("input[type=radio][name=status]") + if (radioList!.length > 0) { + radioList[Number(value)].checked = true + } + } + + get highlight(): boolean { + return this.hasAttribute("expansion"); + } + + set highlight(value: boolean) { + if (value) { + this.setAttribute("highlight", '') + } else { + this.removeAttribute('highlight') + } + } + + initElements(): void { + this.rootEL = this.shadowRoot?.querySelector('.root') + this.checkBoxEL = this.shadowRoot?.querySelector('.lit-check-box') + this.collectEL = this.shadowRoot?.querySelector('.collect') + this.describeEl = this.shadowRoot?.querySelector('.describe') + this.nameEL = this.shadowRoot?.querySelector('.name') + this.canvasContainer = this.shadowRoot?.querySelector('.panel-container') + this.tipEL = this.shadowRoot?.querySelector('.tip') + let canvasNumber = this.args["canvasNumber"]; + for (let i = 0; i < canvasNumber; i++) { + let canvas = document.createElement('canvas'); + canvas.className = "panel"; + this.canvas.push(canvas); + this.canvasContainer!.appendChild(canvas); + } + this.describeEl?.addEventListener('click', () => { + if (this.folder) { + this.expansion = !this.expansion + } + }) + } + + initCanvas(list: Array): void { + let timerShaftCanvas = this.parentElement!.parentElement!.querySelector("timer-shaft-element")!.shadowRoot!.querySelector("canvas"); + let tempHeight:number = 0; + if(this.rowType==TraceRow.ROW_TYPE_FUNC ){ + tempHeight = 20; + }else if(this.rowType==TraceRow.ROW_TYPE_THREAD ){ + tempHeight = 30; + }else{ + tempHeight = 40; + } + list.forEach((canvas, i) => { + // let oldWidth = (this.shadowRoot!.host.clientWidth || 0) - (this.describeEl?.clientWidth || 248) - SpSystemTrace.scrollViewWidth; + this.rootEL!.style.height = `${this.getAttribute("height") || '40'}px` + canvas.style.width = timerShaftCanvas!.style.width; + canvas.style.height = tempHeight + 'px'; + // canvas.style.backgroundColor = `${randomRgbColor()}` + this.canvasWidth = timerShaftCanvas!.width; + this.canvasHeight = Math.ceil(tempHeight * this.dpr); + canvas.width = this.canvasWidth; + canvas.height = this.canvasHeight; + // @ts-ignore + this.offscreen.push(canvas!.transferControlToOffscreen()); + }) + } + + updateWidth(width: number) { + let dpr = window.devicePixelRatio || 1; + let tempHeight = 40; + let tempTop = 0; + if (this.canvas.length > 1) { + tempHeight = 20; + tempTop = 10; + } + this.canvas.forEach(it => { + this.canvasWidth = Math.ceil((width - (this.describeEl?.clientWidth || 248)) * dpr); + this.canvasHeight = Math.ceil(tempHeight * this.dpr); + it!.style.width = (width - (this.describeEl?.clientWidth || 248)) + 'px'; + if (this.args.isOffScreen) { + this.draw(true); + } + }) + } + + connectedCallback() { + this.checkBoxEL!.onchange = (ev: any) => { + if (!ev.target.checked) { + this.rangeSelect = false; + this.checkType = "0" + this.draw(); + } else { + this.rangeSelect = true; + this.checkType = "2" + this.draw(); + } + this.setCheckBox(ev.target.checked); + } + this.collectEL!.onclick = (e) => { + this.collect = !this.collect; + let spacer = this.parentElement!.previousElementSibling! as HTMLDivElement; + if (this.collect) { + spacer.style.height = `${spacer.offsetHeight + this.offsetHeight!}px`; + } else { + spacer.style.height = `${spacer.offsetHeight - this.offsetHeight!}px`; + let parent = this.parentElement!.querySelector>(`trace-row[row-id='${this.rowParentId}']`); + if (parent) { + this.rowHidden = !parent.expansion; + } + } + let collectList = this.parentElement!.querySelectorAll>(`trace-row[collect-type]`); + collectList.forEach((it, i) => { + if (i == 0) { + it.style.top = `${spacer.offsetTop + 48}px`; + } else { + it.style.top = `${collectList[i - 1].offsetTop + collectList[i - 1].offsetHeight}px`; + } + }) + this.favoriteChangeHandler?.(this) + } + this.initCanvas(this.canvas); + let _this = this; + let radioList = this.shadowRoot!.querySelectorAll("input[type=radio][name=status]") + let popover = this.shadowRoot!.querySelector(".popover") + this.shadowRoot!.querySelector("#first-radio")!.onclick = (e) => { + // @ts-ignore + radioList[0]!.checked = true; + // @ts-ignore + popover!.visible = false + setTimeout(() => { + this.onDrawTypeChangeHandler?.(0); + }, 300); + } + this.shadowRoot!.querySelector("#second-radio")!.onclick = (e) => { + // @ts-ignore + radioList[1]!.checked = true; + // @ts-ignore + popover!.visible = false + setTimeout(() => { + this.onDrawTypeChangeHandler?.(1); + }, 300); + } + } + + setCheckBox(isCheck: boolean) { + if (this.folder) { + let allRow = this.parentElement?.querySelectorAll>(`trace-row[row-parent-id='${this.rowId}'][check-type]`) + allRow!.forEach((ck) => { + ck.setAttribute("check-type", isCheck ? "2" : "0") + let allCheck: LitCheckBox | null | undefined = ck?.shadowRoot?.querySelector(".lit-check-box") + allCheck!.checked = isCheck + }) + } else if (this.rowParentId == "" && !this.folder) { + this.selectChangeHandler?.([...this.parentElement!.querySelectorAll>("trace-row[check-type='2']")]) + return; + } + let checkList = this.parentElement?.querySelectorAll>(`trace-row[row-parent-id='${this.folder ? this.rowId : this.rowParentId}'][check-type="2"]`) + let unselectedList = this.parentElement?.querySelectorAll>(`trace-row[row-parent-id='${this.folder ? this.rowId : this.rowParentId}'][check-type="0"]`) + let parentRow = this.parentElement?.querySelector>(`trace-row[row-id='${this.folder ? this.rowId : this.rowParentId}'][folder]`) + let parentCheck: LitCheckBox | null | undefined = parentRow?.shadowRoot?.querySelector(".lit-check-box") + + if (unselectedList!.length == 0) { + parentRow!.setAttribute("check-type", "2") + parentCheck!.checked = true + parentCheck!.indeterminate = false; + checkList?.forEach((it) => { + it.checkType = "2"; + it.rangeSelect = true; + it.draw() + }) + } else { + parentRow!.setAttribute("check-type", "1") + parentCheck!.checked = false + parentCheck!.indeterminate = true; + checkList?.forEach((it) => { + it.checkType = "2"; + it.rangeSelect = true; + it.draw() + }) + unselectedList?.forEach((it) => { + it.checkType = "0"; + it.rangeSelect = false; + it.draw() + }) + } + + if (checkList!.length == 0) { + parentRow!.setAttribute("check-type", "0") + parentCheck!.checked = false + parentCheck!.indeterminate = false; + unselectedList?.forEach((it) => { + it.checkType = "0"; + it.rangeSelect = false; + it.draw() + }) + } + this.selectChangeHandler?.([...this.parentElement!.querySelectorAll>("trace-row[check-type='2']")]) + } + + onMouseHover(x: number, y: number, tip: boolean = true): T | undefined | null { + if (this.tipEL) { + this.tipEL.style.display = 'none'; + } + return null; + } + + setTipLeft(x: number, struct: any) { + if (struct == null && this.tipEL) { + this.tipEL.style.display = 'none'; + return + } + if (this.tipEL) { + this.tipEL.style.display = 'flex'; + if (x + this.tipEL.clientWidth > (this.canvas[0]!.clientWidth || 0)) { + this.tipEL.style.transform = `translateX(${x - this.tipEL.clientWidth - 1}px)`; + } else { + this.tipEL.style.transform = `translateX(${x}px)`; + } + } + } + + onMouseLeave(x: number, y: number) { + if (this.tipEL) { + this.tipEL.style.display = 'none'; + } + } + + draw(useCache: boolean = false) { + if (this.sleeping) { + return; + } + if (!this.isComplete) { + if (this.supplier && !this.isLoading) { + this.isLoading = true; + this.must = true; + if (this.supplier) { + let promise = this.supplier(); + if (promise) { + promise.then(res => { + this.dataList = res + if (this.onComplete) { + this.onComplete(); + } + this.isComplete = true; + this.isLoading = false; + this.draw(false); + }) + } else { + this.isLoading = false; + this.draw(false); + } + } + } + } else { + if (this.onThreadHandler && this.dataList) { + this.onThreadHandler!(useCache) + } + } + } + + clearCanvas(ctx: CanvasRenderingContext2D) { + if (ctx) { + this.canvas.forEach(it => { + ctx.clearRect(0, 0, it!.clientWidth || 0, it!.clientHeight || 0) + }) + } + } + + drawLines(ctx: CanvasRenderingContext2D) { + if (ctx) { + ctx.lineWidth = 1; + ctx.strokeStyle = this.getLineColor(); + TraceRow.range?.xs.forEach(it => { + ctx.moveTo(Math.floor(it), 0) + ctx.lineTo(Math.floor(it), this.shadowRoot?.host.clientHeight || 0) + }) + ctx.stroke(); + } + } + + getLineColor() { + return window.getComputedStyle(this.rootEL!, null).getPropertyValue("border-bottom-color") + } + + drawSelection(ctx: CanvasRenderingContext2D) { + if (this.rangeSelect) { + TraceRow.rangeSelectObject!.startX = Math.floor(ns2x(TraceRow.rangeSelectObject!.startNS!, TraceRow.range!.startNS, TraceRow.range!.endNS, TraceRow.range!.totalNS!, this.frame)); + TraceRow.rangeSelectObject!.endX = Math.floor(ns2x(TraceRow.rangeSelectObject!.endNS!, TraceRow.range!.startNS, TraceRow.range!.endNS, TraceRow.range!.totalNS!, this.frame)); + if (ctx) { + ctx.globalAlpha = 0.5 + ctx.fillStyle = "#666666" + ctx.fillRect(TraceRow.rangeSelectObject!.startX!, this.frame.y, TraceRow.rangeSelectObject!.endX! - TraceRow.rangeSelectObject!.startX!, this.frame.height) + ctx.globalAlpha = 1 + } + } + } + + isInTimeRange(startTime: number, duration: number): boolean { + return ((startTime || 0) + (duration || 0) > (TraceRow.range?.startNS || 0) && (startTime || 0) < (TraceRow.range?.endNS || 0)); + } + + attributeChangedCallback(name: string, oldValue: string, newValue: string) { + switch (name) { + case "name": + if (this.nameEL) { + this.nameEL.textContent = newValue; + this.nameEL.title = newValue; + } + break; + case "height": + if (newValue != oldValue) { + if (!this.args.isOffScreen) { + } + } + break; + case "check-type": + if (newValue === "check") { + this.checkBoxEL?.setAttribute("checked", ""); + } else { + this.checkBoxEL?.removeAttribute("checked"); + } + break; + } + } + + initHtml(): string { + return ` + +
      +
      + + + + +
      +
      Current Bytes
      +
      Native Memory Density
      +
      + +
      + +
      +
      +
      + P:process [1573]
      + T:Thread [675] +
      +
      +
      + `; + } + +} diff --git a/host/ide/src/trace/component/trace/base/TraceRowObject.ts b/host/ide/src/trace/component/trace/base/TraceRowObject.ts new file mode 100644 index 0000000..7aa7207 --- /dev/null +++ b/host/ide/src/trace/component/trace/base/TraceRowObject.ts @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {BaseStruct} from "../../../bean/BaseStruct.js"; +import {Rect} from "../timer-shaft/Rect.js"; +import {TraceRow} from "./TraceRow.js"; + +export class TraceRowObject { + public rowId: string | undefined; + public rowType: string | undefined; + public rowParentId: string | undefined; + public rowHidden: boolean = false; + public rowHeight: number = 40; + public name: string | undefined; + public must: boolean = true; + public folder: boolean = false; + public isLoading: boolean = false; + public children: boolean = false; + public expansion: boolean = false; + public dataList: Array | undefined; + public dataListCache: Array = []; + public color: string | undefined; + public frame: Rect | undefined; + public supplier: (() => Promise>) | undefined | null + public onThreadHandler: ((row: TraceRow, ctx: ImageBitmapRenderingContext | CanvasRenderingContext2D | WebGLRenderingContext | WebGL2RenderingContext | null | undefined) => void) | undefined | null + public onDrawHandler: ((ctx: ImageBitmapRenderingContext | CanvasRenderingContext2D | WebGLRenderingContext | WebGL2RenderingContext | null | undefined) => void) | undefined | null + public top: number = 0; + public rowIndex: number = 0; + public preObject: TraceRowObject | undefined | null; + public nextObject: TraceRowObject | undefined | null; +} diff --git a/host/ide/src/trace/component/trace/base/TraceRowRecyclerView.ts b/host/ide/src/trace/component/trace/base/TraceRowRecyclerView.ts new file mode 100644 index 0000000..f5ce848 --- /dev/null +++ b/host/ide/src/trace/component/trace/base/TraceRowRecyclerView.ts @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {TraceRowObject} from "./TraceRowObject.js"; +import {TraceRow} from "./TraceRow.js"; + +@element("trace-row-recycler-view") +export class TraceRowRecyclerView extends BaseElement { + private recycler: boolean = true; + private gasketEL: HTMLDivElement | null | undefined; + private container: HTMLDivElement | null | undefined; + private visibleRowsCount: number = 0; + private visibleObjects: TraceRowObject[] = []; + private totalHeight: number = 0; + + private _dataSource: Array> = []; + private _renderType: string = 'div'; + + get dataSource(): Array> { + return this._dataSource; + } + + set dataSource(value: Array>) { + this._dataSource = value; + this.measureHeight(); + this.initUI(); + let els = [...(this.shadowRoot!.querySelectorAll>(".recycler-cell"))]; + for (let i = 0; i < els.length; i++) { + this.refreshRow(els[i], this.visibleObjects[i]); + } + } + + get renderType(): string { + return this._renderType; + } + + set renderType(value: string) { + this._renderType = value; + } + + refreshRow(el: TraceRow, obj: TraceRowObject) { + if (!obj) { + return; + } + el.obj = obj; + el.folder = obj.folder; + el.style.top = `${obj.top}px`; + el.name = obj.name || ''; + if (obj.children) { + el.setAttribute("children", ``); + } else { + el.removeAttribute("children"); + } + el.style.visibility = 'visible'; + el.rowId = obj.rowId + el.rowType = obj.rowType + el.rowParentId = obj.rowParentId + el.expansion = obj.expansion + el.rowHidden = obj.rowHidden + el.setAttribute("height", `${obj.rowHeight}`); + requestAnimationFrame(() => { + }) + } + + initElements(): void { + this.container = this.shadowRoot?.querySelector(".container"); + this.gasketEL = this.shadowRoot?.querySelector(".gasket"); + let els: Array> | undefined | null; + this.container!.onscroll = (ev) => { + let top = this.container!.scrollTop; + let skip = 0; + for (let i = 0; i < this.visibleObjects.length; i++) { + if (this.visibleObjects[i].top >= top) { + skip = this.visibleObjects[i].rowIndex - 1; + break; + } + } + if (skip < 0) skip = 0; + if (!els) els = [...(this.shadowRoot!.querySelectorAll>(".recycler-cell"))]; + for (let i = 0; i < els.length; i++) { + let obj = this.visibleObjects[i + skip]; + this.refreshRow(els[i], obj); + } + } + } + + + measureHeight() { + this.visibleObjects = this.dataSource.filter(it => !it.rowHidden); + this.totalHeight = this.visibleObjects.map((it) => it.rowHeight).reduce((a, b) => a + b); + let totalHeight = 0; + for (let i = 0; i < this.visibleObjects.length; i++) { + this.visibleObjects[i].top = totalHeight; + this.visibleObjects[i].rowIndex = i; + totalHeight += this.visibleObjects[i].rowHeight; + this.visibleObjects[i].preObject = i == 0 ? null : this.visibleObjects[i - 1]; + this.visibleObjects[i].nextObject = i == this.visibleObjects.length - 1 ? null : this.visibleObjects[i + 1]; + } + this.gasketEL && (this.gasketEL.style.height = `${this.totalHeight}px`); + } + + initUI() { + this.visibleRowsCount = Math.ceil(this.clientHeight / 40); + if (this.visibleRowsCount >= this.visibleObjects.length) { + this.visibleRowsCount = this.visibleObjects.length; + } + if (!this.recycler) this.visibleRowsCount = this.dataSource.length; + for (let i = 0; i <= this.visibleRowsCount; i++) { + let el = new TraceRow({canvasNumber:1,alpha: true, contextId: '2d', isOffScreen: true}); + el.className = "recycler-cell" + this.container?.appendChild(el); + el.addEventListener('expansion-change', (ev: any) => { + el.obj!.expansion = ev.detail.expansion; + for (let j = 0; j < this.dataSource.length; j++) { + if (this.dataSource[j].rowParentId == ev.detail.rowId) { + this.dataSource[j].rowHidden = !ev.detail.expansion; + } + } + this.measureHeight(); + let els = [...(this.shadowRoot!.querySelectorAll>(".recycler-cell"))]; + let top = this.container!.scrollTop; + let skip = 0; + for (let i = 0; i < this.visibleObjects.length; i++) { + if (this.visibleObjects[i].top >= top) { + skip = this.visibleObjects[i].rowIndex - 1; + break; + } + } + if (skip < 0) skip = 0; + for (let i = 0; i < els.length; i++) { + let obj = this.visibleObjects[i + skip]; + this.refreshRow(els[i], obj); + } + }) + } + } + + initHtml(): string { + return ` + +
      +
      +
      + +`; + } + +} diff --git a/host/ide/src/trace/component/trace/base/TraceSheet.ts b/host/ide/src/trace/component/trace/base/TraceSheet.ts new file mode 100644 index 0000000..0ede469 --- /dev/null +++ b/host/ide/src/trace/component/trace/base/TraceSheet.ts @@ -0,0 +1,586 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTabs} from "../../../../base-ui/tabs/lit-tabs.js"; +import "../../../../base-ui/tabs/lit-tabpane.js"; +import {CpuStruct} from "../../../bean/CpuStruct.js"; +import "../../../../base-ui/table/lit-table.js"; +import {LitTabpane} from "../../../../base-ui/tabs/lit-tabpane.js"; +import "../sheet/TabPaneCpu.js"; +import "../sheet/TabPaneThreadStates.js" +import "../sheet/TabPaneSlices.js" +import "../sheet/TabPaneCounter.js" +import "../sheet/TabPaneCpuByProcess.js" +import "../sheet/TabPaneCpuByThread.js" +import "../sheet/TabPaneFps.js" +import "../sheet/TabPaneSPT.js" +import "../sheet/TabPanePTS.js" +import "../sheet/TabPaneContextSwitch.js" +import "../sheet/TabPaneThreadSwitch.js" +import "../sheet/TabPaneCpuUsage.js"; +import "../sheet/TabPaneBoxChild.js"; +import "../sheet/TabPaneHeap.js"; +import "../sheet/TabPaneNMStatstics.js"; +import "../sheet/TabPaneNMCallInfo.js"; +import "../sheet/TabPaneNMemory.js"; +import "../sheet/TabPaneNMSampleList.js"; +import {BoxJumpParam, SelectionParam} from "../../../bean/BoxSelection.js"; +import {TabPaneThreadStates} from "../sheet/TabPaneThreadStates.js"; +import {TabPaneCpuByProcess} from "../sheet/TabPaneCpuByProcess.js"; +import {TabPaneCpuByThread} from "../sheet/TabPaneCpuByThread.js"; +import {TabPaneSlices} from "../sheet/TabPaneSlices.js"; +import {TabPaneSPT} from "../sheet/TabPaneSPT.js"; +import {TabPanePTS} from "../sheet/TabPanePTS.js"; +import {TabPaneContextSwitch} from "../sheet/TabPaneContextSwitch.js"; +import {TabPaneThreadSwitch} from "../sheet/TabPaneThreadSwitch.js"; +import {TabPaneBoxChild} from "../sheet/TabPaneBoxChild.js"; +import {TabPaneCounter} from "../sheet/TabPaneCounter.js"; +import "../sheet/TabPaneCurrentSelection.js"; +import {TabPaneCurrentSelection} from "../sheet/TabPaneCurrentSelection.js"; +import {FuncStruct} from "../../../bean/FuncStruct.js"; +import {ProcessMemStruct} from "../../../bean/ProcessMemStruct.js"; +import {ThreadStruct} from "../../../bean/ThreadStruct.js"; +import {TabPaneFps} from "../sheet/TabPaneFps.js"; +import {TabPaneCpuUsage} from "../sheet/TabPaneCpuUsage.js"; +import "../timer-shaft/TabPaneFlag.js"; +import {TabPaneFlag} from "../timer-shaft/TabPaneFlag.js"; +import {Flag} from "../timer-shaft/Flag.js"; +import {TabPaneHeap} from "../sheet/TabPaneHeap.js"; +import {TabPaneNMStatstics} from "../sheet/TabPaneNMStatstics.js"; +import {TabPaneNMCallInfo} from "../sheet/TabPaneNMCallInfo.js"; +import {TabPaneNMemory} from "../sheet/TabPaneNMemory.js"; +import {TabPaneNMSampleList} from "../sheet/TabPaneNMSampleList.js"; +import {WakeupBean} from "../../../bean/WakeupBean.js"; +import {LitIcon} from "../../../../base-ui/icon/LitIcon.js"; + +@element("trace-sheet") +export class TraceSheet extends BaseElement { + private litTabs: LitTabs | undefined | null + private nav: HTMLDivElement | undefined | null + private tabCurrentSelection: LitTabpane | undefined | null + private tabBoxCpuThread: LitTabpane | undefined | null + private tabBoxCpuProcess: LitTabpane | undefined | null + private tabBoxThreadStates: LitTabpane | undefined | null + private tabBoxSlices: LitTabpane | undefined | null + private tabBoxCounters: LitTabpane | undefined | null + private tabBoxFps: LitTabpane | undefined | null + private tabBoxSPT: LitTabpane | undefined | null + private tabBoxPTS: LitTabpane | undefined | null + private tabBoxContextSwitch: LitTabpane | undefined | null + private tabBoxThreadSwitch: LitTabpane | undefined | null + private tabBoxCpuUsage: LitTabpane | undefined | null + private tabBoxChild: LitTabpane | undefined | null + private tabBoxFlag: TabPaneFlag | undefined | null + private tabBoxHeap: LitTabpane | undefined | null + private tabBoxNMStatistics: LitTabpane | undefined | null + private tabBoxNMCallInfo: LitTabpane | undefined | null + private tabBoxNMemory: LitTabpane | undefined | null + private tabBoxNMSample: LitTabpane | undefined | null + private tabSPT: TabPaneSPT | undefined | null + private tabPTS: TabPanePTS | undefined | null + private tabCs: TabPaneContextSwitch | undefined | null + private tabTs: TabPaneThreadSwitch | undefined | null + private tabChild: TabPaneBoxChild | undefined | null + private tabNativeStatistics: TabPaneNMStatstics | undefined | null + private tabNativeMemory: TabPaneNMemory | undefined | null + private tabNativeCallInfo: TabPaneNMCallInfo | undefined | null + private tabNativeSample: TabPaneNMSampleList | undefined | null + private currentKey: string = "1"; + private selection: SelectionParam | undefined | null; + + static get observedAttributes() { + return ['mode']; + } + + initElements(): void { + this.litTabs = this.shadowRoot?.querySelector("#tabs"); + this.tabCurrentSelection = this.shadowRoot?.querySelector("#current-selection"); + this.tabBoxCpuThread = this.shadowRoot?.querySelector("#box-cpu-thread"); + this.tabBoxCpuProcess = this.shadowRoot?.querySelector("#box-cpu-process"); + this.tabBoxThreadStates = this.shadowRoot?.querySelector("#box-thread-states"); + this.tabBoxSlices = this.shadowRoot?.querySelector("#box-slices"); + this.tabBoxCounters = this.shadowRoot?.querySelector("#box-counters"); + this.tabBoxFps = this.shadowRoot?.querySelector("#box-fps"); + this.tabBoxSPT = this.shadowRoot?.querySelector("#box-spt"); + this.tabBoxPTS = this.shadowRoot?.querySelector("#box-pts"); + this.tabBoxContextSwitch = this.shadowRoot?.querySelector("#box-cs"); + this.tabBoxThreadSwitch = this.shadowRoot?.querySelector("#box-ts"); + this.tabBoxCpuUsage = this.shadowRoot?.querySelector("#box-cpu-usage"); + this.tabBoxChild = this.shadowRoot?.querySelector("#box-cpu-child"); + this.tabBoxFlag = this.shadowRoot?.querySelector("#box-flag"); + this.tabBoxHeap = this.shadowRoot?.querySelector("#box-heap"); + + this.tabBoxNMStatistics = this.shadowRoot?.querySelector("#box-native-statstics"); + this.tabBoxNMCallInfo = this.shadowRoot?.querySelector("#box-native-callinfo"); + this.tabBoxNMemory = this.shadowRoot?.querySelector("#box-native-memory"); + this.tabBoxNMSample = this.shadowRoot?.querySelector("#box-native-sample"); + + this.tabSPT = this.shadowRoot!.querySelector('#tab-spt'); + this.tabPTS = this.shadowRoot!.querySelector('#tab-pts'); + this.tabCs = this.shadowRoot!.querySelector('#tab-cs'); + this.tabTs = this.shadowRoot!.querySelector('#tab-ts'); + this.tabChild = this.shadowRoot!.querySelector('#tab-box-child'); + + this.tabNativeStatistics = this.shadowRoot!.querySelector('#tab-box-native-stats'); + this.tabNativeCallInfo = this.shadowRoot!.querySelector('#tab-box-native-callinfo'); + this.tabNativeMemory = this.shadowRoot!.querySelector('#tab-box-native-memory'); + this.tabNativeSample = this.shadowRoot!.querySelector('#tab-box-native-sample'); + + let minBtn = this.shadowRoot?.querySelector("#min-btn"); + minBtn?.addEventListener('click', (e) => { + }) + this.litTabs!.onTabClick = (e: any) => { + this.loadTabPaneData(e.detail.key) + } + this.litTabs!.addEventListener("close-handler", (e) => { + this.recoveryBoxSelection(); + this.tabBoxChild!.hidden = true; + this.litTabs?.activeByKey(this.currentKey); + }) + this.tabSPT!.addEventListener("row-click", (e) => { + this.jumpBoxChild("11", e) + }) + this.tabPTS!.addEventListener("row-click", (e) => { + this.jumpBoxChild("12", e) + }) + this.tabCs!.addEventListener("row-click", (e) => { + this.jumpBoxChild("13", e) + }) + this.tabTs!.addEventListener("row-click", (e) => { + this.jumpBoxChild("14", e) + }) + this.tabNativeStatistics!.addEventListener("row-click",(e)=>{ + // @ts-ignore + this.selection!.statisticsSelectData = e.detail + this.tabNativeMemory?.fromStastics(this.selection) + this.litTabs?.activeByKey("18"); + }) + } + + connectedCallback() { + this.nav = this.shadowRoot?.querySelector("#tabs")?.shadowRoot?.querySelector('#nav') + let tabs: HTMLDivElement | undefined | null = this.shadowRoot?.querySelector('#tabs') + let navRoot: HTMLDivElement | null | undefined = this.shadowRoot?.querySelector("#tabs")?.shadowRoot?.querySelector('.nav-root') + + let search: HTMLDivElement | undefined | null = document.querySelector("body > sp-application")?.shadowRoot?.querySelector("div > div.search-container") + let timerShaft: HTMLDivElement | undefined | null = this.parentElement?.querySelector(".timer-shaft") + + let borderTop: number = 1; + let initialHeight = { + tabs: `calc(30vh + 39px)`, + node: "30vh" + } + this.nav!.onmousedown = (event) => { + let litTabpane: NodeListOf | undefined | null = this.shadowRoot?.querySelectorAll("#tabs > lit-tabpane") + let preY = event.pageY; + + let preHeight = tabs!.offsetHeight; + + document.onmousemove = function (event) { + let moveY: number; + + moveY = preHeight - (event.pageY - preY) + litTabpane!.forEach((node: HTMLDivElement, b) => { + if (navRoot!.offsetHeight <= moveY && (search!.offsetHeight + timerShaft!.offsetHeight + borderTop) <= (window.innerHeight - moveY)) { + tabs!.style.height = moveY + "px" + node!.style.height = (moveY - navRoot!.offsetHeight) + "px" + tabsPackUp!.name = "down" + } else if (navRoot!.offsetHeight >= moveY) { + tabs!.style.height = navRoot!.offsetHeight + "px" + node!.style.height = "0px" + tabsPackUp!.name = "up" + } else if ((search!.offsetHeight + timerShaft!.offsetHeight + borderTop) >= (window.innerHeight - moveY)) { + tabs!.style.height = (window.innerHeight - search!.offsetHeight - timerShaft!.offsetHeight - borderTop) + "px" + node!.style.height = (window.innerHeight - search!.offsetHeight - timerShaft!.offsetHeight - navRoot!.offsetHeight - borderTop) + "px" + tabsPackUp!.name = "down" + } + }) + + } + document.onmouseup = function (event) { + litTabpane!.forEach((node: HTMLDivElement, b) => { + if (node!.style.height !== "0px" && tabs!.style.height != "") { + initialHeight.node = node!.style.height; + initialHeight.tabs = tabs!.style.height; + } + }) + this.onmousemove = null; + this.onmouseup = null; + } + } + let tabsOpenUp: LitIcon | undefined | null = this.shadowRoot?.querySelector("#tabs > div > lit-icon:nth-child(1)") + let tabsPackUp: LitIcon | undefined | null = this.shadowRoot?.querySelector("#tabs > div > lit-icon:nth-child(2)") + tabsOpenUp!.onclick = (e) => { + tabs!.style.height = (window.innerHeight - search!.offsetHeight - timerShaft!.offsetHeight - borderTop) + "px" + let litTabpane: NodeListOf | undefined | null = this.shadowRoot?.querySelectorAll("#tabs > lit-tabpane") + litTabpane!.forEach((node: HTMLDivElement, b) => { + node!.style.height = (window.innerHeight - search!.offsetHeight - timerShaft!.offsetHeight - navRoot!.offsetHeight - borderTop) + "px" + initialHeight.node = node!.style.height; + }) + initialHeight.tabs = tabs!.style.height; + tabsPackUp!.name = "down" + } + tabsPackUp!.onclick = (e) => { + let litTabpane: NodeListOf | undefined | null = this.shadowRoot?.querySelectorAll("#tabs > lit-tabpane") + if (tabsPackUp!.name == "down") { + tabs!.style.height = navRoot!.offsetHeight + "px" + litTabpane!.forEach((node: HTMLDivElement, b) => { + node!.style.height = "0px" + }) + tabsPackUp!.name = "up" + } else { + tabsPackUp!.name = "down" + tabs!.style.height = initialHeight.tabs; + litTabpane!.forEach((node: HTMLDivElement, b) => { + node!.style.height = initialHeight.node; + }) + } + } + } + + initHtml(): string { + return ` + +
      + +
      + + +
      + + + + + + + + + + + + + + + + + + + + +
      +
      `; + } + + clear() { + this.shadowRoot?.querySelectorAll("lit-tabpane").forEach(it => this.litTabs?.removeChild(it)) + } + + displayThreadData(data: ThreadStruct, scrollCallback: ((e: ThreadStruct) => void) | undefined) { + this.setAttribute("mode", "max") + this.tabCurrentSelection!.hidden = false; + this.hideBoxTab(); + this.litTabs?.activeByKey("1") + let tabCpu = this.shadowRoot!.querySelector('#tabpane-cpu'); + tabCpu!.setThreadData(data, scrollCallback); + } + + displayMemData(data: ProcessMemStruct) { + this.setAttribute("mode", "max") + this.tabCurrentSelection!.hidden = false; + this.hideBoxTab(); + this.litTabs?.activeByKey("1") + let tabCpu = this.shadowRoot!.querySelector('#tabpane-cpu'); + tabCpu!.setMemData(data) + } + + displayFuncData(data: FuncStruct) { + this.setAttribute("mode", "max") + this.tabCurrentSelection!.hidden = false; + this.hideBoxTab(); + this.litTabs?.activeByKey("1") + let tabCpu = this.shadowRoot!.querySelector('#tabpane-cpu'); + tabCpu!.setFunctionData(data) + } + + displayCpuData(data: CpuStruct, + callback: ((data: WakeupBean | null) => void) | undefined = undefined, + scrollCallback?: (data: CpuStruct) => void) { + this.setAttribute("mode", "max") + this.tabCurrentSelection!.hidden = false; + this.hideBoxTab(); + this.litTabs?.activeByKey("1") + let tabCpu = this.shadowRoot!.querySelector('#tabpane-cpu'); + tabCpu!.setCpuData(data, callback, scrollCallback) + } + + displayFlagData(flagObj: Flag) { + this.setAttribute("mode", "max") + this.tabCurrentSelection!.hidden = true; + this.hideBoxTab(); + this.tabBoxFlag!.hidden = false; + this.litTabs?.activeByKey("10") + let tabFlag = this.shadowRoot!.querySelector('#tab-flag'); + tabFlag!.setFlagObj(flagObj) + } + + boxSelection(selection: SelectionParam):boolean { + this.tabBoxChild!.hidden = true; + this.selection = selection; + if (selection.hasFps || selection.cpus.length > 0 || selection.threadIds.length > 0 + || selection.funTids.length > 0 || selection.trackIds.length > 0 || selection.heapIds.length > 0 + || selection.nativeMemory.length > 0) { + this.setAttribute("mode", "max") + this.tabCurrentSelection!.hidden = true; + this.tabBoxFlag!.hidden = true; + this.tabBoxCpuThread!.hidden = selection.cpus.length == 0; + this.tabBoxCpuProcess!.hidden = selection.cpus.length == 0; + this.tabBoxCpuUsage!.hidden = selection.cpus.length == 0; + this.tabBoxSPT!.hidden = selection.cpus.length == 0; + this.tabBoxPTS!.hidden = selection.cpus.length == 0; + this.tabBoxContextSwitch!.hidden = selection.cpus.length == 0; + this.tabBoxThreadSwitch!.hidden = selection.cpus.length == 0; + this.tabBoxThreadStates!.hidden = selection.threadIds.length == 0; + this.tabBoxSlices!.hidden = selection.funTids.length == 0; + this.tabBoxCounters!.hidden = selection.trackIds.length == 0; + this.tabBoxFps!.hidden = !selection.hasFps; + this.tabBoxHeap!.hidden = selection.heapIds.length == 0; + this.tabBoxNMStatistics!.hidden = selection.nativeMemory.length == 0 + this.tabBoxNMCallInfo!.hidden = selection.nativeMemory.length == 0 + this.tabBoxNMemory!.hidden = selection.nativeMemory.length == 0 + this.tabBoxNMSample!.hidden = selection.nativeMemory.length == 0 + this.setBoxActiveKey(selection); + return true; + } else { + this.setAttribute("mode", "hidden") + return false; + } + } + + recoveryBoxSelection() { + this.tabCurrentSelection!.hidden = true; + this.tabBoxCpuThread!.hidden = !(this.selection!.cpus.length > 0); + this.tabBoxCpuProcess!.hidden = !(this.selection!.cpus.length > 0); + this.tabBoxCpuUsage!.hidden = !(this.selection!.cpus.length > 0); + this.tabBoxSPT!.hidden = !(this.selection!.cpus.length > 0); + this.tabBoxPTS!.hidden = !(this.selection!.cpus.length > 0); + this.tabBoxContextSwitch!.hidden = !(this.selection!.cpus.length > 0); + this.tabBoxThreadSwitch!.hidden = !(this.selection!.cpus.length > 0); + this.tabBoxThreadStates!.hidden = !(this.selection!.threadIds.length > 0); + this.tabBoxSlices!.hidden = !(this.selection!.funTids.length > 0); + this.tabBoxCounters!.hidden = !(this.selection!.trackIds.length > 0) + this.tabBoxHeap!.hidden = !(this.selection!.heapIds.length > 0) + this.tabBoxFps!.hidden = !this.selection?.hasFps; + this.tabBoxNMStatistics!.hidden = !(this.selection!.nativeMemory.length > 0) + this.tabBoxNMCallInfo!.hidden = !(this.selection!.nativeMemory.length > 0) + this.tabBoxNMemory!.hidden = !(this.selection!.nativeMemory.length > 0) + this.tabBoxNMSample!.hidden = !(this.selection!.nativeMemory.length > 0) + } + + setBoxActiveKey(val: SelectionParam) { + if (val.cpus.length > 0) { + this.litTabs?.activeByKey("2") + this.loadTabPaneData("2") + } else if (val.threadIds.length > 0) { + this.litTabs?.activeByKey("4") + this.loadTabPaneData("4") + } else if (val.funTids.length > 0) { + this.litTabs?.activeByKey("5") + this.loadTabPaneData("5") + } else if (val.trackIds.length > 0) { + this.litTabs?.activeByKey("6") + this.loadTabPaneData("6") + } else if (val.hasFps) { + this.litTabs?.activeByKey("7") + this.loadTabPaneData("7") + } else if (val.heapIds.length > 0) { + this.litTabs?.activeByKey("9") + this.loadTabPaneData("9") + } else if(val.nativeMemory.length > 0) { + this.litTabs?.activeByKey("16") + this.loadTabPaneData("16") + }else{ + this.litTabs?.activeByKey("1") + this.loadTabPaneData("1") + } + } + + loadTabPaneData(key: string) { + if (key == "2") { + let tabCpuThread = this.shadowRoot!.querySelector('#tab-cpu-thread'); + tabCpuThread!.data = this.selection; + } else if (key == "3") { + let tabCpuProcess = this.shadowRoot!.querySelector('#tab-cpu-process'); + tabCpuProcess!.data = this.selection; + } else if (key == "4") { + let tabThreadStates = this.shadowRoot!.querySelector('#tab-thread-states'); + tabThreadStates!.data = this.selection; + } else if (key == "5") { + let tabSlices = this.shadowRoot!.querySelector('#tab-slices'); + tabSlices!.data = this.selection; + } else if (key == "6") { + let tabCounters = this.shadowRoot!.querySelector('#tab-counters'); + tabCounters!.data = this.selection; + } else if (key == "7") { + let tabFps = this.shadowRoot!.querySelector('#tab-fps'); + tabFps!.data = this.selection; + } else if (key == "8") { + let tabCpuUsage = this.shadowRoot!.querySelector('#tab-cpu-usage'); + tabCpuUsage!.data = this.selection; + } else if (key == "9") { + let tabHeap = this.shadowRoot!.querySelector('#tab-heap'); + tabHeap!.data = this.selection; + } else if (key == "10") { + + } else if (key == "11") { + this.tabSPT!.data = this.selection; + } else if (key == "12") { + this.tabPTS!.data = this.selection; + } else if (key == "13") { + this.tabCs!.data = this.selection; + } else if (key == "14") { + this.tabTs!.data = this.selection; + } else if (key == "16") { + this.tabNativeStatistics!.data = this.selection; + } else if (key == "17") { + this.tabNativeCallInfo!.data = this.selection; + } else if (key == "18") { + this.tabNativeMemory!.data = this.selection; + } else if (key == "19") { + this.tabNativeSample!.data = this.selection; + } + } + + hideBoxTab() { + this.tabBoxCpuThread!.hidden = true; + this.tabBoxCpuProcess!.hidden = true; + this.tabBoxThreadStates!.hidden = true; + this.tabBoxSlices!.hidden = true; + this.tabBoxCounters!.hidden = true; + this.tabBoxFps!.hidden = true; + this.tabBoxSPT!.hidden = true; + this.tabBoxPTS!.hidden = true; + this.tabBoxContextSwitch!.hidden = true; + this.tabBoxThreadSwitch!.hidden = true; + this.tabBoxCpuUsage!.hidden = true; + this.tabBoxFlag!.hidden = true; + this.tabBoxHeap!.hidden = true; + this.tabBoxChild!.hidden = true; + this.tabBoxNMStatistics!.hidden = true; + this.tabBoxNMCallInfo!.hidden = true; + this.tabBoxNMemory!.hidden = true; + this.tabBoxNMSample!.hidden = true; + } + + hideOtherBoxTab(key: string) { + this.tabBoxCpuThread!.hidden = true; + this.tabBoxCpuProcess!.hidden = true; + this.tabBoxThreadStates!.hidden = true; + this.tabBoxSlices!.hidden = true; + this.tabBoxCounters!.hidden = true; + this.tabBoxFps!.hidden = true; + this.tabBoxCpuUsage!.hidden = true; + this.tabBoxHeap!.hidden = true; + this.tabBoxNMStatistics!.hidden = true; + this.tabBoxNMCallInfo!.hidden = true; + this.tabBoxNMemory!.hidden = true; + this.tabBoxNMSample!.hidden = true; + if (key == "11") { + this.tabBoxPTS!.hidden = true; + this.tabBoxContextSwitch!.hidden = true; + this.tabBoxThreadSwitch!.hidden = true; + } else if (key == "12") { + this.tabBoxSPT!.hidden = true; + this.tabBoxContextSwitch!.hidden = true; + this.tabBoxThreadSwitch!.hidden = true; + } else if (key == "13") { + this.tabBoxSPT!.hidden = true; + this.tabBoxPTS!.hidden = true; + this.tabBoxThreadSwitch!.hidden = true; + } else if (key == "14") { + this.tabBoxSPT!.hidden = true; + this.tabBoxPTS!.hidden = true; + this.tabBoxContextSwitch!.hidden = true; + } + this.tabBoxChild!.hidden = false + this.currentKey = key + this.litTabs?.activeByKey("15") + } + + jumpBoxChild(key: string, e: any) { + this.hideOtherBoxTab(key) + this.tabBoxChild!.tab = e.detail.title + let param = new BoxJumpParam(); + param.leftNs = this.selection!.leftNs; + param.rightNs = this.selection!.rightNs; + param.state = e.detail.state; + param.threadId = e.detail.threadId; + param.processId = e.detail.processId; + this.tabChild!.data = param; + } + +} diff --git a/host/ide/src/trace/component/trace/base/Utils.ts b/host/ide/src/trace/component/trace/base/Utils.ts new file mode 100644 index 0000000..cb7e6da --- /dev/null +++ b/host/ide/src/trace/component/trace/base/Utils.ts @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class Utils { + private static statusMap: Map = new Map(); + private static instance: Utils | null = null; + + constructor() { + Utils.statusMap.set("D", "Uninterruptible Sleep"); + Utils.statusMap.set("S", "Sleeping"); + Utils.statusMap.set("R", "Runnable"); + Utils.statusMap.set("Running", "Running"); + Utils.statusMap.set("R+", "Runnable (Preempted)"); + Utils.statusMap.set("DK", "Uninterruptible Sleep + Wake Kill"); + Utils.statusMap.set("I", "Task Dead"); + Utils.statusMap.set("T", "Traced"); + Utils.statusMap.set("t", "Traced"); + Utils.statusMap.set("X", "Exit (Dead)"); + Utils.statusMap.set("Z", "Exit (Zombie)"); + Utils.statusMap.set("K", "Wake Kill"); + Utils.statusMap.set("W", "Waking"); + Utils.statusMap.set("P", "Parked"); + Utils.statusMap.set("N", "No Load"); + } + + public static getInstance(): Utils { + if (Utils.instance == null) { + Utils.instance = new Utils(); + } + return Utils.instance + } + + public static getEndState(state: string): string | null | undefined { + if (Utils.getInstance().getStatusMap().has(state)) { + return Utils.getInstance().getStatusMap().get(state); + } else { + if ("" == state || state == null) { + return ""; + } + return "Unknown State"; + } + } + + public static getStateColor(state: string): string { + if (state == "D" || state == "DK") { + return "#f19b38" + } else if (state == "R" || state == "R+") { + return "#a0b84d" + } else if (state == "I") { + return "#673ab7" + } else if (state == "Running") { + return "#467b3b" + } else if (state == "S") { + return "#e0e0e0" + } else { + return "#ff6e40" + } + } + + public static getTimeString(ns: number): string { + let currentNs = ns + let hour1 = 3600_000_000_000 + let minute1 = 60_000_000_000 + let second1 = 1_000_000_000; + let millisecond1 = 1_000_000; + let microsecond1 = 1_000; + let res = ""; + if (currentNs >= hour1) { + res += Math.floor(currentNs / hour1) + "h "; + currentNs = currentNs - Math.floor(currentNs / hour1) * hour1 + } + if (currentNs >= minute1) { + res += Math.floor(currentNs / minute1) + "m "; + currentNs = currentNs - Math.floor(ns / minute1) * minute1 + } + if (currentNs >= second1) { + res += Math.floor(currentNs / second1) + "s "; + currentNs = currentNs - Math.floor(currentNs / second1) * second1 + } + if (currentNs >= millisecond1) { + res += Math.floor(currentNs / millisecond1) + "ms "; + currentNs = currentNs - Math.floor(currentNs / millisecond1) * millisecond1 + } + if (currentNs >= microsecond1) { + res += Math.floor(currentNs / microsecond1) + "μs "; + currentNs = currentNs - Math.floor(currentNs / microsecond1) * microsecond1 + } + if (currentNs > 0) { + res += currentNs + "ns "; + } + if (res == "") { + res = ns + ""; + } + return res + } + + public static getTimeStringHMS(ns: number): string { + let currentNs = ns + let hour1 = 3600_000_000_000 + let minute1 = 60_000_000_000 + let second1 = 1_000_000_000; // 1 second + let millisecond1 = 1_000_000; // 1 millisecond + let microsecond1 = 1_000; // 1 microsecond + let res = ""; + if (currentNs >= hour1) { + res += Math.floor(currentNs / hour1) + ":"; + currentNs = currentNs - Math.floor(currentNs / hour1) * hour1 + } + if (currentNs >= minute1) { + res += Math.floor(currentNs / minute1) + ":"; + currentNs = currentNs - Math.floor(ns / minute1) * minute1 + } + if (currentNs >= second1) { + res += Math.floor(currentNs / second1) + ":"; + currentNs = currentNs - Math.floor(currentNs / second1) * second1 + } + if (currentNs >= millisecond1) { + res += Math.floor(currentNs / millisecond1) + "."; + currentNs = currentNs - Math.floor(currentNs / millisecond1) * millisecond1 + } + if (currentNs >= microsecond1) { + res += Math.floor(currentNs / microsecond1) + "."; + currentNs = currentNs - Math.floor(currentNs / microsecond1) * microsecond1 + } + if (currentNs > 0) { + res += currentNs + ""; + } + if (res == "") { + res = ns + ""; + } + return res + } + + public static getByteWithUnit(bytes: number): string { + if (bytes < 0) { + return "-" + this.getByteWithUnit(Math.abs(bytes)) + } + let currentBytes = bytes + let kb1 = 1024 + let mb1 = 1048576 + let gb1 = 1073741824; // 1 gb + let res = "" + if (currentBytes > gb1) { + res += (currentBytes / gb1).toFixed(2) + " Gb"; + } else if (currentBytes > mb1) { + res += (currentBytes / mb1).toFixed(2) + " Mb"; + } else if (currentBytes > kb1) { + res += (currentBytes / kb1).toFixed(2) + " kb"; + } else { + res += currentBytes + " byte"; + } + return res + } + + public getStatusMap(): Map { + return Utils.statusMap; + } +} diff --git a/host/ide/src/trace/component/trace/search/Search.ts b/host/ide/src/trace/component/trace/search/Search.ts new file mode 100644 index 0000000..23ff8f9 --- /dev/null +++ b/host/ide/src/trace/component/trace/search/Search.ts @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; + +@element("lit-search") +export class LitSearch extends BaseElement { + valueChangeHandler: ((str: string) => void) | undefined | null; + private search: HTMLInputElement | undefined | null; + private _total: number = 0; + private _index: number = 0; + private _list: Array = []; + private totalEL: HTMLSpanElement | null | undefined; + private indexEL: HTMLSpanElement | null | undefined; + + get list(): Array { + return this._list; + } + + set list(value: Array) { + this._list = value; + this.total = value.length; + } + + get index(): number { + return this._index; + } + + set index(value: number) { + this._index = value; + this.indexEL!.textContent = `${value+1}`; + } + + get total(): number { + return this._total; + } + + set total(value: number) { + value > 0 ? this.setAttribute("show-search-info", '') : this.removeAttribute("show-search-info"); + this._total = value; + this.indexEL!.textContent = '0'; + this.totalEL!.textContent = value.toString(); + } + + setPercent(name: string = "", value: number) { + let searchHide = this.shadowRoot!.querySelector(".root") + let searchIcon = this.shadowRoot!.querySelector("#search-icon") + if (value > 0 && value <= 100) { + searchHide!.style.display = "flex" + searchHide!.style.backgroundColor = "var(--dark-background5,#e3e3e3)" + searchIcon?.setAttribute('name', "cloud-sync"); + this.search!.setAttribute('placeholder', `${name}${value}%`); + this.search!.setAttribute('readonly', ""); + this.search!.className = "readonly" + } else if (value > 100) { + searchHide!.style.display = "flex" + searchHide!.style.backgroundColor = "var(--dark-background5,#fff)" + searchIcon?.setAttribute('name', "search"); + this.search?.setAttribute('placeholder', `search`); + this.search?.removeAttribute('readonly'); + this.search!.className = "write" + } else if (value == -1) { + searchHide!.style.display = "flex" + searchHide!.style.backgroundColor = "var(--dark-background5,#e3e3e3)" + searchIcon?.setAttribute('name', "cloud-sync"); + this.search!.setAttribute('placeholder', `${name}`); + this.search!.setAttribute('readonly', ""); + this.search!.className = "readonly" + } else { + searchHide!.style.display = "none" + } + } + + clear() { + this.search = this.shadowRoot!.querySelector("input"); + this.search!.value = ""; + this.list = []; + } + + blur(){ + this.search?.blur(); + } + initElements(): void { + this.search = this.shadowRoot!.querySelector("input"); + this.totalEL = this.shadowRoot!.querySelector("#total"); + this.indexEL = this.shadowRoot!.querySelector("#index"); + this.search!.addEventListener("focus", (e) => { + this.dispatchEvent(new CustomEvent("focus", { + detail: { + value: this.search!.value + } + })); + }); + this.search!.addEventListener("blur", (e) => { + this.dispatchEvent(new CustomEvent("blur", { + detail: { + value: this.search!.value + } + })); + }); + this.search!.addEventListener("keyup", (e: KeyboardEvent) => { + if (e.code == "Enter") { + if (e.shiftKey) { + this.dispatchEvent(new CustomEvent("previous-data", { + detail: { + value: this.search!.value + } + })); + } else { + this.dispatchEvent(new CustomEvent("next-data", { + detail: { + value: this.search!.value + } + })); + } + } else { + this.valueChangeHandler?.(this.search!.value); + } + }); + this.shadowRoot?.querySelector("#arrow-left")?.addEventListener("click", (e) => { + this.dispatchEvent(new CustomEvent("previous-data", { + detail: { + value: this.search!.value + } + })); + }); + this.shadowRoot?.querySelector("#arrow-right")?.addEventListener("click", (e) => { + this.dispatchEvent(new CustomEvent("next-data", { + detail: { + value: this.search!.value + } + })); + }); + } + + initHtml(): string { + return ` + + + `; + } + +} diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneBoxChild.ts b/host/ide/src/trace/component/trace/sheet/TabPaneBoxChild.ts new file mode 100644 index 0000000..c37351b --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneBoxChild.ts @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import {BoxJumpParam, SelectionData} from "../../../bean/BoxSelection.js"; +import {getTabBoxChildData} from "../../../database/SqlLite.js"; +import {Utils} from "../base/Utils.js"; +import {SpSystemTrace} from "../../SpSystemTrace.js"; +import {SPTChild} from "../../../bean/StateProcessThread.js"; + +@element('tabpane-box-child') +export class TabPaneBoxChild extends BaseElement { + private tbl: LitTable | null | undefined; + private range: HTMLLabelElement | null | undefined; + private source: Array = []; + private loadDataInCache: boolean = true; + + set data(val: BoxJumpParam) { + // @ts-ignore + this.tbl?.shadowRoot?.querySelector(".table")?.style?.height = (this.parentElement!.clientHeight - 45)+"px"; + this.range!.textContent = "Selected range: " + parseFloat(((val.rightNs - val.leftNs) / 1000000.0).toFixed(5)) + " ms" + if (this.loadDataInCache) { + this.getDataByCache(val).then((arr) => { + this.source = arr; + // @ts-ignore + this.tbl?.recycleDataSource = arr; + }) + } else { + this.getDataByDB(val) + } + } + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#tb-cpu-thread'); + this.range = this.shadowRoot?.querySelector('#time-range'); + this.tbl!.addEventListener('column-click', (evt) => { + // @ts-ignore + this.sortByColumn(evt.detail) + }); + new ResizeObserver((entries) => { + if (this.parentElement?.clientHeight != 0) { + // @ts-ignore + this.tbl?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight - 45)+"px" + this.tbl?.reMeauseHeight() + } + }).observe(this.parentElement!) + } + + getDataByDB(val: BoxJumpParam) { + getTabBoxChildData(val.leftNs, val.rightNs, val.state, val.processId, val.threadId).then((result) => { + if (result.length != null && result.length > 0) { + result.map((e) => { + e.startTime = Utils.getTimeString(e.startNs) + e.state = Utils.getEndState(e.state)! + e.prior = e.priority == undefined || e.priority == null ? "-" : e.priority + "" + e.core = e.cpu == undefined || e.cpu == null ? "-" : "CPU" + e.cpu + e.processName = (e.process == undefined || e.process == null ? "process" : e.process) + "(" + e.processId + ")" + e.threadName = (e.thread == undefined || e.thread == null ? "thread" : e.thread) + "(" + e.threadId + ")" + }) + this.source = result; + // @ts-ignore + this.tbl?.dataSource = result; + } else { + this.source = []; + // @ts-ignore + this.tbl?.dataSource = [] + } + }) + } + + getDataByCache(val: BoxJumpParam): Promise> { + return new Promise>((resolve, reject) => { + let time = Date.now(); + let arr: Array = []; + SpSystemTrace.SPT_DATA.map((spt) => { + let b1 = (val.state != undefined && val.state != '') ? spt.state == val.state : true + let b2 = (val.processId != undefined && val.processId != -1) ? spt.processId == val.processId : true + let b3 = (val.threadId != undefined && val.threadId != -1) ? spt.threadId == val.threadId : true + if(!(spt.end_ts < val.leftNs || spt.start_ts > val.rightNs) && b1 && b2 && b3){ + let sptChild = new SPTChild(); + sptChild.startTime = Utils.getTimeString(spt.start_ts) + sptChild.state = Utils.getEndState(spt.state)! + sptChild.prior = spt.priority == undefined || spt.priority == null ? "-" : spt.priority + "" + sptChild.core = spt.cpu == undefined || spt.cpu == null ? "-" : "CPU" + spt.cpu + sptChild.processName = (spt.process == undefined || spt.process == null || spt.process == "" ? "process" : spt.process) + "(" + spt.processId + ")" + sptChild.threadName = (spt.thread == undefined || spt.thread == null || spt.thread == "" ? "thread" : spt.thread) + "(" + spt.threadId + ")" + arr.push(sptChild); + } + }) + resolve(arr); + }) + + } + + initHtml(): string { + return ` + + + + + + + + + + + + `; + } + + sortByColumn(detail: any) { + // @ts-ignore + function compare(property, sort, type) { + return function (a: SelectionData, b: SelectionData) { + if (type === 'number') { + // @ts-ignore + return sort === 2 ? parseFloat(b[property]) - parseFloat(a[property]) : parseFloat(a[property]) - parseFloat(b[property]); + } else { + // @ts-ignore + if (b[property] > a[property]) { + return sort === 2 ? 1 : -1; + } else { // @ts-ignore + if (b[property] == a[property]) { + return 0; + } else { + return sort === 2 ? -1 : 1; + } + } + } + } + } + + // @ts-ignore + this.source.sort(compare(detail.key, detail.sort, 'string')) + this.tbl!.dataSource = this.source; + } + +} \ No newline at end of file diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneContextSwitch.ts b/host/ide/src/trace/component/trace/sheet/TabPaneContextSwitch.ts new file mode 100644 index 0000000..0098d18 --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneContextSwitch.ts @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import {SelectionParam} from "../../../bean/BoxSelection.js"; +import { + getTabStatesGroupByProcess, + getTabStatesGroupByProcessThread, + getTabStatesGroupByStatePidTid +} from "../../../database/SqlLite.js"; +import {StateProcessThread} from "../../../bean/StateProcessThread.js"; +import {Utils} from "../base/Utils.js"; +import {SpSystemTrace} from "../../SpSystemTrace.js"; + +@element('tabpane-context-switch') +export class TabPaneContextSwitch extends BaseElement { + private tbl: LitTable | null | undefined; + private range: HTMLLabelElement | null | undefined; + private loadDataInCache: boolean = true; + + set data(val: SelectionParam | any) { + // @ts-ignore + this.tbl?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight - 45)+"px" + this.range!.textContent = "Selected range: " + parseFloat(((val.rightNs - val.leftNs) / 1000000.0).toFixed(5)) + " ms" + if (this.loadDataInCache) { + this.queryDataInCacheData(val).then((arr) => { + this.tbl!.recycleDataSource = arr; + }); + } else { + this.queryDataByDB(val) + } + } + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#tb-cs'); + this.range = this.shadowRoot?.querySelector('#time-range') + new ResizeObserver((entries) => { + if (this.parentElement?.clientHeight != 0) { + // @ts-ignore + this.tbl?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight - 45)+"px" + this.tbl?.reMeauseHeight() + } + }).observe(this.parentElement!) + } + + async queryDataInCacheData(val: SelectionParam | any): Promise> { + return new Promise>((resolve, reject) => { + let pMap: Map = new Map(); + let ptMap: Map = new Map(); + let ptsMap: Map = new Map(); + SpSystemTrace.SPT_DATA.map((d)=>{ + if(!(d.end_ts < val.leftNs || d.start_ts > val.rightNs)){ + if (pMap.has(d.processId + "")) { + let obj1 = pMap.get(d.processId + ""); + obj1!.count++; + obj1!.wallDuration += d.dur; + obj1!.avgDuration = (obj1!.wallDuration / obj1!.count).toFixed(2); + if (d.dur > obj1!.maxDuration) { + obj1!.maxDuration = d.dur; + } + if (d.dur < obj1!.minDuration) { + obj1!.minDuration = d.dur; + } + } else { + let obj1 = new StateProcessThread(); + obj1.id = "p" + d.processId; + obj1.title = (d.process == null || d.process == "" ? "Process" : d.process) + "(" + d.processId + ")"; + obj1.process = d.process; + obj1.processId = d.processId; + obj1.minDuration = d.dur; + obj1.maxDuration = d.dur; + obj1.count = 1; + obj1.avgDuration = d.dur + ""; + obj1.wallDuration = d.dur; + pMap.set(d.processId + "", obj1); + } + if (ptMap.has(d.processId + "_" + d.threadId)) { + let obj2 = ptMap.get(d.processId + "_" + d.threadId); + obj2!.count++; + obj2!.wallDuration += d.dur; + obj2!.avgDuration = (obj2!.wallDuration / obj2!.count).toFixed(2); + if (d.dur > obj2!.maxDuration) { + obj2!.maxDuration = d.dur; + } + if (d.dur < obj2!.minDuration) { + obj2!.minDuration = d.dur; + } + } else { + let obj2 = new StateProcessThread(); + obj2.id = "p" + d.processId + "_" + "t" + d.threadId; + obj2.pid = "p" + d.processId; + obj2.title = (d.thread == null || d.thread == "" ? "Thread" : d.thread) + "(" + d.threadId + ")" + obj2.processId = d.processId; + obj2.process = d.process; + obj2.thread = d.thread; + obj2.threadId = d.threadId; + obj2.minDuration = d.dur; + obj2.maxDuration = d.dur; + obj2.count = 1; + obj2.avgDuration = d.dur + ""; + obj2.wallDuration = d.dur; + ptMap.set(d.processId + "_" + d.threadId, obj2); + } + if (ptsMap.has(d.processId + "_" + d.threadId + "_" + d.state)) { + let obj3 = ptsMap.get(d.processId + "_" + d.threadId + "_" + d.state); + obj3!.count++; + obj3!.wallDuration += d.dur; + obj3!.avgDuration = (obj3!.wallDuration / obj3!.count).toFixed(2); + if (d.dur > obj3!.maxDuration) { + obj3!.maxDuration = d.dur; + } + if (d.dur < obj3!.minDuration) { + obj3!.minDuration = d.dur; + } + } else { + let obj3 = new StateProcessThread(); + obj3.id = "p" + d.processId + "_" + "t" + d.threadId + "_" + (d.state == "R+" ? "RP" : d.state) + obj3.pid = "p" + d.processId + "_" + "t" + d.threadId; + obj3.title = Utils.getEndState(d.state) + obj3.processId = d.processId; + obj3.process = d.process; + obj3.thread = d.thread; + obj3.threadId = d.threadId; + obj3.state = d.state; + obj3.minDuration = d.dur; + obj3.maxDuration = d.dur; + obj3.count = 1; + obj3.avgDuration = d.dur + ""; + obj3.wallDuration = d.dur; + ptsMap.set(d.processId + "_" + d.threadId + "_" + d.state, obj3); + } + } + }) + let arr: Array = []; + for (let key of pMap.keys()) { + let s = pMap.get(key); + s!.children = []; + for (let ks of ptMap.keys()) { + if (ks.startsWith(key + "_")) { + let sp = ptMap.get(ks) + sp!.children = []; + for (let kst of ptsMap.keys()) { + if (kst.startsWith(ks + "_")) { + let spt = ptsMap.get(kst) + sp!.children.push(spt!); + } + } + s!.children.push(sp!) + } + } + arr.push(s!) + } + resolve(arr); + }) + } + + queryDataByDB(val: SelectionParam | any) { + Promise.all([ + getTabStatesGroupByProcess(val.leftNs, val.rightNs), + getTabStatesGroupByProcessThread(val.leftNs, val.rightNs), + getTabStatesGroupByStatePidTid(val.leftNs, val.rightNs)]).then((values) => { + let processes = values[0]; + processes.map((spt) => { + spt.id = "p" + spt.processId + spt.title = (spt.process == null || spt.process == "" ? "Process" : spt.process) + "(" + spt.processId + ")" + }); + let threadMap = this.groupByProcessToMap(values[1]); + let stateMap = this.groupByProcessThreadToMap(values[2]); + for (let process of processes) { + let threads = threadMap.get(process.processId); + if (threads != undefined) { + threads!.map((spt) => { + spt.id = "p" + spt.processId + "_" + "t" + spt.threadId; + spt.pid = "p" + spt.processId; + spt.title = (spt.thread == null || spt.thread == "" ? "Thread" : spt.thread) + "(" + spt.threadId + ")" + }) + } + process.children = threads ?? []; + let map = stateMap.get(process.processId); + for (let thread of threads!) { + let states = map!.get(thread.threadId); + states!.map((spt) => { + spt.id = "p" + spt.processId + "_" + "t" + spt.threadId + "_" + (spt.state == "R+" ? "RP" : spt.state) + spt.pid = "p" + spt.processId + "_" + "t" + spt.threadId; + spt.title = Utils.getEndState(spt.state) + }) + thread.children = states ?? []; + } + } + this.tbl!.dataSource = processes; + }) + } + + groupByThreadToMap(arr: Array): Map> { + let map = new Map>(); + for (let spt of arr) { + if (map.has(spt.threadId)) { + map.get(spt.threadId)!.push(spt); + } else { + let list: Array = []; + list.push(spt); + map.set(spt.threadId, list); + } + } + return map; + } + + groupByProcessToMap(arr: Array): Map> { + let map = new Map>(); + for (let spt of arr) { + if (map.has(spt.processId)) { + map.get(spt.processId)!.push(spt); + } else { + let list: Array = []; + list.push(spt); + map.set(spt.processId, list); + } + } + return map; + } + + groupByProcessThreadToMap(arr: Array): Map>> { + let map = new Map>>(); + let processMap = this.groupByProcessToMap(arr); + for (let key of processMap.keys()) { + let threadMap = this.groupByThreadToMap(processMap.get(key)!) + map.set(key, threadMap); + } + return map; + } + + initHtml(): string { + return ` + + + + + + + `; + } + +} \ No newline at end of file diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneCounter.ts b/host/ide/src/trace/component/trace/sheet/TabPaneCounter.ts new file mode 100644 index 0000000..582811f --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneCounter.ts @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import {Counter, SelectionData, SelectionParam} from "../../../bean/BoxSelection.js"; +import {getTabCounters} from "../../../database/SqlLite.js"; + +@element('tabpane-counter') +export class TabPaneCounter extends BaseElement { + private tbl: LitTable | null | undefined; + private range: HTMLLabelElement | null | undefined; + private source: Array = [] + + set data(val: SelectionParam | any) { + this.range!.textContent = "Selected range: " + parseFloat(((val.rightNs - val.leftNs) / 1000000.0).toFixed(5)) + " ms" + getTabCounters(val.trackIds, val.rightNs).then((result) => { + if (result != null && result.length > 0) { + let dataSource: Array = []; + let collect = this.groupByTrackIdToMap(result); + let sumCount = 0; + for (let key of collect.keys()) { + let counters = collect.get(key); + let list:Array = []; + let index = counters!.findIndex((item) => item.startTime >= val.leftNs); + if (index != -1) { + list = counters!.splice(index > 0 ? index - 1 : index) + } else { + list.push(counters![counters!.length - 1]); + } + let sd = this.createSelectCounterData(list, val.leftNs, val.rightNs); + sumCount += Number.parseInt(sd.count); + dataSource.push(sd); + } + let sumData = new SelectionData(); + sumData.count = sumCount.toString(); + sumData.process = " "; + dataSource.splice(0, 0, sumData); + this.source = dataSource + this.tbl!.dataSource = dataSource + } else { + this.source = []; + this.tbl!.dataSource = this.source + } + }); + } + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#tb-counter'); + this.range = this.shadowRoot?.querySelector('#time-range'); + this.tbl!.addEventListener('column-click', (evt) => { + // @ts-ignore + this.sortByColumn(evt.detail) + }); + } + + initHtml(): string { + return ` + + + + + + + + + + + + + + `; + } + + groupByTrackIdToMap(arr: Array): Map> { + let map = new Map>(); + for (let counter of arr) { + if (map.has(counter.trackId)) { + map.get(counter.trackId)!.push(counter); + } else { + let list: Array = []; + list.push(counter); + map.set(counter.trackId, list); + } + } + return map; + } + + createSelectCounterData(list: Array, leftNs: number, rightNs: number): SelectionData { + let selectData = new SelectionData(); + if (list.length > 0) { + let range = rightNs - leftNs; + let first = list[0]; + selectData.trackId = first.trackId; + selectData.name = first.name; + selectData.first = first.value + ""; + selectData.count = list.length + ""; + selectData.last = list[list.length - 1].value + ""; + selectData.delta = (parseInt(selectData.last) - parseInt(selectData.first)) + ""; + selectData.rate = (parseInt(selectData.delta) / (range * 1.0 / 1000000000)).toFixed(4); + selectData.min = first.value + ""; + selectData.max = "0"; + let weightAvg = 0.0; + for (let i = 0; i < list.length; i++) { + let counter = list[i]; + if (counter.value < parseInt(selectData.min)) { + selectData.min = counter.value.toString(); + } + if (counter.value > parseInt(selectData.max)) { + selectData.max = counter.value.toString(); + } + let start = i == 0 ? leftNs : counter.startTime + let end = i == list.length - 1 ? rightNs : list[i + 1].startTime + weightAvg += counter.value * ((end - start) * 1.0 / range); + } + selectData.avgWeight = weightAvg.toFixed(2) + } + return selectData; + } + + sortByColumn(detail: any) { + // @ts-ignore + function compare(property, sort, type) { + return function (a: SelectionData, b: SelectionData) { + if (a.process == " " || b.process == " ") { + return 0; + } + if (type === 'number') { + // @ts-ignore + return sort === 2 ? parseFloat(b[property]) - parseFloat(a[property]) : parseFloat(a[property]) - parseFloat(b[property]); + } else { + // @ts-ignore + if (b[property] > a[property]) { + return sort === 2 ? 1 : -1; + } else { // @ts-ignore + if (b[property] == a[property]) { + return 0; + } else { + return sort === 2 ? -1 : 1; + } + } + } + } + } + + if (detail.key === 'name') { + this.source.sort(compare(detail.key, detail.sort, 'string')) + } else { + this.source.sort(compare(detail.key, detail.sort, 'number')) + } + this.tbl!.dataSource = this.source; + } + +} \ No newline at end of file diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneCpu.ts b/host/ide/src/trace/component/trace/sheet/TabPaneCpu.ts new file mode 100644 index 0000000..cb5a3a7 --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneCpu.ts @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import {CpuStruct} from "../../../bean/CpuStruct.js"; + +@element('tabpane-cpu') +export class TabPaneCpu extends BaseElement { + private tbl: LitTable | null | undefined; + + set data(val: CpuStruct) { + this.tbl!.dataSource = [ + {name: 'Process', value: `${val.processName || 'Process'} [${val.processId}]`}, + {name: 'Thread', value: `${val.name || 'Thread'} [${val.tid}]`}, + {name: 'Cmdline', value: val.processCmdLine}, + {name: 'Start time', value: val.startTime}, + {name: 'Duration', value: val.dur}, + {name: 'Prio', value: val.priority}, + {name: 'End State', value: val.end_state}, + {name: 'Slice ID', value: val.id} + ] + } + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#tbl3'); + } + + initHtml(): string { + return ` + + + + + + + `; + } + +} \ No newline at end of file diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneCpuByProcess.ts b/host/ide/src/trace/component/trace/sheet/TabPaneCpuByProcess.ts new file mode 100644 index 0000000..5537ba5 --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneCpuByProcess.ts @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import {SelectionData, SelectionParam} from "../../../bean/BoxSelection.js"; +import {getTabCpuByProcess} from "../../../database/SqlLite.js"; + +@element('tabpane-cpu-process') +export class TabPaneCpuByProcess extends BaseElement { + private tbl: LitTable | null | undefined; + private range: HTMLLabelElement | null | undefined; + private source: Array = [] + + set data(val: SelectionParam | any) { + this.range!.textContent = "Selected range: " + parseFloat(((val.rightNs - val.leftNs) / 1000000.0).toFixed(5)) + " ms" + getTabCpuByProcess(val.cpus, val.leftNs, val.rightNs).then((result) => { + if (result != null && result.length > 0) { + let sumWall = 0.0; + let sumOcc = 0; + for (let e of result) { + e.process = e.process == null || e.process.length == 0 ? "[NULL]" : e.process + sumWall += e.wallDuration + sumOcc += e.occurrences + e.wallDuration = parseFloat((e.wallDuration / 1000000.0).toFixed(5)); + e.avgDuration = parseFloat((parseFloat(e.avgDuration) / 1000000.0).toFixed(5)).toString(); + } + let count = new SelectionData() + count.process = " " + count.wallDuration = parseFloat((sumWall / 1000000.0).toFixed(5)); + count.occurrences = sumOcc; + result.splice(0, 0, count) + this.source = result + this.tbl!.dataSource = result + } else { + this.source = []; + this.tbl!.dataSource = this.source + } + }); + } + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#tb-cpu-process'); + this.range = this.shadowRoot?.querySelector('#time-range') + this.tbl!.addEventListener('column-click', (evt) => { + // @ts-ignore + this.sortByColumn(evt.detail) + }); + } + + initHtml(): string { + return ` + + + + + + + + + + `; + } + + sortByColumn(detail: any) { + // @ts-ignore + function compare(property, sort, type) { + return function (a: SelectionData, b: SelectionData) { + if (a.process == " " || b.process == " ") { + return 0; + } + if (type === 'number') { + // @ts-ignore + return sort === 2 ? parseFloat(b[property]) - parseFloat(a[property]) : parseFloat(a[property]) - parseFloat(b[property]); + } else { + // @ts-ignore + if (b[property] > a[property]) { + return sort === 2 ? 1 : -1; + } else { // @ts-ignore + if (b[property] == a[property]) { + return 0; + } else { + return sort === 2 ? -1 : 1; + } + } + } + } + } + + if (detail.key === 'pid' || detail.key === 'wallDuration' || detail.key === 'avgDuration' || detail.key === 'occurrences') { + this.source.sort(compare(detail.key, detail.sort, 'number')) + } else { + this.source.sort(compare(detail.key, detail.sort, 'string')) + } + this.tbl!.dataSource = this.source; + } + +} \ No newline at end of file diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneCpuByThread.ts b/host/ide/src/trace/component/trace/sheet/TabPaneCpuByThread.ts new file mode 100644 index 0000000..69192dd --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneCpuByThread.ts @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import {SelectionData, SelectionParam} from "../../../bean/BoxSelection.js"; +import {getTabCpuByThread} from "../../../database/SqlLite.js"; + +@element('tabpane-cpu-thread') +export class TabPaneCpuByThread extends BaseElement { + private tbl: LitTable | null | undefined; + private range: HTMLLabelElement | null | undefined; + private source: Array = [] + + set data(val: SelectionParam | any) { + this.range!.textContent = "Selected range: " + parseFloat(((val.rightNs - val.leftNs) / 1000000.0).toFixed(5)) + " ms" + getTabCpuByThread(val.cpus, val.leftNs, val.rightNs).then((result) => { + if (result != null && result.length > 0) { + let sumWall = 0.0; + let sumOcc = 0; + for (let e of result) { + e.process = e.process == null || e.process.length == 0 ? "[NULL]" : e.process + e.thread = e.thread == null || e.thread.length == 0 ? "[NULL]" : e.thread + sumWall += e.wallDuration + sumOcc += e.occurrences + e.wallDuration = parseFloat((e.wallDuration / 1000000.0).toFixed(5)); + e.avgDuration = parseFloat((parseFloat(e.avgDuration) / 1000000.0).toFixed(5)).toString(); + } + let count = new SelectionData() + count.process = " " + count.wallDuration = parseFloat((sumWall / 1000000.0).toFixed(7)); + count.occurrences = sumOcc; + result.splice(0, 0, count) + this.source = result + this.tbl!.dataSource = result + } else { + this.source = []; + this.tbl!.dataSource = this.source + } + }) + } + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#tb-cpu-thread'); + this.range = this.shadowRoot?.querySelector('#time-range'); + this.tbl!.addEventListener('column-click', (evt) => { + // @ts-ignore + this.sortByColumn(evt.detail) + }); + + } + + initHtml(): string { + return ` + + + + + + + + + + + + `; + } + + sortByColumn(detail: any) { + // @ts-ignore + function compare(property, sort, type) { + return function (a: SelectionData, b: SelectionData) { + if (a.process == " " || b.process == " ") { + return 0; + } + if (type === 'number') { + // @ts-ignore + return sort === 2 ? parseFloat(b[property]) - parseFloat(a[property]) : parseFloat(a[property]) - parseFloat(b[property]); + } else { + // @ts-ignore + if (b[property] > a[property]) { + return sort === 2 ? 1 : -1; + } else { // @ts-ignore + if (b[property] == a[property]) { + return 0; + } else { + return sort === 2 ? -1 : 1; + } + } + } + } + } + + if (detail.key === 'pid' || detail.key == "tid" || detail.key === 'wallDuration' || detail.key === 'avgDuration' || detail.key === 'occurrences') { + this.source.sort(compare(detail.key, detail.sort, 'number')) + } else { + this.source.sort(compare(detail.key, detail.sort, 'string')) + } + this.tbl!.dataSource = this.source; + } + +} \ No newline at end of file diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneCpuUsage.ts b/host/ide/src/trace/component/trace/sheet/TabPaneCpuUsage.ts new file mode 100644 index 0000000..e66a633 --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneCpuUsage.ts @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import {SelectionParam} from "../../../bean/BoxSelection.js"; +import {getTabCpuFreq, getTabCpuUsage} from "../../../database/SqlLite.js"; +import {CpuUsage, Freq} from "../../../bean/CpuUsage.js"; + +@element('tabpane-cpu-usage') +export class TabPaneCpuUsage extends BaseElement { + private tbl: LitTable | null | undefined; + private range: HTMLLabelElement | null | undefined; + private orderByOldList: any[] = []; + + set data(val: SelectionParam | any) { + this.range!.textContent = "Selected range: " + parseFloat(((val.rightNs - val.leftNs) / 1000000.0).toFixed(5)) + " ms" + Promise.all([getTabCpuUsage(val.cpus, val.leftNs, val.rightNs), getTabCpuFreq(val.cpus, val.leftNs, val.rightNs)]).then((result) => { + let usages = result[0]; + let freqMap = this.groupByCpuToMap(result[1]) + let data = []; + let range = val.rightNs - val.leftNs; + for (let cpu of val.cpus) { + let usage = new CpuUsage(); + usage.cpu = cpu; + let u = usages.find((e) => e.cpu == cpu); + if (u != undefined && u != null) { + usage.usage = u.usage + } else { + usage.usage = 0; + } + if (usage.usage > 1) { + usage.usage = 1; + } + usage.usageStr = (usage.usage * 100.0).toFixed(2) + "%" + let arr = []; + if (freqMap.has(usage.cpu)) { + let freqList = freqMap.get(usage.cpu) + let list = [] + for (let i = 0; i < freqList!.length; i++) { + let freq = freqList![i]; + if (i == freqList!.length - 1) { + freq.dur = val.rightNs - freq.startNs + } else { + freq.dur = freqList![i + 1].startNs - freq.startNs + } + if (freq.startNs + freq.dur > val.leftNs) { + list.push(freq); + } + } + if (list.length > 0) { + if (list[0].startNs < val.leftNs) { + list[0].dur = list[0].startNs + list[0].dur - val.leftNs + list[0].startNs = val.leftNs; + } + } + arr = this.sortFreq(list); + this.getFreqTop3(usage, arr[0], arr[1], arr[2], range) + } + data.push(usage) + } + this.tbl!.dataSource = data; + this.orderByOldList = [...data] + }) + } + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#tb-cpu-usage'); + this.range = this.shadowRoot?.querySelector('#time-range') + this.tbl?.addEventListener("column-click", event => { + // @ts-ignore + let orderType = event.detail; + if (orderType.sort == 1) {//倒序 注意 sort会改变原数组,需要传入table上的数组 不能传入缓存排序数组 + this.sortTable(this.tbl!.dataSource, orderType.key, false) + } else if (orderType.sort == 2) {//正序 + this.sortTable(this.tbl!.dataSource, orderType.key, true) + } else {//默认排序 + this.tbl!.dataSource = [...this.orderByOldList]; + } + }) + } + + sortTable(arr: any[], key: string, sort: boolean) { + this.tbl!.dataSource = arr.sort((item1, item2) => { + let value1 = Number(item1[key].toString().replace("%", "")); + let value2 = Number(item2[key].toString().replace("%", "")); + if (value1 > value2) { + return sort ? -1 : 1 + } else if (value1 < value2) { + return sort ? 1 : -1 + } else { + return 0 + } + }); + } + + sortFreq(arr: Array): Array> { + let map = new Map(); + for (let freq of arr) { + if (map.has(freq.value)) { + let sumDur = map.get(freq.value)! + freq.dur; + map.set(freq.value, sumDur) + } else { + map.set(freq.value, freq.dur); + } + } + let array = Array.from(map); + array.sort((a, b) => b[1] - a[1]) + return array + } + + getFreqTop3(usage: CpuUsage, top1: Array, top2: Array, top3: Array, range: number) { + // @ts-ignore + usage.top1 = top1 == undefined ? '-' : top1[0] + usage.top1Percent = top1 == undefined ? 0 : top1[1] * 1.0 / range; + usage.top1PercentStr = top1 == undefined ? "-" : (usage.top1Percent * 100).toFixed(2) + "%" + // @ts-ignore + usage.top2 = top2 == undefined ? '-' : top2[0] + usage.top2Percent = top2 == undefined ? 0 : top2[1] * 1.0 / range; + usage.top2PercentStr = top2 == undefined ? "-" : (usage.top2Percent * 100).toFixed(2) + "%" + // @ts-ignore + usage.top3 = top3 == undefined ? '-' : top3[0] + usage.top3Percent = top3 == undefined ? 0 : top3[1] * 1.0 / range; + usage.top3PercentStr = top3 == undefined ? "-" : (usage.top3Percent * 100).toFixed(2) + "%" + } + + groupByCpuToMap(arr: Array): Map> { + let map = new Map>(); + for (let spt of arr) { + if (map.has(spt.cpu)) { + map.get(spt.cpu)!.push(spt); + } else { + let list: Array = []; + list.push(spt); + map.set(spt.cpu, list); + } + } + return map; + } + + initHtml(): string { + return ` + + + + + + + + + + + + + `; + } +} \ No newline at end of file diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts b/host/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts new file mode 100644 index 0000000..6ffdaf1 --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneCurrentSelection.ts @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {CpuStruct} from "../../../bean/CpuStruct.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import "../../../../base-ui/table/lit-table-column.js"; + +import { + queryBinderArgsByArgset, + queryWakeUpThread_WakeThread, + queryWakeUpThread_WakeTime +} from "../../../database/SqlLite.js"; +import {WakeupBean} from "../../../bean/WakeupBean.js"; +import {ThreadStruct} from "../../../bean/ThreadStruct.js"; +import {ProcessMemStruct} from "../../../bean/ProcessMemStruct.js"; +import {FuncStruct} from "../../../bean/FuncStruct.js"; +import {SpApplication} from "../../../SpApplication.js"; + +const STATUS_MAP: any = { + D: "Uninterruptible Sleep", + S: "Sleeping", + R: "Runnable", + "Running": "Running", + "R+": "Runnable (Preempted)", + DK: "Uninterruptible Sleep + Wake Kill", + I: "Task Dead", + T: "Traced", + t: "Traced", + X: "Exit (Dead)", + Z: "Exit (Zombie)", + K: "Wake Kill", + W: "Waking", + P: "Parked", + N: "No Load" +} +const INPUT_WORD = "This is the interval from when the task became eligible to run \n(e.g.because of notifying a wait queue it was a suspended on) to\n when it started running." + +export function getTimeString(ns: number): string { + let currentNs = ns + let hour1 = 3600_000_000_000 + let minute1 = 60_000_000_000 + let second1 = 1_000_000_000; // 1 second + let millisecond1 = 1_000_000; // 1 millisecond + let microsecond1 = 1_000; // 1 microsecond + let res = ""; + if (currentNs >= hour1) { + res += Math.floor(currentNs / hour1) + "h "; + currentNs = currentNs - Math.floor(currentNs / hour1) * hour1 + } + if (currentNs >= minute1) { + res += Math.floor(currentNs / minute1) + "m "; + currentNs = currentNs - Math.floor(ns / minute1) * minute1 + } + if (currentNs >= second1) { + res += Math.floor(currentNs / second1) + "s "; + currentNs = currentNs - Math.floor(currentNs / second1) * second1 + } + if (currentNs >= millisecond1) { + res += Math.floor(currentNs / millisecond1) + "ms "; + currentNs = currentNs - Math.floor(currentNs / millisecond1) * millisecond1 + } + if (currentNs >= microsecond1) { + res += Math.floor(currentNs / microsecond1) + "μs "; + currentNs = currentNs - Math.floor(currentNs / microsecond1) * microsecond1 + } + if (currentNs > 0) { + res += currentNs + "ns "; + } + return res +} + +@element('tabpane-current-selection') +export class TabPaneCurrentSelection extends BaseElement { + weakUpBean: WakeupBean | null | undefined; + private tbl: LitTable | null | undefined; + private tableObserver: MutationObserver | undefined + // @ts-ignore + private dpr: any = window.devicePixelRatio || window.webkitDevicePixelRatio || window.mozDevicePixelRatio || 1; + + set data(value: any) { + this.setCpuData(value) + } + + setCpuData(data: CpuStruct, callback: ((data: WakeupBean | null) => void) | undefined = undefined, scrollCallback?: (data: CpuStruct) => void) { + let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector("#leftTitle"); + if (leftTitle) { + leftTitle.innerText = "Slice Details" + } + let list: any[] = [] + let process = data.processName + let processId = data.processId + if (process == null || process == "") { + process = data.name + processId = data.tid + } + let state = "" + if (data.end_state) { + state = STATUS_MAP[data.end_state] + } else if (data.end_state == "" || data.end_state == null) { + state = "" + } else { + state = "Unknown State" + } + + list.push({name: 'Process', value: `${process || 'Process'} [${processId}]`}) + list.push({ + name: 'Thread', value: `
      +
      ${data.name || 'Process'} [${data.tid}]
      + +
      ` + }) + list.push({name: 'CmdLine', value: `${data.processCmdLine}`}) + list.push({name: 'StartTime', value: getTimeString(data.startTime || 0)}) + list.push({name: 'Duration', value: getTimeString(data.dur || 0)}) + list.push({name: 'Prio', value: data.priority || 0}) + list.push({name: 'End State', value: state}) + this.queryWakeUpData(data).then((bean) => { + if (callback) { + callback(bean) + } + this.tbl!.dataSource = list + let rightArea: HTMLElement | null | undefined = this?.shadowRoot?.querySelector("#right-table"); + let rightTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector("#rightTitle"); + let threadClick = this.tbl?.shadowRoot?.querySelector("#thread-id") + threadClick?.addEventListener("click", () => { + //cpu点击 + if (scrollCallback) { + scrollCallback(data) + } + }) + let canvas = this.initCanvas(); + if (bean != null) { + this.weakUpBean = bean; + if (rightArea != null && rightArea) { + rightArea.style.visibility = "visible" + } + if (rightTitle != null && rightTitle) { + rightTitle.style.visibility = "visible" + } + this.drawRight(canvas, bean) + } else { + this.weakUpBean = null; + if (rightArea != null && rightArea) { + rightArea.style.visibility = "hidden" + } + if (rightTitle != null && rightTitle) { + rightTitle.style.visibility = "hidden" + } + } + }) + } + + setFunctionData(data: FuncStruct) {//方法信息 + this.initCanvas() + let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector("#leftTitle"); + let rightTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector("#rightTitle"); + if (rightTitle) { + rightTitle.style.visibility = "hidden" + } + if (leftTitle) { + leftTitle.innerText = "Slice Details" + } + let list: any[] = [] + list.push({name: 'Name', value: data.funName}) + // list.push({name: 'Category', value:data.category}) 暂无参数 + list.push({name: 'StartTime', value: getTimeString(data.startTs || 0)}) + list.push({name: 'Duration', value: getTimeString(data.dur || 0)}) + if (FuncStruct.isBinder(data)) { + if (data.argsetid != undefined) { + queryBinderArgsByArgset(data.argsetid).then((argset) => { + argset.forEach((item) => { + list.push({name: item.keyName, value: item.strValue}) + }) + + }); + } + list.push({name: 'depth', value: data.depth}) + list.push({name: 'arg_set_id', value: data.argsetid}) + } + this.tbl!.dataSource = list + + } + + setMemData(data: ProcessMemStruct) {//时钟信息 + this.initCanvas() + let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector("#leftTitle"); + if (leftTitle) { + leftTitle.innerText = "Counter Details" + } + let list: any[] = [] + list.push({name: 'Start time', value: getTimeString(data.startTime || 0)}) + list.push({name: 'Value', value: data.value}) + list.push({name: 'Delta', value: data.delta}) + list.push({name: 'Duration', value: getTimeString(data.duration || 0)}) + this.tbl!.dataSource = list + + } + + setThreadData(data: ThreadStruct, scrollCallback: ((d: any) => void) | undefined) {//线程信息 + this.initCanvas() + let leftTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector("#leftTitle"); + let rightTitle: HTMLElement | null | undefined = this?.shadowRoot?.querySelector("#rightTitle"); + if (rightTitle) { + rightTitle.style.visibility = "hidden" + } + if (leftTitle) { + leftTitle.innerText = "Counter Details" + } + let list: any[] = [] + list.push({name: 'StartTime', value: getTimeString(data.startTime || 0)}) + list.push({name: 'Duration', value: getTimeString(data.dur || 0)}) + let state = "" + if (data.state) { + state = STATUS_MAP[data.state] + } else if (data.state == "" || data.state == null) { + state = "" + } else { + state = "Unknown State" + } + if ("Running" == state) { + state = state + " on CPU " + data.cpu; + } + if (data.cpu == null || data.cpu == undefined) { + list.push({name: 'State', value: `${state}`}) + } else { + list.push({ + name: 'State', value: `
      +
      ${state}
      + +
      ` + }) + } + let processName = data.processName; + if (processName == null || processName == "" || processName.toLowerCase() == "null") { + processName = data.name; + } + list.push({name: 'Process', value: processName + " [" + data.pid + "] "}) + this.tbl!.dataSource = list + this.tbl?.shadowRoot?.querySelector("#state-click")?.addEventListener("click", () => { + //线程点击 + if (scrollCallback) { + scrollCallback(data) + } + }) + } + + async queryWakeUpData(data: CpuStruct) { + let wb: WakeupBean | null = null + if (data.id == undefined || data.startTime == undefined) { + return null + } + let wakeupTimes = await queryWakeUpThread_WakeTime(data.id, data.startTime)// 3,4835380000 + if (wakeupTimes != undefined && wakeupTimes.length > 0) { + let wakeupTime = wakeupTimes[0] + if (wakeupTime.wakeTs != undefined && wakeupTime.preRow != undefined && wakeupTime.wakeTs < wakeupTime.preRow) { + return null + } + if (wakeupTime.wakeTs == undefined) { + return null + } + let wakeupBeans = await queryWakeUpThread_WakeThread(wakeupTime.wakeTs) + if (wakeupBeans != undefined && wakeupBeans.length > 0) { + wb = wakeupBeans[0] + if (wb != null) { + if (wakeupTime.wakeTs != undefined && wakeupTime.startTs != undefined) { + wb.wakeupTime = wakeupTime.wakeTs - wakeupTime.startTs + } + wb.schedulingLatency = (data.startTime || 0) - (wb.wakeupTime || 0) + if (wb.process == null) { + wb.process = wb.thread; + } + if (wb.pid == undefined) { + wb.pid = wb.tid; + } + wb.schedulingDesc = INPUT_WORD + } + } + } + return wb + } + + initCanvas(): HTMLCanvasElement | null { + let canvas = this.shadowRoot!.querySelector("#rightDraw") + let width = getComputedStyle(this.tbl!).getPropertyValue("width") + let height = getComputedStyle(this.tbl!).getPropertyValue("height") + if (canvas != null) { + canvas.width = Math.round(Number(width.replace("px", "")) * this.dpr) + canvas.height = Math.round(Number(height.replace("px", "")) * this.dpr) + canvas.style.width = width + canvas.style.height = height + canvas.getContext("2d")!.scale(this.dpr, this.dpr) + } + SpApplication.skinChange = (val: boolean) => { + this.drawRight(canvas, this.weakUpBean!) + } + return canvas + } + + drawRight(cavs: HTMLCanvasElement | null, wakeupBean: WakeupBean | null) { + if (cavs == null) { + return + } + let context = cavs.getContext("2d"); + if (context != null) { + //绘制竖线 + if (document.querySelector("sp-application")!.dark) { + context.strokeStyle = "#ffffff"; + context.fillStyle = "#ffffff"; + } else { + context.strokeStyle = "#000000"; + context.fillStyle = "#000000"; + } + context.lineWidth = 2; + context.moveTo(10, 15); + context.lineTo(10, 125); + context.stroke(); + //绘制菱形 + context.lineWidth = 1; + context.beginPath() + context.moveTo(10, 30); + context.lineTo(4, 40); + context.lineTo(10, 50); + context.lineTo(16, 40); + context.lineTo(10, 30); + context.closePath() + context.fill() + context.font = 12 + "px sans-serif"; + //绘制wake up 文字 + let strList = [] + strList.push("wakeup @ " + getTimeString(wakeupBean?.wakeupTime || 0) + " on CPU " + wakeupBean?.cpu + " by") + strList.push("P:" + wakeupBean?.process + " [ " + wakeupBean?.pid + " ]") + strList.push("F:" + wakeupBean?.thread + " [ " + wakeupBean?.tid + " ]") + strList.forEach((str, index) => { + if (context != null) { + context.fillText(str, 40, 40 + 16 * index) + } + }) + //绘制左右箭头 + context.lineWidth = 2; + context.lineJoin = "bevel" + context.moveTo(10, 95) + context.lineTo(20, 90) + context.moveTo(10, 95) + context.lineTo(20, 100) + context.moveTo(10, 95) + context.lineTo(80, 95) + context.lineTo(70, 90) + context.moveTo(80, 95) + context.lineTo(70, 100) + context.stroke(); + //绘制latency + context.font = 12 + "px sans-serif"; + context.fillText("Scheduling latency:" + getTimeString(wakeupBean?.schedulingLatency || 0) + , 90, 100) + //绘制最下方提示语句 + context.font = 10 + "px sans-serif"; + INPUT_WORD.split("\n").forEach((str, index) => { + context?.fillText(str, 90, 120 + 12 * index) + }) + + } + } + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#selectionTbl'); + this.tbl?.addEventListener("column-click", (ev: any) => { + }) + this.addTableObserver() + } + + addTableObserver() { + let MutationObserver = window.MutationObserver + this.tableObserver = new MutationObserver((list) => { + if (this.tbl) { + let width = getComputedStyle(this.tbl).getPropertyValue("width") + let height = getComputedStyle(this.tbl).getPropertyValue("height") + } + }) + let selector = this.shadowRoot?.querySelector(".left-table"); + this.tableObserver?.observe(selector!, {attributes: true, attributeFilter: ['style'], attributeOldValue: true}) + } + + initHtml(): string { + return ` + +
      +
      +

      +

      Scheduling Latency

      +
      +
      +
      + + + + + + + + +
      +
      + +
      +
      +
      + `; + } + +} diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneFilter.ts b/host/ide/src/trace/component/trace/sheet/TabPaneFilter.ts new file mode 100644 index 0000000..4ce88d3 --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneFilter.ts @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import "../../../../base-ui/select/LitSelect.js"; +import "../../../../base-ui/select/LitSelectOption.js"; +import '../../../../base-ui/icon/LitIcon.js' +import {LitIcon} from "../../../../base-ui/icon/LitIcon.js"; + +export interface FilterData{ + inputValue:string, + firstSelect:string|null|undefined, + secondSelect:string|null|undefined, + mark:boolean|null|undefined, + icon:string|null, +} + +@element('tab-pane-filter') +export class TabPaneFilter extends BaseElement { + private filterInputEL: HTMLInputElement | null | undefined; + private firstSelectEL: HTMLSelectElement | null | undefined; + private secondSelectEL: HTMLSelectElement | null | undefined; + private markButtonEL: HTMLButtonElement | null | undefined; + private iconEL: LitIcon | null | undefined; + private getFilter: ((e:FilterData)=>void) | undefined; + + initElements(): void { + // this.firstSelectEL = this.shadowRoot?.querySelector("#first-select") + // this.secondSelectEL = this.shadowRoot?.querySelector("#second-select") + this.filterInputEL = this.shadowRoot?.querySelector("#filter-input") + this.markButtonEL = this.shadowRoot?.querySelector("#mark") + this.iconEL = this.shadowRoot?.querySelector("#icon") + + this.iconEL!.onclick=(e)=>{ + if (this.iconEL!.name == "statistics") { + this.iconEL!.name = "menu"; + this.iconEL!.size = 18; + if (this.getFilter) { + this.getFilter({ + inputValue:this.filterInputEL!.value, + firstSelect:this.firstSelectEL?.value, + secondSelect:this.secondSelectEL?.value, + mark:false, + icon:this.icon + }) + } + }else if (this.iconEL!.name == "menu") { + this.iconEL!.name = "statistics"; + this.iconEL!.size = 16; + if (this.getFilter) { + this.getFilter({ + inputValue:this.filterInputEL!.value, + firstSelect:this.firstSelectEL?.value, + secondSelect:this.secondSelectEL?.value, + mark:false, + icon:this.icon + }) + } + } + } + + this.markButtonEL!.onclick=(e)=>{ + if (this.getFilter) { + this.getFilter({ + inputValue:this.filterInputEL!.value, + firstSelect:this.firstSelectEL?.value, + secondSelect:this.secondSelectEL?.value, + mark:true, + icon:this.icon + }) + } + } + + this.filterInputEL?.addEventListener("keydown", (event:any) => { + if (event.keyCode == 13) { + this.iconEL!.name="menu" + if (this.getFilter) { + this.getFilter({ + inputValue:event.target.value, + firstSelect:this.firstSelectEL?.value, + secondSelect:this.secondSelectEL?.value, + mark:false, + icon:this.icon + }) + } + } + }); + + // this.firstSelectEL!.onchange = (e)=>{ + // if (this.getFilter) { + // this.getFilter({ + // inputValue:this.filterInputEL!.value, + // firstSelect:this.firstSelectEL?.value, + // secondSelect:this.secondSelectEL?.value, + // mark:false + // }) + // } + // } + // this.secondSelectEL!.onchange = (e)=>{ + // if (this.getFilter) { + // this.getFilter({ + // inputValue:this.filterInputEL!.value, + // firstSelect:this.firstSelectEL?.value, + // secondSelect:this.secondSelectEL?.value, + // mark:false + // }) + // } + // } + this.setSelectList() + } + + set firstSelect(value:string){ + this.firstSelectEL!.value = value; + } + + get firstSelect(){ + return this.firstSelectEL?.value||"" + } + + set secondSelect(value:string){ + this.secondSelectEL!.value = value; + } + + get secondSelect(){ + return this.secondSelectEL?.value||"" + } + + set filterValue(value:string){ + this.filterInputEL!.value = value; + } + get filterValue(){ + return this.filterInputEL!.value + } + + get inputPlaceholder(){ + return this.getAttribute("inputPlaceholder") || "Detail Filter"; + } + + get icon(){ + if (this.getAttribute("icon") != "false") { + if (this.iconEL!.name == "statistics") { + return "tree" + }else if (this.iconEL!.name == "menu") { + return "block" + }else { + return "" + } + } else { + return ""; + } + } + + set icon(value:string){ + if (value == "block") { + this.iconEL!.name = "menu"; + this.iconEL!.size = 18; + }else if (value == "tree") { + this.iconEL!.name = "statistics"; + this.iconEL!.size = 16; + } + } + + + getFilterData(getFilter:(v:FilterData)=>void){ + this.getFilter = getFilter + } + + setSelectList(firstList :Array|null|undefined = [ "All Allocations" ,"Created & Existing" ,"Created & Destroyed" ], + secondList :Array|null|undefined = ["All Heap & Anonymous VM", "All Heap", "All Anonymous VM"]){ + if (!firstList && !secondList) return; + let sLE = this.shadowRoot?.querySelector("#load") + let html = ``; + if (firstList) { + html += ` + Allocation Lifespan` + firstList!.forEach((a,b)=>{ + html+=`${a}` + }) + html+=`` + } + if (secondList) { + html+=` + Allocation Type` + secondList!.forEach((a,b)=>{ + html+=`${a}` + }) + html+=`` + } + if (!firstList) { + this.secondSelectEL!.outerHTML = html; + } else if (!secondList) { + this.firstSelectEL!.outerHTML = html; + }else { + sLE!.innerHTML=html; + } + + this.firstSelectEL = this.shadowRoot?.querySelector("#first-select") + this.secondSelectEL = this.shadowRoot?.querySelector("#second-select") + + this.firstSelectEL!.onchange = (e)=>{ + if (this.getFilter) { + this.getFilter({ + inputValue:this.filterInputEL!.value, + firstSelect:this.firstSelectEL?.value, + secondSelect:this.secondSelectEL?.value, + mark:false, + icon:this.icon + }) + } + } + this.secondSelectEL!.onchange = (e)=>{ + if (this.getFilter) { + this.getFilter({ + inputValue:this.filterInputEL!.value, + firstSelect:this.firstSelectEL?.value, + secondSelect:this.secondSelectEL?.value, + mark:false, + icon:this.icon + }) + } + } + + } + + initHtml(): string { + return ` + + +Input Filter + + +
      + +
      + + + + + + + + + + + + + Call Tree + Call Tree Constraints + Data Mining + `; + } +} diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneFps.ts b/host/ide/src/trace/component/trace/sheet/TabPaneFps.ts new file mode 100644 index 0000000..c98bab2 --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneFps.ts @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import {SelectionParam} from "../../../bean/BoxSelection.js"; +import {getTabFps} from "../../../database/SqlLite.js"; +import {Utils} from "../base/Utils.js"; + +@element('tabpane-fps') +export class TabPaneFps extends BaseElement { + private tbl: LitTable | null | undefined; + private range: HTMLLabelElement | null | undefined; + + set data(val: SelectionParam | any) { + this.range!.textContent = "Selected range: " + parseFloat(((val.rightNs - val.leftNs) / 1000000.0).toFixed(5)) + " ms" + getTabFps(val.leftNs, val.rightNs).then((result) => { + if (result != null && result.length > 0) { + let index = result.findIndex((d) => d.startNS >= val.leftNs); + if (index != -1) { + let arr = result.splice(index > 0 ? index - 1 : index) + arr.map(e => e.timeStr = Utils.getTimeString(e.startNS)) + this.tbl!.dataSource = arr + } else { + let last = result[result.length - 1] + last.timeStr = Utils.getTimeString(last.startNS) + this.tbl!.dataSource = [last] + } + } else { + this.tbl!.dataSource = [] + } + }); + } + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#tb-fps'); + this.range = this.shadowRoot?.querySelector('#time-range') + } + + initHtml(): string { + return ` + + + + + + + `; + } + +} \ No newline at end of file diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneHeap.ts b/host/ide/src/trace/component/trace/sheet/TabPaneHeap.ts new file mode 100644 index 0000000..ae9da25 --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneHeap.ts @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import "../../../../base-ui/table/lit-table-column.js"; +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import {SelectionParam} from "../../../bean/BoxSelection.js"; +import {queryHeapAllData, queryHeapAllTable, queryHeapTable, queryHeapTreeTable} from "../../../database/SqlLite.js"; +import {Utils} from "../base/Utils.js"; +import {HeapBean} from "../../../bean/HeapBean.js"; +import {HeapTreeDataBean} from "../../../bean/HeapTreeDataBean.js"; +import {SpSystemTrace} from "../../SpSystemTrace.js"; + +@element('tabpane-heap') +export class TabPaneHeap extends BaseElement { + private tbl: LitTable | null | undefined; + private range: HTMLLabelElement | null | undefined; + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#tb-heap'); + this.range = this.shadowRoot?.querySelector('#time-range') + new ResizeObserver((entries) => { + if (this.parentElement?.clientHeight != 0) { + // @ts-ignore + this.tbl?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight - 20)+"px" + this.tbl?.reMeauseHeight() + } + }).observe(this.parentElement!) + } + + + + set data(val: SelectionParam|any) { + // @ts-ignore + this.tbl?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight - 20)+"px" + queryHeapAllData(val.leftNs,val.rightNs,val.heapIds).then((allHeap)=>{ + if(allHeap.length>0){ + let groups:any = {}; + let treeGroup:any = {} + let treeData:HeapBean[] = [] + allHeap.forEach((heapData)=>{ + groups[heapData.eventId] = heapData + }) + SpSystemTrace.HEAP_FRAME_DATA.map((frame)=>{ + if(groups[frame.eventId]){ + treeGroup[frame.eventId] = treeGroup[frame.eventId]||[] + frame.heapSize = groups[frame.eventId].heapSize + frame.startTs = groups[frame.eventId].startTs + frame.endTs = groups[frame.eventId].endTs + frame.eventType = groups[frame.eventId].eventType + treeGroup[frame.eventId].push(frame) + } + }) + Object.keys(treeGroup).forEach((key)=>{ + if (treeGroup[key].length > 0) { + if(treeData.length>0){ + this.merageTree(0,treeData,treeGroup[key],val) + }else { + let currentData = new HeapBean() + let firstData = treeGroup[key][0] + currentData.AllocationFunction = firstData.AllocationFunction + currentData.depth = firstData.depth + currentData.MoudleName = firstData.MoudleName + treeData.push(currentData) + this.merageTree(0,treeData,treeGroup[key],val) + } + } + }) + this.setTreeDataSize(treeData) + this.tbl!.recycleDataSource = treeData + }else { + this.tbl!.recycleDataSource = [] + } + + }) + } + + setTreeDataSize(list:HeapBean[]){ + list.forEach((item)=>{ + item.AllocationSize = Utils.getByteWithUnit(Number(item.AllocationSize)) + item.DeAllocationSize = Utils.getByteWithUnit(Number(item.DeAllocationSize)) + item.RemainingSize = Utils.getByteWithUnit(Number(item.RemainingSize)) + if(item.children.length>0){ + this.setTreeDataSize(item.children) + } + }) + } + + merageTree(depth: number,beanList:HeapBean[],list:HeapTreeDataBean[],selection: SelectionParam|any){ + if(beanList.length>0){ + if(depth < list.length){ + let treeData = list[depth] + let currentData = beanList.find((item)=>{ + return treeData.MoudleName == item.MoudleName && treeData.AllocationFunction == item.AllocationFunction + }) + if(currentData!=undefined){ + (currentData.Allocations as number) += selection.leftNstreeData.endTs?1:0; + (currentData.AllocationSize as number) += selection.leftNstreeData.endTs?treeData.heapSize:0; + ( currentData.Total as number) = (currentData.Allocations as number) - (currentData.Deallocations as number); + currentData.RemainingSize = (currentData.AllocationSize as number) - (currentData.DeAllocationSize as number) + }else { + currentData = new HeapBean() + currentData.AllocationFunction = treeData.AllocationFunction + currentData.depth = treeData.depth + currentData.MoudleName = (treeData.MoudleName as string); + (currentData.Allocations as number) += selection.leftNstreeData.endTs?1:0; + (currentData.AllocationSize as number) += selection.leftNstreeData.endTs?treeData.heapSize:0; + currentData.Total = (currentData.Allocations as number) - (currentData.Deallocations as number); + currentData.RemainingSize = (currentData.AllocationSize as number) - (currentData.DeAllocationSize as number); + beanList.push(currentData) + } + if(depth+1 +:host{ + display: flex; + flex-direction: column; + padding: 10px 10px; +} + + + + + + + + + + + + + + + + + + + + + `; + } + +} \ No newline at end of file diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneNMCallInfo.ts b/host/ide/src/trace/component/trace/sheet/TabPaneNMCallInfo.ts new file mode 100644 index 0000000..0ad18bf --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneNMCallInfo.ts @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import {SelectionParam} from "../../../bean/BoxSelection.js"; +import {queryNativeHookEventTid} from "../../../database/SqlLite.js"; +import {NativeHookCallInfo, NativeHookStatistics} from "../../../bean/NativeHook.js"; +import {SpSystemTrace} from "../../SpSystemTrace.js"; +import {Utils} from "../base/Utils.js"; +import "./TabPaneFilter.js" +import {FilterData, TabPaneFilter} from "./TabPaneFilter"; + +@element('tabpane-native-callinfo') +export class TabPaneNMCallInfo extends BaseElement { + private tbl: LitTable | null | undefined; + private tblData: LitTable | null | undefined; + private source: Array = [] + private queryResult: Array = [] + private native_type:Array = ["All Heap & Anonymous VM","All Heap","All Anonymous VM"]; + private filterAllocationType:string = "0" + private filterNativeType:string = "0" + private currentSelection:SelectionParam|undefined + set data(val: SelectionParam | any) { + if(val!=this.currentSelection){ + this.currentSelection = val + this.initFilterTypes() + } + let types:Array = [] + if(val.nativeMemory.indexOf(this.native_type[0]) != -1){ + types.push("'AllocEvent'"); + types.push("'MmapEvent'"); + }else{ + if(val.nativeMemory.indexOf(this.native_type[1]) != -1){ + types.push("'AllocEvent'"); + } + if(val.nativeMemory.indexOf(this.native_type[2]) != -1){ + types.push("'MmapEvent'"); + } + } + // @ts-ignore + this.tbl?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight - 20 - 31) + "px" + // @ts-ignore + this.tblData?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight) + "px" + // @ts-ignore + this.tblData?.recycleDataSource = []; + // @ts-ignore + this.tbl?.recycleDataSource = []; + queryNativeHookEventTid(val.leftNs,val.rightNs,types).then((result)=>{ + if(result.length > 0){ + this.queryResult = result + this.source = this.handleQueryResult(result); + }else{ + this.source = []; + } + this.filterQueryData() + }) + } + + handleQueryResult(result:Array):Array{ + let resultMap = new Map(); + result.map((r)=>{ + resultMap.set(r.eventId,r); + }) + let map = new Map(); + SpSystemTrace.HEAP_FRAME_DATA.map((frame) => { + let frameEventId = parseInt(frame.eventId); + if(frameEventId >= result[0].eventId && frameEventId <= result[result.length - 1].eventId){ + if(resultMap.has(frameEventId)){ + let hook = resultMap.get(frameEventId); + if(hook != undefined){ + let target = new NativeHookCallInfo(); + target.id = frame.eventId + "_" + frame.depth; + target.eventId = frameEventId; + target.depth = frame.depth; + target.count = 1; + target.heapSize = hook.heapSize; + target.threadId = hook.tid; + target.heapSizeStr = Utils.getByteWithUnit(target.heapSize); + let sym_arr = frame.AllocationFunction?.split("/"); + let lib_arr = frame.MoudleName?.split("/"); + target.symbol = sym_arr![sym_arr!.length - 1]; + target.library = lib_arr![lib_arr!.length - 1]; + target.title = `[ ${target.symbol} ] ${target.library}`; + target.type = (target.library.endsWith(".so.1") || target.library.endsWith(".dll") || target.library.endsWith(".so")) ? 0 : 1; + if(map.has(frame.eventId)){ + let src = map.get(frame.eventId); + this.listToTree(target,src!); + }else{ + map.set(frame.eventId,target); + } + } + } + } + if(frameEventId > result[result.length -1].eventId){ + return false; + } + }); + let groupMap = new Map>(); + for (let value of map.values()) { + let key = value.threadId+ "_" + value.symbol; + if(groupMap.has(key)){ + groupMap.get(key)!.push(value); + }else{ + let arr:Array = []; + arr.push(value); + groupMap.set(key,arr); + } + } + let data:Array = []; + for (let arr of groupMap.values()) { + if(arr.length > 1){ + for (let i = 1; i < arr.length; i++) { + if(arr[i].children.length > 0){ + this.mergeTree(arr[i].children[0],arr[0]); + }else{ + arr[0].heapSize += arr[i].heapSize; + arr[0].heapSizeStr = Utils.getByteWithUnit(arr[0].heapSize); + } + } + } + arr[0].count = arr.length; + data.push(arr[0]); + } + return this.groupByWithTid(data) + } + + groupByWithTid(data:Array):Array{ + let tidMap = new Map(); + for (let call of data) { + call.pid = "tid_"+call.threadId; + if(tidMap.has(call.threadId)){ + let tidCall = tidMap.get(call.threadId); + tidCall!.heapSize += call.heapSize; + tidCall!.heapSizeStr = Utils.getByteWithUnit(tidCall!.heapSize); + tidCall!.count += call.count; + tidCall!.children.push(call); + }else{ + let tidCall = new NativeHookCallInfo(); + tidCall.id = "tid_" + call.threadId; + tidCall.count = call.count; + tidCall.heapSize = call.heapSize; + tidCall.heapSizeStr = Utils.getByteWithUnit(call.heapSize); + tidCall.title = "Thread " + call.threadId; + tidCall.type = -1; + tidCall.children.push(call); + tidMap.set(call.threadId,tidCall); + } + } + return Array.from(tidMap.values()); + } + + listToTree(target:NativeHookCallInfo,src:NativeHookCallInfo){ + if(target.depth == src.depth + 1){ + target.pid = src.id; + src.children.push(target) + }else{ + if(src.children.length > 0){ + this.listToTree(target,src.children[0]); + } + } + } + + mergeTree(target:NativeHookCallInfo,src:NativeHookCallInfo){ + let len = src.children.length; + if(len == 0){ + target.pid = src.id; + src.heapSize += target.heapSize; + src.heapSizeStr = Utils.getByteWithUnit(src.heapSize); + src.children.push(target); + }else{ + let index = src.children.findIndex((hook) => hook.symbol == target.symbol && hook.depth == target.depth); + src.heapSize += target.heapSize; + src.heapSizeStr = Utils.getByteWithUnit(src.heapSize); + if(index != -1){ + let srcChild = src.children[index]; + srcChild.count += 1; + if(target.children.length > 0){ + this.mergeTree(target.children[0],srcChild) + }else{ + srcChild.heapSize += target.heapSize; + srcChild.heapSizeStr = Utils.getByteWithUnit(srcChild.heapSize) + } + }else{ + target.pid = src.id; + src.children.push(target) + } + } + } + + setRightTableData(hook:NativeHookCallInfo){ + let arr:Array = []; + let maxEventId = hook.eventId; + let maxHeap = 0; + function findMaxStack(hook:NativeHookCallInfo){ + if(hook.children.length == 0){ + if(hook.heapSize > maxHeap){ + maxHeap = hook.heapSize; + maxEventId = hook.eventId; + } + }else{ + hook.children.map((hookChild)=>{ + findMaxStack(hookChild); + }) + } + } + findMaxStack(hook); + SpSystemTrace.HEAP_FRAME_DATA.map((frame) => { + let eventId = parseInt(frame.eventId); + if(eventId == maxEventId){ + let target = new NativeHookCallInfo(); + target.eventId = eventId; + target.depth = frame.depth; + let sym_arr = frame.AllocationFunction?.split("/"); + let lib_arr = frame.MoudleName?.split("/"); + target.symbol = sym_arr![sym_arr!.length - 1]; + target.library = lib_arr![lib_arr!.length - 1]; + target.title = `[ ${target.symbol} ] ${target.library}`; + target.type = (target.library.endsWith(".so.1") || target.library.endsWith(".dll") || target.library.endsWith(".so")) ? 0 : 1; + arr.push(target); + } + if(eventId > maxEventId){ + return false; + } + }); + // @ts-ignore + this.tblData?.recycleDataSource = arr; + } + + initFilterTypes(){ + let filter = this.shadowRoot?.querySelector("#filter") + this.queryResult = [] + filter!.firstSelect = "0" + filter!.secondSelect = "0" + this.filterAllocationType = "0" + this.filterNativeType = "0" + } + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#tb-native-callinfo'); + this.tblData = this.shadowRoot?.querySelector('#tb-native-data'); + this.tbl!.addEventListener("row-click", (e) => { + // @ts-ignore + let data = (e.detail as NativeHookCallInfo) + this.setRightTableData(data); + }) + new ResizeObserver((entries) => { + if (this.parentElement?.clientHeight != 0) { + // @ts-ignore + this.tbl?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight)-10-31+"px"; + this.tbl?.reMeauseHeight() + // @ts-ignore + this.tblData?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight) -10+"px" + this.tblData?.reMeauseHeight() + } + }).observe(this.parentElement!); + this.shadowRoot?.querySelector("#filter")!.getFilterData((data:FilterData)=>{ + this.filterAllocationType = data.firstSelect||"0" + this.filterNativeType = data.secondSelect||"0" + this.filterQueryData() + }) + this.initFilterTypes() + } + + filterQueryData(){ + if (this.queryResult.length > 0&&this.currentSelection) { + let filter = this.queryResult.filter((item)=>{ + let filterAllocation = true + let filterNative = true + if(this.filterAllocationType=="1"){ + filterAllocation = item.startTs>=this.currentSelection!.leftNs&&item.startTs<=this.currentSelection!.rightNs&&item.endTs>this.currentSelection!.rightNs + }else if(this.filterAllocationType=="2"){ + filterAllocation = item.startTs>=this.currentSelection!.leftNs&&item.startTs<=this.currentSelection!.rightNs&&item.endTs<=this.currentSelection!.rightNs + } + if(this.filterNativeType=="1"){ + filterNative = item.eventType == "AllocEvent" + }else if(this.filterNativeType=="2"){ + filterNative = item.eventType == "MmapEvent" + } + return filterAllocation&&filterNative + }) + if(filter.length>0){ + this.source = this.handleQueryResult(filter); + this.tbl!.recycleDataSource = this.source; + }else { + this.source = [] + this.tbl!.recycleDataSource = []; + } + } + } + + initHtml(): string { + return ` + +
      +
      + + + + + + + + + +
      +
      + + + + + + +
      +
      + + `; + } +} \ No newline at end of file diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneNMSampleList.ts b/host/ide/src/trace/component/trace/sheet/TabPaneNMSampleList.ts new file mode 100644 index 0000000..df7e7eb --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneNMSampleList.ts @@ -0,0 +1,456 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import "../../../../base-ui/table/lit-table-column.js"; +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import {Counter, SelectionData, SelectionParam} from "../../../bean/BoxSelection.js"; +import { + getTabCounters, queryAllHookData, + queryNativeHookEventId, + queryNativeHookSnapshot, + queryNativeHookSnapshotTypes +} from "../../../database/SqlLite.js"; +import {SpSystemTrace} from "../../SpSystemTrace.js"; +import { + NativeHookCallInfo, + NativeHookSampleQueryInfo, + NativeHookSamplerInfo, + NativeMemory +} from "../../../bean/NativeHook.js"; +import {Utils} from "../base/Utils.js"; +import "./TabPaneFilter.js" +import {FilterData, TabPaneFilter} from "./TabPaneFilter.js"; + +@element('tabpane-native-sample') +export class TabPaneNMSampleList extends BaseElement { + static tblData: LitTable | null | undefined; + static tbl: LitTable | null | undefined; + static filter: any + static filterSelect: string = "0" + static source: Array = []; + static groups:any = undefined; + static types:Array = [] + static native_type:Array = ["All Heap & Anonymous VM","All Heap","All Anonymous VM"]; + static tableMarkData: Array = [] + static selectionParam:SelectionParam|undefined = undefined + static sampleTypes:Array = [] + static sampleTypesList:any[] = [] + set data(val: SelectionParam | any) { + TabPaneNMSampleList.serSelection(val) + this.filterAllList() + } + + static serSelection(val: SelectionParam){ + if(this.selectionParam !== val){ + this.clearData() + this.selectionParam = val + this.initTypes() + } + if(val.nativeMemory.indexOf(this.native_type[0]) != -1){ + this.types.push("'AllocEvent'"); + this.types.push("'MmapEvent'"); + }else{ + if(val.nativeMemory.indexOf(this.native_type[1]) != -1){ + this.types.push("'AllocEvent'"); + } + if(val.nativeMemory.indexOf(this.native_type[2]) != -1){ + this.types.push("'MmapEvent'"); + } + } + } + + static initTypes(){ + queryNativeHookSnapshotTypes().then((result)=>{ + if(result.length>0){ + this.sampleTypes = result + } + }) + } + + + static addSampleData(data:any){ + if(TabPaneNMSampleList.tableMarkData.indexOf(data)!=-1){ + return + } + TabPaneNMSampleList.tableMarkData.push(data) + this.initGroups() + let rootSample = new NativeHookSamplerInfo() + rootSample.snapshot = "Snapshot"+this.numberToWord(this.source.length+1) + rootSample.startTs = data.startTs + rootSample.timestamp = Utils.getTimeString(data.startTs) + rootSample.eventId = data.eventId + this.queryAllHookInfo(data,rootSample) + } + + static querySnapshot(data:any,rootSample:NativeHookSamplerInfo){ + let copyTypes = this.sampleTypes.map((type)=>{ + let copyType = new NativeHookSampleQueryInfo() + copyType.eventType = type.eventType + copyType.subType = type.subType + return copyType + }) + queryNativeHookSnapshot(data.startTs).then((result)=>{ + if(result.length>0){ + let nameGroup:any = {} + copyTypes.forEach((item)=> { + nameGroup[item.eventType] = nameGroup[item.eventType] || [] + nameGroup[item.eventType].push(item) + }) + result.forEach((item)=>{ + if(nameGroup[item.eventType]!=undefined){ + if(item.subType == null){ + nameGroup[item.eventType][0].existing = item.existing + nameGroup[item.eventType][0].growth = item.growth + }else{ + let filter = nameGroup[item.eventType].filter((type:any)=>{ + return type.subType == item.subType + }) + if (filter.length > 0) { + filter[0].existing = item.existing + filter[0].growth = item.growth + } + } + } + }) + if(this.sampleTypesList.length>0){ + let sampleTypesListElement = this.sampleTypesList[this.sampleTypesList.length-1]; + sampleTypesListElement.forEach((item:any,index:number)=>{ + copyTypes[index].current = copyTypes[index].growth + if(index{ + item.current = item.growth + }) + } + this.sampleTypesList.push(copyTypes) + this.createTree(nameGroup,rootSample) + rootSample.tempList = [...rootSample.children] + this.source.push(rootSample) + } + }) + } + + static merageSampleData(leftTime:number,startNs:number,rootSample:NativeHookSampleQueryInfo,merageSample:NativeHookSampleQueryInfo){ + if(merageSample.endTs >= startNs){ + rootSample.growth += merageSample.growth + } + if(merageSample.startTs > leftTime){ + rootSample.existing++; + let childSample = new NativeHookSamplerInfo()//新增最下层的叶子节点 + childSample.snapshot = "0x"+merageSample.addr + childSample.eventId = merageSample.eventId; + childSample.heapSize = merageSample.growth + childSample.growth = Utils.getByteWithUnit(merageSample.growth) + childSample.totalGrowth = childSample.growth + childSample.startTs = merageSample.startTs + childSample.timestamp = Utils.getTimeString(merageSample.startTs); + (childSample as any).existing = "" + rootSample.children.push(childSample) + } + rootSample.total += merageSample.growth + } + + static queryAllHookInfo(data:any,rootSample:NativeHookSamplerInfo){ + let copyTypes = this.sampleTypes.map((type)=>{ + let copyType = new NativeHookSampleQueryInfo() + copyType.eventType = type.eventType + copyType.subType = type.subType + return copyType + }) + queryAllHookData(data.startTs).then((result)=>{ + if(result.length > 0){ + let nameGroup:any = {} + copyTypes.forEach((item)=> { + nameGroup[item.eventType] = nameGroup[item.eventType] || [] + nameGroup[item.eventType].push(item) + }) + let leftTime = TabPaneNMSampleList.tableMarkData.length == 1?0:TabPaneNMSampleList.tableMarkData[TabPaneNMSampleList.tableMarkData.length - 2].startTs + result.forEach((item)=>{ + if(nameGroup[item.eventType]!=undefined){ + if(item.subType == null){ + this.merageSampleData(leftTime,data.startTs,nameGroup[item.eventType][0],item) + }else{ + let filter = nameGroup[item.eventType].filter((type:any)=>{ + return type.subType == item.subType + }) + if (filter.length > 0) { + this.merageSampleData(leftTime,data.startTs,filter[0],item) + } + } + } + }) + if(this.sampleTypesList.length>0){ + let sampleTypesListElement = this.sampleTypesList[this.sampleTypesList.length-1]; + sampleTypesListElement.forEach((item:any,index:number)=>{ + copyTypes[index].current = copyTypes[index].growth + if(index{ + item.current = item.growth + }) + } + this.sampleTypesList.push(copyTypes) + this.createTree(nameGroup,rootSample) + rootSample.tempList = [...rootSample.children] + this.source.push(rootSample) + } + }) + } + + static initGroups(){ + if(this.groups==undefined){ + this.groups = {} + SpSystemTrace.HEAP_FRAME_DATA.map((frame)=>{ + this.groups[frame.eventId] = this.groups[frame.eventId]||[] + this.groups[frame.eventId].push(frame) + }) + } + } + + static createTree(nameGroup:any,rootSample:NativeHookSamplerInfo){ + Object.keys(nameGroup).forEach((key)=>{ + let parentSample = new NativeHookSamplerInfo() + parentSample.snapshot = key + if (nameGroup[key].length > 0) { + nameGroup[key].forEach((child:any)=>{ + let childSample = new NativeHookSamplerInfo() + childSample.snapshot = child.subType||child.eventType + childSample.heapSize = child.growth + childSample.growth = Utils.getByteWithUnit(child.growth) + childSample.total = child.total + childSample.totalGrowth = Utils.getByteWithUnit(child.total) + childSample.existing = child.existing + childSample.currentSize = child.current + childSample.current = Utils.getByteWithUnit(child.current) + parentSample.merageObj(childSample) + if(childSample.snapshot != parentSample.snapshot){//根据名称是否一致来判断是否需要添加子节点 + childSample.children.push(...child.children) + parentSample.children.push(childSample) + }else { + parentSample.children.push(...child.children) + } + }) + } + rootSample.merageObj(parentSample) + rootSample.children.push(parentSample) + }) + } + + static prepChild(currentSample:NativeHookSamplerInfo,rootSample:NativeHookSamplerInfo){ + currentSample.heapSize -= rootSample.heapSize + currentSample.growth = Utils.getByteWithUnit(currentSample.heapSize) + let currentMap:any = {} + currentSample.children.forEach((currentChild)=>{ + currentMap[currentChild.snapshot] = currentChild + }) + rootSample.children.forEach((rootChild)=>{ + if (currentMap[rootChild.snapshot] == undefined) { + let perpSample = new NativeHookSamplerInfo() + perpSample.snapshot =rootChild.snapshot + currentMap[rootChild.snapshot] = perpSample + currentSample.children.push(perpSample) + } + this.prepChild(currentMap[rootChild.snapshot],rootChild) + }) + } + + static clearData(){ + this.types = [] + this.source = [] + this.tblData!.dataSource = [] + this.tbl!.recycleDataSource = [] + this.sampleTypesList = [] + this.tableMarkData = [] + TabPaneNMSampleList.filter!.firstSelect = "0" + } + + static numberToWord(num:number){ + let word = "" + while (num>0){ + let end = num%26 + end = end === 0?(end = 26):end; + word = String.fromCharCode(96 + end)+word + num = ( num - end ) / 26 + } + return word.toUpperCase() + } + + setRightTableData(eventId:number){ + let arr:Array = []; + let frameArr = TabPaneNMSampleList.groups[eventId]; + if(frameArr){ + frameArr.map((frame:any)=>{ + let target = new NativeHookCallInfo(); + target.eventId = parseInt(frame.eventId); + target.depth = frame.depth; + let sym_arr = frame.AllocationFunction?.split("/"); + let lib_arr = frame.MoudleName?.split("/"); + target.symbol = sym_arr![sym_arr!.length - 1]; + target.library = lib_arr![lib_arr!.length - 1]; + target.title = `[ ${target.symbol} ] ${target.library}`; + target.type = (target.library.endsWith(".so.1") || target.library.endsWith(".dll") || target.library.endsWith(".so")) ? 0 : 1; + arr.push(target); + }) + } + // @ts-ignore + TabPaneNMSampleList.tblData?.recycleDataSource = arr; + } + + initElements(): void { + TabPaneNMSampleList.tbl = this.shadowRoot?.querySelector('#tb-native-sample'); + TabPaneNMSampleList.tbl!.addEventListener('row-click', (evt:any) => { + // @ts-ignore + this.setRightTableData(evt.detail.eventId); + }) + TabPaneNMSampleList.tblData = this.shadowRoot?.querySelector('#tb-native-data'); + new ResizeObserver((entries) => { + if (this.parentElement?.clientHeight != 0) { + // @ts-ignore + TabPaneNMSampleList.tbl?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight - 10 - 31)+"px" + TabPaneNMSampleList.tbl?.reMeauseHeight() + // @ts-ignore + TabPaneNMSampleList.tblData?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight - 10)+"px" + TabPaneNMSampleList.tblData?.reMeauseHeight() + } + }).observe(this.parentElement!) + TabPaneNMSampleList.filter = this.shadowRoot?.querySelector("#filter") + this.shadowRoot?.querySelector("#filter")!.setSelectList(TabPaneNMSampleList.native_type,null) + this.shadowRoot?.querySelector("#filter")!.getFilterData((data:FilterData)=>{ + if(data.firstSelect){ + TabPaneNMSampleList.filterSelect = data.firstSelect + this.filterAllList() + } + }) + TabPaneNMSampleList.filter!.firstSelect = TabPaneNMSampleList.filterSelect + + } + + filterAllList(){ + TabPaneNMSampleList.source.forEach((rootSample)=>{ + rootSample.heapSize = 0 + rootSample.existing = 0 + rootSample.total = 0 + if(TabPaneNMSampleList.filterSelect == "0"){ + rootSample.children = [...rootSample.tempList] + rootSample.tempList.forEach((parentSample)=>{ + rootSample.heapSize +=parentSample.heapSize + rootSample.existing +=parentSample.existing + rootSample.total +=parentSample.total + }) + rootSample.growth = Utils.getByteWithUnit(rootSample.heapSize) + rootSample.totalGrowth =Utils.getByteWithUnit(rootSample.total) + }else if(TabPaneNMSampleList.filterSelect == "2"){ + if(rootSample.tempList.length>1){ + rootSample.children = [rootSample.tempList[1]] + rootSample.heapSize +=rootSample.tempList[1].heapSize + rootSample.existing +=rootSample.tempList[1].existing + rootSample.growth = Utils.getByteWithUnit(rootSample.heapSize) + rootSample.total += rootSample.tempList[1].total + rootSample.totalGrowth = Utils.getByteWithUnit(rootSample.total) + }else { + rootSample.children = [] + rootSample.growth = "" + rootSample.totalGrowth = "" + } + }else { + if(rootSample.tempList.length>0){ + rootSample.children = [rootSample.tempList[0]] + rootSample.heapSize +=rootSample.tempList[0].heapSize + rootSample.existing +=rootSample.tempList[0].existing + rootSample.growth = Utils.getByteWithUnit(rootSample.heapSize) + rootSample.total += rootSample.tempList[0].total + rootSample.totalGrowth = Utils.getByteWithUnit(rootSample.total) + }else { + rootSample.children = [] + rootSample.growth = "" + rootSample.totalGrowth = "" + } + } + }) + TabPaneNMSampleList.tbl!.recycleDataSource = TabPaneNMSampleList.source; + } + + initHtml(): string { + return ` + +
      +
      + + + + + + + + +
      +
      + + + + + + +
      +
      + `; + } + + sortByColumn(detail: any) { + // @ts-ignore + function compare(property, sort, type) { + return function (a: SelectionData, b: SelectionData) { + if (a.process == " " || b.process == " ") { + return 0; + } + if (type === 'number') { + // @ts-ignore + return sort === 2 ? parseFloat(b[property]) - parseFloat(a[property]) : parseFloat(a[property]) - parseFloat(b[property]); + } else { + // @ts-ignore + if (b[property] > a[property]) { + return sort === 2 ? 1 : -1; + } else { // @ts-ignore + if (b[property] == a[property]) { + return 0; + } else { + return sort === 2 ? -1 : 1; + } + } + } + } + } + + TabPaneNMSampleList.tbl!.recycleDataSource = TabPaneNMSampleList.source; + } + +} \ No newline at end of file diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneNMStatstics.ts b/host/ide/src/trace/component/trace/sheet/TabPaneNMStatstics.ts new file mode 100644 index 0000000..e022aaf --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneNMStatstics.ts @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import { SelectionParam} from "../../../bean/BoxSelection.js"; +import { + queryNativeHookStatistics, + queryNativeHookStatisticsMalloc, + queryNativeHookStatisticsSubType +} from "../../../database/SqlLite.js"; +import { + NativeHookMalloc, + NativeHookStatistics, + NativeHookStatisticsTableData, + NativeMemory +} from "../../../bean/NativeHook.js"; +import {Utils} from "../base/Utils.js"; +import {SpSystemTrace} from "../../SpSystemTrace.js"; +import "./TabProgressBar.js" + +@element('tabpane-native-statistics') +export class TabPaneNMStatstics extends BaseElement { + private tbl: LitTable | null | undefined; + private source: Array = [] + private native_type:Array = ["All Heap & Anonymous VM","All Heap","All Anonymous VM"]; + private allMax:number = 0; + + set data(val: SelectionParam | any) { + this.allMax = 0; + SpSystemTrace.EVENT_HEAP.map((heap)=>{ + this.allMax += heap.sumHeapSize; + }); + // @ts-ignore + this.tbl?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight - 20) + "px" + // @ts-ignore + this.tbl?.recycleDataSource = []; + Promise.all([queryNativeHookStatistics(val.leftNs,val.rightNs), + queryNativeHookStatisticsSubType(val.leftNs,val.rightNs), + queryNativeHookStatisticsMalloc(val.leftNs,val.rightNs) + ]).then((values)=>{ + let arr:Array = []; + let index1 = val.nativeMemory.indexOf(this.native_type[0]) + let index2 = val.nativeMemory.indexOf(this.native_type[1]) + let index3 = val.nativeMemory.indexOf(this.native_type[2]) + this.setMemoryTypeData(val,values[0],arr); + if(index1 != -1 || index3 != -1){ + this.setSubTypeTableData(values[1],arr); + } + if(index1 != -1 || index2 != -1){ + this.setMallocTableData(values[2],arr); + } + this.tbl!.recycleDataSource = arr; + }) + } + + setMallocTableData(result:Array,arr:Array){ + result.map((malloc)=>{ + let data = new NativeHookStatisticsTableData(); + data.memoryTap = "Malloc " + Utils.getByteWithUnit(malloc.heapSize); + data.existing = malloc.allocByte; + data.allocCount = malloc.allocCount; + data.freeCount = malloc.freeCount; + data.totalBytes = malloc.allocByte + malloc.freeByte; + data.totalCount = malloc.allocCount + malloc.freeCount; + data.max = malloc.heapSize; + data.existingString = Utils.getByteWithUnit(data.existing); + data.totalBytesString = Utils.getByteWithUnit(data.totalBytes); + data.maxStr = Utils.getByteWithUnit(malloc.heapSize); + data.existingValue = [data.existing,data.totalBytes,this.allMax]; + arr.push(data); + }) + } + + setSubTypeTableData(result:Array,arr:Array){ + result.map((sub)=>{ + let data = new NativeHookStatisticsTableData(); + data.memoryTap = sub.subType + data.existing = sub.allocByte + data.allocCount = sub.allocCount; + data.freeCount = sub.freeCount; + data.totalBytes = sub.allocByte + sub.freeByte; + data.totalCount = sub.allocCount + sub.freeCount; + data.max = sub.heapSize; + data.existingString = Utils.getByteWithUnit(data.existing); + data.totalBytesString = Utils.getByteWithUnit(data.totalBytes); + data.maxStr = Utils.getByteWithUnit(sub.heapSize); + data.existingValue = [data.existing,data.totalBytes,this.allMax]; + arr.push(data); + }) + } + + setMemoryTypeData(val:SelectionParam,result:Array,arr:Array){ + let all:NativeHookStatisticsTableData | null = null + let heap:NativeHookStatisticsTableData | null = null + let anonymous:NativeHookStatisticsTableData | null = null + if(val.nativeMemory.indexOf(this.native_type[0]) != -1){ + all = new NativeHookStatisticsTableData(); + all.memoryTap = this.native_type[0]; + } + if(val.nativeMemory.indexOf(this.native_type[1]) != -1){ + heap = new NativeHookStatisticsTableData(); + heap.memoryTap = this.native_type[1]; + } + if(val.nativeMemory.indexOf(this.native_type[2]) != -1){ + anonymous = new NativeHookStatisticsTableData(); + anonymous.memoryTap = this.native_type[2]; + } + for (let hook of result) { + if(all != null){ + if(hook.eventType == "AllocEvent" || hook.eventType == "MmapEvent"){ + all.existing += hook.sumHeapSize; + all.allocCount += hook.count; + all.totalBytes += hook.sumHeapSize; + all.totalCount += hook.count; + if(hook.max > all.max){ + all.max = hook.max; + all.maxStr = Utils.getByteWithUnit(all.max); + } + }else if(hook.eventType == "FreeEvent" || hook.eventType == "MunmapEvent"){ + all.totalBytes += hook.sumHeapSize; + all.freeCount += hook.count; + all.totalCount += hook.count; + } + } + if(heap != null){ + if(hook.eventType == "AllocEvent" ){ + heap.existing += hook.sumHeapSize; + heap.allocCount += hook.count; + heap.totalBytes += hook.sumHeapSize; + heap.totalCount += hook.count; + if(hook.max > heap.max){ + heap.max = hook.max; + heap.maxStr = Utils.getByteWithUnit(heap.max); + } + }else if(hook.eventType == "FreeEvent"){ + heap.totalBytes += hook.sumHeapSize; + heap.totalCount += hook.count; + heap.freeCount += hook.count; + } + } + if(anonymous != null){ + if(hook.eventType == "MmapEvent" ){ + anonymous.existing += hook.sumHeapSize; + anonymous.allocCount += hook.count; + anonymous.totalBytes += hook.sumHeapSize; + anonymous.totalCount += hook.count; + if(hook.max > anonymous.max){ + anonymous.max = hook.max; + anonymous.maxStr = Utils.getByteWithUnit(anonymous.max); + } + }else if(hook.eventType == "MunmapEvent"){ + anonymous.totalBytes += hook.sumHeapSize; + anonymous.freeCount += hook.count; + anonymous.totalCount += hook.count; + } + } + } + if(all != null){ + all.existingString = Utils.getByteWithUnit(all.existing) + all.totalBytesString = Utils.getByteWithUnit(all.totalBytes) + all.existingValue = [all.existing,all.totalBytes,this.allMax] + arr.push(all) + } + if(heap != null){ + heap.existingString = Utils.getByteWithUnit(heap.existing) + heap.totalBytesString = Utils.getByteWithUnit(heap.totalBytes) + heap.existingValue = [heap.existing,heap.totalBytes,this.allMax] + arr.push(heap) + } + if(anonymous != null){ + anonymous.existingString = Utils.getByteWithUnit(anonymous.existing) + anonymous.totalBytesString = Utils.getByteWithUnit(anonymous.totalBytes) + anonymous.existingValue = [anonymous.existing,anonymous.totalBytes,this.allMax] + arr.push(anonymous) + } + } + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#tb-native-statstics'); + new ResizeObserver((entries) => { + if (this.parentElement?.clientHeight != 0) { + // @ts-ignore + this.tbl?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight - 20)+"px" + this.tbl?.reMeauseHeight() + } + }).observe(this.parentElement!) + } + + + initHtml(): string { + return ` + + + + + + + + + + + + + + `; + } +} diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneNMemory.ts b/host/ide/src/trace/component/trace/sheet/TabPaneNMemory.ts new file mode 100644 index 0000000..43fde1a --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneNMemory.ts @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import {SelectionParam} from "../../../bean/BoxSelection.js"; +import {queryNativeHookEventId} from "../../../database/SqlLite.js"; +import {NativeHookCallInfo, NativeHookStatistics, NativeMemory} from "../../../bean/NativeHook.js"; +import {SpSystemTrace} from "../../SpSystemTrace.js"; +import {HeapTreeDataBean} from "../../../bean/HeapTreeDataBean.js"; +import {Utils} from "../base/Utils.js"; +import "./TabPaneFilter.js" +import {FilterData, TabPaneFilter} from "./TabPaneFilter"; +import {TabPaneNMSampleList} from "./TabPaneNMSampleList.js"; + +@element('tabpane-native-memory') +export class TabPaneNMemory extends BaseElement { + private defaultNativeTypes = ["All Heap & Anonymous VM","All Heap","All Anonymous VM"]; + private tbl: LitTable | null | undefined; + private tblData: LitTable | null | undefined; + private source: Array = [] + private native_type:Array = [...this.defaultNativeTypes]; + private statsticsSelection: Array = [] + private queryResult: Array = [] + private filterAllocationType:string = "0" + private filterNativeType:string = "0" + private currentSelection:SelectionParam|undefined + private rowSelectData:any = undefined; + set data(val: SelectionParam | any) { + if(val==this.currentSelection){ + return + } + this.currentSelection = val + this.initFilterTypes() + let types:Array = [] + if(val.nativeMemory.indexOf(this.defaultNativeTypes[0]) != -1){ + types.push("'AllocEvent'"); + types.push("'MmapEvent'"); + }else{ + if(val.nativeMemory.indexOf(this.defaultNativeTypes[1]) != -1){ + types.push("'AllocEvent'"); + } + if(val.nativeMemory.indexOf(this.defaultNativeTypes[2]) != -1){ + types.push("'MmapEvent'"); + } + } + TabPaneNMSampleList.serSelection(val) + // @ts-ignore + this.tbl?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight - 20 - 31) + "px" + // @ts-ignore + this.tblData?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight -20)+"px" + // @ts-ignore + this.tblData?.recycleDataSource = []; + // @ts-ignore + this.tbl?.recycleDataSource = []; + queryNativeHookEventId(val.leftNs,val.rightNs,types).then((result)=>{ + if(result.length > 0){ + this.queryResult = result + this.source = this.handleQueryResult(result); + }else{ + this.source = []; + } + this.filterQueryData() + }) + } + + fromStastics(val: SelectionParam | any){ + let filter = this.shadowRoot?.querySelector("#filter") + let typeIndexOf = this.native_type.indexOf(val.statisticsSelectData.memoryTap); + if(this.statsticsSelection.indexOf(val.statisticsSelectData) == -1&&typeIndexOf == -1){ + this.statsticsSelection.push(val.statisticsSelectData) + this.native_type.push(val.statisticsSelectData.memoryTap) + typeIndexOf = this.native_type.length - 1 + } + if(this.currentSelection != val){ + //设置选项后刷新当前的数据 + this.data = val + //todo 设置filter当前的选项和选中项 + filter!.setSelectList(null,this.native_type) + filter!.secondSelect = typeIndexOf+"" + this.filterNativeType = typeIndexOf+"" + }else{ + this.tblData!.recycleDataSource = []; + this.rowSelectData = undefined + filter!.setSelectList(null,this.native_type) + filter!.secondSelect = typeIndexOf+"" + this.filterNativeType = typeIndexOf+"" + //直接将当前数据过滤即可 + this.filterQueryData() + } + } + + getTypeFromIndex(indexOf:number,item:NativeHookStatistics):boolean{ + if(indexOf == -1){ + return false; + } + if(indexOf < 3){ + if(indexOf == 0){ + return true + }else if(indexOf == 1){ + return item.eventType == "AllocEvent" + }else if(indexOf == 2){ + return item.eventType == "MmapEvent" + } + }else if(indexOf-3 < this.statsticsSelection.length){ + let selectionElement = this.statsticsSelection[indexOf - 3]; + if(selectionElement.memoryTap!=undefined&&selectionElement.max!=undefined){ + if (selectionElement.memoryTap.indexOf("Malloc") != -1) { + return item.eventType == "AllocEvent"&&item.heapSize == selectionElement.max + }else { + return item.subType == selectionElement.memoryTap&&item.heapSize == selectionElement.max + } + } + } + return false; + } + + handleQueryResult(result:Array):Array{ + let resultMap = new Map(); + result.map((r)=>{ + resultMap.set(r.eventId,r); + }) + let data :Array = []; + let frameArr: Array = []; + SpSystemTrace.HEAP_FRAME_DATA.map((frame) => { + let frameEventId = parseInt(frame.eventId); + if(frameEventId >= result[0].eventId && frameEventId <= result[result.length - 1].eventId){ + if(resultMap.has(frameEventId) && frame.depth == 0){ + frameArr.push(frame); + } + } + if(frameEventId > result[result.length -1].eventId){ + return false; + } + }); + + let frameMap = new Map(); + frameArr.map((frame)=>{ + frameMap.set(parseInt(frame.eventId),frame); + }) + for (let i = 0,len = result.length; i < len; i++) { + let hook = result[i]; + let memory = new NativeMemory(); + memory.index = i; + memory.eventId = hook.eventId; + memory.eventType = hook.eventType; + memory.subType = hook.subType; + memory.heapSize = hook.heapSize; + memory.heapSizeUnit = Utils.getByteWithUnit(hook.heapSize); + memory.addr = "0x"+hook.addr; + memory.startTs = hook.startTs; + memory.timestamp = Utils.getTimeString(hook.startTs); + (memory as any).isSelected = hook.isSelected; + let frame = frameMap.get(hook.eventId); + if(frame != null && frame != undefined){ + let sym_arr = frame.AllocationFunction?.split("/"); + let lib_arr = frame.MoudleName?.split("/"); + memory.symbol = sym_arr![sym_arr!.length - 1]; + memory.library = lib_arr![lib_arr!.length - 1]; + } + data.push(memory); + } + return data + } + + initFilterTypes(){ + let filter = this.shadowRoot?.querySelector("#filter") + this.queryResult = [] + filter!.setSelectList(null,this.defaultNativeTypes) + filter!.firstSelect = "0" + filter!.secondSelect = "0" + this.filterAllocationType = "0" + this.filterNativeType = "0" + this.rowSelectData = undefined + } + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#tb-native-memory'); + this.tblData = this.shadowRoot?.querySelector('#tb-native-data'); + this.tbl!.addEventListener("row-click", (e) => { + // @ts-ignore + let data = (e.detail as NativeMemory); + this.rowSelectData = data + this.setRightTableData(data); + document.dispatchEvent(new CustomEvent('triangle-flag', {detail: {time:data.startTs,type:"triangle"}})); + }) + new ResizeObserver((entries) => { + if (this.parentElement?.clientHeight != 0) { + // @ts-ignore + this.tbl?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight)-10-31+"px"; + this.tbl?.reMeauseHeight(); + // @ts-ignore + this.tblData?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight)-10+"px" + this.tblData?.reMeauseHeight() + } + }).observe(this.parentElement!) + let filter = this.shadowRoot?.querySelector("#filter") + this.shadowRoot?.querySelector("#filter")!.getFilterData((data:FilterData)=>{ + if (data.mark) { + if(this.rowSelectData!=undefined){ + let filterTemp = this.queryResult.filter((tempItem)=>{ + return tempItem.eventId == this.rowSelectData.eventId + }) + if(filterTemp.length>0){ + filterTemp[0].isSelected = true + } + TabPaneNMSampleList.addSampleData(this.rowSelectData) + if(this.rowSelectData.selectedCallback){ + this.rowSelectData.isSelected = true + this.rowSelectData.selectedCallback() + } + document.dispatchEvent(new CustomEvent('triangle-flag', {detail: {time:this.rowSelectData.startTs,type:"square"}})); + } + }else { + this.filterAllocationType = data.firstSelect||"0" + this.filterNativeType = data.secondSelect||"0" + this.filterQueryData() + } + }) + filter!.firstSelect = "1" + } + + filterQueryData(){ + if (this.queryResult.length > 0&&this.currentSelection) { + let filter = this.queryResult.filter((item)=>{ + let filterAllocation = true + if(this.filterAllocationType=="1"){ + filterAllocation = item.startTs>=this.currentSelection!.leftNs&&item.startTs<=this.currentSelection!.rightNs&&item.endTs>this.currentSelection!.rightNs + }else if(this.filterAllocationType=="2"){ + filterAllocation = item.startTs>=this.currentSelection!.leftNs&&item.startTs<=this.currentSelection!.rightNs&&item.endTs<=this.currentSelection!.rightNs + } + let filterNative = this.getTypeFromIndex(parseInt(this.filterNativeType),item) + return filterAllocation&&filterNative + }) + if(filter.length>0){ + this.source = this.handleQueryResult(filter); + this.tbl!.recycleDataSource = this.source; + }else { + this.source = [] + this.tbl!.recycleDataSource = []; + } + } + } + + setRightTableData(hook:NativeMemory){ + let arr:Array = []; + let frameArr = SpSystemTrace.HEAP_FRAME_DATA.filter((frame) => parseInt(frame.eventId) == hook.eventId); + frameArr.map((frame)=>{ + let target = new NativeHookCallInfo(); + target.eventId = parseInt(frame.eventId); + target.depth = frame.depth; + let sym_arr = frame.AllocationFunction?.split("/"); + let lib_arr = frame.MoudleName?.split("/"); + target.symbol = sym_arr![sym_arr!.length - 1]; + target.library = lib_arr![lib_arr!.length - 1]; + target.title = `[ ${target.symbol} ] ${target.library}`; + target.type = (target.library.endsWith(".so.1") || target.library.endsWith(".dll") || target.library.endsWith(".so")) ? 0 : 1; + arr.push(target); + }) + // @ts-ignore + this.tblData?.recycleDataSource = arr; + } + + initHtml(): string { + return ` + +
      +
      + + + + + + + + + + +
      +
      + + + + + + +
      +
      + `; + } +} \ No newline at end of file diff --git a/host/ide/src/trace/component/trace/sheet/TabPanePTS.ts b/host/ide/src/trace/component/trace/sheet/TabPanePTS.ts new file mode 100644 index 0000000..9b3bf4c --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPanePTS.ts @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import {SelectionParam} from "../../../bean/BoxSelection.js"; +import { + getTabStatesGroupByProcess, + getTabStatesGroupByProcessThread, + getTabStatesGroupByStatePidTid +} from "../../../database/SqlLite.js"; +import {StateProcessThread} from "../../../bean/StateProcessThread.js"; +import {Utils} from "../base/Utils.js"; +import {SpSystemTrace} from "../../SpSystemTrace.js"; + +@element('tabpane-pts') +export class TabPanePTS extends BaseElement { + private tbl: LitTable | null | undefined; + private range: HTMLLabelElement | null | undefined; + private loadDataInCache: boolean = true; + + set data(val: SelectionParam | any) { + // @ts-ignore + this.tbl?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight - 45) + "px" + this.range!.textContent = "Selected range: " + parseFloat(((val.rightNs - val.leftNs) / 1000000.0).toFixed(5)) + " ms" + if (this.loadDataInCache) { + this.queryDataInCacheData(val).then((arr) => { + this.tbl!.recycleDataSource = arr; + }); + } else { + this.queryDataByDB(val) + } + } + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#tb-states'); + this.range = this.shadowRoot?.querySelector('#time-range') + new ResizeObserver((entries) => { + if (this.parentElement?.clientHeight != 0) { + // @ts-ignore + this.tbl?.shadowRoot.querySelector(".table").style.height = (this.parentElement.clientHeight - 45)+"px" + this.tbl?.reMeauseHeight() + } + }).observe(this.parentElement!) + } + + async queryDataInCacheData(val: SelectionParam | any): Promise> { + return new Promise>((resolve, reject) => { + let pMap: Map = new Map(); + let ptMap: Map = new Map(); + let ptsMap: Map = new Map(); + SpSystemTrace.SPT_DATA.map((d)=>{ + if(!(d.end_ts < val.leftNs || d.start_ts > val.rightNs)){ + if (pMap.has(d.processId + "")) { + let obj1 = pMap.get(d.processId + ""); + obj1!.count++; + obj1!.wallDuration += d.dur; + obj1!.avgDuration = (obj1!.wallDuration / obj1!.count).toFixed(2); + if (d.dur > obj1!.maxDuration) { + obj1!.maxDuration = d.dur; + } + if (d.dur < obj1!.minDuration) { + obj1!.minDuration = d.dur; + } + } else { + let obj1 = new StateProcessThread(); + obj1.id = "p" + d.processId; + obj1.title = (d.process == null || d.process == "" ? "Process" : d.process) + "(" + d.processId + ")"; + obj1.process = d.process; + obj1.processId = d.processId; + obj1.minDuration = d.dur; + obj1.maxDuration = d.dur; + obj1.count = 1; + obj1.avgDuration = d.dur + ""; + obj1.wallDuration = d.dur; + pMap.set(d.processId + "", obj1); + } + if (ptMap.has(d.processId + "_" + d.threadId)) { + let obj2 = ptMap.get(d.processId + "_" + d.threadId); + obj2!.count++; + obj2!.wallDuration += d.dur; + obj2!.avgDuration = (obj2!.wallDuration / obj2!.count).toFixed(2); + if (d.dur > obj2!.maxDuration) { + obj2!.maxDuration = d.dur; + } + if (d.dur < obj2!.minDuration) { + obj2!.minDuration = d.dur; + } + } else { + let obj2 = new StateProcessThread(); + obj2.id = "p" + d.processId + "_" + "t" + d.threadId; + obj2.pid = "p" + d.processId; + obj2.title = (d.thread == null || d.thread == "" ? "Thread" : d.thread) + "(" + d.threadId + ")" + obj2.processId = d.processId; + obj2.process = d.process; + obj2.thread = d.thread; + obj2.threadId = d.threadId; + obj2.minDuration = d.dur; + obj2.maxDuration = d.dur; + obj2.count = 1; + obj2.avgDuration = d.dur + ""; + obj2.wallDuration = d.dur; + ptMap.set(d.processId + "_" + d.threadId, obj2); + } + if (ptsMap.has(d.processId + "_" + d.threadId + "_" + d.state)) { + let obj3 = ptsMap.get(d.processId + "_" + d.threadId + "_" + d.state); + obj3!.count++; + obj3!.wallDuration += d.dur; + obj3!.avgDuration = (obj3!.wallDuration / obj3!.count).toFixed(2); + if (d.dur > obj3!.maxDuration) { + obj3!.maxDuration = d.dur; + } + if (d.dur < obj3!.minDuration) { + obj3!.minDuration = d.dur; + } + } else { + let obj3 = new StateProcessThread(); + obj3.id = "p" + d.processId + "_" + "t" + d.threadId + "_" + (d.state == "R+" ? "RP" : d.state) + obj3.pid = "p" + d.processId + "_" + "t" + d.threadId; + obj3.title = Utils.getEndState(d.state) + obj3.processId = d.processId; + obj3.process = d.process; + obj3.thread = d.thread; + obj3.threadId = d.threadId; + obj3.state = d.state; + obj3.minDuration = d.dur; + obj3.maxDuration = d.dur; + obj3.count = 1; + obj3.avgDuration = d.dur + ""; + obj3.wallDuration = d.dur; + ptsMap.set(d.processId + "_" + d.threadId + "_" + d.state, obj3); + } + } + }) + let arr: Array = []; + for (let key of pMap.keys()) { + let s = pMap.get(key); + s!.children = []; + for (let ks of ptMap.keys()) { + if (ks.startsWith(key + "_")) { + let sp = ptMap.get(ks) + sp!.children = []; + for (let kst of ptsMap.keys()) { + if (kst.startsWith(ks + "_")) { + let spt = ptsMap.get(kst) + sp!.children.push(spt!); + } + } + s!.children.push(sp!) + } + } + arr.push(s!) + } + resolve(arr); + }); + } + + queryDataByDB(val: SelectionParam | any) { + Promise.all([ + getTabStatesGroupByProcess(val.leftNs, val.rightNs), + getTabStatesGroupByProcessThread(val.leftNs, val.rightNs), + getTabStatesGroupByStatePidTid(val.leftNs, val.rightNs)]).then((values) => { + let processes = values[0]; + processes.map((spt) => { + spt.id = "p" + spt.processId + spt.title = (spt.process == null || spt.process == "" ? "Process" : spt.process) + "(" + spt.processId + ")" + }); + let threadMap = this.groupByProcessToMap(values[1]); + let stateMap = this.groupByProcessThreadToMap(values[2]); + for (let process of processes) { + let threads = threadMap.get(process.processId); + if (threads != undefined) { + threads!.map((spt) => { + spt.id = "p" + spt.processId + "_" + "t" + spt.threadId; + spt.pid = "p" + spt.processId; + spt.title = (spt.thread == null || spt.thread == "" ? "Thread" : spt.thread) + "(" + spt.threadId + ")" + }) + } + process.children = threads ?? []; + let map = stateMap.get(process.processId); + for (let thread of threads!) { + let states = map!.get(thread.threadId); + states!.map((spt) => { + spt.id = "p" + spt.processId + "_" + "t" + spt.threadId + "_" + (spt.state == "R+" ? "RP" : spt.state) + spt.pid = "p" + spt.processId + "_" + "t" + spt.threadId; + spt.title = Utils.getEndState(spt.state) + }) + thread.children = states ?? []; + } + } + this.tbl!.dataSource = processes; + }) + } + + groupByThreadToMap(arr: Array): Map> { + let map = new Map>(); + for (let spt of arr) { + if (map.has(spt.threadId)) { + map.get(spt.threadId)!.push(spt); + } else { + let list: Array = []; + list.push(spt); + map.set(spt.threadId, list); + } + } + return map; + } + + groupByProcessToMap(arr: Array): Map> { + let map = new Map>(); + for (let spt of arr) { + if (map.has(spt.processId)) { + map.get(spt.processId)!.push(spt); + } else { + let list: Array = []; + list.push(spt); + map.set(spt.processId, list); + } + } + return map; + } + + groupByProcessThreadToMap(arr: Array): Map>> { + let map = new Map>>(); + let processMap = this.groupByProcessToMap(arr); + for (let key of processMap.keys()) { + let threadMap = this.groupByThreadToMap(processMap.get(key)!) + map.set(key, threadMap); + } + return map; + } + + initHtml(): string { + return ` + + + + + + + + + + + + `; + } + +} \ No newline at end of file diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneSPT.ts b/host/ide/src/trace/component/trace/sheet/TabPaneSPT.ts new file mode 100644 index 0000000..2c068cd --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneSPT.ts @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import {SelectionParam} from "../../../bean/BoxSelection.js"; +import { + getTabStatesGroupByState, + getTabStatesGroupByStatePid, + getTabStatesGroupByStatePidTid +} from "../../../database/SqlLite.js"; +import {StateProcessThread} from "../../../bean/StateProcessThread.js"; +import {Utils} from "../base/Utils.js"; +import {SpSystemTrace} from "../../SpSystemTrace.js"; + +@element('tabpane-spt') +export class TabPaneSPT extends BaseElement { + private tbl: LitTable | null | undefined; + private range: HTMLLabelElement | null | undefined; + private loadDataInCache: boolean = true; + + set data(val: SelectionParam | any) { + // @ts-ignore + this.tbl?.shadowRoot?.querySelector(".table").style.height = (this.parentElement!.clientHeight - 45)+"px" + this.range!.textContent = "Selected range: " + parseFloat(((val.rightNs - val.leftNs) / 1000000.0).toFixed(5)) + " ms" + if (this.loadDataInCache) { + this.queryDataByCacheData(val).then((result) => { + this.tbl!.recycleDataSource = result; + }); + } else { + this.queryDataByDB(val); + } + } + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#tb-states'); + this.range = this.shadowRoot?.querySelector('#time-range') + new ResizeObserver((entries) => { + if (this.parentElement?.clientHeight != 0) { + // @ts-ignore + this.tbl?.shadowRoot?.querySelector(".table").style.height = (this.parentElement!.clientHeight - 45)+"px" + this.tbl?.reMeauseHeight() + } + }).observe(this.parentElement!) + } + + async queryDataByCacheData(val: SelectionParam | any): Promise> { + //1. 框选时间区间的数据过滤出来 + return new Promise>((resolve, reject) => { + let statesMap: Map = new Map(); + let spMap: Map = new Map(); + let sptMap: Map = new Map(); + // @ts-ignore + SpSystemTrace.SPT_DATA.map((d) => { + if(!(d.end_ts < val.leftNs || d.start_ts > val.rightNs)){ + if (statesMap.has(d.state)) { + let obj1 = statesMap.get(d.state); + obj1!.count++; + obj1!.wallDuration += d.dur; + obj1!.avgDuration = (obj1!.wallDuration / obj1!.count).toFixed(2); + if (d.dur > obj1!.maxDuration) { + obj1!.maxDuration = d.dur; + } + if (d.dur < obj1!.minDuration) { + obj1!.minDuration = d.dur; + } + } else { + let obj1 = new StateProcessThread(); + obj1.id = (d.state == "R+" ? "RP" : d.state) + obj1.title = Utils.getEndState(d.state); + obj1.state = d.state; + obj1.minDuration = d.dur; + obj1.maxDuration = d.dur; + obj1.count = 1; + obj1.avgDuration = d.dur + ""; + obj1.wallDuration = d.dur; + statesMap.set(d.state, obj1); + } + if (spMap.has(d.state + "_" + d.processId)) { + let obj2 = spMap.get(d.state + "_" + d.processId); + obj2!.count++; + obj2!.wallDuration += d.dur; + obj2!.avgDuration = (obj2!.wallDuration / obj2!.count).toFixed(2); + if (d.dur > obj2!.maxDuration) { + obj2!.maxDuration = d.dur; + } + if (d.dur < obj2!.minDuration) { + obj2!.minDuration = d.dur; + } + } else { + let obj2 = new StateProcessThread(); + obj2.id = (d.state == "R+" ? "RP" : d.state) + "_" + d.processId; + obj2.pid = (d.state == "R+" ? "RP" : d.state); + obj2.title = (d.process == null || d.process == "" ? "Process" : d.process) + "(" + d.processId + ")" + obj2.processId = d.processId; + obj2.process = d.process; + obj2.state = d.state; + obj2.minDuration = d.dur; + obj2.maxDuration = d.dur; + obj2.count = 1; + obj2.avgDuration = d.dur + ""; + obj2.wallDuration = d.dur; + spMap.set(d.state + "_" + d.processId, obj2); + } + if (sptMap.has(d.state + "_" + d.processId + "_" + d.threadId)) { + let obj3 = sptMap.get(d.state + "_" + d.processId + "_" + d.threadId); + obj3!.count++; + obj3!.wallDuration += d.dur; + obj3!.avgDuration = (obj3!.wallDuration / obj3!.count).toFixed(2); + if (d.dur > obj3!.maxDuration) { + obj3!.maxDuration = d.dur; + } + if (d.dur < obj3!.minDuration) { + obj3!.minDuration = d.dur; + } + } else { + let obj3 = new StateProcessThread(); + obj3.id = (d.state == "R+" ? "RP" : d.state) + "_" + d.processId + "_" + d.threadId + obj3.pid = (d.state == "R+" ? "RP" : d.state) + "_" + d.processId + obj3.title = (d.thread == null || d.thread == "" ? "Thread" : d.thread) + "(" + d.threadId + ")" + obj3.processId = d.processId; + obj3.process = d.process; + obj3.thread = d.thread; + obj3.threadId = d.threadId; + obj3.state = d.state; + obj3.minDuration = d.dur; + obj3.maxDuration = d.dur; + obj3.count = 1; + obj3.avgDuration = d.dur + ""; + obj3.wallDuration = d.dur; + sptMap.set(d.state + "_" + d.processId + "_" + d.threadId, obj3); + } + } + }); + let arr: Array = []; + for (let key of statesMap.keys()) { + let s = statesMap.get(key); + s!.children = []; + for (let ks of spMap.keys()) { + if (ks.startsWith(key + "_")) { + let sp = spMap.get(ks) + sp!.children = []; + for (let kst of sptMap.keys()) { + if (kst.startsWith(ks + "_")) { + let spt = sptMap.get(kst) + sp!.children.push(spt!); + } + } + s!.children.push(sp!) + } + } + arr.push(s!) + } + resolve(arr); + }); + } + + queryDataByDB(val: SelectionParam | any) { + Promise.all([ + getTabStatesGroupByState(val.leftNs, val.rightNs), + getTabStatesGroupByStatePid(val.leftNs, val.rightNs), + getTabStatesGroupByStatePidTid(val.leftNs, val.rightNs)]).then((values) => { + let states = values[0]; + states.map((spt) => { + spt.id = (spt.state == "R+" ? "RP" : spt.state) + spt.title = Utils.getEndState(spt.state); + }); + let processMap = this.groupByStateToMap(values[1]); + let threadMap = this.groupByStateProcessToMap(values[2]); + for (let state of states) { + let processes = processMap.get(state.state); + processes!.map((spt) => { + spt.id = (spt.state == "R+" ? "RP" : spt.state) + "_" + spt.processId; + spt.pid = (spt.state == "R+" ? "RP" : spt.state); + spt.title = (spt.process == null || spt.process == "" ? "Process" : spt.process) + "(" + spt.processId + ")" + }) + state.children = processes ?? []; + let map = threadMap.get(state.state); + for (let process of processes!) { + let threads = map!.get(process.processId); + threads!.map((spt) => { + spt.id = (spt.state == "R+" ? "RP" : spt.state) + "_" + spt.processId + "_" + spt.threadId + spt.pid = (spt.state == "R+" ? "RP" : spt.state) + "_" + spt.processId + spt.title = (spt.thread == null || spt.thread == "" ? "Thread" : spt.thread) + "(" + spt.threadId + ")" + }) + process.children = threads ?? []; + } + } + this.tbl!.recycleDataSource = states; + }) + } + + groupByStateToMap(arr: Array): Map> { + let map = new Map>(); + for (let spt of arr) { + if (map.has(spt.state)) { + map.get(spt.state)!.push(spt); + } else { + let list: Array = []; + list.push(spt); + map.set(spt.state, list); + } + } + return map; + } + + groupByProcessToMap(arr: Array): Map> { + let map = new Map>(); + for (let spt of arr) { + if (map.has(spt.processId)) { + map.get(spt.processId)!.push(spt); + } else { + let list: Array = []; + list.push(spt); + map.set(spt.processId, list); + } + } + return map; + } + + groupByStateProcessToMap(arr: Array): Map>> { + let map = new Map>>(); + let stateMap = this.groupByStateToMap(arr); + for (let key of stateMap.keys()) { + let processMap = this.groupByProcessToMap(stateMap.get(key)!) + map.set(key, processMap); + } + return map; + } + + initHtml(): string { + return ` + + + + + + + + + + + + `; + } + +} \ No newline at end of file diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneSlices.ts b/host/ide/src/trace/component/trace/sheet/TabPaneSlices.ts new file mode 100644 index 0000000..705749b --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneSlices.ts @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import {SelectionData, SelectionParam} from "../../../bean/BoxSelection.js"; +import {getTabSlices} from "../../../database/SqlLite.js"; + +@element('tabpane-slices') +export class TabPaneSlices extends BaseElement { + private tbl: LitTable | null | undefined; + private range: HTMLLabelElement | null | undefined; + private source: Array = [] + + set data(val: SelectionParam | any) { + this.range!.textContent = "Selected range: " + parseFloat(((val.rightNs - val.leftNs) / 1000000.0).toFixed(5)) + " ms" + getTabSlices(val.funTids, val.leftNs, val.rightNs).then((result) => { + if (result != null && result.length > 0) { + let sumWall = 0.0; + let sumOcc = 0; + for (let e of result) { + e.name = e.name == null ? "" : e.name + sumWall += e.wallDuration + sumOcc += e.occurrences + e.wallDuration = parseFloat((e.wallDuration / 1000000.0).toFixed(5)); + e.avgDuration = parseFloat((e.avgDuration / 1000000.0).toFixed(5)); + } + let count = new SelectionData() + count.process = " "; + count.wallDuration = parseFloat((sumWall / 1000000.0).toFixed(5)); + count.occurrences = sumOcc; + result.splice(0, 0, count) + this.source = result + this.tbl!.dataSource = result + } else { + this.source = []; + this.tbl!.dataSource = this.source; + } + }); + } + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#tb-slices'); + this.range = this.shadowRoot?.querySelector('#time-range'); + this.tbl!.addEventListener('column-click', (evt) => { + // @ts-ignore + this.sortByColumn(evt.detail) + }); + } + + initHtml(): string { + return ` + + + + + + + + + `; + } + + sortByColumn(detail: any) { + // @ts-ignore + function compare(property, sort, type) { + return function (a: SelectionData, b: SelectionData) { + if (a.process == " " || b.process == " ") { + return 0; + } + if (type === 'number') { + // @ts-ignore + return sort === 2 ? parseFloat(b[property]) - parseFloat(a[property]) : parseFloat(a[property]) - parseFloat(b[property]); + } else { + // @ts-ignore + if (b[property] > a[property]) { + return sort === 2 ? 1 : -1; + } else { // @ts-ignore + if (b[property] == a[property]) { + return 0; + } else { + return sort === 2 ? -1 : 1; + } + } + } + } + } + + if (detail.key === "name") { + this.source.sort(compare(detail.key, detail.sort, 'string')) + } else { + this.source.sort(compare(detail.key, detail.sort, 'number')) + } + this.tbl!.dataSource = this.source; + } + +} \ No newline at end of file diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneThreadStates.ts b/host/ide/src/trace/component/trace/sheet/TabPaneThreadStates.ts new file mode 100644 index 0000000..a9ab4a4 --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneThreadStates.ts @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import {SelectionData, SelectionParam} from "../../../bean/BoxSelection.js"; +import "../../StackBar.js" +import {getTabThreadStates} from "../../../database/SqlLite.js"; +import {Utils} from "../base/Utils.js"; +import {StackBar} from "../../StackBar.js"; + +@element('tabpane-thread-states') +export class TabPaneThreadStates extends BaseElement { + private tbl: LitTable | null | undefined; + private range: HTMLLabelElement | null | undefined; + private stackBar: StackBar | null | undefined; + private source: Array = [] + + set data(val: SelectionParam | any) { + // // @ts-ignore + this.range!.textContent = "Selected range: " + ((val.rightNs - val.leftNs) / 1000000.0).toFixed(5) + " ms" + getTabThreadStates(val.threadIds, val.leftNs, val.rightNs).then((result) => { + if (result != null && result.length > 0) { + let sumWall = 0.0; + let sumOcc = 0; + for (let e of result) { + e.process = e.process == null || e.process.length == 0 ? "[NULL]" : e.process + e.thread = e.thread == null || e.thread.length == 0 ? "[NULL]" : e.thread + sumWall += e.wallDuration + sumOcc += e.occurrences + e.stateJX = e.state + e.state = Utils.getEndState(e.stateJX); + e.wallDuration = parseFloat((e.wallDuration / 1000000.0).toFixed(5)); + e.avgDuration = parseFloat((e.avgDuration / 1000000.0).toFixed(5)); + } + let count = new SelectionData() + count.process = " " + count.state = " " + count.wallDuration = parseFloat((sumWall / 1000000.0).toFixed(5)); + count.occurrences = sumOcc; + result.splice(0, 0, count) + this.source = result; + this.tbl!.dataSource = result + this.stackBar!.data = result; + } else { + this.source = [] + this.stackBar!.data = [] + this.tbl!.dataSource = [] + } + }) + } + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#tb-thread-states'); + this.range = this.shadowRoot?.querySelector('#time-range'); + this.stackBar = this.shadowRoot?.querySelector('#stack-bar'); + this.tbl!.addEventListener('column-click', (evt: any) => { + this.sortByColumn(evt.detail) + }); + } + + initHtml(): string { + return ` + +
      + + +
      + + + + + + + + + + + `; + } + + sortByColumn(detail: any) { + function compare(property: any, sort: any, type: any) { + return function (a: SelectionData | any, b: SelectionData | any) { + if (a.process == " " || b.process == " ") { + return 0; + } + if (type === 'number') { + return sort === 2 ? parseFloat(b[property]) - parseFloat(a[property]) : parseFloat(a[property]) - parseFloat(b[property]); + } else { + if (b[property] > a[property]) { + return sort === 2 ? 1 : -1; + } else if (b[property] == a[property]) { + return 0; + } else { + return sort === 2 ? -1 : 1; + } + } + } + } + + if (detail.key === "name" || detail.key === "thread" || detail.key === "state") { + this.source.sort(compare(detail.key, detail.sort, 'string')) + } else { + this.source.sort(compare(detail.key, detail.sort, 'number')) + } + this.tbl!.dataSource = this.source; + } + +} \ No newline at end of file diff --git a/host/ide/src/trace/component/trace/sheet/TabPaneThreadSwitch.ts b/host/ide/src/trace/component/trace/sheet/TabPaneThreadSwitch.ts new file mode 100644 index 0000000..9af0690 --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabPaneThreadSwitch.ts @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {LitTable} from "../../../../base-ui/table/lit-table.js"; +import {SelectionParam} from "../../../bean/BoxSelection.js"; +import { + getTabStatesGroupByState, + getTabStatesGroupByStatePid, + getTabStatesGroupByStatePidTid +} from "../../../database/SqlLite.js"; +import {StateProcessThread} from "../../../bean/StateProcessThread.js"; +import {Utils} from "../base/Utils.js"; +import {SpSystemTrace} from "../../SpSystemTrace.js"; + +@element('tabpane-thread-switch') +export class TabPaneThreadSwitch extends BaseElement { + private tbl: LitTable | null | undefined; + private range: HTMLLabelElement | null | undefined; + private loadDataInCache: boolean = true; + + set data(val: SelectionParam | any) { + //@ts-ignore + this.tbl?.shadowRoot?.querySelector(".table")?.style?.height = (this.parentElement!.clientHeight - 45)+"px"; + this.range!.textContent = "Selected range: " + parseFloat(((val.rightNs - val.leftNs) / 1000000.0).toFixed(5)) + " ms"; + if (this.loadDataInCache) { + this.queryDataByCacheData(val).then((arr) => { + this.tbl!.recycleDataSource = arr; + }); + } else { + this.queryDataByDB(val); + } + } + + initElements(): void { + this.tbl = this.shadowRoot?.querySelector('#tb-ts'); + this.range = this.shadowRoot?.querySelector('#time-range'); + new ResizeObserver((entries) => { + if (this.parentElement?.clientHeight != 0) { + // @ts-ignore + this.tbl?.shadowRoot?.querySelector(".table").style.height = (this.parentElement!.clientHeight - 45)+"px" + this.tbl?.reMeauseHeight() + } + }).observe(this.parentElement!); + } + + async queryDataByCacheData(val: SelectionParam | any): Promise> { + //1. 框选时间区间的数据过滤出来 + return new Promise>(((resolve, reject) => { + let statesMap: Map = new Map(); + let spMap: Map = new Map(); + let sptMap: Map = new Map(); + SpSystemTrace.SPT_DATA.map((d) => { + if(!(d.end_ts < val.leftNs || d.start_ts > val.rightNs)){ + if (statesMap.has(d.state)) { + let obj1 = statesMap.get(d.state); + obj1!.count++; + obj1!.wallDuration += d.dur; + obj1!.avgDuration = (obj1!.wallDuration / obj1!.count).toFixed(2); + if (d.dur > obj1!.maxDuration) { + obj1!.maxDuration = d.dur; + } + if (d.dur < obj1!.minDuration) { + obj1!.minDuration = d.dur; + } + } else { + let obj1 = new StateProcessThread(); + obj1.id = (d.state == "R+" ? "RP" : d.state) + obj1.title = Utils.getEndState(d.state); + obj1.state = d.state; + obj1.minDuration = d.dur; + obj1.maxDuration = d.dur; + obj1.count = 1; + obj1.avgDuration = d.dur + ""; + obj1.wallDuration = d.dur; + statesMap.set(d.state, obj1); + } + if (spMap.has(d.state + "_" + d.processId)) { + let obj2 = spMap.get(d.state + "_" + d.processId); + obj2!.count++; + obj2!.wallDuration += d.dur; + obj2!.avgDuration = (obj2!.wallDuration / obj2!.count).toFixed(2); + if (d.dur > obj2!.maxDuration) { + obj2!.maxDuration = d.dur; + } + if (d.dur < obj2!.minDuration) { + obj2!.minDuration = d.dur; + } + } else { + let obj2 = new StateProcessThread(); + obj2.id = (d.state == "R+" ? "RP" : d.state) + "_" + d.processId; + obj2.pid = (d.state == "R+" ? "RP" : d.state); + obj2.title = (d.process == null || d.process == "" ? "Process" : d.process) + "(" + d.processId + ")" + obj2.processId = d.processId; + obj2.process = d.process; + obj2.state = d.state; + obj2.minDuration = d.dur; + obj2.maxDuration = d.dur; + obj2.count = 1; + obj2.avgDuration = d.dur + ""; + obj2.wallDuration = d.dur; + spMap.set(d.state + "_" + d.processId, obj2); + } + if (sptMap.has(d.state + "_" + d.processId + "_" + d.threadId)) { + let obj3 = sptMap.get(d.state + "_" + d.processId + "_" + d.threadId); + obj3!.count++; + obj3!.wallDuration += d.dur; + obj3!.avgDuration = (obj3!.wallDuration / obj3!.count).toFixed(2); + if (d.dur > obj3!.maxDuration) { + obj3!.maxDuration = d.dur; + } + if (d.dur < obj3!.minDuration) { + obj3!.minDuration = d.dur; + } + } else { + let obj3 = new StateProcessThread(); + obj3.id = (d.state == "R+" ? "RP" : d.state) + "_" + d.processId + "_" + d.threadId + obj3.pid = (d.state == "R+" ? "RP" : d.state) + "_" + d.processId + obj3.title = (d.thread == null || d.thread == "" ? "Thread" : d.thread) + "(" + d.threadId + ")" + obj3.processId = d.processId; + obj3.process = d.process; + obj3.thread = d.thread; + obj3.threadId = d.threadId; + obj3.state = d.state; + obj3.minDuration = d.dur; + obj3.maxDuration = d.dur; + obj3.count = 1; + obj3.avgDuration = d.dur + ""; + obj3.wallDuration = d.dur; + sptMap.set(d.state + "_" + d.processId + "_" + d.threadId, obj3); + } + } + }) + let arr: Array = []; + for (let key of statesMap.keys()) { + let s = statesMap.get(key); + s!.children = []; + for (let ks of spMap.keys()) { + if (ks.startsWith(key + "_")) { + let sp = spMap.get(ks) + sp!.children = []; + for (let kst of sptMap.keys()) { + if (kst.startsWith(ks + "_")) { + let spt = sptMap.get(kst) + sp!.children.push(spt!); + } + } + s!.children.push(sp!) + } + } + arr.push(s!) + } + resolve(arr); + })); + } + + queryDataByDB(val: SelectionParam | any) { + Promise.all([ + getTabStatesGroupByState(val.leftNs, val.rightNs), + getTabStatesGroupByStatePid(val.leftNs, val.rightNs), + getTabStatesGroupByStatePidTid(val.leftNs, val.rightNs)]).then((values) => { + let states = values[0]; + states.map((spt) => { + spt.id = (spt.state == "R+" ? "RP" : spt.state) + spt.title = Utils.getEndState(spt.state); + }); + let processMap = this.groupByStateToMap(values[1]); + let threadMap = this.groupByStateProcessToMap(values[2]); + for (let state of states) { + let processes = processMap.get(state.state); + processes!.map((spt) => { + spt.id = (spt.state == "R+" ? "RP" : spt.state) + "_" + spt.processId; + spt.pid = (spt.state == "R+" ? "RP" : spt.state); + spt.title = (spt.process == null || spt.process == "" ? "Process" : spt.process) + "(" + spt.processId + ")" + }) + state.children = processes ?? []; + let map = threadMap.get(state.state); + for (let process of processes!) { + let threads = map!.get(process.processId); + threads!.map((spt) => { + spt.id = (spt.state == "R+" ? "RP" : spt.state) + "_" + spt.processId + "_" + spt.threadId + spt.pid = (spt.state == "R+" ? "RP" : spt.state) + "_" + spt.processId + spt.title = (spt.thread == null || spt.thread == "" ? "Thread" : spt.thread) + "(" + spt.threadId + ")" + }) + process.children = threads ?? []; + } + } + this.tbl!.recycleDataSource = states; + }) + } + + groupByStateToMap(arr: Array): Map> { + let map = new Map>(); + for (let spt of arr) { + if (map.has(spt.state)) { + map.get(spt.state)!.push(spt); + } else { + let list: Array = []; + list.push(spt); + map.set(spt.state, list); + } + } + return map; + } + + groupByProcessToMap(arr: Array): Map> { + let map = new Map>(); + for (let spt of arr) { + if (map.has(spt.processId)) { + map.get(spt.processId)!.push(spt); + } else { + let list: Array = []; + list.push(spt); + map.set(spt.processId, list); + } + } + return map; + } + + groupByStateProcessToMap(arr: Array): Map>> { + let map = new Map>>(); + let stateMap = this.groupByStateToMap(arr); + for (let key of stateMap.keys()) { + let processMap = this.groupByProcessToMap(stateMap.get(key)!) + map.set(key, processMap); + } + return map; + } + + initHtml(): string { + return ` + + + + + + + `; + } +} \ No newline at end of file diff --git a/host/ide/src/trace/component/trace/sheet/TabProgressBar.ts b/host/ide/src/trace/component/trace/sheet/TabProgressBar.ts new file mode 100644 index 0000000..74640f8 --- /dev/null +++ b/host/ide/src/trace/component/trace/sheet/TabProgressBar.ts @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; + + +@element('tab-progress-bar') +export class TabProgressBar extends BaseElement { + + initElements(): void { + let data:Array = this.getAttribute("data")!.split(",") + let first: HTMLDivElement | undefined | null =this.shadowRoot?.querySelector("#first") + let second: HTMLDivElement | undefined | null =this.shadowRoot?.querySelector("#second") + if (data!.length > 0 && data && data![2]!="0") { + if (parseInt(data[0])< 0) { + first!.style.width = (Number((Math.abs(parseInt(data[0]))/parseInt(data[2]))*100)).toFixed(2) + "%" + first!.style.background = "#FC74FF" + }else { + first!.style.width = (Number((parseInt(data[0])/parseInt(data[2]))*100)).toFixed(2) + "%" + } + if (parseInt(data[1])< 0) { + second!.style.width = (Number((Math.abs(parseInt(data[1])) / parseInt(data[2])) * 100)).toFixed(2) + "%" + first!.style.background = "#CC34CF" + }else { + second!.style.width = (Number((parseInt(data[1]) / parseInt(data[2])) * 100)).toFixed(2) + "%" + } + } + + } + + + + initHtml(): string { + return ` + +
      +
      + `; + } +} diff --git a/host/ide/src/trace/component/trace/timer-shaft/Flag.ts b/host/ide/src/trace/component/trace/timer-shaft/Flag.ts new file mode 100644 index 0000000..aaf2f29 --- /dev/null +++ b/host/ide/src/trace/component/trace/timer-shaft/Flag.ts @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class Flag { + x: number = 0 + y: number = 0 + width: number = 0 + height: number = 0 + time: number = 0 + color: string = "" + selected: boolean = false + text: string = "" + hidden: boolean = false; + type:string = ""; + constructor(x: number, y: number, width: number, height: number, time: number, color: string = "#999999", selected = false, type:string="") { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.time = time; + this.color = color; + this.selected = selected; + this.type = type; + } +} + diff --git a/host/ide/src/trace/component/trace/timer-shaft/Graph.ts b/host/ide/src/trace/component/trace/timer-shaft/Graph.ts new file mode 100644 index 0000000..23a2278 --- /dev/null +++ b/host/ide/src/trace/component/trace/timer-shaft/Graph.ts @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Rect} from "./Rect.js"; + +export abstract class Graph { + frame: Rect + c: CanvasRenderingContext2D; + canvas: HTMLCanvasElement | undefined | null; + + protected constructor(canvas: HTMLCanvasElement | undefined | null, c: CanvasRenderingContext2D, frame: Rect) { + this.canvas = canvas; + this.frame = frame; + this.c = c; + } + + abstract draw(): void; +} diff --git a/host/ide/src/trace/component/trace/timer-shaft/RangeRuler.ts b/host/ide/src/trace/component/trace/timer-shaft/RangeRuler.ts new file mode 100644 index 0000000..3836cbf --- /dev/null +++ b/host/ide/src/trace/component/trace/timer-shaft/RangeRuler.ts @@ -0,0 +1,469 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Graph} from "./Graph.js"; +import {Rect} from "./Rect.js"; +import {ns2s, TimerShaftElement} from "../TimerShaftElement.js"; +import {ColorUtils} from "../base/ColorUtils.js"; +import {CpuStruct} from "../../../bean/CpuStruct.js"; + +const markPadding = 5; + +export class Mark extends Graph { + name: string | undefined + inspectionFrame: Rect + private _isHover: boolean = false + + constructor(canvas: HTMLCanvasElement | undefined | null, name: string, c: CanvasRenderingContext2D, frame: Rect) { + super(canvas, c, frame); + this.name = name; + this.inspectionFrame = new Rect(frame.x - markPadding, frame.y, frame.width + markPadding * 2, frame.height) + } + + get isHover(): boolean { + return this._isHover; + } + + set isHover(value: boolean) { + this._isHover = value; + if (value) { + document.body.style.cursor = 'ew-resize' + } else { + document.body.style.cursor = 'default' + } + } + + draw(): void { + this.c.beginPath(); + this.c.lineWidth = 7 + this.c.strokeStyle = '#999999' + this.c.moveTo(this.frame.x, this.frame.y); + this.c.lineTo(this.frame.x, this.frame.y + this.frame.height / 3) + this.c.stroke(); + this.c.lineWidth = 1 + this.c.strokeStyle = '#999999' + this.c.moveTo(this.frame.x, this.frame.y); + this.c.lineTo(this.frame.x, this.frame.y + this.frame.height) + this.c.stroke(); + this.c.closePath(); + } +} + +export interface TimeRange { + totalNS: number + startX: number + endX: number + startNS: number + endNS: number + xs: Array + xsTxt: Array +} + +export class RangeRuler extends Graph { + public rangeRect: Rect + public markA: Mark + public markB: Mark + public range: TimeRange; + mouseDownOffsetX = 0 + mouseDownMovingMarkX = 0 + movingMark: Mark | undefined | null; + isMouseDown: boolean = false; + isMovingRange: boolean = false; + isNewRange: boolean = false; + markAX: number = 0; + markBX: number = 0; + isPress: boolean = false + pressFrameId: number = -1 + currentDuration: number = 0 + centerXPercentage: number = 0; + animaStartTime: number | undefined + animTime: number = 100; + p: number = 800; + private readonly notifyHandler: (r: TimeRange) => void; + private scale: number = 0; + //缩放级别 + private scales: Array = [50, 100, 200, 500, 1_000, 2_000, 5_000, 10_000, 20_000, 50_000, 100_000, 200_000, 500_000, + 1_000_000, 2_000_000, 5_000_000, 10_000_000, 20_000_000, 50_000_000, 100_000_000, 200_000_000, 500_000_000, + 1_000_000_000, 2_000_000_000, 5_000_000_000, 10_000_000_000, 20_000_000_000, 50_000_000_000, + 100_000_000_000, 200_000_000_000, 500_000_000_000]; + private _cpuUsage: Array<{ cpu: number, ro: number, rate: number }> = [] + + constructor(timerShaftEL:TimerShaftElement, frame: Rect, range: TimeRange, notifyHandler: (r: TimeRange) => void) { + super(timerShaftEL.canvas, timerShaftEL.ctx!, frame) + this.range = range; + this.notifyHandler = notifyHandler; + this.markA = new Mark(timerShaftEL.canvas, 'A', timerShaftEL.ctx!, new Rect(range.startX, frame.y, 1, frame.height)) + this.markB = new Mark(timerShaftEL.canvas, 'B', timerShaftEL.ctx!, new Rect(range.endX, frame.y, 1, frame.height)) + this.rangeRect = new Rect(range.startX, frame.y, range.endX - range.startX, frame.height) + } + + set cpuUsage(value: Array<{ cpu: number, ro: number, rate: number }>) { + this._cpuUsage = value + this.draw(); + } + + drawCpuUsage() { + let maxNum = Math.round(this._cpuUsage.length / 100) + let miniHeight = Math.round(this.frame.height / CpuStruct.cpuCount);//每格高度 + let miniWidth = Math.ceil(this.frame.width / 100);//每格宽度 + for (let i = 0; i < this._cpuUsage.length; i++) { + let it = this._cpuUsage[i] + this.c.fillStyle = ColorUtils.MD_PALETTE[it.cpu] + this.c.globalAlpha = it.rate + this.c.fillRect(this.frame.x + miniWidth * it.ro, this.frame.y + it.cpu * miniHeight, miniWidth, miniHeight) + } + } + + draw(discardNotify: boolean = false): void { + this.c.clearRect(this.frame.x - markPadding, this.frame.y, this.frame.width + markPadding * 2, this.frame.height) + this.c.beginPath(); + if (this._cpuUsage.length > 0) { + this.drawCpuUsage() + this.c.globalAlpha = 0; + } else { + this.c.globalAlpha = 1; + } + //绘制选中区域 + this.c.fillStyle = window.getComputedStyle(this.canvas!, null).getPropertyValue("background-color");//"#ffffff" + this.rangeRect.x = this.markA.frame.x < this.markB.frame.x ? this.markA.frame.x : this.markB.frame.x + this.rangeRect.width = Math.abs(this.markB.frame.x - this.markA.frame.x) + this.c.fillRect(this.rangeRect.x, this.rangeRect.y, this.rangeRect.width, this.rangeRect.height) + this.c.globalAlpha = 1; + this.c.globalAlpha = .5; + this.c.fillStyle = "#999999" + this.c.fillRect(this.frame.x, this.frame.y, this.rangeRect.x, this.rangeRect.height) + this.c.fillRect(this.rangeRect.x + this.rangeRect.width, this.frame.y, this.frame.width - this.rangeRect.width, this.rangeRect.height) + this.c.globalAlpha = 1; + this.c.closePath(); + this.markA.draw(); + this.markB.draw(); + if (this.notifyHandler) { + this.range.startX = this.rangeRect.x + this.range.endX = this.rangeRect.x + this.rangeRect.width + this.range.startNS = this.range.startX * this.range.totalNS / (this.canvas?.clientWidth || 0) + this.range.endNS = this.range.endX * this.range.totalNS / (this.canvas?.clientWidth || 0) + let l20 = (this.range.endNS - this.range.startNS) / 20; + let min = 0; + let max = 0; + let weight = 0; + for (let index = 0; index < this.scales.length; index++) { + if (this.scales[index] > l20) { + if (index > 0) { + min = this.scales[index - 1]; + } else { + min = 0; + } + max = this.scales[index]; + weight = (l20 - min) * 1.0 / (max - min); + if (weight > 0.243) { + this.scale = max; + } else { + this.scale = min; + } + break; + } + } + if (this.scale == 0) { + this.scale = this.scales[0]; + } + let tmpNs = 0; + let yu = this.range.startNS % this.scale; + let realW = (this.scale * this.frame.width) / (this.range.endNS - this.range.startNS); + let startX = 0; + if (this.range.xs) { + this.range.xs.length = 0 + } else { + this.range.xs = [] + } + if (this.range.xsTxt) { + this.range.xsTxt.length = 0 + } else { + this.range.xsTxt = [] + } + if (yu != 0) { + let firstNodeWidth = ((this.scale - yu) / this.scale * realW); + startX += firstNodeWidth; + tmpNs += yu; + this.range.xs.push(startX) + this.range.xsTxt.push(ns2s(tmpNs)) + } + while (tmpNs < this.range.endNS - this.range.startNS) { + startX += realW; + tmpNs += this.scale; + this.range.xs.push(startX) + this.range.xsTxt.push(ns2s(tmpNs)) + } + if (!discardNotify) { + this.notifyHandler(this.range) + } + } + } + + mouseDown(ev: MouseEvent) { + let x = ev.offsetX - (this.canvas?.offsetLeft || 0) + let y = ev.offsetY - (this.canvas?.offsetTop || 0) + this.isMouseDown = true; + this.mouseDownOffsetX = x; + if (this.markA.isHover) { + this.movingMark = this.markA; + this.mouseDownMovingMarkX = this.movingMark.frame.x || 0 + } else if (this.markB.isHover) { + this.movingMark = this.markB; + this.mouseDownMovingMarkX = this.movingMark.frame.x || 0 + } else { + this.movingMark = null; + } + if (this.rangeRect.containsWithPadding(x, y, 5, 0)) { + this.isMovingRange = true; + this.markAX = this.markA.frame.x; + this.markBX = this.markB.frame.x; + document.body.style.cursor = "move" + } else if (this.frame.containsWithMargin(x, y, 20, 0, 0, 0) && !this.rangeRect.containsWithMargin(x, y, 0, markPadding, 0, markPadding)) { + this.isNewRange = true; + } + } + + mouseUp(ev: MouseEvent) { + this.isMouseDown = false; + this.isMovingRange = false; + this.isNewRange = false; + this.movingMark = null; + } + + mouseMove(ev: MouseEvent) { + let x = ev.offsetX - (this.canvas?.offsetLeft || 0); + let y = ev.offsetY - (this.canvas?.offsetTop || 0) + this.centerXPercentage = x / (this.canvas?.clientWidth || 0) + if (this.centerXPercentage <= 0) { + this.centerXPercentage = 0 + } else if (this.centerXPercentage >= 1) { + this.centerXPercentage = 1 + } + let maxX = this.canvas?.clientWidth || 0 + if (this.markA.inspectionFrame.contains(x, y)) { + this.markA.isHover = true + } else if (this.markB.inspectionFrame.contains(x, y)) { + this.markB.isHover = true; + } else { + this.markA.isHover = false; + this.markB.isHover = false; + } + if (this.movingMark) { + let result = x - this.mouseDownOffsetX + this.mouseDownMovingMarkX; + if (result >= 0 && result <= maxX) { + this.movingMark.frame.x = result + } else if (result < 0) { + this.movingMark.frame.x = 0 + } else { + this.movingMark.frame.x = maxX + } + this.movingMark.inspectionFrame.x = this.movingMark.frame.x - markPadding + requestAnimationFrame(() => this.draw()); + } else if (this.rangeRect.containsWithPadding(x, y, markPadding, 0)) { + document.body.style.cursor = "move" + } else if (this.frame.containsWithMargin(x, y, 20, 0, 0, 0) && !this.rangeRect.containsWithMargin(x, y, 0, markPadding, 0, markPadding)) { + document.body.style.cursor = "crosshair"; + } + if (this.isMovingRange && this.isMouseDown) { + let result = x - this.mouseDownOffsetX; + let mA = result + this.markAX + let mB = result + this.markBX + if (mA >= 0 && mA <= maxX) { + this.markA.frame.x = mA + } else if (mA < 0) { + this.markA.frame.x = 0 + } else { + this.markA.frame.x = maxX + } + this.markA.inspectionFrame.x = this.markA.frame.x - markPadding + if (mB >= 0 && mB <= maxX) { + this.markB.frame.x = mB; + } else if (mB < 0) { + this.markB.frame.x = 0 + } else { + this.markB.frame.x = maxX + } + this.markB.inspectionFrame.x = this.markB.frame.x - markPadding + requestAnimationFrame(() => this.draw()); + } else if (this.isNewRange) { + this.markA.frame.x = this.mouseDownOffsetX; + this.markA.inspectionFrame.x = this.mouseDownOffsetX - markPadding; + if (x >= 0 && x <= maxX) { + this.markB.frame.x = x; + } else if (x < 0) { + this.markB.frame.x = 0; + } else { + this.markB.frame.x = maxX; + } + this.markB.inspectionFrame.x = this.markB.frame.x - markPadding; + requestAnimationFrame(() => this.draw()); + } + } + + mouseOut(ev: MouseEvent) { + this.movingMark = null; + } + + fillX() { + if (this.range.startNS < 0) this.range.startNS = 0; + if (this.range.endNS < 0) this.range.endNS = 0; + if (this.range.endNS > this.range.totalNS) this.range.endNS = this.range.totalNS; + if (this.range.startNS > this.range.totalNS) this.range.startNS = this.range.totalNS; + this.range.startX = this.range.startNS * (this.canvas?.clientWidth || 0) / this.range.totalNS + this.range.endX = this.range.endNS * (this.canvas?.clientWidth || 0) / this.range.totalNS + this.markA.frame.x = this.range.startX + this.markA.inspectionFrame.x = this.markA.frame.x - markPadding + this.markB.frame.x = this.range.endX + this.markB.inspectionFrame.x = this.markB.frame.x - markPadding + } + + setRangeNS(startNS:number,endNS:number) { + this.range.startNS = startNS + this.range.endNS = endNS + this.fillX() + this.draw(); + } + getRange():TimeRange{ + return this.range; + } + + keyPress(ev: KeyboardEvent) { + if (this.animaStartTime === undefined) { + this.animaStartTime = new Date().getTime(); + } + let startTime = new Date().getTime(); + let duration = (startTime - this.animaStartTime); + if (duration < this.animTime) duration = this.animTime + this.currentDuration = duration + if (this.isPress) return + this.isPress = true + switch (ev.key.toLocaleLowerCase()) { + case "w": + let animW = () => { + if (this.scale === 50) return; + this.range.startNS += (this.centerXPercentage * this.currentDuration * 2 * this.scale / this.p); + this.range.endNS -= ((1 - this.centerXPercentage) * this.currentDuration * 2 * this.scale / this.p); + this.fillX(); + this.draw(); + this.pressFrameId = requestAnimationFrame(animW) + } + this.pressFrameId = requestAnimationFrame(animW) + break; + case "s": + let animS = () => { + if (this.range.startNS <= 0 && this.range.endNS >= this.range.totalNS) return; + this.range.startNS -= (this.centerXPercentage * this.currentDuration * 2 * this.scale / this.p); + this.range.endNS += ((1 - this.centerXPercentage) * this.currentDuration * 2 * this.scale / this.p); + this.fillX(); + this.draw(); + this.pressFrameId = requestAnimationFrame(animS) + } + this.pressFrameId = requestAnimationFrame(animS) + break; + case "a": + let animA = () => { + if (this.range.startNS == 0) return; + let s = this.scale / this.p * this.currentDuration; + this.range.startNS -= s; + this.range.endNS -= s; + this.fillX(); + this.draw(); + this.pressFrameId = requestAnimationFrame(animA) + } + this.pressFrameId = requestAnimationFrame(animA) + break; + case "d": + let animD = () => { + if (this.range.endNS >= this.range.totalNS) return; + this.range.startNS += this.scale / this.p * this.currentDuration; + this.range.endNS += this.scale / this.p * this.currentDuration; + this.fillX(); + this.draw(); + this.pressFrameId = requestAnimationFrame(animD) + } + this.pressFrameId = requestAnimationFrame(animD) + break; + } + } + + keyUp(ev: KeyboardEvent) { + this.animaStartTime = undefined; + this.isPress = false + if (this.pressFrameId != -1) { + cancelAnimationFrame(this.pressFrameId) + } + let startTime = new Date().getTime(); + switch (ev.key) { + case "w": + let animW = () => { + if (this.scale === 50) return; + let dur = (new Date().getTime() - startTime); + this.range.startNS += (this.centerXPercentage * 100 * this.scale / this.p); + this.range.endNS -= ((1 - this.centerXPercentage) * 100 * this.scale / this.p); + this.fillX(); + this.draw(); + if (dur < 200) { + requestAnimationFrame(animW) + } + } + requestAnimationFrame(animW) + break; + case "s": + let animS = () => { + if (this.range.startNS <= 0 && this.range.endNS >= this.range.totalNS) return; + let dur = (new Date().getTime() - startTime); + this.range.startNS -= (this.centerXPercentage * 100 * this.scale / this.p); + this.range.endNS += ((1 - this.centerXPercentage) * 100 * this.scale / this.p); + this.fillX(); + this.draw(); + if (dur < 200) { + requestAnimationFrame(animS) + } + } + requestAnimationFrame(animS) + break; + case "a": + let animA = () => { + if (this.range.startNS <= 0) return + let dur = (new Date().getTime() - startTime); + let s = this.scale * 80 / this.p; + this.range.startNS -= s; + this.range.endNS -= s; + this.fillX(); + this.draw(); + if (dur < 200) { + requestAnimationFrame(animA) + } + } + animA(); + break; + case "d": + let animD = () => { + if (this.range.endNS >= this.range.totalNS) return; + let dur = (new Date().getTime() - startTime); + let s = this.scale * 80 / this.p; + this.range.startNS += s; + this.range.endNS += s; + this.fillX(); + this.draw(); + if (dur < 200) { + requestAnimationFrame(animD) + } + } + animD(); + break; + } + } +} diff --git a/host/ide/src/trace/component/trace/timer-shaft/Rect.ts b/host/ide/src/trace/component/trace/timer-shaft/Rect.ts new file mode 100644 index 0000000..1197ec5 --- /dev/null +++ b/host/ide/src/trace/component/trace/timer-shaft/Rect.ts @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class Rect { + x: number = 0 + y: number = 0 + width: number = 0 + height: number = 0 + + constructor(x: number, y: number, width: number, height: number) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + static contains(rect: Rect, x: number, y: number): boolean { + return rect.x <= x && x <= rect.x + rect.width && rect.y <= y && y <= rect.y + rect.height; + } + + static containsWithPadding(rect: Rect, x: number, y: number, paddingLeftRight: number, paddingTopBottom: number): boolean { + return rect.x + paddingLeftRight <= x + && x <= rect.x + rect.width - paddingLeftRight + && rect.y + paddingTopBottom <= y + && y <= rect.y + rect.height - paddingTopBottom; + } + + static containsWithMargin(rect: Rect, x: number, y: number, t: number, r: number, b: number, l: number): boolean { + return rect.x - l <= x + && x <= rect.x + rect.width + r + && rect.y - t <= y + && y <= rect.y + rect.height + b; + } + + static intersect(r1: Rect, rect: Rect): boolean { + let maxX = r1.x + r1.width > rect.x + rect.width ? r1.x + r1.width : rect.x + rect.width; + let maxY = r1.y + r1.height > rect.y + rect.height ? r1.y + r1.height : rect.y + rect.height; + let minX = r1.x < rect.x ? r1.x : rect.x; + let minY = r1.y < rect.y ? r1.y : rect.y; + if (maxX - minX < rect.width + r1.width && maxY - minY < r1.height + rect.height) { + return true; + } else { + return false; + } + } + + contains(x: number, y: number): boolean { + return this.x <= x && x <= this.x + this.width && this.y <= y && y <= this.y + this.height; + } + + containsWithPadding(x: number, y: number, paddingLeftRight: number, paddingTopBottom: number): boolean { + return this.x + paddingLeftRight <= x + && x <= this.x + this.width - paddingLeftRight + && this.y + paddingTopBottom <= y + && y <= this.y + this.height - paddingTopBottom; + } + + containsWithMargin(x: number, y: number, t: number, r: number, b: number, l: number): boolean { + return this.x - l <= x + && x <= this.x + this.width + r + && this.y - t <= y + && y <= this.y + this.height + b; + } + + /** + * 判断是否相交 + * @param rect + */ + intersect(rect: Rect): boolean { + let maxX = this.x + this.width >= rect.x + rect.width ? this.x + this.width : rect.x + rect.width; + let maxY = this.y + this.height >= rect.y + rect.height ? this.y + this.height : rect.y + rect.height; + let minX = this.x <= rect.x ? this.x : rect.x; + let minY = this.y <= rect.y ? this.y : rect.y; + if (maxX - minX <= rect.width + this.width && maxY - minY <= this.height + rect.height) { + return true; + } else { + return false; + } + } +} + +export class Point { + x: number = 0 + y: number = 0 + + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } +} diff --git a/host/ide/src/trace/component/trace/timer-shaft/SportRuler.ts b/host/ide/src/trace/component/trace/timer-shaft/SportRuler.ts new file mode 100644 index 0000000..ac43c90 --- /dev/null +++ b/host/ide/src/trace/component/trace/timer-shaft/SportRuler.ts @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Graph} from "./Graph.js"; +import {Rect} from "./Rect.js"; +import {TimeRange} from "./RangeRuler.js"; +import {Flag} from "./Flag.js"; +import {ns2s, ns2x, randomRgbColor, TimerShaftElement} from "../TimerShaftElement.js"; +import {TraceRow} from "../base/TraceRow.js"; +import {SpApplication} from "../../../SpApplication.js"; + +export class SportRuler extends Graph { + public flagList: Array = []; + private hoverFlag: Flag = new Flag(0, 0, 0, 0, 0); + private lineColor: string | null = null; + private rulerW = 0; + private _range: TimeRange = {} as TimeRange; + private readonly notifyHandler: ((hoverFlag: Flag | undefined | null, selectFlag: Flag | undefined | null) => void) | undefined; + private readonly flagClickHandler: ((flag: Flag | undefined | null) => void) | undefined; + private invertedTriangleTime: number | null | undefined = null; + isRangeSelect: boolean = false;//region selection + constructor(timerShaftEL: TimerShaftElement, frame: Rect, notifyHandler: (hoverFlag: Flag | undefined | null, selectFlag: Flag | undefined | null) => void, flagClickHandler: (flag: Flag | undefined | null) => void) { + super(timerShaftEL.canvas, timerShaftEL.ctx!, frame) + this.notifyHandler = notifyHandler; + this.flagClickHandler = flagClickHandler; + } + + get range(): TimeRange { + return this._range; + } + + set range(value: TimeRange) { + this._range = value; + this.draw() + } + + modifyFlagList(flag: Flag | null | undefined) { + if (flag) { + if (flag.hidden) { + let i = this.flagList.findIndex(it => it.time == flag.time) + this.flagList.splice(i, 1) + } else { + let i = this.flagList.findIndex(it => it.time == flag.time) + this.flagList[i] = flag; + } + } else { + this.flagList.forEach(it => it.selected = false); + } + this.draw(); + } + + draw(): void { + this.rulerW = this.canvas!.offsetWidth + this.c.clearRect(this.frame.x, this.frame.y, this.frame.width, this.frame.height+1) + this.c.beginPath(); + this.lineColor = window.getComputedStyle(this.canvas!, null).getPropertyValue("color"); + this.c.strokeStyle = this.lineColor //"#dadada" + this.c.lineWidth = 1; + this.c.moveTo(this.frame.x, this.frame.y) + this.c.lineTo(this.frame.x + this.frame.width, this.frame.y) + this.c.stroke(); + this.c.closePath(); + this.c.beginPath(); + this.c.lineWidth = 3; + this.c.strokeStyle = "#999999" + this.c.moveTo(this.frame.x, this.frame.y) + this.c.lineTo(this.frame.x, this.frame.y + this.frame.height) + this.c.stroke(); + this.c.closePath(); + this.c.beginPath(); + this.c.lineWidth = 1; + this.c.strokeStyle = this.lineColor;//"#999999" + this.c.fillStyle = '#999999' + this.c.font = '8px sans-serif' + this.range.xs?.forEach((it, i) => { + this.c.moveTo(it, this.frame.y) + this.c.lineTo(it, this.frame.y + this.frame.height) + this.c.fillText(`+${this.range.xsTxt[i]}`, it + 3, this.frame.y + 12) + }) + this.c.stroke(); + this.c.closePath(); + //绘制旗子 + this.flagList.forEach((flagObj: Flag, b) => { + if (flagObj.time >= this.range.startNS && flagObj.time <= this.range.endNS) { + flagObj.x = Math.round(this.rulerW * (flagObj.time - this.range.startNS) / (this.range.endNS - this.range.startNS)); + this.drawFlag(flagObj.x, flagObj.color, flagObj.selected, flagObj.text, flagObj.type) + } + }) + !this.hoverFlag.hidden && this.drawFlag(this.hoverFlag.x, this.hoverFlag.color, true, this.hoverFlag.text) + //If region selection is enabled, the serial number draws a line on the axis to show the length of the box selection + if (this.isRangeSelect) { + let range = TraceRow.rangeSelectObject; + this.c.beginPath(); + if (document.querySelector("sp-application")!.dark) { + this.c.strokeStyle = "#FFF" + this.c.fillStyle = "#FFF" + } else { + this.c.strokeStyle = "#000" + this.c.fillStyle = "#000" + } + let startX = ns2x(range?.startNS || 0, this.range.startNS, this.range.endNS, this.range.totalNS, this.frame); + let endX = ns2x(range?.endNS || 0, this.range.startNS, this.range.endNS, this.range.totalNS, this.frame); + let lineWidth = endX - startX; + let txt = ns2s((range?.endNS || 0) - (range?.startNS || 0)); + this.c.moveTo(startX, this.frame.y + 22); + this.c.lineTo(endX, this.frame.y + 22); + this.c.moveTo(startX, this.frame.y + 22 - 5); + this.c.lineTo(startX, this.frame.y + 22 + 5); + this.c.moveTo(endX, this.frame.y + 22 - 5); + this.c.lineTo(endX, this.frame.y + 22 + 5); + let txtWidth = this.c.measureText(txt).width; + if (lineWidth > txtWidth) { + this.c.fillText(`${txt}`, startX + (lineWidth - txtWidth) / 2, this.frame.y + 20) + } else { + if (endX + txtWidth >= this.frame.width) { + this.c.fillText(`${txt}`, startX - 5 - txtWidth, this.frame.y + 20) + } else { + this.c.fillText(`${txt}`, endX + 5, this.frame.y + 20) + } + } + this.c.stroke(); + this.c.closePath(); + } + if (this.invertedTriangleTime != null && typeof (this.invertedTriangleTime) != undefined) { + this.drawInvertedTriangle(this.invertedTriangleTime,document.querySelector("sp-application")!.dark?"#FFFFFF":"#000000") + } + } + + drawTriangle(time: number, type: string) { + if (time != null && typeof (time) != undefined) { + let i = this.flagList.findIndex(it => it.time == time); + if (type == "triangle") { + let triangle = this.flagList.findIndex(it => it.type == type); + if (i !== -1) { + if (triangle !== -1) { + this.flagList[i].type == "" ? this.flagList.splice(triangle, 1) : "" + } + this.flagList.forEach(it => it.selected = false) + this.flagList[i].selected = true; + + } else { + if (triangle == -1) { + this.flagList.forEach(it => it.selected = false) + this.flagList.push(new Flag(0, 125, 18, 18, time, randomRgbColor(), true, "triangle")); + } else { + this.flagList.forEach(it => it.selected = false) + this.flagList[triangle].time = time; + this.flagList[triangle].selected = true; + } + } + } else if (type == "square") { + if (i != -1) { + this.flagList[i].type = ""; + } + } else if (type == "inverted") { + this.invertedTriangleTime = time + } + this.draw(); + this.notifyHandler && this.notifyHandler( + !this.hoverFlag.hidden ? this.hoverFlag : null, + this.flagList.find(it => it.selected) || null + ) + } + } + + removeTriangle(type:string){ + if (type == "inverted") { + this.invertedTriangleTime = null; + }else { + let i = this.flagList.findIndex(it => it.type == type) + if (i !== -1) { + this.flagList.splice(i, 1) + } + } + this.draw(); + this.notifyHandler && this.notifyHandler( + !this.hoverFlag.hidden ? this.hoverFlag : null, + this.flagList.find(it => it.selected) || null + ) + } + + drawInvertedTriangle(time: number, color: string = "#000000"){ + if (time != null && typeof (time) != undefined) { + let x = Math.round(this.rulerW * (time - this.range.startNS) / (this.range.endNS - this.range.startNS)); + this.c.beginPath(); + this.c.fillStyle = color; + this.c.strokeStyle = color; + this.c.moveTo(x-2, 142); + this.c.lineTo(x+2, 142); + this.c.lineTo(x, 145); + this.c.fill() + this.c.closePath() + this.c.stroke(); + } + } + + //绘制旗子 + drawFlag(x: number, color: string = "#999999", isFill: boolean = false, text: string = "", type: string = "") { + this.c.beginPath(); + this.c.fillStyle = color; + this.c.strokeStyle = color; + this.c.moveTo(x, 125); + if (type == "triangle") { + this.c.lineTo(x + 15, 131); + } else { + this.c.lineTo(x + 10, 125); + this.c.lineTo(x + 10, 127); + this.c.lineTo(x + 18, 127); + this.c.lineTo(x + 18, 137); + this.c.lineTo(x + 10, 137); + this.c.lineTo(x + 10, 135); + } + this.c.lineTo(x + 2, 135); + this.c.lineTo(x + 2, 142); + this.c.lineTo(x, 142); + this.c.closePath() + isFill && this.c.fill() + this.c.stroke(); + if (text !== "") { + this.c.font = "10px Microsoft YaHei" + const {width} = this.c.measureText(text); + this.c.fillStyle = 'rgba(255, 255, 255, 0.8)'; // + this.c.fillRect(x + 21, 132, width + 4, 12); + this.c.fillStyle = "black"; + this.c.fillText(text, x + 23, 142); + this.c.stroke(); + } + } + + mouseUp(ev: MouseEvent) { + if (this.edgeDetection(ev)) { + this.flagList.forEach(it => it.selected = false) + let x = ev.offsetX - (this.canvas?.offsetLeft || 0) + let findFlag = this.flagList.find(it => x >= it.x && x <= it.x + 18) + if (findFlag) { + findFlag.selected = true; + } else { + let flagAtRulerTime = Math.round((this.range.endNS - this.range.startNS) * x / this.rulerW) + if (flagAtRulerTime > 0 && (this.range.startNS + flagAtRulerTime) < this.range.endNS) { + this.flagList.push(new Flag(x, 125, 18, 18, flagAtRulerTime + this.range.startNS, randomRgbColor(), true)); + } + } + this.flagClickHandler && this.flagClickHandler(this.flagList.find(it => it.selected)); + } + } + + mouseMove(ev: MouseEvent) { + if (this.edgeDetection(ev)) { + let x = ev.offsetX - (this.canvas?.offsetLeft || 0) + let flg = this.flagList.find((it) => x >= it.x && x <= it.x + 18); + if (flg) { + this.hoverFlag.hidden = true; + } else { + this.hoverFlag.hidden = false; + this.hoverFlag.x = x; + this.hoverFlag.color = "#999999"; + } + } else { + this.hoverFlag.hidden = true; + } + this.draw(); + this.notifyHandler && this.notifyHandler( + !this.hoverFlag.hidden ? this.hoverFlag : null, + this.flagList.find(it => it.selected) || null + ) + } + + static isMouseInSportRuler = false; + + edgeDetection(ev: MouseEvent): boolean { + let x = ev.offsetX - (this.canvas?.offsetLeft || 0) + let y = ev.offsetY - (this.canvas?.offsetTop || 0) + SportRuler.isMouseInSportRuler = x > 0 && x < this.canvas!.offsetWidth && ev.offsetY - this.frame.y > 20 && ev.offsetY - this.frame.y < this.frame.height; + return SportRuler.isMouseInSportRuler; + } +} diff --git a/host/ide/src/trace/component/trace/timer-shaft/TabPaneFlag.ts b/host/ide/src/trace/component/trace/timer-shaft/TabPaneFlag.ts new file mode 100644 index 0000000..e397fbe --- /dev/null +++ b/host/ide/src/trace/component/trace/timer-shaft/TabPaneFlag.ts @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseElement, element} from "../../../../base-ui/BaseElement.js"; +import {Flag} from "./Flag.js"; +import {ns2s} from "../TimerShaftElement.js"; + +@element('tabpane-flag') +export class TabPaneFlag extends BaseElement { + private flagListIdx: number | null = null; + private flag: Flag | null = null; + + initElements(): void { + this.shadowRoot?.querySelector("#color-input")?.addEventListener("change", (event: any) => { + if (this.flag) { + this.flag.color = event?.target.value + document.dispatchEvent(new CustomEvent('flag-change', {detail: this.flag})); + } + }); + this.shadowRoot?.querySelector("#text-input")?.addEventListener("keydown", (event: any) => { + if (event.keyCode == "13") { + if (this.flag) { + this.flag.text = event?.target.value + document.dispatchEvent(new CustomEvent('flag-change', {detail: this.flag})); + } + } + }); + this.shadowRoot?.querySelector("#remove-flag")?.addEventListener("click", (event: any) => { + if (this.flag) { + this.flag.hidden = true; + document.dispatchEvent(new CustomEvent('flag-change', {detail: this.flag})); + } + }); + } + + setFlagObj(flagObj: Flag) { + this.flag = flagObj; + this.shadowRoot!.querySelector("#color-input")!.value = flagObj.color; + this.shadowRoot!.querySelector("#text-input")!.value = flagObj.text; + this.shadowRoot!.querySelector("#flag-time")!.innerHTML = ns2s(flagObj.time) + } + + initHtml(): string { + return ` + +
      +
      Annotation at
      + + Change color: + +
      + `; + } + +} diff --git a/host/ide/src/trace/component/trace/timer-shaft/TimeRuler.ts b/host/ide/src/trace/component/trace/timer-shaft/TimeRuler.ts new file mode 100644 index 0000000..a7ae0cc --- /dev/null +++ b/host/ide/src/trace/component/trace/timer-shaft/TimeRuler.ts @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Graph} from "./Graph.js"; +import {Rect} from "./Rect.js"; +import {ns2s, TimerShaftElement} from "../TimerShaftElement.js"; + +export class TimeRuler extends Graph { + totalNS: number + private stepSmall: number; + private step: number; + private stepNS: number; + + constructor(timerShaftEL: TimerShaftElement, frame: Rect, totalNS: number = 10_000_000_000) { + super(timerShaftEL.canvas, timerShaftEL.ctx!, frame) + this.totalNS = totalNS; + this.step = this.frame.width / 10; + this.stepSmall = this.frame.width / 100; + this.stepNS = this.totalNS / 10; + } + + draw() { + this.step = this.frame.width / 10; + this.stepSmall = this.frame.width / 100; + this.stepNS = this.totalNS / 10; + this.c.clearRect(this.frame.x, this.frame.y, this.frame.width, this.frame.height) + this.c.beginPath(); + this.c.strokeStyle = "#999" + this.c.lineWidth = 1; + for (let i = 0; i <= 10; i++) { + let x = Math.floor(i * this.step) + this.frame.x; + this.c.moveTo(x, 0); + this.c.lineTo(x, this.frame.height); + if (i == 10) break; + for (let j = 1; j < 10; j++) { + this.c.moveTo(x + Math.floor(j * this.stepSmall), 0); + this.c.lineTo(x + Math.floor(j * this.stepSmall), this.frame.height / 4); + } + this.c.fillStyle = '#999' + this.c.fillText(`${ns2s(i * this.stepNS)}`, x + 5, this.frame.height - 1) + } + this.c.stroke(); + this.c.closePath(); + } +} + + + diff --git a/host/ide/src/trace/database/Procedure.ts b/host/ide/src/trace/database/Procedure.ts new file mode 100644 index 0000000..8793eaf --- /dev/null +++ b/host/ide/src/trace/database/Procedure.ts @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class ProcedureThread extends Worker { + busy: boolean = false; + isCancelled: boolean = false; + id: number = -1; + taskMap: any = {}; + name: string | undefined + + uuid(): string { + // @ts-ignore + return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)); + } + + queryFunc(type: string, args: any, transfer: any, handler: Function) { + this.busy = true; + let id = this.uuid(); + this.taskMap[id] = handler + let pam = { + id: id, + type: type, + params: args, + } + if (transfer) { + try { + this.postMessage(pam, [transfer]); + } catch (e: any) { + } + } else { + this.postMessage(pam); + } + } + + cancel() { + this.isCancelled = true; + this.terminate(); + } +} + +class ProcedurePool { + static cpuCount = Math.floor((window.navigator.hardwareConcurrency || 4) / 2); + maxThreadNumber: number = 1 + works: Array = [] + proxyRunningNum: any; + timelineChange: ((a: any) => void) | undefined | null = null; + cpusLen = ProcedurePool.build('cpu', 8); + freqLen = ProcedurePool.build('freq', 2); + processLen = ProcedurePool.build('process', 8); + // names = [...this.cpusLen, ...this.freqLen, ...this.processLen, ...this.memLen, ...this.threadLen, ...this.funcLen]; + names = [...this.cpusLen, ...this.processLen, ...this.freqLen]; + onComplete: Function | undefined;//任务完成回调 + + constructor(threadBuild: (() => ProcedureThread) | undefined = undefined) { + this.init(threadBuild); + } + + static build(name: string, len: number) { + return [...Array(len).keys()].map(it => `${name}${it}`) + } + + init(threadBuild: (() => ProcedureThread) | undefined = undefined) { + this.maxThreadNumber = this.names.length + for (let i = 0; i < this.maxThreadNumber; i++) { + this.newThread(); + } + } + + newThread() { + let thread: ProcedureThread = new ProcedureThread("trace/database/ProcedureWorker.js", {type: "module"}) + thread.name = this.names[this.works.length] + thread.onmessage = (event: MessageEvent) => { + thread.busy = false; + if ((event.data.type as string) == "timeline-range-changed") { + this.timelineChange && this.timelineChange(event.data.results); + thread.busy = false; + return; + } + if (Reflect.has(thread.taskMap, event.data.id)) { + if (event.data) { + let fun = thread.taskMap[event.data.id]; + if (fun) { + fun(event.data.results, event.data.hover); + } + Reflect.deleteProperty(thread.taskMap, event.data.id) + } + } + if (this.isIdle() && this.onComplete) { + this.onComplete(); + } + } + thread.onmessageerror = e => { + } + thread.onerror = e => { + } + thread.id = this.works.length + thread.busy = false + this.works?.push(thread) + return thread; + } + + close = () => { + for (let i = 0; i < this.works.length; i++) { + let thread = this.works[i]; + thread.terminate(); + } + this.works.length = 0; + } + + clearCache = () => { + for (let i = 0; i < this.works.length; i++) { + let thread = this.works[i]; + thread.queryFunc("clear", {}, undefined, () => { + }) + } + } + + submitWithName(name: string, type: string, args: any, transfer: any, handler: Function): ProcedureThread | undefined { + let noBusyThreads = this.works.filter(it => it.name === name); + let thread: ProcedureThread | undefined + if (noBusyThreads.length > 0) { //取第一个空闲的线程进行任务 + thread = noBusyThreads[0]; + thread!.queryFunc(type, args, transfer, handler) + } + return thread; + } + + isIdle() { + return this.works.every(it => !it.busy); + } +} + + +export const procedurePool = new ProcedurePool() \ No newline at end of file diff --git a/host/ide/src/trace/database/ProcedureWorker.ts b/host/ide/src/trace/database/ProcedureWorker.ts new file mode 100644 index 0000000..0adeae3 --- /dev/null +++ b/host/ide/src/trace/database/ProcedureWorker.ts @@ -0,0 +1,457 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {cpu, CpuStruct, WakeupBean} from "./ProcedureWorkerCPU.js"; +import {drawFlagLine, drawLines, ns2s, ns2x, Rect} from "./ProcedureWorkerCommon.js"; +import {CpuFreqStruct, freq} from "./ProcedureWorkerFreq.js"; +import {proc, ProcessStruct} from "./ProcedureWorkerProcess.js"; +import {mem, ProcessMemStruct} from "./ProcedureWorkerMem.js"; +import {thread, ThreadStruct} from "./ProcedureWorkerThread.js"; +import {func, FuncStruct} from "./ProcedureWorkerFunc.js"; +import {fps, FpsStruct} from "./ProcedureWorkerFPS.js"; +import {heap, HeapStruct} from "./ProcedureWorkerHeap.js"; +import {timeline} from "./ProcedureWorkerTimeline.js"; + +let dataList: any = {} +let dataFilter: any = {} +let canvasList: any = {} +let contextList: any = {} + +function drawSelection(context: any, params: any) { + if (params.isRangeSelect) { + params.rangeSelectObject!.startX = Math.floor(ns2x(params.rangeSelectObject!.startNS!, params.startNS, params.endNS, params.totalNS, params.frame)); + params.rangeSelectObject!.endX = Math.floor(ns2x(params.rangeSelectObject!.endNS!, params.startNS, params.endNS, params.totalNS, params.frame)); + if (context) { + context.globalAlpha = 0.5 + context.fillStyle = "#666666" + context.fillRect(params.rangeSelectObject!.startX!, params.frame.y, params.rangeSelectObject!.endX! - params.rangeSelectObject!.startX!, params.frame.height) + context.globalAlpha = 1 + } + } +} + +function drawWakeUp(context: CanvasRenderingContext2D | any, wake: WakeupBean | null, startNS: number, endNS: number, totalNS: number, frame: Rect, selectCpuStruct: CpuStruct | undefined = undefined, currentCpu: number | undefined = undefined) { + if (wake) { + let x1 = Math.floor(ns2x((wake.wakeupTime || 0), startNS, endNS, totalNS, frame)); + context.beginPath(); + context.lineWidth = 2; + context.fillStyle = "#000000"; + if (x1 > 0 && x1 < frame.x + frame.width) { + context.moveTo(x1, frame.y); + context.lineTo(x1, frame.y + frame.height); + if (currentCpu == wake.cpu) { + let centerY = Math.floor(frame.y + frame.height / 2); + context.moveTo(x1, centerY - 6); + context.lineTo(x1 + 4, centerY); + context.lineTo(x1, centerY + 6); + context.lineTo(x1 - 4, centerY); + context.lineTo(x1, centerY - 6); + context.fill(); + } + } + if (selectCpuStruct) { + let x2 = Math.floor(ns2x((selectCpuStruct.startTime || 0), startNS, endNS, totalNS, frame)); + let y = frame.y + frame.height - 10; + context.moveTo(x1, y); + context.lineTo(x2, y); + + let s = ns2s((selectCpuStruct.startTime || 0) - (wake.wakeupTime || 0)); + let distance = x2 - x1; + if (distance > 12) { + context.moveTo(x1, y); + context.lineTo(x1 + 6, y - 3); + context.moveTo(x1, y); + context.lineTo(x1 + 6, y + 3); + context.moveTo(x2, y); + context.lineTo(x2 - 6, y - 3); + context.moveTo(x2, y); + context.lineTo(x2 - 6, y + 3); + let measure = context.measureText(s); + let tHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent + let xStart = x1 + Math.floor(distance / 2 - measure.width / 2); + if (distance > measure.width + 4) { + context.fillStyle = "#ffffff" + context.fillRect(xStart - 2, y - 4 - tHeight, measure.width + 4, tHeight + 4); + context.font = "10px solid"; + context.fillStyle = "#000000"; + context.textBaseline = "bottom"; + context.fillText(s, xStart, y - 2); + } + + } + } + context.strokeStyle = "#000000"; + context.stroke(); + context.closePath(); + } +} + +self.onmessage = function (e: any) { + if ((e.data.type as string).startsWith("clear")) { + dataList = {}; + dataFilter = {}; + canvasList = {}; + contextList = {}; + // @ts-ignore + self.postMessage({ + id: e.data.id, + type: e.data.type, + results: null, + }); + return; + } + let res: any + if (e.data.params.list) { + dataList[e.data.type] = e.data.params.list; + dataFilter[e.data.type] = new Set(); + if (e.data.params.offscreen) { + canvasList[e.data.type] = e.data.params.offscreen; + contextList[e.data.type] = e.data.params.offscreen!.getContext('2d'); + contextList[e.data.type].scale(e.data.params.dpr, e.data.params.dpr); + } + } + if(!dataFilter[e.data.type]){ + dataFilter[e.data.type] = new Set(); + } + let canvas = canvasList[e.data.type]; + let context = contextList[e.data.type]; + let type = e.data.type as string; + let params = e.data.params; + let isRangeSelect = e.data.params.isRangeSelect; + let isHover = e.data.params.isHover; + let xs = e.data.params.xs; + let frame = e.data.params.frame; + let flagMoveInfo = e.data.params.flagMoveInfo; + let flagSelectedInfo = e.data.params.flagSelectedInfo; + let hoverX = e.data.params.hoverX; + let hoverY = e.data.params.hoverY; + let startNS = e.data.params.startNS; + let endNS = e.data.params.endNS; + let totalNS = e.data.params.totalNS; + let canvasWidth = e.data.params.canvasWidth; + let canvasHeight = e.data.params.canvasHeight; + let useCache = e.data.params.useCache; + let lineColor = e.data.params.lineColor; + let wakeupBean: WakeupBean | null = e.data.params.wakeupBean; + if (canvas) { + if (canvas.width !== canvasWidth || canvas.height !== canvasHeight) { + canvas.width = canvasWidth; + canvas.height = canvasHeight; + context.scale(e.data.params.dpr, e.data.params.dpr); + } + } + if (type.startsWith("timeline")) { + timeline(canvas, context, startNS, endNS, totalNS, frame, + e.data.params.keyPressCode, e.data.params.keyUpCode, + e.data.params.mouseDown, e.data.params.mouseUp, + e.data.params.mouseMove, e.data.params.mouseOut, + e.data.params.offsetLeft, e.data.params.offsetTop, + (a: any) => { + //@ts-ignore + self.postMessage({ + id: "timeline", + type: "timeline-range-changed", + results: a, + }); + } + ); + // @ts-ignore + self.postMessage({ + id: e.data.id, + type: type, + results: null, + }); + } else if (type.startsWith("cpu")) { + if (!useCache) { + cpu(dataList[type], dataFilter[type], startNS, endNS, totalNS, frame); + } + if (canvas) { + context.clearRect(0, 0, canvas.width, canvas.height); + context.beginPath(); + drawLines(context, xs, frame.height, lineColor); + CpuStruct.hoverCpuStruct = undefined; + if (isHover) { + for (let re of dataFilter[e.data.type]) { + if (hoverX >= re.frame.x && hoverX <= re.frame.x + re.frame.width && hoverY >= re.frame.y && hoverY <= re.frame.y + re.frame.height) { + CpuStruct.hoverCpuStruct = re; + break; + } + } + } else { + CpuStruct.hoverCpuStruct = e.data.params.hoverCpuStruct; + } + CpuStruct.selectCpuStruct = e.data.params.selectCpuStruct; + for (let re of dataFilter[type]) { + CpuStruct.draw(context, re); + } + drawSelection(context, params); + context.closePath(); + drawFlagLine(context, flagMoveInfo, flagSelectedInfo, startNS, endNS, totalNS, frame); + let currentCpu = parseInt(type.replace("cpu", "")); + drawWakeUp(context, wakeupBean, startNS, endNS, totalNS, frame, type == `cpu${CpuStruct.selectCpuStruct?.cpu || 0}` ? CpuStruct.selectCpuStruct : undefined, currentCpu); + } + // @ts-ignore + self.postMessage({ + id: e.data.id, + type: type, + results: canvas ? undefined : dataFilter[type], + hover: CpuStruct.hoverCpuStruct + }); + } else if (type.startsWith("fps")) { + if (!useCache) { + fps(dataList[type], dataFilter[type], startNS, endNS, totalNS, frame); + } + if (canvas) { + context.clearRect(0, 0, canvas.width, canvas.height); + context.beginPath(); + drawLines(context, xs, frame.height, lineColor) + for (let re of dataFilter[type]) { + FpsStruct.draw(context, re) + } + drawSelection(context, params); + context.closePath(); + let maxFps = FpsStruct.maxFps + "FPS" + let textMetrics = context.measureText(maxFps); + context.globalAlpha = 0.8 + context.fillStyle = "#f0f0f0" + context.fillRect(0, 5, textMetrics.width + 8, 18) + context.globalAlpha = 1 + context.fillStyle = "#333" + context.textBaseline = "middle" + context.fillText(maxFps, 4, 5 + 9); + drawWakeUp(context, wakeupBean, startNS, endNS, totalNS, frame); + drawFlagLine(context, flagMoveInfo, flagSelectedInfo, startNS, endNS, totalNS, frame); + } + // @ts-ignore + self.postMessage({id: e.data.id, type: type, results: canvas ? undefined : dataFilter[type], hover: undefined}); + } else if (type.startsWith("freq")) { + if (!useCache) { + freq(dataList[type], dataFilter[type], startNS, endNS, totalNS, frame) + } + if (canvas) { + context.clearRect(0, 0, canvas.width, canvas.height); + context.beginPath(); + CpuFreqStruct.maxFreq = e.data.params.maxFreq; + CpuFreqStruct.maxFreqName = e.data.params.maxFreqName; + drawLines(context, xs, frame.height, lineColor) + CpuFreqStruct.hoverCpuFreqStruct = undefined; + if (isHover) { + for (let re of dataFilter[type]) { + if (hoverX >= re.frame.x && hoverX <= re.frame.x + re.frame.width && hoverY >= re.frame.y && hoverY <= re.frame.y + re.frame.height) { + CpuFreqStruct.hoverCpuFreqStruct = re; + break; + } + } + } else { + CpuFreqStruct.hoverCpuFreqStruct = e.data.params.hoverCpuFreqStruct; + } + CpuFreqStruct.selectCpuFreqStruct = e.data.params.selectCpuFreqStruct; + for (let re of dataFilter[type]) { + CpuFreqStruct.draw(context, re) + } + drawSelection(context, params); + context.closePath(); + let s = CpuFreqStruct.maxFreqName + let textMetrics = context.measureText(s); + context.globalAlpha = 0.8 + context.fillStyle = "#f0f0f0" + context.fillRect(0, 5, textMetrics.width + 8, 18) + context.globalAlpha = 1 + context.fillStyle = "#333" + context.textBaseline = "middle" + context.fillText(s, 4, 5 + 9) + drawWakeUp(context, wakeupBean, startNS, endNS, totalNS, frame); + drawFlagLine(context, flagMoveInfo, flagSelectedInfo, startNS, endNS, totalNS, frame); + } + // @ts-ignore + self.postMessage({ + id: e.data.id, + type: type, + results: canvas ? undefined : dataFilter[type], + hover: CpuFreqStruct.hoverCpuFreqStruct + }); + } else if (type.startsWith("process")) { + if (!useCache) { + proc(dataList[type], dataFilter[type], startNS, endNS, totalNS, frame) + } + if (canvas) { + context.clearRect(0, 0, canvas.width, canvas.height); + context.beginPath(); + CpuStruct.cpuCount = e.data.params.cpuCount; + drawLines(context, xs, frame.height, lineColor) + for (let re of dataFilter[type]) { + ProcessStruct.draw(context, re) + } + drawSelection(context, params); + drawWakeUp(context, wakeupBean, startNS, endNS, totalNS, frame); + context.closePath(); + drawFlagLine(context, flagMoveInfo, flagSelectedInfo, startNS, endNS, totalNS, frame); + } + // @ts-ignore + self.postMessage({id: e.data.id, type: type, results: canvas ? undefined : dataFilter[type], hover: undefined}); + } else if (type.startsWith("heap")) { + if (!useCache) { + heap(dataList[type], dataFilter[type], startNS, endNS, totalNS, frame); + } + if (canvas) { + context.clearRect(0, 0, canvas.width, canvas.height); + context.beginPath(); + drawLines(context, xs, frame.height, lineColor) + HeapStruct.hoverHeapStruct = undefined; + if (isHover) { + for (let re of dataFilter[e.data.type]) { + if (hoverX >= re.frame.x && hoverX <= re.frame.x + re.frame.width && hoverY >= re.frame.y && hoverY <= re.frame.y + re.frame.height) { + HeapStruct.hoverHeapStruct = re; + break; + } + } + } else { + HeapStruct.hoverHeapStruct = e.data.params.hoverHeapStruct; + } + // HeapStruct.selectHeapStruct = e.data.params.selectHeapStruct; + for (let re of dataFilter[type]) { + HeapStruct.draw(context, re) + } + drawSelection(context, params); + drawWakeUp(context, wakeupBean, startNS, endNS, totalNS, frame); + context.closePath(); + drawFlagLine(context, flagMoveInfo, flagSelectedInfo, startNS, endNS, totalNS, frame); + } + // @ts-ignore + self.postMessage({ + id: e.data.id, + type: type, + results: canvas ? undefined : dataFilter[type], + hover: HeapStruct.hoverHeapStruct + }); + } else if (type.startsWith("mem")) { + if (!useCache) { + mem(dataList[type], dataFilter[type], startNS, endNS, totalNS, frame) + } + if (canvas) { + context.clearRect(0, 0, canvas.width, canvas.height); + context.beginPath(); + drawLines(context, xs, frame.height, lineColor) + for (let re of dataFilter[type]) { + ProcessMemStruct.draw(context, re) + } + drawSelection(context, params); + drawWakeUp(context, wakeupBean, startNS, endNS, totalNS, frame); + context.closePath(); + drawFlagLine(context, flagMoveInfo, flagSelectedInfo, startNS, endNS, totalNS, frame); + } + // @ts-ignore + self.postMessage({id: e.data.id, type: type, results: canvas ? undefined : dataFilter[type], hover: undefined}); + } else if (type.startsWith("thread")) { + if (!useCache) { + thread(dataList[type], dataFilter[type], startNS, endNS, totalNS, frame) + } + if (canvas) { + context.clearRect(0, 0, canvas.width, canvas.height); + context.beginPath(); + drawLines(context, xs, frame.height, lineColor) + ThreadStruct.hoverThreadStruct = undefined; + if (isHover) { + for (let re of dataFilter[e.data.type]) { + if (hoverX >= re.frame.x && hoverX <= re.frame.x + re.frame.width && hoverY >= re.frame.y && hoverY <= re.frame.y + re.frame.height) { + ThreadStruct.hoverThreadStruct = re; + break; + } + } + } else { + ThreadStruct.hoverThreadStruct = e.data.params.hoverThreadStruct; + } + ThreadStruct.selectThreadStruct = e.data.params.selectThreadStruct; + for (let re of dataFilter[type]) { + ThreadStruct.draw(context, re) + } + drawSelection(context, params); + drawWakeUp(context, wakeupBean, startNS, endNS, totalNS, frame); + context.closePath(); + drawFlagLine(context, flagMoveInfo, flagSelectedInfo, startNS, endNS, totalNS, frame); + } + // @ts-ignore + self.postMessage({ + id: e.data.id, + type: type, + results: canvas ? undefined : dataFilter[type], + hover: ThreadStruct.hoverThreadStruct + }); + } else if (type.startsWith("func")) { + if (!useCache) { + func(dataList[type], dataFilter[type], startNS, endNS, totalNS, frame) + } + if (canvas) { + if (canvas.height == 150) { + canvas.width = frame.width; + canvas.height = e.data.params.maxHeight; + context.scale(e.data.params.dpr, e.data.params.dpr); + } + // canvasList[type]!.width = frame.width; + // canvasList[type]!.height = frame.height; + context.clearRect(0, 0, canvas.width, canvas.height); + context.beginPath(); + drawLines(context, xs, frame.height, lineColor) + FuncStruct.hoverFuncStruct = undefined; + if (isHover) { + for (let re of dataFilter[type]) { + if (re.dur && re.dur > 0 && hoverX >= re.frame.x && hoverX <= re.frame.x + re.frame.width && hoverY >= re.frame.y + (re.depth * 20) && hoverY <= re.frame.y + re.frame.height + (re.depth * 20)) { + FuncStruct.hoverFuncStruct = re; + break; + } + } + } else { + FuncStruct.hoverFuncStruct = e.data.params.hoverFuncStruct; + } + FuncStruct.selectFuncStruct = e.data.params.selectFuncStruct; + for (let re of dataFilter[type]) { + FuncStruct.draw(context, re) + } + drawSelection(context, params); + drawWakeUp(context, wakeupBean, startNS, endNS, totalNS, frame); + context.closePath(); + drawFlagLine(context, flagMoveInfo, flagSelectedInfo, startNS, endNS, totalNS, frame); + } + // @ts-ignore + self.postMessage({ + id: e.data.id, + type: type, + results: canvas ? undefined : dataFilter[type], + hover: FuncStruct.hoverFuncStruct + }); + } else if (type.startsWith("native")) { + if (canvas) { + context.clearRect(0, 0, canvas.width, canvas.height); + context.beginPath(); + drawLines(context, xs, frame.height, lineColor) + drawSelection(context, params); + context.closePath(); + drawFlagLine(context, flagMoveInfo, flagSelectedInfo, startNS, endNS, totalNS, frame); + } + // @ts-ignore + self.postMessage({ + id: e.data.id, + type: type, + results: canvas ? undefined : dataFilter[type], + hover: ThreadStruct.hoverThreadStruct + }); + } +}; +self.onmessageerror = function (e: any) { +} + + + + diff --git a/host/ide/src/trace/database/ProcedureWorkerCPU.ts b/host/ide/src/trace/database/ProcedureWorkerCPU.ts new file mode 100644 index 0000000..a0f22b2 --- /dev/null +++ b/host/ide/src/trace/database/ProcedureWorkerCPU.ts @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseStruct, ColorUtils} from "./ProcedureWorkerCommon.js"; + +export function cpu(list: Array, res: Set, startNS: number, endNS: number, totalNS: number, frame: any) { + res.clear(); + if (list) { + let pns = (endNS - startNS) / frame.width; + let y = frame.y + 5; + let height = frame.height - 10; + for (let i = 0, len = list.length; i < len; i++) { + let it = list[i]; + if ((it.startTime || 0) + (it.dur || 0) > startNS && (it.startTime || 0) < endNS) { + // setCpuFrame(list[i], 5, startNS, endNS, totalNS, frame) + if (!list[i].frame) { + list[i].frame = {}; + list[i].frame.y = y; + list[i].frame.height = height; + } + CpuStruct.setCpuFrame(list[i], pns, startNS, endNS, frame) + if (i > 0 && ((list[i - 1].frame?.x || 0) == (list[i].frame?.x || 0) && ((list[i - 1].frame?.width || 0) == (list[i].frame?.width || 0)))) { + + } else { + res.add(list[i]) + } + } + } + } +} + +export class CpuStruct extends BaseStruct { + static cpuCount: number = 1 //最大cpu数量 + static hoverCpuStruct: CpuStruct | undefined; + static selectCpuStruct: CpuStruct | undefined; + cpu: number | undefined + dur: number | undefined + end_state: string | undefined + id: number | undefined + name: string | undefined + priority: number | undefined + processCmdLine: string | undefined + processId: number | undefined + processName: string | undefined + schedId: number | undefined + startTime: number | undefined + tid: number | undefined + type: string | undefined + + // static setFrame(node: CpuStruct, padding: number, startNS: number, endNS: number, totalNS: number, frame: Rect) { + // let x1: number; + // let x2: number; + // if ((node.startTime || 0) < startNS) { + // x1 = 0; + // } else { + // x1 = ns2x((node.startTime || 0), startNS, endNS, totalNS, frame); + // } + // if ((node.startTime || 0) + (node.dur || 0) > endNS) { + // x2 = frame.width; + // } else { + // x2 = ns2x((node.startTime || 0) + (node.dur || 0), startNS, endNS, totalNS, frame); + // } + // let getV: number = x2 - x1 <= 1 ? 1 : x2 - x1; + // let rectangle: Rect = new Rect(Math.floor(x1), frame.y + padding, Math.ceil(getV), frame.height - padding * 2); + // node.frame = rectangle; + // node.isHover = false; + // } + + static draw(ctx: CanvasRenderingContext2D, data: CpuStruct) { + if (data.frame) { + let width = data.frame.width || 0; + if (data.processId === CpuStruct.hoverCpuStruct?.processId || !CpuStruct.hoverCpuStruct) { + ctx.fillStyle = ColorUtils.colorForTid((data.processId || 0) > 0 ? (data.processId || 0) : (data.tid || 0)) + } else { + ctx.fillStyle = "#e0e0e0" + } + ctx.fillRect(data.frame.x, data.frame.y, width, data.frame.height) + if (width > textPadding * 2) { + let process = `${(data.processName || "Process")} [${data.processId}]` + let thread = `${data.name || "Thread"} [${data.tid}]` + let processMeasure = ctx.measureText(process); + let threadMeasure = ctx.measureText(thread); + let processCharWidth = Math.round(processMeasure.width / process.length) + let threadCharWidth = Math.round(threadMeasure.width / thread.length) + ctx.fillStyle = "#ffffff" + let y = data.frame.height / 2 + data.frame.y; + if (processMeasure.width < width - textPadding * 2) { + let x1 = Math.floor(width / 2 - processMeasure.width / 2 + data.frame.x + textPadding) + ctx.textBaseline = "bottom"; + ctx.fillText(process, x1, y, width - textPadding * 2) + } else if (width - textPadding * 2 > processCharWidth * 4) { + let chatNum = (width - textPadding * 2) / processCharWidth; + let x1 = data.frame.x + textPadding + ctx.textBaseline = "bottom"; + ctx.fillText(process.substring(0, chatNum - 4) + '...', x1, y, width - textPadding * 2) + } + if (threadMeasure.width < width - textPadding * 2) { + ctx.textBaseline = "top"; + let x2 = Math.floor(width / 2 - threadMeasure.width / 2 + data.frame.x + textPadding) + ctx.fillText(thread, x2, y + 2, width - textPadding * 2) + } else if (width - textPadding * 2 > threadCharWidth * 4) { + let chatNum = (width - textPadding * 2) / threadCharWidth; + let x1 = data.frame.x + textPadding + ctx.textBaseline = "top"; + ctx.fillText(thread.substring(0, chatNum - 4) + '...', x1, y + 2, width - textPadding * 2) + } + } + if (CpuStruct.selectCpuStruct && CpuStruct.equals(CpuStruct.selectCpuStruct, data)) { + ctx.strokeStyle = '#232c5d' + ctx.lineWidth = 2 + ctx.strokeRect(data.frame.x, data.frame.y, width - 2, data.frame.height) + } + } + } + + static setCpuFrame(node: any, pns: number, startNS: number, endNS: number, frame: any) { + if ((node.startTime || 0) < startNS) { + node.frame.x = 0; + } else { + node.frame.x = Math.floor(((node.startTime || 0) - startNS) / pns); + } + if ((node.startTime || 0) + (node.dur || 0) > endNS) { + node.frame.width = frame.width - node.frame.x; + } else { + node.frame.width = Math.ceil(((node.startTime || 0) + (node.dur || 0) - startNS) / pns - node.frame.x); + } + if (node.frame.width < 1) { + node.frame.width = 1; + } + } + + static equals(d1: CpuStruct, d2: CpuStruct): boolean { + if (d1 && d2 && d1.cpu == d2.cpu && + d1.tid == d2.tid && + d1.processId == d2.processId && + d1.startTime == d2.startTime && + d1.dur == d2.dur) { + return true; + } else { + return false; + } + } +} + +export class WakeupBean { + wakeupTime: number | undefined + cpu: number | undefined + process: string | undefined + pid: number | undefined + thread: string | undefined + tid: number | undefined + schedulingLatency: number | undefined + schedulingDesc: string | undefined + +} + +const textPadding = 2; \ No newline at end of file diff --git a/host/ide/src/trace/database/ProcedureWorkerCommon.ts b/host/ide/src/trace/database/ProcedureWorkerCommon.ts new file mode 100644 index 0000000..2672f45 --- /dev/null +++ b/host/ide/src/trace/database/ProcedureWorkerCommon.ts @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function ns2s(ns: number): string { + let second1 = 1_000_000_000; // 1 second + let millisecond1 = 1_000_000; // 1 millisecond + let microsecond1 = 1_000; // 1 microsecond + let nanosecond1 = 1000.0; + let res; + if (ns >= second1) { + res = (ns / 1000 / 1000 / 1000).toFixed(1) + " s"; + } else if (ns >= millisecond1) { + res = (ns / 1000 / 1000).toFixed(1) + " ms"; + } else if (ns >= microsecond1) { + res = (ns / 1000).toFixed(1) + " μs"; + } else if (ns > 0) { + res = ns.toFixed(1) + " ns"; + } else { + res = ns.toFixed(1) + " s"; + } + return res; +} + +export function ns2x(ns: number, startNS: number, endNS: number, duration: number, rect: any) { + // @ts-ignore + // return _ns2x(ns,startNS,endNS,duration,rect.width); + if (endNS == 0) { + endNS = duration; + } + let xSize: number = (ns - startNS) * rect.width / (endNS - startNS); + if (xSize < 0) { + xSize = 0; + } else if (xSize > rect.width) { + xSize = rect.width; + } + return xSize; +} + +export class Rect { + x: number = 0 + y: number = 0 + width: number = 0 + height: number = 0 + + constructor(x: number, y: number, width: number, height: number) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + static contains(rect: Rect, x: number, y: number): boolean { + return rect.x <= x && x <= rect.x + rect.width && rect.y <= y && y <= rect.y + rect.height; + } + + static containsWithPadding(rect: Rect, x: number, y: number, paddingLeftRight: number, paddingTopBottom: number): boolean { + return rect.x + paddingLeftRight <= x + && x <= rect.x + rect.width - paddingLeftRight + && rect.y + paddingTopBottom <= y + && y <= rect.y + rect.height - paddingTopBottom; + } + + static containsWithMargin(rect: Rect, x: number, y: number, t: number, r: number, b: number, l: number): boolean { + return rect.x - l <= x + && x <= rect.x + rect.width + r + && rect.y - t <= y + && y <= rect.y + rect.height + b; + } + + static intersect(r1: Rect, rect: Rect): boolean { + let maxX = r1.x + r1.width >= rect.x + rect.width ? r1.x + r1.width : rect.x + rect.width; + let maxY = r1.y + r1.height >= rect.y + rect.height ? r1.y + r1.height : rect.y + rect.height; + let minX = r1.x <= rect.x ? r1.x : rect.x; + let minY = r1.y <= rect.y ? r1.y : rect.y; + if (maxX - minX <= rect.width + r1.width && maxY - minY <= r1.height + rect.height) { + return true; + } else { + return false; + } + } + + contains(x: number, y: number): boolean { + return this.x <= x && x <= this.x + this.width && this.y <= y && y <= this.y + this.height; + } + + containsWithPadding(x: number, y: number, paddingLeftRight: number, paddingTopBottom: number): boolean { + return this.x + paddingLeftRight <= x + && x <= this.x + this.width - paddingLeftRight + && this.y + paddingTopBottom <= y + && y <= this.y + this.height - paddingTopBottom; + } + + containsWithMargin(x: number, y: number, t: number, r: number, b: number, l: number): boolean { + return this.x - l <= x + && x <= this.x + this.width + r + && this.y - t <= y + && y <= this.y + this.height + b; + } + + /** + * 判断是否相交 + * @param rect + */ + intersect(rect: Rect): boolean { + let maxX = this.x + this.width >= rect.x + rect.width ? this.x + this.width : rect.x + rect.width; + let maxY = this.y + this.height >= rect.y + rect.height ? this.y + this.height : rect.y + rect.height; + let minX = this.x <= rect.x ? this.x : rect.x; + let minY = this.y <= rect.y ? this.y : rect.y; + if (maxX - minX <= rect.width + this.width && maxY - minY <= this.height + rect.height) { + return true; + } else { + return false; + } + } +} + +export class Point { + x: number = 0 + y: number = 0 + + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } +} + +export class BaseStruct { + frame: Rect | undefined + isHover: boolean = false; +} + + +export class ColorUtils { + // public static GREY_COLOR:string = Color.getHSBColor(0, 0, 62); // grey + public static GREY_COLOR: string = "#f0f0f0" + /** + * Color array of all current columns + */ + public static MD_PALETTE: Array = [ + "#3391ff",// red + "#0076ff",// pink + "#66adff",// purple + "#2db3aa",// deep purple + "#008078",// indigo + "#73e6de",// blue + "#535da6",// light blue + "#38428c", // cyan + "#7a84cc",// teal + "#ff9201",// green + "#ff7500",// light green + "#ffab40",// lime + "#2db4e2",// amber 0xffc105 + "#0094c6", // orange + "#7cdeff",// deep orange + "#ffd44a", // brown + "#fbbf00",// blue gray + "#ffe593",// yellow 0xffec3d + ]; + public static FUNC_COLOR: Array = [ + "#3391ff", // purple + "#2db4e2", + "#2db3aa", // deep purple + "#ffd44a", + "#535da6", // indigo + "#008078", // blue + "#ff9201", + "#38428c"]; + + /** + * Get the color value according to the length of the string + * + * @param str str + * @param max max + * @return int + */ + public static hash(str: string, max: number): number { + let colorA: number = 0x811c9dc5; + let colorB: number = 0xfffffff; + let colorC: number = 16777619; + let colorD: number = 0xffffffff; + let hash: number = colorA & colorB; + + for (let index: number = 0; index < str.length; index++) { + hash ^= str.charCodeAt(index); + hash = (hash * colorC) & colorD; + } + return Math.abs(hash) % max; + } + + /** + * Get color according to tid + * + * @param tid tid + * @return Color + */ + public static colorForTid(tid: number): string { + let colorIdx: number = ColorUtils.hash(`${tid}`, ColorUtils.MD_PALETTE.length); + return ColorUtils.MD_PALETTE[colorIdx]; + } + + public static formatNumberComma(str: number): string { + let l = str.toString().split("").reverse(); + let t: string = ""; + for (let i = 0; i < l.length; i++) { + t += l[i] + ((i + 1) % 3 == 0 && (i + 1) != l.length ? "," : ""); + } + return t.split("").reverse().join("") + } +} + +export function drawLines(ctx: any, xs: Array, height: number, lineColor: string) { + if (ctx) { + ctx.lineWidth = 1; + // ctx.strokeStyle = window.getComputedStyle(this.rootEL!, null).getPropertyValue("border-bottom-color");//"#dadada" "#474e59";// + ctx.strokeStyle = lineColor || "#dadada"; + xs?.forEach(it => { + ctx.moveTo(Math.floor(it), 0) + ctx.lineTo(Math.floor(it), height) + }) + ctx.stroke(); + } +} + +export function drawFlagLine(ctx: any, hoverFlag: any, selectFlag: any, startNS: number, endNS: number, totalNS: number, frame: any) { + if (ctx) { + if (hoverFlag) { + ctx.beginPath(); + ctx.lineWidth = 2; + ctx.strokeStyle = hoverFlag?.color || "#dadada"; + ctx.moveTo(Math.floor(hoverFlag.x), 0) + ctx.lineTo(Math.floor(hoverFlag.x), frame.height) + ctx.stroke(); + ctx.closePath(); + } + if (selectFlag) { + ctx.beginPath(); + ctx.lineWidth = 2; + ctx.strokeStyle = selectFlag?.color || "#dadada"; + selectFlag.x = ns2x(selectFlag.time, startNS, endNS, totalNS, frame); + ctx.moveTo(Math.floor(selectFlag.x), 0) + ctx.lineTo(Math.floor(selectFlag.x), frame.height) + ctx.stroke(); + ctx.closePath(); + } + } +} + diff --git a/host/ide/src/trace/database/ProcedureWorkerFPS.ts b/host/ide/src/trace/database/ProcedureWorkerFPS.ts new file mode 100644 index 0000000..48bb896 --- /dev/null +++ b/host/ide/src/trace/database/ProcedureWorkerFPS.ts @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseStruct, ns2x, Rect} from "./ProcedureWorkerCommon.js"; + +export function fps(list: Array, res: Set, startNS: number, endNS: number, totalNS: number, frame: any) { + res.clear(); + FpsStruct.maxFps = 0 + if (list) { + for (let i = 0, len = list.length; i < len; i++) { + let it = list[i]; + if ((it.fps || 0) > FpsStruct.maxFps) { + FpsStruct.maxFps = it.fps || 0 + } + if (i === list.length - 1) { + it.dur = (endNS || 0) - (it.startNS || 0) + } else { + it.dur = (list[i + 1].startNS || 0) - (it.startNS || 0) + } + if ((it.startNS || 0) + (it.dur || 0) > (startNS) && (it.startNS || 0) < (endNS)) { + FpsStruct.setFrame(list[i], 5, startNS, endNS, totalNS, frame) + if (i > 0 && ((list[i - 1].frame?.x || 0) == (list[i].frame?.x || 0) && (list[i - 1].frame?.width || 0) == (list[i].frame?.width || 0))) { + + } else { + res.add(list[i]) + } + } + } + } +} + +export class FpsStruct extends BaseStruct { + static maxFps: number = 0 + static maxFpsName: string = "0 FPS" + static hoverFpsStruct: FpsStruct | undefined; + static selectFpsStruct: FpsStruct | undefined; + fps: number | undefined + startNS: number | undefined = 0 + dur: number | undefined //自补充,数据库没有返回 + + static draw(ctx: CanvasRenderingContext2D, data: FpsStruct) { + if (data.frame) { + let width = data.frame.width || 0; + ctx.fillStyle = '#535da6' + ctx.strokeStyle = '#535da6' + if (data.startNS === FpsStruct.hoverFpsStruct?.startNS) { + ctx.lineWidth = 1; + ctx.globalAlpha = 0.6; + let drawHeight: number = ((data.fps || 0) * (data.frame.height || 0) * 1.0) / FpsStruct.maxFps; + ctx.fillRect(data.frame.x, data.frame.y + data.frame.height - drawHeight, width, drawHeight) + ctx.beginPath() + ctx.arc(data.frame.x, data.frame.y + data.frame.height - drawHeight, 3, 0, 2 * Math.PI, true) + ctx.fill() + ctx.globalAlpha = 1.0; + ctx.stroke(); + ctx.beginPath() + ctx.moveTo(data.frame.x + 3, data.frame.y + data.frame.height - drawHeight); + ctx.lineWidth = 3; + ctx.lineTo(data.frame.x + width, data.frame.y + data.frame.height - drawHeight) + ctx.stroke(); + } else { + ctx.globalAlpha = 0.6; + ctx.lineWidth = 1; + let drawHeight: number = ((data.fps || 0) * (data.frame.height || 0) * 1.0) / FpsStruct.maxFps; + ctx.fillRect(data.frame.x, data.frame.y + data.frame.height - drawHeight, width, drawHeight) + } + } + ctx.globalAlpha = 1.0; + ctx.lineWidth = 1; + } + + static setFrame(node: FpsStruct, padding: number, startNS: number, endNS: number, totalNS: number, frame: Rect) { + let x1: number, x2: number; + if ((node.startNS || 0) < startNS) { + x1 = 0; + } else { + x1 = ns2x((node.startNS || 0), startNS, endNS, totalNS, frame); + } + if ((node.startNS || 0) + (node.dur || 0) > endNS) { + x2 = frame.width; + } else { + x2 = ns2x((node.startNS || 0) + (node.dur || 0), startNS, endNS, totalNS, frame); + } + let getV: number = x2 - x1 <= 1 ? 1 : x2 - x1; + let rectangle: Rect = new Rect(Math.floor(x1), Math.ceil(frame.y + padding), Math.ceil(getV), Math.floor(frame.height - padding * 2)); + node.frame = rectangle; + } +} + +const textPadding = 2; \ No newline at end of file diff --git a/host/ide/src/trace/database/ProcedureWorkerFreq.ts b/host/ide/src/trace/database/ProcedureWorkerFreq.ts new file mode 100644 index 0000000..8a2097d --- /dev/null +++ b/host/ide/src/trace/database/ProcedureWorkerFreq.ts @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseStruct, ColorUtils, ns2x} from "./ProcedureWorkerCommon.js"; + +export function freq(list: Array, res: Set, startNS: number, endNS: number, totalNS: number, frame: any) { + res.clear(); + if (list) { + for (let i = 0, len = list.length; i < len; i++) { + let it = list[i]; + if (i === list.length - 1) { + it.dur = (endNS || 0) - (it.startNS || 0) + } else { + it.dur = (list[i + 1].startNS || 0) - (it.startNS || 0) + } + if ((it.startNS || 0) + (it.dur || 0) > (startNS || 0) && (it.startNS || 0) < (endNS || 0)) { + CpuFreqStruct.setFreqFrame(list[i], 5, startNS || 0, endNS || 0, totalNS || 0, frame) + if (i > 0 && ((list[i - 1].frame?.x || 0) == (list[i].frame?.x || 0) && (list[i - 1].frame?.width || 0) == (list[i].frame?.width || 0))) { + + } else { + res.add(it) + } + } + } + } + +} + +export class CpuFreqStruct extends BaseStruct { + static maxFreq: number = 0 + static maxFreqName: string = "0 GHz" + static hoverCpuFreqStruct: CpuFreqStruct | undefined; + static selectCpuFreqStruct: CpuFreqStruct | undefined; + cpu: number | undefined + value: number | undefined + startNS: number | undefined + dur: number | undefined //自补充,数据库没有返回 + + static draw(ctx: CanvasRenderingContext2D, data: CpuFreqStruct) { + if (data.frame) { + let width = data.frame.width || 0; + let index = data.cpu || 0 + index += 2 + ctx.fillStyle = ColorUtils.colorForTid(index) + ctx.strokeStyle = ColorUtils.colorForTid(index) + if (data.startNS === CpuFreqStruct.hoverCpuFreqStruct?.startNS) { + ctx.lineWidth = 1; + ctx.globalAlpha = 0.6; + let drawHeight: number = Math.floor(((data.value || 0) * (data.frame.height || 0) * 1.0) / CpuFreqStruct.maxFreq); + ctx.fillRect(data.frame.x, data.frame.y + data.frame.height - drawHeight, width, drawHeight) + ctx.beginPath() + ctx.arc(data.frame.x, data.frame.y + data.frame.height - drawHeight, 3, 0, 2 * Math.PI, true) + ctx.fill() + ctx.globalAlpha = 1.0; + ctx.stroke(); + ctx.beginPath() + ctx.moveTo(data.frame.x + 3, data.frame.y + data.frame.height - drawHeight); + ctx.lineWidth = 3; + ctx.lineTo(data.frame.x + width, data.frame.y + data.frame.height - drawHeight) + ctx.stroke(); + } else { + ctx.globalAlpha = 0.6; + ctx.lineWidth = 1; + let drawHeight: number = Math.floor(((data.value || 0) * (data.frame.height || 0)) / CpuFreqStruct.maxFreq); + ctx.fillRect(data.frame.x, data.frame.y + data.frame.height - drawHeight, width, drawHeight) + } + } + ctx.globalAlpha = 1.0; + ctx.lineWidth = 1; + } + + static setFreqFrame(node: any, padding: number, startNS: number, endNS: number, totalNS: number, frame: any) { + let x1: number, x2: number; + if ((node.startNS || 0) < startNS) { + x1 = 0; + } else { + x1 = ns2x((node.startNS || 0), startNS, endNS, totalNS, frame); + } + if ((node.startNS || 0) + (node.dur || 0) > endNS) { + x2 = frame.width; + } else { + x2 = ns2x((node.startNS || 0) + (node.dur || 0), startNS, endNS, totalNS, frame); + } + let getV: number = x2 - x1 <= 1 ? 1 : x2 - x1; + if (!node.frame) { + node.frame = {}; + } + node.frame.x = Math.floor(x1); + node.frame.y = frame.y + padding; + node.frame.width = Math.ceil(getV); + node.frame.height = Math.floor(frame.height - padding * 2); + } +} + +const textPadding = 2; \ No newline at end of file diff --git a/host/ide/src/trace/database/ProcedureWorkerFunc.ts b/host/ide/src/trace/database/ProcedureWorkerFunc.ts new file mode 100644 index 0000000..773ff7b --- /dev/null +++ b/host/ide/src/trace/database/ProcedureWorkerFunc.ts @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseStruct, ColorUtils, ns2x, Rect} from "./ProcedureWorkerCommon.js"; + +export function func(list: Array, res: Set, startNS: number, endNS: number, totalNS: number, frame: any) { + res.clear(); + if (list) { + for (let i = 0, len = list.length; i < len; i++) { + let it = list[i]; + if ((it.startTs || 0) + (it.dur || 0) > (startNS || 0) && (it.startTs || 0) < (endNS || 0)) { + FuncStruct.setFuncFrame(list[i], 0, startNS || 0, endNS || 0, totalNS || 0, frame) + if (i > 0 && (list[i - 1].frame?.y || 0) == (list[i].frame?.y || 0) && ((list[i - 1].frame?.x || 0) == (list[i].frame?.x || 0) && (list[i - 1].frame?.width || 0) == (list[i].frame?.width || 0))) { + + } else { + res.add(list[i]) + } + } + } + } +} + +export class FuncStruct extends BaseStruct { + static hoverFuncStruct: FuncStruct | undefined; + static selectFuncStruct: FuncStruct | undefined; + argsetid: number | undefined // 53161 + depth: number | undefined // 0 + dur: number | undefined // 570000 + funName: string | undefined //"binder transaction" + id: number | undefined // 92749 + is_main_thread: number | undefined // 0 + parent_id: number | undefined // null + startTs: number | undefined // 9729867000 + threadName: string | undefined // "Thread-15" + tid: number | undefined // 2785 + track_id: number | undefined // 414 + + static setFuncFrame(node: any, padding: number, startNS: number, endNS: number, totalNS: number, frame: any) { + let x1: number, x2: number; + if ((node.startTs || 0) < startNS) { + x1 = 0; + } else { + x1 = ns2x((node.startTs || 0), startNS, endNS, totalNS, frame); + } + if ((node.startTs || 0) + (node.dur || 0) > endNS) { + x2 = frame.width; + } else { + x2 = ns2x((node.startTs || 0) + (node.dur || 0), startNS, endNS, totalNS, frame); + } + let getV: number = x2 - x1 <= 1 ? 1 : x2 - x1; + if (!node.frame) { + node.frame = {}; + } + node.frame.x = Math.floor(x1); + node.frame.y = 0; + node.frame.width = Math.floor(getV); + node.frame.height = 20; + } + + static draw(ctx: CanvasRenderingContext2D, data: FuncStruct) { + if (data.frame) { + let isBinder = FuncStruct.isBinder(data); + if (data.dur == undefined || data.dur == null || data.dur == 0) { + } else { + ctx.fillStyle = ColorUtils.FUNC_COLOR[(data.funName?.length || 0) % ColorUtils.FUNC_COLOR.length] + let miniHeight = 20 + ctx.fillRect(data.frame.x, data.frame.y, data.frame.width, miniHeight - padding * 2) + if (data.frame.width > 10) { + ctx.fillStyle = "#fff" + FuncStruct.drawString(ctx, data.funName || '', 5, data.frame) + } + if (FuncStruct.isSelected(data)) { + ctx.strokeStyle = "#000" + ctx.lineWidth = 1 + ctx.strokeRect(data.frame.x, data.frame.y, data.frame.width, miniHeight - padding * 2) + } + } + } + } + + static drawString(ctx: CanvasRenderingContext2D, str: string, textPadding: number, frame: Rect): boolean { + let textMetrics = ctx.measureText(str); + let charWidth = Math.round(textMetrics.width / str.length) + if (textMetrics.width < frame.width - textPadding * 2) { + let x2 = Math.floor(frame.width / 2 - textMetrics.width / 2 + frame.x + textPadding) + ctx.fillText(str, x2, Math.floor(frame.y + frame.height / 2 + 2), frame.width - textPadding * 2) + return true; + } + if (frame.width - textPadding * 2 > charWidth * 4) { + let chatNum = (frame.width - textPadding * 2) / charWidth; + let x1 = frame.x + textPadding + ctx.fillText(str.substring(0, chatNum - 4) + '...', x1, Math.floor(frame.y + frame.height / 2 + 2), frame.width - textPadding * 2) + return true; + } + return false; + } + + static isSelected(data: FuncStruct): boolean { + return (FuncStruct.selectFuncStruct != undefined && + FuncStruct.selectFuncStruct.startTs == data.startTs && + FuncStruct.selectFuncStruct.depth == data.depth && + FuncStruct.selectFuncStruct.dur == data.dur && + FuncStruct.selectFuncStruct.funName == data.funName) + } + + static isBinder(data: FuncStruct): boolean { + if (data.funName != null && + ( + data.funName.toLowerCase().startsWith("binder transaction") + || data.funName.toLowerCase().startsWith("binder async") + || data.funName.toLowerCase().startsWith("binder reply") + ) + ) { + return true; + } else { + return false; + } + } +} + +const padding = 1; \ No newline at end of file diff --git a/host/ide/src/trace/database/ProcedureWorkerHeap.ts b/host/ide/src/trace/database/ProcedureWorkerHeap.ts new file mode 100644 index 0000000..9a11b40 --- /dev/null +++ b/host/ide/src/trace/database/ProcedureWorkerHeap.ts @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseStruct, ns2x, Rect} from "./ProcedureWorkerCommon.js"; + +export function heap(list: Array, res: Set, startNS: number, endNS: number, totalNS: number, frame: any) { + res.clear(); + if (list) { + for (let i = 0; i < list.length; i++) { + let it = list[i]; + if ((it.startTime || 0) + (it.dur || 0) > (startNS || 0) && (it.startTime || 0) < (endNS || 0)) { + HeapStruct.setFrame(list[i], 5, startNS || 0, endNS || 0, totalNS || 0, frame) + if (i > 0 && ((list[i - 1].frame?.x || 0) == (list[i].frame?.x || 0) && (list[i - 1].frame?.width || 0) == (list[i].frame?.width || 0))) { + continue; + } else { + res.add(list[i]) + } + } + } + } +} + +export class HeapStruct extends BaseStruct { + static hoverHeapStruct: HeapStruct | undefined; + startTime: number | undefined + endTime: number | undefined + dur: number | undefined + heapsize: number | undefined + maxHeapSize: number = 0 + minHeapSize: number = 0 + + static setFrame(node: HeapStruct, padding: number, startNS: number, endNS: number, totalNS: number, frame: Rect) { + let x1: number, x2: number; + if ((node.startTime || 0) < startNS) { + x1 = 0; + } else { + x1 = ns2x((node.startTime || 0), startNS, endNS, totalNS, frame); + } + if ((node.startTime || 0) + (node.dur || 0) > endNS) { + x2 = frame.width; + } else { + // @ts-ignore + x2 = ns2x(node.startTime + node.dur, startNS, endNS, totalNS, frame); + } + let getV: number = x2 - x1 <= 1 ? 1 : x2 - x1; + let rectangle: Rect = new Rect(Math.floor(x1), Math.ceil(frame.y + padding), Math.ceil(getV), Math.floor(frame.height - padding * 2)); + node.frame = rectangle; + } + + static draw(ctx: CanvasRenderingContext2D, data: HeapStruct) { + if (data.frame) { + let width = data.frame.width || 0; + ctx.fillStyle = "#2db3aa" + ctx.strokeStyle = "#2db3aa" + if (data.startTime === HeapStruct.hoverHeapStruct?.startTime) { + ctx.lineWidth = 1; + ctx.globalAlpha = 0.6; + let drawHeight:number = 0; + if(data.minHeapSize < 0 ){ + drawHeight = Math.ceil((((data.heapsize || 0) - data.minHeapSize) * (data.frame.height || 0)) / (data.maxHeapSize - data.minHeapSize)); + }else{ + drawHeight = Math.ceil(((data.heapsize || 0) * (data.frame.height || 0)) / data.maxHeapSize); + } + ctx.fillRect(data.frame.x, data.frame.y + data.frame.height - drawHeight, width, drawHeight) + ctx.beginPath() + ctx.arc(data.frame.x, data.frame.y + data.frame.height - drawHeight, 3, 0, 2 * Math.PI, true) + ctx.fill() + ctx.globalAlpha = 1.0; + ctx.stroke(); + ctx.beginPath() + ctx.moveTo(data.frame.x + 3, data.frame.y + data.frame.height - drawHeight); + ctx.lineWidth = 3; + ctx.lineTo(data.frame.x + width, data.frame.y + data.frame.height - drawHeight) + ctx.stroke(); + } else { + ctx.globalAlpha = 0.6; + ctx.lineWidth = 1; + let drawHeight: number = Math.ceil(((data.heapsize || 0) * (data.frame.height || 0)) / data.maxHeapSize); + ctx.fillRect(data.frame.x, data.frame.y + data.frame.height - drawHeight, width, drawHeight) + } + } + ctx.globalAlpha = 1.0; + ctx.lineWidth = 1; + } +} \ No newline at end of file diff --git a/host/ide/src/trace/database/ProcedureWorkerMem.ts b/host/ide/src/trace/database/ProcedureWorkerMem.ts new file mode 100644 index 0000000..1405ee9 --- /dev/null +++ b/host/ide/src/trace/database/ProcedureWorkerMem.ts @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseStruct, ColorUtils, ns2x} from "./ProcedureWorkerCommon.js"; + +function setMemFrame(node: any, padding: number, startNS: number, endNS: number, totalNS: number, frame: any) { + let x1: number; + let x2: number; + if ((node.startTime || 0) <= startNS) { + x1 = 0; + } else { + x1 = ns2x((node.startTime || 0), startNS, endNS, totalNS, frame); + } + if ((node.startTime || 0) + (node.duration || 0) >= endNS) { + x2 = frame.width; + } else { + x2 = ns2x((node.startTime || 0) + (node.duration || 0), startNS, endNS, totalNS, frame); + } + let getV: number = x2 - x1 <= 1 ? 1 : x2 - x1; + if (!node.frame) { + node.frame = {}; + } + node.frame.x = Math.floor(x1); + node.frame.y = Math.floor(frame.y + padding); + node.frame.width = Math.ceil(getV); + node.frame.height = Math.floor(frame.height - padding * 2); +} + +export function mem(list: Array, res: Set, startNS: number, endNS: number, totalNS: number, frame: any) { + res.clear(); + if (list) { + for (let i = 0, len = list.length; i < len; i++) { + let it = list[i]; + if ((it.startTime || 0) + (it.duration || 0) > startNS && (it.startTime || 0) < endNS) { + setMemFrame(list[i], 5, startNS, endNS, totalNS, frame) + if (i > 0 && ((list[i - 1].frame?.x || 0) == (list[i].frame?.x || 0) && (list[i - 1].frame?.width || 0) == (list[i].frame?.width || 0))) { + + } else { + res.add(list[i]) + } + } + } + } +} + +export class ProcessMemStruct extends BaseStruct { + trackId: number | undefined + processName: string | undefined + pid: number | undefined + upid: number | undefined + trackName: string | undefined + + type: string | undefined + track_id: string | undefined + value: number | undefined + startTime: number | undefined + duration: number | undefined + maxValue: number | undefined + delta: number | undefined; + + static draw(ctx: CanvasRenderingContext2D, data: ProcessMemStruct) { + if (data.frame) { + let width = data.frame.width || 0; + ctx.fillStyle = ColorUtils.colorForTid(data.maxValue || 0) + ctx.strokeStyle = ColorUtils.colorForTid(data.maxValue || 0) + ctx.globalAlpha = 0.6; + ctx.lineWidth = 1; + let drawHeight: number = ((data.value || 0) * (data.frame.height || 0) * 1.0) / (data.maxValue || 1); + ctx.fillRect(data.frame.x, data.frame.y + data.frame.height - drawHeight, width, drawHeight) + } + ctx.globalAlpha = 1.0; + ctx.lineWidth = 1; + } +} \ No newline at end of file diff --git a/host/ide/src/trace/database/ProcedureWorkerProcess.ts b/host/ide/src/trace/database/ProcedureWorkerProcess.ts new file mode 100644 index 0000000..01669ed --- /dev/null +++ b/host/ide/src/trace/database/ProcedureWorkerProcess.ts @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseStruct, ColorUtils, ns2x} from "./ProcedureWorkerCommon.js"; +import {CpuStruct} from "./ProcedureWorkerCPU.js"; + +export function proc(list: Array, res: Set, startNS: number, endNS: number, totalNS: number, frame: any) { + res.clear(); + if (list) { + for (let i = 0, len = list.length; i < len; i++) { + let it = list[i]; + if ((it.startTime || 0) + (it.dur || 0) > (startNS || 0) && (it.startTime || 0) < (endNS || 0)) { + ProcessStruct.setProcessFrame(list[i], 5, startNS || 0, endNS || 0, totalNS || 0, frame) + if (i > 0 && ((list[i - 1].frame?.x || 0) == (list[i].frame?.x || 0) && (list[i - 1].frame?.width || 0) == (list[i].frame?.width || 0))) { + + } else { + res.add(list[i]) + } + } + } + } +} + +const padding = 1; + +export class ProcessStruct extends BaseStruct { + cpu: number | undefined + dur: number | undefined + id: number | undefined + pid: number | undefined + process: string | undefined + startTime: number | undefined + state: string | undefined + thread: string | undefined + tid: number | undefined + ts: number | undefined + type: string | undefined + utid: number | undefined + + static draw(ctx: CanvasRenderingContext2D, data: ProcessStruct) { + if (data.frame) { + let width = data.frame.width || 0; + ctx.fillStyle = ColorUtils.colorForTid(data.pid || 0) + let miniHeight = Math.round(data.frame.height / CpuStruct.cpuCount) + ctx.fillRect(data.frame.x, data.frame.y + (data.cpu || 0) * miniHeight + padding, data.frame.width, miniHeight - padding * 2) + } + } + + static setFrame(node: any, pns: number, startNS: number, endNS: number, frame: any) { + if ((node.startTime || 0) < startNS) { + node.frame.x = 0; + } else { + node.frame.x = Math.floor(((node.startTime || 0) - startNS) / pns); + } + if ((node.startTime || 0) + (node.dur || 0) > endNS) { + node.frame.width = frame.width - node.frame.x; + } else { + node.frame.width = Math.ceil(((node.startTime || 0) + (node.dur || 0) - startNS) / pns - node.frame.x); + } + if (node.frame.width < 1) { + node.frame.width = 1; + } + } + + static setProcessFrame(node: any, padding: number, startNS: number, endNS: number, totalNS: number, frame: any) { + let x1: number; + let x2: number; + if ((node.startTime || 0) < startNS) { + x1 = 0; + } else { + x1 = ns2x((node.startTime || 0), startNS, endNS, totalNS, frame); + } + if ((node.startTime || 0) + (node.dur || 0) > endNS) { + x2 = frame.width; + } else { + x2 = ns2x((node.startTime || 0) + (node.dur || 0), startNS, endNS, totalNS, frame); + } + let getV: number = x2 - x1 <= 1 ? 1 : x2 - x1; + if (!node.frame) { + node.frame = {}; + } + node.frame.x = Math.floor(x1); + node.frame.y = Math.floor(frame.y + padding); + node.frame.width = Math.ceil(getV); + node.frame.height = Math.floor(frame.height - padding * 2); + } +} \ No newline at end of file diff --git a/host/ide/src/trace/database/ProcedureWorkerThread.ts b/host/ide/src/trace/database/ProcedureWorkerThread.ts new file mode 100644 index 0000000..a285e79 --- /dev/null +++ b/host/ide/src/trace/database/ProcedureWorkerThread.ts @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {BaseStruct, ns2x, Rect} from "./ProcedureWorkerCommon.js"; + +export function thread(list: Array, res: Set, startNS: number, endNS: number, totalNS: number, frame: any) { + res.clear(); + if (list) { + for (let i = 0, len = list.length; i < len; i++) { + let it = list[i]; + if ((it.startTime || 0) + (it.dur || 0) > startNS && (it.startTime || 0) < endNS) { + ThreadStruct.setThreadFrame(list[i], 5, startNS, endNS, totalNS, frame) + if (i > 0 && ((list[i - 1].frame?.x || 0) == (list[i].frame?.x || 0) && (list[i - 1].frame?.width || 0) == (list[i].frame?.width || 0))) { + + } else { + res.add(list[i]) + } + } + } + } +} + +const padding = 3; + +export class ThreadStruct extends BaseStruct { + static runningColor: string = "#467b3b"; + static rColor = "#a0b84d"; + static otherColor = "#673ab7"; + static uninterruptibleSleepColor = "#f19d38"; + static traceColor = "#0d47a1"; + static sColor = "#FBFBFB"; + static hoverThreadStruct: ThreadStruct | undefined; + static selectThreadStruct: ThreadStruct | undefined; + static statusMap: any = { + "D": "Uninterruptible Sleep", + "S": "Sleeping", + "R": "Runnable", + "Running": "Running", + "R+": "Runnable (Preempted)", + "DK": "Uninterruptible Sleep + Wake Kill", + "I": "Task Dead", + "T": "Traced", + "t": "Traced", + "X": "Exit (Dead)", + "Z": "Exit (Zombie)", + "K": "Wake Kill", + "W": "Waking", + "P": "Parked", + "N": "No Load" + } + hasSched: number | undefined;// 14724852000 + pid: number | undefined// 2519 + processName: string | undefined //null + threadName: string | undefined//"ACCS0" + tid: number | undefined //2716 + upid: number | undefined // 1 + utid: number | undefined // 1 + cpu: number | undefined // null + dur: number | undefined // 405000 + end_ts: number | undefined // null + id: number | undefined // 1 + is_main_thread: number | undefined // 0 + name: string | undefined // "ACCS0" + startTime: number | undefined // 58000 + start_ts: number | undefined // null + state: string | undefined // "S" + type: string | undefined // "thread" + + static setThreadFrame(node: any, padding: number, startNS: number, endNS: number, totalNS: number, frame: any) { + let x1: number; + let x2: number; + if ((node.startTime || 0) < startNS) { + x1 = 0; + } else { + x1 = ns2x((node.startTime || 0), startNS, endNS, totalNS, frame); + } + if ((node.startTime || 0) + (node.dur || 0) > endNS) { + x2 = frame.width; + } else { + x2 = ns2x((node.startTime || 0) + (node.dur || 0), startNS, endNS, totalNS, frame); + } + let getV: number = x2 - x1 <= 1 ? 1 : x2 - x1; + if (!node.frame) { + node.frame = {}; + } + node.frame.x = Math.floor(x1); + node.frame.y = frame.y + padding; + node.frame.width = Math.ceil(getV); + node.frame.height = 30 - padding * 2; + } + + static draw(ctx: CanvasRenderingContext2D, data: ThreadStruct) { + if (data.frame) { + ctx.globalAlpha = 1 + let stateText = data.state || ''; + if ("S" == data.state) { + ctx.fillStyle = ThreadStruct.sColor; + ctx.globalAlpha = 0.2; // transparency + ctx.fillRect(data.frame.x, data.frame.y + padding, data.frame.width, data.frame.height - padding * 2) + ctx.globalAlpha = 1; // transparency + } else if ("R" == data.state || "R+" == data.state) { + ctx.fillStyle = ThreadStruct.rColor; + ctx.fillRect(data.frame.x, data.frame.y + padding, data.frame.width, data.frame.height - padding * 2) + ctx.fillStyle = "#fff"; + data.frame.width > 4 && ThreadStruct.drawString(ctx, ThreadStruct.getEndState(data.state || ''), 2, data.frame); + } else if ("D" == data.state) { + ctx.fillStyle = ThreadStruct.uninterruptibleSleepColor; + ctx.fillRect(data.frame.x, data.frame.y + padding, data.frame.width, data.frame.height - padding * 2) + ctx.fillStyle = "#fff"; + data.frame.width > 4 && ThreadStruct.drawString(ctx, ThreadStruct.getEndState(data.state || ''), 2, data.frame); + } else if ("Running" == data.state) { + ctx.fillStyle = ThreadStruct.runningColor; + ctx.fillRect(data.frame.x, data.frame.y + padding, data.frame.width, data.frame.height - padding * 2) + ctx.fillStyle = "#fff"; + data.frame.width > 4 && ThreadStruct.drawString(ctx, ThreadStruct.getEndState(data.state || ''), 2, data.frame); + } else if("T" == data.state || "t" == data.state) { + ctx.fillStyle = ThreadStruct.traceColor; + ctx.fillRect(data.frame.x, data.frame.y + padding, data.frame.width, data.frame.height - padding * 2) + ctx.fillStyle = "#fff"; + ThreadStruct.drawString(ctx, ThreadStruct.getEndState(data.state || ''), 2, data.frame); + } else { + ctx.fillStyle = ThreadStruct.otherColor; + ctx.fillRect(data.frame.x, data.frame.y + padding, data.frame.width, data.frame.height - padding * 2) + ctx.fillStyle = "#fff"; + data.frame.width > 4 && ThreadStruct.drawString(ctx, ThreadStruct.getEndState(data.state || ''), 2, data.frame); + } + if (ThreadStruct.selectThreadStruct && ThreadStruct.equals(ThreadStruct.selectThreadStruct, data) && ThreadStruct.selectThreadStruct.state != "S") { + ctx.strokeStyle = '#232c5d' + ctx.lineWidth = 2 + ctx.strokeRect(data.frame.x, data.frame.y + padding, data.frame.width - 2, data.frame.height - padding * 2) + } + } + } + + static drawString(ctx: CanvasRenderingContext2D, str: string, textPadding: number, frame: Rect) { + let textMetrics = ctx.measureText(str); + let charWidth = Math.round(textMetrics.width / str.length) + if (textMetrics.width < frame.width - textPadding * 2) { + let x2 = Math.floor(frame.width / 2 - textMetrics.width / 2 + frame.x + textPadding) + ctx.textBaseline = "middle" + ctx.font = "8px sans-serif"; + ctx.fillText(str, x2, Math.floor(frame.y + frame.height / 2), frame.width - textPadding * 2) + return; + } + if (frame.width - textPadding * 2 > charWidth * 4) { + let chatNum = (frame.width - textPadding * 2) / charWidth; + let x1 = frame.x + textPadding + ctx.textBaseline = "middle" + ctx.font = "8px sans-serif"; + ctx.fillText(str.substring(0, chatNum - 4) + '...', x1, Math.floor(frame.y + frame.height / 2), frame.width - textPadding * 2) + return; + } + } + + static getEndState(state: string): string { + let statusMapElement = ThreadStruct.statusMap[state]; + if (statusMapElement) { + return statusMapElement + } else { + if ("" == statusMapElement || statusMapElement == null) { + return ""; + } + return "Unknown State"; + } + } + + static equals(d1: ThreadStruct, d2: ThreadStruct): boolean { + if (d1 && d2 && d1.cpu == d2.cpu && + d1.tid == d2.tid && + d1.state == d2.state && + d1.startTime == d2.startTime && + d1.dur == d2.dur) { + return true; + } else { + return false; + } + } +} \ No newline at end of file diff --git a/host/ide/src/trace/database/ProcedureWorkerTimeline.ts b/host/ide/src/trace/database/ProcedureWorkerTimeline.ts new file mode 100644 index 0000000..4d9d47d --- /dev/null +++ b/host/ide/src/trace/database/ProcedureWorkerTimeline.ts @@ -0,0 +1,728 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {ns2s, Rect} from "./ProcedureWorkerCommon.js"; +import {CpuStruct} from "../bean/CpuStruct.js"; +import {ColorUtils} from "../component/trace/base/ColorUtils.js"; + +//绘制时间轴 +let timeRuler: TimeRuler | undefined; +let rangeRuler: RangeRuler | undefined; +let sportRuler: SportRuler | undefined; +let offsetTop: number = 0; +let offsetLeft: number = 0; + +// @ts-ignore +export function timeline(canvas: OffscreenCanvas, ctx: OffscreenCanvasRenderingContext2D, startNS: number, endNS: number, totalNS: number, frame: Rect, keyPressCode: any, keyUpCode: any, mouseDown: any, mouseUp: any, mouseMove: any, mouseOut: any, _offsetLeft: number, _offsetTop: number, changeHandler: Function) { + offsetLeft = _offsetLeft; + offsetTop = _offsetTop; + if (timeRuler == undefined) { + timeRuler = new TimeRuler(canvas, ctx, new Rect(0, 0, frame.width, 20), totalNS); + } + if (!sportRuler) { + sportRuler = new SportRuler(canvas, ctx, new Rect(0, 100.5, frame.width, frame.height - 100)); + } + if (!rangeRuler) { + rangeRuler = new RangeRuler(canvas, ctx!, new Rect(0, 25, frame.width, 75), { + startX: 0, + endX: frame.width, + startNS: 0, + endNS: totalNS, + totalNS: totalNS, + xs: [], + xsTxt: [] + }, (a) => { + if (sportRuler) { + sportRuler.range = a; + } + changeHandler(a); + }); + } + + rangeRuler.frame.width = frame.width; + sportRuler.frame.width = frame.width; + timeRuler.frame.width = frame.width; + if (keyPressCode) { + rangeRuler.keyPress(keyPressCode); + } else if (keyUpCode) { + rangeRuler.keyUp(keyUpCode); + } else if (mouseDown) { + rangeRuler.mouseDown(mouseDown); + } else if (mouseUp) { + rangeRuler.mouseUp(mouseUp); + } else if (mouseMove) { + rangeRuler.mouseMove(mouseMove); + } else if (mouseOut) { + rangeRuler.mouseOut(mouseOut); + } else { + timeRuler.draw(); + rangeRuler.draw(); + } +} + +export abstract class Graph { + // @ts-ignore + c: OffscreenCanvasRenderingContext2D; + // @ts-ignore + canvas: OffscreenCanvas | undefined | null; + frame: Rect; + + // @ts-ignore + protected constructor(canvas: OffscreenCanvas | undefined | null, c: OffscreenCanvasRenderingContext2D, frame: Rect) { + this.canvas = canvas; + this.frame = frame; + this.c = c; + } + + abstract draw(): void; +} + +export class TimeRuler extends Graph { + totalNS: number + private stepSmall: number; + private step: number; + private stepNS: number; + + // @ts-ignore + constructor(canvas: OffscreenCanvas | undefined | null, c: OffscreenCanvasRenderingContext2D, frame: Rect, totalNS: number = 10_000_000_000) { + super(canvas, c, frame) + this.totalNS = totalNS; + this.step = this.frame.width / 10; + this.stepSmall = this.frame.width / 100; + this.stepNS = this.totalNS / 10; + } + + draw() { + this.step = this.frame.width / 10; + this.stepSmall = this.frame.width / 100; + this.stepNS = this.totalNS / 10; + this.c.clearRect(this.frame.x, this.frame.y, this.frame.width, this.frame.height) + this.c.beginPath(); + this.c.strokeStyle = "#999" + this.c.lineWidth = 1; + for (let i = 0; i <= 10; i++) { + let x = Math.floor(i * this.step) + this.frame.x; + this.c.moveTo(x, 0); + this.c.lineTo(x, this.frame.height); + if (i == 10) break; + for (let j = 1; j < 10; j++) { + this.c.moveTo(x + Math.floor(j * this.stepSmall), 0); + this.c.lineTo(x + Math.floor(j * this.stepSmall), this.frame.height / 4); + } + this.c.fillStyle = '#999' + this.c.fillText(`${ns2s(i * this.stepNS)}`, x + 5, this.frame.height - 1) + } + this.c.stroke(); + this.c.closePath(); + } +} + +/** + * SportRuler + */ +export class SportRuler extends Graph { + public static rulerFlagObj: Flag | null = null; + public flagList: Array = []; + public flagListIdx: number | null = null + public obj = [{x: 3}, {x: 2}]; + lineColor: string | null = null; + private rangeFlag = new Flag(0, 0, 0, 0, 0); + private ruler_w = 1022; + private _range: TimeRange = {} as TimeRange; + + // @ts-ignore + constructor(canvas: OffscreenCanvas | undefined | null, c: OffscreenCanvasRenderingContext2D, frame: Rect) { + super(canvas, c, frame) + } + + get range(): TimeRange { + return this._range; + } + + set range(value: TimeRange) { + this._range = value; + this.draw() + } + + modifyFlagList(type: string, flag: any = {}) { + if (type == "amend") { + if (flag.text && this.flagListIdx !== null) { + this.flagList[this.flagListIdx].text = flag.text + } + if (flag.color && this.flagListIdx !== null) { + this.flagList[this.flagListIdx].color = flag.color + } + } else if (type == "remove") { + if (this.flagListIdx !== null) { + this.flagList.splice(this.flagListIdx, 1) + } + } + this.draw() + } + + draw(): void { + this.ruler_w = this.frame.width; + this.c.clearRect(this.frame.x, this.frame.y, this.frame.width, this.frame.height) + this.c.beginPath(); + this.lineColor = "#dadada"; + this.c.strokeStyle = this.lineColor //"#dadada" + this.c.lineWidth = 1; + this.c.moveTo(this.frame.x, this.frame.y) + this.c.lineTo(this.frame.x + this.frame.width, this.frame.y) + this.c.stroke(); + this.c.closePath(); + this.c.beginPath(); + this.c.lineWidth = 3; + this.c.strokeStyle = "#999999" + this.c.moveTo(this.frame.x, this.frame.y) + this.c.lineTo(this.frame.x, this.frame.y + this.frame.height) + this.c.stroke(); + this.c.closePath(); + this.c.beginPath(); + this.c.lineWidth = 1; + this.c.strokeStyle = this.lineColor;//"#999999" + this.c.fillStyle = '#999999' + this.c.font = '8px sans-serif' + this.range.xs?.forEach((it, i) => { + this.c.moveTo(it, this.frame.y) + this.c.lineTo(it, this.frame.y + this.frame.height) + this.c.fillText(`+${this.range.xsTxt[i]}`, it + 3, this.frame.y + 12) + }) + + this.c.stroke(); + this.c.closePath(); + } + + // drawTheFlag + drawTheFlag(x: number, color: string = "#999999", isFill: boolean = false, text: string = "") { + this.c.beginPath(); + this.c.fillStyle = color; + this.c.strokeStyle = color; + this.c.moveTo(x, 125); + this.c.lineTo(x + 10, 125); + this.c.lineTo(x + 10, 127); + this.c.lineTo(x + 18, 127); + this.c.lineTo(x + 18, 137); + this.c.lineTo(x + 10, 137); + this.c.lineTo(x + 10, 135); + this.c.lineTo(x + 2, 135); + this.c.lineTo(x + 2, 143); + this.c.lineTo(x, 143); + this.c.closePath() + if (isFill) { + this.c.fill() + } + this.c.stroke(); + + + if (text !== "") { + this.c.font = "10px Microsoft YaHei" + const {width} = this.c.measureText(text); + this.c.fillStyle = 'rgba(255, 255, 255, 0.8)'; // + this.c.fillRect(x + 21, 132, width + 4, 12); + this.c.fillStyle = "black"; + this.c.fillText(text, x + 23, 142); + this.c.stroke(); + } + } + + //随机生成十六位进制颜色 + randomRgbColor() { + const letters = '0123456789ABCDEF'; + let color = '#'; + for (let i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * 16)] + } + return color; + } + + //鼠标点击绘画旗子、点击旗子 + mouseUp(ev: MouseEvent) { + } + + //选中的旗子 + onFlagRangeEvent(flagObj: Flag, idx: number) { + SportRuler.rulerFlagObj = flagObj; + this.flagListIdx = idx; + } + + //鼠标移动 绘画旗子 + mouseMove(ev: MouseEvent) { + let x = ev.offsetX - (offsetLeft || 0) + let y = ev.offsetY - (offsetTop || 0) + if (y >= 50 && y < 200) { + this.draw() + if (y >= 123 && y < 142 && x > 0) { + let onFlagRange = this.flagList.findIndex((flagObj: Flag) => { + let flag_x = Math.round(this.ruler_w * (flagObj.time - this.range.startNS) / (this.range.endNS - this.range.startNS)); + return (x >= flag_x && x <= flag_x + 18) + }); + } + } + } +} + +const markPadding = 5; + +export class Mark extends Graph { + name: string | undefined + inspectionFrame: Rect + private _isHover: boolean = false + + // @ts-ignore + constructor(canvas: OffscreenCanvas | undefined | null, name: string, c: OffscreenCanvasRenderingContext2D, frame: Rect) { + super(canvas, c, frame); + this.name = name; + this.inspectionFrame = new Rect(frame.x - markPadding, frame.y, frame.width + markPadding * 2, frame.height) + } + + get isHover(): boolean { + return this._isHover; + } + + set isHover(value: boolean) { + this._isHover = value; + } + + draw(): void { + this.c.beginPath(); + this.c.lineWidth = 7 + this.c.strokeStyle = '#999999' + this.c.moveTo(this.frame.x, this.frame.y); + this.c.lineTo(this.frame.x, this.frame.y + this.frame.height / 3) + this.c.stroke(); + this.c.lineWidth = 1 + this.c.strokeStyle = '#999999' + this.c.moveTo(this.frame.x, this.frame.y); + this.c.lineTo(this.frame.x, this.frame.y + this.frame.height) + this.c.stroke(); + this.c.closePath(); + } +} + +export interface TimeRange { + totalNS: number + startX: number + endX: number + startNS: number + endNS: number + xs: Array + xsTxt: Array +} + +export class RangeRuler extends Graph { + public rangeRect: Rect + public markA: Mark + public markB: Mark + public range: TimeRange; + mouseDownOffsetX = 0 + mouseDownMovingMarkX = 0 + movingMark: Mark | undefined | null; + isMouseDown: boolean = false; + isMovingRange: boolean = false; + isNewRange: boolean = false; + markAX: number = 0; + markBX: number = 0; + isPress: boolean = false + pressFrameId: number = -1 + currentDuration: number = 0 + centerXPercentage: number = 0; + animaStartTime: number | undefined + animTime: number = 100; + p: number = 800; + private readonly notifyHandler: (r: TimeRange) => void; + private scale: number = 0; + //缩放级别 + private scales: Array = [50, 100, 200, 500, 1_000, 2_000, 5_000, 10_000, 20_000, 50_000, 100_000, 200_000, 500_000, + 1_000_000, 2_000_000, 5_000_000, 10_000_000, 20_000_000, 50_000_000, 100_000_000, 200_000_000, 500_000_000, + 1_000_000_000, 2_000_000_000, 5_000_000_000, 10_000_000_000, 20_000_000_000, 50_000_000_000, + 100_000_000_000, 200_000_000_000, 500_000_000_000]; + private _cpuUsage: Array<{ cpu: number, ro: number, rate: number }> = [] + + // @ts-ignore + constructor(canvas: OffscreenCanvas | undefined | null, c: OffscreenCanvasRenderingContext2D, frame: Rect, range: TimeRange, notifyHandler: (r: TimeRange) => void) { + super(canvas, c, frame) + this.range = range; + this.notifyHandler = notifyHandler; + this.markA = new Mark(canvas, 'A', c, new Rect(range.startX, frame.y, 1, frame.height)) + this.markB = new Mark(canvas, 'B', c, new Rect(range.endX, frame.y, 1, frame.height)) + this.rangeRect = new Rect(range.startX, frame.y, range.endX - range.startX, frame.height) + } + + set cpuUsage(value: Array<{ cpu: number, ro: number, rate: number }>) { + this._cpuUsage = value + this.draw(); + } + + drawCpuUsage() { + let maxNum = Math.round(this._cpuUsage.length / 100) + let miniHeight = Math.round(this.frame.height / CpuStruct.cpuCount);//每格高度 + let miniWidth = Math.ceil(this.frame.width / 100);//每格宽度 + for (let i = 0; i < this._cpuUsage.length; i++) { + //cpu: 0, ro: 0, rate: 0.987620037556431 + let it = this._cpuUsage[i] + this.c.fillStyle = ColorUtils.MD_PALETTE[it.cpu] + this.c.globalAlpha = it.rate + this.c.fillRect(this.frame.x + miniWidth * it.ro, this.frame.y + it.cpu * miniHeight, miniWidth, miniHeight) + } + } + + draw(discardNotify: boolean = false): void { + this.c.clearRect(this.frame.x - markPadding, this.frame.y, this.frame.width + markPadding * 2, this.frame.height) + this.c.beginPath(); + if (this._cpuUsage.length > 0) { + this.drawCpuUsage() + this.c.globalAlpha = 0; + } else { + this.c.globalAlpha = 1; + } + //绘制选中区域 + this.c.fillStyle = "#ffffff"; + this.rangeRect.x = this.markA.frame.x < this.markB.frame.x ? this.markA.frame.x : this.markB.frame.x + this.rangeRect.width = Math.abs(this.markB.frame.x - this.markA.frame.x) + this.c.fillRect(this.rangeRect.x, this.rangeRect.y, this.rangeRect.width, this.rangeRect.height) + this.c.globalAlpha = 1; + this.c.globalAlpha = .5; + this.c.fillStyle = "#999999" + this.c.fillRect(this.frame.x, this.frame.y, this.rangeRect.x, this.rangeRect.height) + this.c.fillRect(this.rangeRect.x + this.rangeRect.width, this.frame.y, this.frame.width - this.rangeRect.width, this.rangeRect.height) + this.c.globalAlpha = 1; + this.c.closePath(); + this.markA.draw(); + this.markB.draw(); + if (this.notifyHandler) { + this.range.startX = this.rangeRect.x + this.range.endX = this.rangeRect.x + this.rangeRect.width + this.range.startNS = this.range.startX * this.range.totalNS / (this.frame.width || 0) + this.range.endNS = this.range.endX * this.range.totalNS / (this.frame.width || 0) + let l20 = (this.range.endNS - this.range.startNS) / 20; + let min = 0; + let max = 0; + let weight = 0; + for (let index = 0; index < this.scales.length; index++) { + if (this.scales[index] > l20) { + if (index > 0) { + min = this.scales[index - 1]; + } else { + min = 0; + } + max = this.scales[index]; + weight = (l20 - min) * 1.0 / (max - min); + if (weight > 0.243) { + this.scale = max; + } else { + this.scale = min; + } + break; + } + } + if (this.scale == 0) { + this.scale = this.scales[0]; + } + let tmpNs = 0; + let yu = this.range.startNS % this.scale; + let realW = (this.scale * this.frame.width) / (this.range.endNS - this.range.startNS); + let startX = 0; + if (this.range.xs) { + this.range.xs.length = 0 + } else { + this.range.xs = [] + } + if (this.range.xsTxt) { + this.range.xsTxt.length = 0 + } else { + this.range.xsTxt = [] + } + if (yu != 0) { + let firstNodeWidth = ((this.scale - yu) / this.scale * realW); + startX += firstNodeWidth; + tmpNs += yu; + this.range.xs.push(startX) + this.range.xsTxt.push(ns2s(tmpNs)) + } + while (tmpNs < this.range.endNS - this.range.startNS) { + startX += realW; + tmpNs += this.scale; + this.range.xs.push(startX) + this.range.xsTxt.push(ns2s(tmpNs)) + } + if (!discardNotify) { + this.notifyHandler(this.range) + } + } + } + + mouseDown(ev: MouseEvent) { + let x = ev.offsetX - (offsetLeft || 0) + let y = ev.offsetY - (offsetTop || 0) + this.isMouseDown = true; + this.mouseDownOffsetX = x; + if (this.markA.isHover) { + this.movingMark = this.markA; + this.mouseDownMovingMarkX = this.movingMark.frame.x || 0 + } else if (this.markB.isHover) { + this.movingMark = this.markB; + this.mouseDownMovingMarkX = this.movingMark.frame.x || 0 + } else { + this.movingMark = null; + } + if (this.rangeRect.containsWithPadding(x, y, 5, 0)) { + this.isMovingRange = true; + this.markAX = this.markA.frame.x; + this.markBX = this.markB.frame.x; + } else if (this.frame.containsWithMargin(x, y, 20, 0, 0, 0) && !this.rangeRect.containsWithMargin(x, y, 0, markPadding, 0, markPadding)) { + this.isNewRange = true; + } + } + + mouseUp(ev: MouseEvent) { + this.isMouseDown = false; + this.isMovingRange = false; + this.isNewRange = false; + this.movingMark = null; + } + + mouseMove(ev: MouseEvent) { + let x = ev.offsetX - (offsetLeft || 0); + let y = ev.offsetY - (offsetTop || 0) + this.centerXPercentage = x / (this.frame.width || 0) + if (this.centerXPercentage <= 0) { + this.centerXPercentage = 0 + } else if (this.centerXPercentage >= 1) { + this.centerXPercentage = 1 + } + let maxX = this.frame.width || 0 + if (this.markA.inspectionFrame.contains(x, y)) { + this.markA.isHover = true + } else if (this.markB.inspectionFrame.contains(x, y)) { + this.markB.isHover = true; + } else { + this.markA.isHover = false; + this.markB.isHover = false; + } + if (this.movingMark) { + let result = x - this.mouseDownOffsetX + this.mouseDownMovingMarkX; + if (result >= 0 && result <= maxX) { + this.movingMark.frame.x = result + } else if (result < 0) { + this.movingMark.frame.x = 0 + } else { + this.movingMark.frame.x = maxX + } + this.movingMark.inspectionFrame.x = this.movingMark.frame.x - markPadding + requestAnimationFrame(() => this.draw()); + } + if (this.isMovingRange && this.isMouseDown) { + let result = x - this.mouseDownOffsetX; + let mA = result + this.markAX + let mB = result + this.markBX + if (mA >= 0 && mA <= maxX) { + this.markA.frame.x = mA + } else if (mA < 0) { + this.markA.frame.x = 0 + } else { + this.markA.frame.x = maxX + } + this.markA.inspectionFrame.x = this.markA.frame.x - markPadding + if (mB >= 0 && mB <= maxX) { + this.markB.frame.x = mB; + } else if (mB < 0) { + this.markB.frame.x = 0 + } else { + this.markB.frame.x = maxX + } + this.markB.inspectionFrame.x = this.markB.frame.x - markPadding + requestAnimationFrame(() => this.draw()); + } else if (this.isNewRange) { + this.markA.frame.x = this.mouseDownOffsetX; + this.markA.inspectionFrame.x = this.mouseDownOffsetX - markPadding; + if (x >= 0 && x <= maxX) { + this.markB.frame.x = x; + } else if (x < 0) { + this.markB.frame.x = 0; + } else { + this.markB.frame.x = maxX; + } + this.markB.inspectionFrame.x = this.markB.frame.x - markPadding; + requestAnimationFrame(() => this.draw()); + } + } + + mouseOut(ev: MouseEvent) { + this.movingMark = null; + } + + fillX() { + if (this.range.startNS < 0) this.range.startNS = 0; + if (this.range.endNS < 0) this.range.endNS = 0; + if (this.range.endNS > this.range.totalNS) this.range.endNS = this.range.totalNS; + if (this.range.startNS > this.range.totalNS) this.range.startNS = this.range.totalNS; + this.range.startX = this.range.startNS * (this.frame.width || 0) / this.range.totalNS + this.range.endX = this.range.endNS * (this.frame.width || 0) / this.range.totalNS + this.markA.frame.x = this.range.startX + this.markA.inspectionFrame.x = this.markA.frame.x - markPadding + this.markB.frame.x = this.range.endX + this.markB.inspectionFrame.x = this.markB.frame.x - markPadding + } + + keyPress(ev: KeyboardEvent) { + if (this.animaStartTime === undefined) { + this.animaStartTime = new Date().getTime(); + } + let startTime = new Date().getTime(); + let duration = (startTime - this.animaStartTime); + if (duration < this.animTime) duration = this.animTime + this.currentDuration = duration + if (this.isPress) return + this.isPress = true + switch (ev.key.toLocaleLowerCase()) { + case "w": + let animW = () => { + if (this.scale === 50) return; + this.range.startNS += (this.centerXPercentage * this.currentDuration * 2 * this.scale / this.p); + this.range.endNS -= ((1 - this.centerXPercentage) * this.currentDuration * 2 * this.scale / this.p); + this.fillX(); + this.draw(); + this.pressFrameId = requestAnimationFrame(animW) + } + this.pressFrameId = requestAnimationFrame(animW) + break; + case "s": + let animS = () => { + if (this.range.startNS <= 0 && this.range.endNS >= this.range.totalNS) return; + this.range.startNS -= (this.centerXPercentage * this.currentDuration * 2 * this.scale / this.p); + this.range.endNS += ((1 - this.centerXPercentage) * this.currentDuration * 2 * this.scale / this.p); + this.fillX(); + this.draw(); + this.pressFrameId = requestAnimationFrame(animS) + } + this.pressFrameId = requestAnimationFrame(animS) + break; + case "a": + let animA = () => { + if (this.range.startNS == 0) return; + let s = this.scale / this.p * this.currentDuration; + this.range.startNS -= s; + this.range.endNS -= s; + this.fillX(); + this.draw(); + this.pressFrameId = requestAnimationFrame(animA) + } + this.pressFrameId = requestAnimationFrame(animA) + break; + case "d": + let animD = () => { + if (this.range.endNS >= this.range.totalNS) return; + this.range.startNS += this.scale / this.p * this.currentDuration; + this.range.endNS += this.scale / this.p * this.currentDuration; + this.fillX(); + this.draw(); + this.pressFrameId = requestAnimationFrame(animD) + } + this.pressFrameId = requestAnimationFrame(animD) + break; + } + } + + keyUp(ev: KeyboardEvent) { + this.animaStartTime = undefined; + this.isPress = false + if (this.pressFrameId != -1) { + cancelAnimationFrame(this.pressFrameId) + } + let startTime = new Date().getTime(); + switch (ev.key) { + case "w": + let animW = () => { + if (this.scale === 50) return; + let dur = (new Date().getTime() - startTime); + this.range.startNS += (this.centerXPercentage * 100 * this.scale / this.p); + this.range.endNS -= ((1 - this.centerXPercentage) * 100 * this.scale / this.p); + this.fillX(); + this.draw(); + if (dur < 300) { + requestAnimationFrame(animW) + } + } + requestAnimationFrame(animW) + break; + case "s": + let animS = () => { + if (this.range.startNS <= 0 && this.range.endNS >= this.range.totalNS) return; + let dur = (new Date().getTime() - startTime); + this.range.startNS -= (this.centerXPercentage * 100 * this.scale / this.p); + this.range.endNS += ((1 - this.centerXPercentage) * 100 * this.scale / this.p); + this.fillX(); + this.draw(); + if (dur < 300) { + requestAnimationFrame(animS) + } + } + requestAnimationFrame(animS) + break; + case "a": + let animA = () => { + if (this.range.startNS <= 0) return + let dur = (new Date().getTime() - startTime); + let s = this.scale * 80 / this.p; + this.range.startNS -= s; + this.range.endNS -= s; + this.fillX(); + this.draw(); + if (dur < 300) { + requestAnimationFrame(animA) + } + } + animA(); + break; + case "d": + let animD = () => { + if (this.range.endNS >= this.range.totalNS) return; + let dur = (new Date().getTime() - startTime); + let s = this.scale * 80 / this.p; + this.range.startNS += s; + this.range.endNS += s; + this.fillX(); + this.draw(); + if (dur < 300) { + requestAnimationFrame(animD) + } + } + animD(); + break; + } + } +} + +export class Flag { + x: number = 0 + y: number = 0 + width: number = 0 + height: number = 0 + time: number = 0 + color: string = "" + selected: boolean = false + text: string = "" + + constructor(x: number, y: number, width: number, height: number, time: number, color: string = "#999999", selected = false) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.time = time; + this.color = color; + this.selected = selected; + } +} \ No newline at end of file diff --git a/host/ide/src/trace/database/SqlLite.ts b/host/ide/src/trace/database/SqlLite.ts new file mode 100644 index 0000000..3e49266 --- /dev/null +++ b/host/ide/src/trace/database/SqlLite.ts @@ -0,0 +1,874 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import './sql-wasm.js' +import {CpuStruct} from "../bean/CpuStruct.js"; +import {CpuFreqStruct} from "../bean/CpuFreqStruct.js"; +import {ThreadStruct} from "../bean/ThreadStruct.js"; +import {ProcessMemStruct} from "../bean/ProcessMemStruct.js"; +import {Counter, Fps, SelectionData} from "../bean/BoxSelection.js"; +import {FuncStruct} from "../bean/FuncStruct.js"; +import {WakeUpTimeBean} from "../bean/WakeUpTimeBean.js"; +import {WakeupBean} from "../bean/WakeupBean.js"; +import {BinderArgBean} from "../bean/BinderArgBean.js"; +import {FpsStruct} from "../bean/FpsStruct.js"; +import {HeapBean} from "../bean/HeapBean.js"; +import {SPT, SPTChild, StateProcessThread} from "../bean/StateProcessThread.js"; +import {CpuUsage, Freq} from "../bean/CpuUsage.js"; +import {HeapStruct} from "../bean/HeapStruct.js"; +import {HeapTreeDataBean} from "../bean/HeapTreeDataBean.js"; +import { + NativeEventHeap, NativeHookMalloc, + NativeHookProcess, + NativeHookSampleQueryInfo, + NativeHookStatistics +} from "../bean/NativeHook.js"; + +class DbThread extends Worker { + busy: boolean = false; + isCancelled: boolean = false; + id: number = -1; + taskMap: any = {}; + cacheArray: Array = []; + + uuid(): string { + // @ts-ignore + return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)); + } + + queryFunc(name: string, sql: string, args: any, handler: Function) { + this.busy = true; + let id = this.uuid(); + this.taskMap[id] = handler + this.postMessage({ + id: id, + name: name, + action: "exec", + sql: sql, + params: args, + }) + } + + dbOpen = async (): Promise<{ status: boolean, msg: string }> => { + return new Promise((resolve, reject) => { + let id = this.uuid(); + this.taskMap[id] = (res: any) => { + if (res.init) { + resolve({status: res.init, msg: res.msg}); + } else { + resolve({status: res.init, msg: res.msg}); + } + } + this.postMessage({ + id: id, + action: "open", + buffer: DbPool.sharedBuffer!, /*Optional. An ArrayBuffer representing an SQLite Database file*/ + }, [DbPool.sharedBuffer!]); + }) + } +} + +export class DbPool { + static sharedBuffer: ArrayBuffer | null = null; + maxThreadNumber: number = 0; + works: Array = []; + progress: Function | undefined | null; + num = Math.floor(Math.random() * 10 + 1) + 20; + init = async (type: string, threadBuild: (() => DbThread) | undefined = undefined) => { // wasm | server | sqlite + await this.close(); + if (type === "wasm") { + this.maxThreadNumber = 1; + } else if (type === "server") { + this.maxThreadNumber = 1; + } else if (type === "sqlite") { + this.maxThreadNumber = 1; + } else if (type === "duck") { + this.maxThreadNumber = 1; + } + for (let i = 0; i < this.maxThreadNumber; i++) { + let thread: DbThread + if (threadBuild) { + thread = threadBuild() + } else { + if (type === "wasm") { + thread = new DbThread("trace/database/TraceWorker.js") + } else if (type === "server") { + thread = new DbThread("trace/database/SqlLiteWorker.js") + } else if (type === "sqlite") { + thread = new DbThread("trace/database/SqlLiteWorker.js") + } + } + thread!.onmessage = (event: MessageEvent) => { + thread.busy = false; + if (Reflect.has(thread.taskMap, event.data.id)) { + if (event.data.results) { + let fun = thread.taskMap[event.data.id]; + if (fun) { + fun(event.data.results); + } + Reflect.deleteProperty(thread.taskMap, event.data.id); + } else if (Reflect.has(event.data, 'ready')) { + this.progress!("database opened", this.num + event.data.index) + } else if (Reflect.has(event.data, 'init')) { + this.progress!("database ready", 40) + let fun = thread.taskMap[event.data.id]; + if (fun) { + fun(event.data) + } + Reflect.deleteProperty(thread.taskMap, event.data.id) + } else { + let fun = thread.taskMap[event.data.id]; + if (fun) { + fun([]) + } + Reflect.deleteProperty(thread.taskMap, event.data.id) + } + + } + } + thread!.onmessageerror = e => { + } + thread!.onerror = e => { + } + thread!.id = i; + thread!.busy = false; + this.works?.push(thread!); + } + } + + initServer = async (url: string, progress: Function) => { + this.progress = progress; + progress("database loaded", 15) + let buf = await fetch(url).then(res => res.arrayBuffer()); + DbPool.sharedBuffer = buf; + progress("open database", 20) + for (let i = 0; i < this.works.length; i++) { + let thread = this.works[i]; + let {status, msg} = await thread.dbOpen() + if (!status) { + return {status, msg} + } + } + return {status: true, msg: "ok"}; + } + initSqlite = async (buf: ArrayBuffer, progress: Function) => { + this.progress = progress; + progress("database loaded", 15) + DbPool.sharedBuffer = buf; + progress("parse database", 20) + for (let i = 0; i < this.works.length; i++) { + let thread = this.works[i]; + let {status, msg} = await thread.dbOpen() + if (!status) { + return {status, msg} + } + } + return {status: true, msg: "ok"}; + } + + close = async () => { + for (let i = 0; i < this.works.length; i++) { + let thread = this.works[i]; + thread.terminate(); + } + this.works.length = 0; + } + + submit(name: string, sql: string, args: any, handler: Function) { + let noBusyThreads = this.works.filter(it => !it.busy); + let thread: DbThread + if (noBusyThreads.length > 0) { //取第一个空闲的线程进行任务 + thread = noBusyThreads[0]; + thread.queryFunc(name, sql, args, handler) + } else { // 随机插入一个线程中 + thread = this.works[Math.floor(Math.random() * this.works.length)] + thread.queryFunc(name, sql, args, handler) + } + } +} + +export const threadPool = new DbPool() + +function query(name: string, sql: string, args: any = null): Promise> { + return new Promise>((resolve, reject) => { + threadPool.submit(name, sql, args, (res: any) => { + resolve(res) + }) + }) +} + +export const querySql = (sql: string): Promise> => query("queryProcess", sql) +/*-------------------------------------------------------------------------------------*/ +export const queryProcess = (): Promise> => + query("queryProcess", `SELECT pid,processName FROM temp_query_process`) +/*-------------------------------------------------------------------------------------*/ +export const queryTotalTime = (): Promise> => + query("queryTotalTime", `select end_ts-start_ts as total from trace_section;`) +/*-------------------------------------------------------------------------------------*/ +export const queryCpu = async (): Promise> => + query("queryCpu", `select cpu from cpu_measure_filter where name='cpu_idle' order by cpu;`) +/*-------------------------------------------------------------------------------------*/ +export const getAsyncEvents = (): Promise> => + query("getAsyncEvents", `select *,p.pid as pid,c.ts - t.start_ts as "startTime" from callstack c,trace_section t +left join process p on c.callid = p.id where cookie is not null;`) + +export const getCpuUtilizationRate = (startNS: number, endNS: number): Promise> => + query("getCpuUtilizationRate", `select * from temp_get_cpu_rate;`, {}) +/*-------------------------------------------------------------------------------------*/ +export const getFps = () => + query("getFps", `select distinct(ts-tb.start_ts) as startNS,fps +from hidump c ,trace_section tb +where startNS >= 0 +order by startNS;`, {}) + +/*-------------------------------------------------------------------------------------*/ +export const getFunDataByTid = (tid: number): Promise> => + query("getFunDataByTid", `select * from temp_query_thread_function where tid = $tid`, {$tid: tid}) +/*-------------------------------------------------------------------------------------*/ +export const getStatesProcessThreadDataCount = (): Promise> => + query("getStatesProcessThreadData", `select count(1) as count from (select IP.name as process, + IP.pid as processId, + A.name as thread, + B.state as state, + A.tid as threadId, + B.dur, + (B.ts - TR.start_ts + B.dur) as end_ts, + (B.ts - TR.start_ts) as start_ts, + B.cpu, + C.priority, + '-' as note +from thread_state as B + left join thread as A on B.itid = A.id + left join process as IP on A.ipid = IP.id + left join trace_section as TR + left join sched_slice as C on B.itid = C.itid and C.ts = B.ts +where + B.dur > 0 and IP.pid not null and (B.ts - TR.start_ts) >= 0); +`, {}); +export const getStatesProcessThreadData = (limit:number,offset:number): Promise> => + query("getStatesProcessThreadData", `select IP.name as process, + IP.pid as processId, + A.name as thread, + B.state as state, + A.tid as threadId, + B.dur, + (B.ts - TR.start_ts + B.dur) as end_ts, + (B.ts - TR.start_ts) as start_ts, + B.cpu, + C.priority, + '-' as note +from thread_state as B + left join thread as A on B.itid = A.id + left join process as IP on A.ipid = IP.id + left join trace_section as TR + left join sched_slice as C on B.itid = C.itid and C.ts = B.ts +where + B.dur > 0 and IP.pid not null and (B.ts - TR.start_ts) >= 0 limit $limit offset $offset; +`, {$limit: limit, $offset: offset}); + +export const getTabStatesGroupByProcessThread = (leftNs: number, rightNs: number): Promise> => + query("getTabStatesGroupByProcessThread", `select process, + processId, + thread, + threadId, + sum(dur) as wallDuration, + round(avg(dur),2) as avgDuration, + min(dur) as minDuration, + max(dur) as maxDuration, + count(threadId) as count +from temp_get_process_thread_state_data +where not (end_ts < $leftNS or start_ts > $rightNS) +group by process, processId,thread,threadId`, {$leftNS: leftNs, $rightNS: rightNs}); + +export const getTabStatesGroupByProcess = (leftNs: number, rightNs: number): Promise> => + query("getTabStatesGroupByProcess", `select process, processId, + sum(dur) as wallDuration, + round(avg(dur),2) as avgDuration, + min(dur) as minDuration, + max(dur) as maxDuration, + count(processId) as count +from temp_get_process_thread_state_data +where not (end_ts < $leftNS or start_ts > $rightNS) +group by process,processId`, {$leftNS: leftNs, $rightNS: rightNs}); + +// todo wasm模式报错 +export const getTabStatesGroupByState = (leftNs: number, rightNs: number): Promise> => + query("getTabStatesGroupByState", `select state, + sum(dur) as wallDuration, + round(avg(dur),2) as avgDuration, + min(dur) as minDuration, + max(dur) as maxDuration, + count(state) as count +from temp_get_process_thread_state_data +where not (end_ts < $leftNS or start_ts > $rightNS) +group by state`, {$leftNS: leftNs, $rightNS: rightNs}); + +export const getTabStatesGroupByStatePid = (leftNs: number, rightNs: number): Promise> => + query("getTabStatesGroupByStatePid", `select process, + processId, + state, + sum(dur) as wallDuration, + round(avg(dur),2) as avgDuration, + min(dur) as minDuration, + max(dur) as maxDuration, + count(processId) as count +from temp_get_process_thread_state_data +where not (end_ts < $leftNS or start_ts > $rightNS) +group by process,processId,state`, {$leftNS: leftNs, $rightNS: rightNs}); + +export const getTabStatesGroupByStatePidTid = (leftNs: number, rightNs: number): Promise> => + query("getTabStatesGroupByStatePidTid", `select process, + processId, + thread, + state, + threadId, + sum(dur) as wallDuration, + round(avg(dur),2) as avgDuration, + min(dur) as minDuration, + max(dur) as maxDuration, + count(threadId) as count +from temp_get_process_thread_state_data +where not (end_ts < $leftNS or start_ts > $rightNS) +group by process, processId, thread, threadId,state`, {$leftNS: leftNs, $rightNS: rightNs}); + +export const getTabBoxChildData = (leftNs: number, rightNs: number, state: string | undefined, processId: number | undefined, threadId: number | undefined): Promise> => + query("getTabBoxChildData", `select IP.name as process, + IP.pid as processId, + A.name as thread, + B.state as state, + A.tid as threadId, + B.dur as duration, + B.ts - TR.start_ts as startNs, + B.cpu, + C.priority, + '-' as note +from thread_state AS B + left join thread as A on B.itid = A.id + left join process AS IP on A.ipid = IP.id + left join trace_section AS TR + left join sched_slice as C on B.itid = C.itid and C.ts = B.ts +where + B.dur > 0 and IP.pid not null + and not ((B.ts - TR.start_ts + B.dur < $leftNS) or (B.ts - TR.start_ts > $rightNS)) + ${state != undefined && state != '' ? 'and B.state = $state' : ''} + ${processId != undefined && processId != -1 ? 'and IP.pid = $processID' : ''} + ${threadId != undefined && threadId != -1 ? 'and A.tid = $threadID' : ''} + `, {$leftNS: leftNs, $rightNS: rightNs, $state: state, $processID: processId, $threadID: threadId}) + +/*-------------------------------------------------------------------------------------*/ +export const getTabCpuUsage = (cpus: Array, leftNs: number, rightNs: number): Promise> => + query("getTabCpuUsage", `select cpu, + sum(case + when (A.ts - B.start_ts) < $leftNS then (A.ts - B.start_ts + A.dur - $leftNS) + when (A.ts - B.start_ts) >= $leftNS and (A.ts - B.start_ts + A.dur) <= $rightNS then A.dur + when (A.ts - B.start_ts + A.dur) > $rightNS then ($rightNS - (A.ts - B.start_ts)) end) / cast($rightNS - $leftNS as float) as usage +from thread_state A ,trace_section B +where (A.ts - B.start_ts) > 0 and A.dur > 0 + and cpu in (${cpus.join(",")}) + and (A.ts - B.start_ts + A.dur) > $leftNS and (A.ts - B.start_ts) < $rightNS +group by cpu`, {$leftNS: leftNs, $rightNS: rightNs}) + +export const getTabCpuFreq = (cpus: Array, leftNs: number, rightNs: number): Promise> => + query("getTabCpuFreq", `select cpu,value,(ts - tb.start_ts) as startNs +from measure c ,trace_section tb +inner join cpu_measure_filter t on c.filter_id = t.id +where (name = 'cpufreq' or name='cpu_frequency') + and cpu in (${cpus.join(",")}) + and startNs > 0 + and startNs < $rightNS + order by startNs`, {$leftNS: leftNs, $rightNS: rightNs}) +/*-------------------------------------------------------------------------------------*/ +export const getTabFps = (leftNs: number, rightNs: number): Promise> => + query("getTabFps", `select distinct(ts-tb.start_ts) as startNS,fps +from hidump c ,trace_section tb +where startNS <= $rightNS and startNS >= 0 +order by startNS;`, {$leftNS: leftNs, $rightNS: rightNs}) +/*-------------------------------------------------------------------------------------*/ +export const getTabCounters = (filterIds: Array, startTime: number) => + query("getTabCounters", `select t1.filter_id as trackId,t2.name,value, t1.ts - t3.start_ts as startTime +from measure t1 +left join process_measure_filter t2 on t1.filter_id = t2.id +left join trace_section t3 where filter_id in (${filterIds.join(",")}) +and startTime <= $startTime +order by startTime asc;`, {$startTime: startTime}) +/*-------------------------------------------------------------------------------------*/ +export const getTabCpuByProcess = (cpus: Array, leftNS: number, rightNS: number) => + query("getTabCpuByProcess", `select IP.name as process, + IP.pid as pid, + sum(B.dur) as wallDuration, + avg(B.dur) as avgDuration, + count(A.tid) as occurrences +from thread_state AS B + left join thread as A on B.itid = A.id + left join trace_section AS TR + left join process AS IP on A.ipid = IP.id +where B.cpu in (${cpus.join(",")}) + and not ((B.ts - TR.start_ts + B.dur < $leftNS) or (B.ts - TR.start_ts > $rightNS )) +group by IP.name, IP.pid +order by wallDuration desc;`, {$rightNS: rightNS, $leftNS: leftNS}) +/*-------------------------------------------------------------------------------------*/ +export const getTabCpuByThread = (cpus: Array, leftNS: number, rightNS: number) => + query("getTabCpuByThread", `select IP.name as process, + IP.pid as pid, + A.name as thread, + A.tid as tid, + sum(B.dur) as wallDuration, + avg(B.dur) as avgDuration, + count(A.tid) as occurrences +from thread_state AS B + left join thread as A on B.itid = A.id + left join trace_section AS TR + left join process AS IP on A.ipid = IP.id +where B.cpu in (${cpus.join(",")}) + and not ((B.ts - TR.start_ts + B.dur < $leftNS) or (B.ts - TR.start_ts > $rightNS)) +group by IP.name, IP.pid, A.name, A.tid +order by wallDuration desc;`, {$rightNS: rightNS, $leftNS: leftNS}) +/*-------------------------------------------------------------------------------------*/ +export const getTabSlices = (funTids: Array, leftNS: number, rightNS: number): Promise> => + query("getTabSlices", `select + c.name as name, + sum(c.dur) as wallDuration, + avg(c.dur) as avgDuration, + count(c.name) as occurrences +from thread A,trace_section D +left join callstack C on A.id = C.callid +where C.ts not null + and c.dur >= 0 + and A.tid in (${funTids.join(",")}) + and c.name not like 'binder%' + and not ((C.ts - D.start_ts + C.dur < $leftNS) or (C.ts - D.start_ts > $rightNS)) +group by c.name +order by wallDuration desc;`, {$leftNS: leftNS, $rightNS: rightNS}) +/*-------------------------------------------------------------------------------------*/ +export const getTabThreadStates = (tIds: Array, leftNS: number, rightNS: number): Promise> => + query("getTabThreadStates", `select + IP.name as process, + IP.pid, + A.name as thread, + A.tid, + B.state, + sum(B.dur) as wallDuration, + avg(ifnull(B.dur,0)) as avgDuration, + count(A.tid) as occurrences +from thread_state AS B +left join thread as A on A.id = B.itid +left join trace_section AS TR +left join process AS IP on IP.id=ipid +where A.tid in (${tIds.join(",")}) +and not ((B.ts - TR.start_ts + ifnull(B.dur,0) < $leftNS) or (B.ts - TR.start_ts > $rightNS)) +group by IP.name, IP.pid, A.name, A.tid, B.state +order by wallDuration desc;`, {$leftNS: leftNS, $rightNS: rightNS}) +/*-------------------------------------------------------------------------------------*/ +export const getThreadFuncData = (tId: number): Promise> => + query("getThreadFuncData", `select tid, + A.start_ts, + A.end_ts, + A.name as threadName, + is_main_thread, + c.callid as track_id, + c.ts-D.start_ts as startTs, + c.ts + c.dur as endTs, + c.dur, + c.name as funName, + c.depth, + c.parent_id, + c.id +from thread A,trace_section D +left join callstack C on A.id = C.callid +where startTs not null and A.tid = $tid;`, {$tid: tId}) +/*-------------------------------------------------------------------------------------*/ +export const queryBinderArgsByArgset = (argset: number): Promise> => + query("queryBinderArgsByArgset", `select * from args_view where argset = $argset;`, {$argset: argset}) +/*-------------------------------------------------------------------------------------*/ +export const queryClockFrequency = (): Promise> => + query("queryClockFrequency", `with freq as ( select measure.filter_id, measure.ts, measure.type, measure.value from clock_event_filter +left join measure +where clock_event_filter.name = '%s' and clock_event_filter.type = 'clock_set_rate' and clock_event_filter.id = measure.filter_id +order by measure.ts) +select freq.filter_id,freq.ts - r.start_ts as ts,freq.type,freq.value from freq,trace_section r;`, {}) +/*-------------------------------------------------------------------------------------*/ +export const queryClockList = (): Promise> => + query("queryClockList", `with list as ( + select distinct name from clock_event_filter + where clock_event_filter.type = 'clock_set_rate' order by name +),freq as( + select measure.filter_id, measure.ts, measure.type, measure.value , clock_event_filter.name from clock_event_filter + left join measure + where clock_event_filter.type = 'clock_set_rate' and clock_event_filter.id = measure.filter_id + order by measure.ts +),state as ( + select filter_id, ts, endts, endts-ts as dur, type, value,name from + (select measure.filter_id, measure.ts, lead(ts, 1, null) over( order by measure.ts) endts, measure.type, measure.value,clock_event_filter.name from clock_event_filter,trace_section + left join measure + where clock_event_filter.type != 'clock_set_rate' and clock_event_filter.id = measure.filter_id + order by measure.ts) +),count_freq as ( + select COUNT(*) num,name srcname from freq group by name +),count_state as ( + select COUNT(*) num,name srcname from state group by name +) +select count_freq.srcname||' Frequency' as name,* from count_freq union select count_state.srcname||' State' as name,* from count_state order by name;`) +/*-------------------------------------------------------------------------------------*/ +export const queryClockState = (): Promise> => + query("queryClockState", `with state as ( +select filter_id, ts, endts, endts-ts as dur, type, value from +(select measure.filter_id, measure.ts, lead(ts, 1, null) over( order by measure.ts) endts, measure.type, measure.value from clock_event_filter,trace_section +left join measure +where clock_event_filter.name = '%s' and clock_event_filter.type != 'clock_set_rate' and clock_event_filter.id = measure.filter_id +order by measure.ts)) +-- select * from state; +select s.filter_id,s.ts-r.start_ts as ts,s.type,s.value,s.dur from state s,trace_section r;`) +/*-------------------------------------------------------------------------------------*/ +export const queryCpuData = (cpu: number, startNS: number, endNS: number): Promise> => + query("queryCpuData", `select * from temp_query_cpu_data where cpu = $cpu and startTime between $startNS and $endNS;`, { + $cpu: cpu, + $startNS: startNS, + $endNS: endNS + }) +/*-------------------------------------------------------------------------------------*/ +export const queryCpuFreq = (): Promise> => +query("queryCpuFreq", `select cpu from cpu_measure_filter where (name='cpufreq' or name='cpu_frequency') order by cpu;`) +/*-------------------------------------------------------------------------------------*/ +export const queryCpuFreqData = (cpu: number): Promise> => + query("queryCpuFreqData", `select cpu,value,ts-tb.start_ts as startNS +from measure c ,trace_section tb +inner join cpu_measure_filter t on c.filter_id = t.id +where (name = 'cpufreq' or name='cpu_frequency') and cpu= $cpu +order by ts;`, {$cpu: cpu}); +/*-------------------------------------------------------------------------------------*/ +export const queryCpuMax = (): Promise> => + query("queryCpuMax", `select cpu from sched_slice order by cpu desc limit 1;`) +/*-------------------------------------------------------------------------------------*/ +export const queryCpuMaxFreq = (): Promise> => +query("queryCpuMaxFreq", `select max(value) as maxFreq +from measure c +inner join cpu_measure_filter t on c.filter_id = t.id +where (name = 'cpufreq' or name='cpu_frequency');`) +// /*-------------------------------------------------------------------------------------*/ +export const queryLogs = (): Promise> => + query("queryLogs", `select l.*,l.ts-t.start_ts as "startTime" from log as l left join trace_section AS t + where "startTime" between %s and %s order by "startTime" + limit %s offset %s;`) +/*-------------------------------------------------------------------------------------*/ +export const queryLogsCount = (): Promise> => + query("queryLogsCount", `select l.*,l.ts-t.start_ts as "startTime" from log as l left join trace_section AS t + where "startTime" between %s and %s;`) +/*-------------------------------------------------------------------------------------*/ +export const queryProcessData = (pid: number, startNS: number, endNS: number): Promise> => + query("queryProcessData", `select * from temp_query_process_data where tid != 0 and pid = $pid and startTime between $startNS and $endNS;`, { + $pid: pid, + $startNS: startNS, + $endNS: endNS + }) +/*-------------------------------------------------------------------------------------*/ +export const queryProcessDataCount = (): Promise> => + query("queryProcessDataCount", `select ta.id,type, ts, dur, ta.cpu, itid as utid, state + ,ts-tb.start_ts as startTime,tc.tid,tc.pid,tc.process,tc.thread +from thread_state ta,trace_section tb +left join ( + select it.id,tid,pid,ip.name as process,it.name as thread from thread as it left join process ip on it.ipid = ip.id + ) tc on ta.itid = tc.id +where tc.pid = %d + and startTime between %s and %s +and ta.cpu is not null +order by startTime;`) +/*-------------------------------------------------------------------------------------*/ +export const queryProcessDataLimit = (pid: number, startNS: number, endNS: number, limit: number): Promise> => + query("queryProcessDataLimit", `with list as (select ta.id,type, ts, dur, ta.cpu, itid as utid, state + ,ts-tb.start_ts as startTime,tc.tid,tc.pid,tc.process,tc.thread +from thread_state ta,trace_section tb +left join ( + select it.id,tid,pid,ip.name as process,it.name as thread from thread as it left join process ip on it.ipid = ip.id + ) tc on ta.itid = tc.id +where tc.pid = $pid + and startTime between $startNS and $endNS +and ta.cpu is not null +order by startTime ) +select * from list order by random() limit $limit;`, {$pid: pid, $startNS: startNS, $endNS: endNS, $limit: limit}) +/*-------------------------------------------------------------------------------------*/ +export const queryProcessMem = (): Promise> => + query("queryProcessMem", `select process_measure_filter.id as trackId, + process_measure_filter.name as trackName, + ipid as upid, + process_view.pid, + process_view.name as processName +from process_measure_filter join process_view using (ipid) +order by trackName;`) +/*-------------------------------------------------------------------------------------*/ +export const queryProcessMemData = (trackId: number): Promise> => + query("queryProcessMemData", `select c.type, + ts, value, + filter_id as track_id, + c.ts-tb.start_ts startTime +from measure c,trace_section tb where filter_id = $id;`, {$id: trackId}) +/*-------------------------------------------------------------------------------------*/ +export const queryProcessNOrder = (): Promise> => + query("queryProcessNOrder", `select pid,name as processName from process;`) +/*-------------------------------------------------------------------------------------*/ +export const queryProcessThreads = (): Promise> => + query("queryProcessThreads", `select + the_tracks.ipid as upid, + the_tracks.itid as utid, + total_dur as hasSched, + process_view.pid as pid, + thread_view.tid as tid, + process_view.name as processName, + thread_view.name as threadName +from ( + select ipid, itid from sched_view join thread_view using(itid) group by itid +) the_tracks +left join (select ipid, sum(dur) as total_dur + from sched_view join thread_view using(itid) + group by ipid +) using(ipid) +left join thread_view using(itid) +left join process_view using(ipid) +order by + total_dur desc, + the_tracks.ipid, + the_tracks.itid;`, {}) +/*-------------------------------------------------------------------------------------*/ +export const queryProcessThreadsNOrder = (): Promise> => + query("queryProcessThreadsNOrder", `select p.id as upid, + t.id as utid, + p.pid, + t.tid, + p.name as processName, + t.name as threadName + from thread t left join process p on t.ipid = p.id;`) +/*-------------------------------------------------------------------------------------*/ +export const queryScreenState = (): Promise> => + query("queryScreenState", `select m.type, m.ts-r.start_ts as ts, value, filter_id from measure m,trace_section r where filter_id in (select id from process_measure_filter where name = 'ScreenState');`) +/*-------------------------------------------------------------------------------------*/ +export const queryThreadData = (tid: number): Promise> => + query("queryThreadData", `select * from temp_query_thread_data where tid = $tid;`, {$tid: tid}) +/*-------------------------------------------------------------------------------------*/ +export const queryWakeUpThread_Desc = (): Promise> => + query("queryWakeUpThread_Desc", `This is the interval from when the task became eligible to run +(e.g.because of notifying a wait queue it was a suspended on) to when it started running.`) +/*-------------------------------------------------------------------------------------*/ +export const queryWakeUpThread_WakeThread = (wakets: number): Promise> => + query("queryWakeUpThread_WakeThread", `select TB.tid,TB.name as thread,TA.cpu,TC.pid,TC.name as process +from sched_view TA +left join thread TB on TA.itid = TB.id +left join process TC on TB.ipid = TC.id +where itid = (select itid from raw where name = 'sched_waking' and ts = $wakets ) + and TA.ts < $wakets + and Ta.ts + Ta.dur >= $wakets`, {$wakets: wakets}) +/*-------------------------------------------------------------------------------------*/ +export const queryWakeUpThread_WakeTime = (tid: number, startTime: number): Promise> => + query("queryWakeUpThread_WakeTime", `select * from + ( select ts as wakeTs,start_ts as startTs from instants_view,trace_section + where name = 'sched_waking' + and ref = $tid + and ts < start_ts + $startTime + order by ts desc limit 1) TA + left join + (select ts as preRow from sched_view,trace_section + where itid = $tid + and ts < start_ts + $startTime + order by ts desc limit 1) TB`, {$tid: tid, $startTime: startTime}) +/*-------------------------------------------------------------------------------------*/ +export const queryThreadsByPid = (pid: number): Promise> => + query("queryThreadsByPid", `select + the_tracks.ipid as upid, + the_tracks.itid as utid, + total_dur as hasSched, + process_view.pid as pid, + thread_view.tid as tid, + process_view.name as processName, + thread_view.name as threadName + from ( + select ipid, itid from sched_view join thread_view using(itid) group by itid + ) the_tracks + left join (select ipid, sum(dur) as total_dur + from sched_view join thread_view using(itid) + group by ipid + ) using(ipid) + left join thread_view using(itid) + left join process_view using(ipid) + where pid = $pid + order by + total_dur desc, + the_tracks.ipid, + the_tracks.itid`, {$pid: pid}) +/*-------------------------------------------------------------------------------------*/ +export const queryHeapByPid = (startTs: number, endTs: number, ipid: number): Promise> => + query("queryHeapByPid", `select a.maxheap maxHeapSize,current_size_dur as dur,h.all_heap_size heapsize,h.start_ts - t.start_ts as startTime,h.end_ts - t.start_ts as endTime +from native_hook h left join trace_section t left join (select max(all_heap_size) maxheap from native_hook) a where ipid = ${ipid} and startTime between ${startTs} and ${endTs}; +`, {$ipid: ipid, $startTs: startTs, $endTs: endTs}) + +export const queryHeapGroupByEvent = (): Promise> => + query("queryHeapGroupByEvent", `select event_type as eventType,sum(heap_size) as sumHeapSize from native_hook group by event_type`, {}) + +export const queryHeapByEventType = (startTs: number, endTs: number, arg1: string,arg2: string): Promise> => + query("queryHeapByEventType", ` +select a.maxHeap maxHeapSize, + current_size_dur as dur, + h.all_heap_size heapsize, + h.start_ts - t.start_ts as startTime, + h.end_ts - t.start_ts as endTime, + h.event_type as eventType +from native_hook h +left join trace_section t +left join ( +select max(all_heap_size) maxHeap +from native_hook ${arg1}) a +where startTime between ${startTs} and ${endTs} ${arg2} +`, {$startTs: startTs, $endTs: endTs,$arg1:arg1,$arg2:arg2}) +/*-------------------------------------------------------------------------------------*/ +export const queryHeapPid = (): Promise> => + query("queryHeapPid", `select ipid,pid from native_hook h left join process p on h.ipid = p.id group by ipid,pid`, {}) +/*-------------------------------------------------------------------------------------*/ +export const queryHeapTable = (startTs: number, endTs: number, ipids: Array): Promise> => + query("queryHeapTable", `select *,Allocations - Deallocations Total,AllocationSize - DeAllocationSize RemainingSize from (select f.file_path MoudleName, + sum(case when h.event_type = 'AllocEvent' then 1 else 0 end) Allocations, + sum(case when h.event_type = 'FreeEvent' then 1 else 0 end) Deallocations, + sum(case when h.event_type = 'AllocEvent' then heap_size else 0 end) AllocationSize, + sum(case when h.event_type = 'FreeEvent' then heap_size else 0 end) DeAllocationSize, + f.symbol_name AllocationFunction + from (select native_hook.start_ts - t.start_ts as startTime,* from native_hook + left join trace_range t where ipid in (${ipids.join(",")}) and startTime between ${startTs} and ${endTs}) h + left join (select * from native_hook_frame where depth = 0) f + on f.eventId = h.eventId group by f.file_path)`, + {ipids: ipids, $startTs: startTs, $endTs: endTs}) +export const queryHeapTreeTable = (startTs: number, endTs: number, ipids: Array): Promise> => + query("queryHeapTable", ` + select h.start_ts - t.start_ts as startTs, + h.end_ts - t.start_ts as endTs, + h.heap_size as heapSize, + h.event_type as eventType, + f.symbol_name as AllocationFunction, + f.file_path as MoudleName, + f.depth, + f.eventId + from native_hook h + inner join trace_range t + inner join native_hook_frame f on h.eventId = f.eventId where event_type = 'AllocEvent' + and ipid in (${ipids.join(",")}) + and (h.start_ts - t.start_ts between ${startTs} and ${endTs} or h.end_ts - t.start_ts between ${startTs} and ${endTs})`, + {ipids: ipids, $startTs: startTs, $endTs: endTs}) +export const queryHeapAllTable = (limit: number, offset: number): Promise> => + query("queryHeapAllTable", ` + select + h.symbol_name as AllocationFunction, + h.file_path as MoudleName, + h.depth, + h.eventId + from native_hook_frame h limit $limit offset $offset`, + { $limit: limit, $offset: offset}) +export const queryHeapAllData = (startTs: number, endTs: number, ipids: Array): Promise> => + query("queryHeapAllData", ` + select h.start_ts - t.start_ts as startTs, + h.end_ts - t.start_ts as endTs, + h.heap_size as heapSize, + h.event_type as eventType, + h.eventId + from native_hook h + inner join trace_range t + where event_type = 'AllocEvent' + and ipid in (${ipids.join(",")}) + and (h.start_ts - t.start_ts between ${startTs} and ${endTs} or h.end_ts - t.start_ts between ${startTs} and ${endTs})`, + {ipids: ipids, $startTs: startTs, $endTs: endTs}) +export const queryHeapFrameCount = (): Promise> => + query("queryHeapAllTable", ` + select + count(*) as count + from native_hook_frame `, + {}) + +export const queryNativeHookStatistics = (leftNs:number,rightNs:number): Promise> => + query("queryNativeHookStatistics",` + select event_type as eventType,sub_type as subType,max(all_heap_size) as max,sum(heap_size) as sumHeapSize,count(event_type) as count +from native_hook A,trace_range B +where (A.start_ts - B.start_ts) between ${leftNs} and ${rightNs} +--not ((A.start_ts - B.start_ts + ifnull(A.dur,0)) < ${leftNs} or (A.start_ts - B.start_ts) > ${rightNs}) +group by event_type, sub_type`,{$leftNs:leftNs,$rightNs:rightNs}) + +export const queryNativeHookStatisticsMalloc = (leftNs:number,rightNs:number):Promise> => + query('queryNativeHookStatisticsMalloc', + `select event_type as eventType, + heap_size as heapSize, + sum(case when ((A.start_ts - B.start_ts) between ${leftNs} and ${rightNs}) then heap_size else 0 end) as allocByte, + sum(case when ((A.start_ts - B.start_ts) between ${leftNs} and ${rightNs}) then 1 else 0 end) as allocCount, + sum(case when ((A.end_ts - B.start_ts) between ${leftNs} and ${rightNs} ) then heap_size else 0 end) as freeByte, + sum(case when ((A.end_ts - B.start_ts) between ${leftNs} and ${rightNs} ) then 1 else 0 end) as freeCount +from native_hook A,trace_range B +where ((A.start_ts - B.start_ts) between ${leftNs} and ${rightNs} + or (A.end_ts - B.start_ts) between ${leftNs} and ${rightNs} ) + and (event_type = 'AllocEvent') +group by event_type,heap_size; + `,{$leftNs:leftNs,$rightNs:rightNs}) + +export const queryNativeHookStatisticsSubType = (leftNs:number,rightNs:number):Promise> => + query('queryNativeHookStatisticsSubType', + `select event_type as eventType, + sub_type as subType, + max(heap_size) as heapSize, + sum(case when ((A.start_ts - B.start_ts) between ${leftNs} and ${rightNs}) then heap_size else 0 end) as allocByte, + sum(case when ((A.start_ts - B.start_ts) between ${leftNs} and ${rightNs}) then 1 else 0 end) as allocCount, + sum(case when ((A.end_ts - B.start_ts) between ${leftNs} and ${rightNs} ) then heap_size else 0 end) as freeByte, + sum(case when ((A.end_ts - B.start_ts) between ${leftNs} and ${rightNs} ) then 1 else 0 end) as freeCount +from native_hook A,trace_range B +where ((A.start_ts - B.start_ts) between ${leftNs} and ${rightNs} + or (A.end_ts - B.start_ts) between ${leftNs} and ${rightNs} ) + and (event_type = 'MmapEvent') +group by event_type,sub_type; + `,{$leftNs:leftNs,$rightNs:rightNs}) + +export const queryNativeHookEventId = (leftNs:number,rightNs:number,types:Array): Promise> => + query("queryNativeHookEventId",` + select eventId,event_type as eventType,sub_type as subType,heap_size as heapSize,addr,(A.start_ts - B.start_ts) as startTs,(A.end_ts - B.start_ts) as endTs +from native_hook A, trace_range B +where A.start_ts - B.start_ts between ${leftNs} and ${rightNs} and A.event_type in (${types.join(",")})`,{$leftNs:leftNs,$rightNs:rightNs,$types:types}) + +export const queryNativeHookEventTid = (leftNs:number,rightNs:number,types:Array): Promise> => + query("queryNativeHookEventTid",` + select eventId, + event_type as eventType, + sub_type as subType, + heap_size as heapSize, + addr, + (A.start_ts - B.start_ts) as startTs, + (A.end_ts - B.start_ts) as endTs, + tid +from native_hook A, trace_range B +left join thread t on A.itid = t.id +where A.start_ts - B.start_ts between ${leftNs} and ${rightNs} and A.event_type in (${types.join(",")})`,{$leftNs:leftNs,$rightNs:rightNs,$types:types}) + +export const queryNativeHookProcess = ():Promise> => + query("queryNativeHookProcess",`select distinct ipid,pid,name from native_hook left join process p on native_hook.ipid = p.id`,{}) + +export const queryNativeHookSnapshot = (rightNs:number):Promise> => + query("queryNativeHookSnapshot",`select event_type as eventType,sub_type as subType,sum(heap_size) as growth,count(*) as existing from native_hook n, trace_range t +where (event_type = 'AllocEvent' or event_type = 'MmapEvent') + and n.start_ts between 0 and ${rightNs} + t.start_ts + and n.end_ts > ${rightNs} + t.start_ts +group by event_type,sub_type`,{$rightNs:rightNs}) + +export const queryNativeHookSnapshotTypes = ():Promise> => + query("queryNativeHookSnapshotTypes",`select event_type as eventType,sub_type as subType from native_hook where (event_type = 'AllocEvent' or event_type = 'MmapEvent') group by event_type,sub_type;`,{}) + +export const queryAllHookData = (rightNs:number):Promise> => + query("queryAllHookData",`select eventId,event_type as eventType,sub_type as subType,addr,heap_size as growth,(n.start_ts - t.start_ts) as startTs,(n.end_ts - t.start_ts) as endTs from native_hook n, trace_range t +where (event_type = 'AllocEvent' or event_type = 'MmapEvent') + and n.start_ts between t.start_ts and ${rightNs} + t.start_ts`,{$rightNs:rightNs}) \ No newline at end of file diff --git a/host/ide/src/trace/database/SqlLiteWorker.ts b/host/ide/src/trace/database/SqlLiteWorker.ts new file mode 100644 index 0000000..f39a1d2 --- /dev/null +++ b/host/ide/src/trace/database/SqlLiteWorker.ts @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +importScripts('sql-wasm.js', "TempSql.js"); +let conn: any = null; + +function initIndexedDB() { + return new Promise((resolve, reject) => { + let request = indexedDB.open("systrace"); + request.onerror = function (event) { + }; + request.onsuccess = function (event) { + let db = request.result; + resolve(db); + }; + request.onupgradeneeded = function (event) { + // @ts-ignore + let db = event!.target!.result; + if (!db.objectStoreNames.contains("connection")) { + db.createObjectStore("connection", {autoIncrement: true}); + } + }; + }) +} + +function readConnection(store: IDBObjectStore) { + return new Promise((resolve, reject) => { + let request = store.get(1); + request.onsuccess = function (event) { + // @ts-ignore + resolve(event.target.result); + }; + request.onerror = function (event) { + // @ts-ignore + reject(event.target.result); + }; + }) +} + +function deleteConnection(store: IDBObjectStore, id: number) { + return new Promise((resolve, reject) => { + let request = store.delete(id); + request.onsuccess = function (event) { + // @ts-ignore + resolve(event.target.result); + }; + request.onerror = function (event) { + // @ts-ignore + reject(event.target.result); + }; + }) +} + +self.onerror = function (error) { +} + + +self.onmessage = async (e: any) => { + if (e.data.action === "open") { + let array = new Uint8Array(e.data.buffer); + // @ts-ignore + initSqlJs({locateFile: filename => `${filename}`}).then((SQL: any) => { + conn = new SQL.Database(array); + // @ts-ignore + self.postMessage({id: e.data.id, ready: true, index: 0}); + temp_init_sql_list.forEach((item, index) => { + let r = conn.exec(item); + // @ts-ignore + self.postMessage({id: e.data.id, ready: true, index: index + 1}); + }); + // @ts-ignore + self.postMessage({id: e.data.id, init: true}); + }); + } else if (e.data.action === "close") { + } else if (e.data.action === "exec") { + try { + let action = e.data.action; //: "exec" + let sql = e.data.sql; + let params = e.data.params; + const stmt = conn.prepare(sql); + stmt.bind(params); + let res = []; + while (stmt.step()) { // + res.push(stmt.getAsObject()); + } + stmt.free(); + // @ts-ignore + self.postMessage({id: e.data.id, results: res}); + } catch (err: any) { + // @ts-ignore + self.postMessage({id: e.data.id, results: [], error: err.message}); + } + } +} \ No newline at end of file diff --git a/host/ide/src/trace/database/TempSql.ts b/host/ide/src/trace/database/TempSql.ts new file mode 100644 index 0000000..a04efb3 --- /dev/null +++ b/host/ide/src/trace/database/TempSql.ts @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +let temp_query_process = `create table temp_query_process as select + distinct process_view.pid as pid, + process_view.name as processName +from ( + select ipid, itid from sched_slice join thread_view using(itid) group by itid +) the_tracks +left join (select ipid, sum(dur) as total_dur + from sched_view join thread_view using(itid) + group by ipid +) using(ipid) +left join process_view using(ipid) +where pid is not null +order by + total_dur desc, + the_tracks.ipid, + processName, + the_tracks.itid; +` +let temp_query_cpu_data = `create table temp_query_cpu_data as with list as (SELECT + IP.name as processName, + IP.name processCmdLine, + IP.pid as processId,B.cpu, + A.name, + C.id as schedId, + A.tid, + A.id, + A.type, + B.dur, + B.ts - TR.start_ts AS startTime, + C.priority, + C.end_state +from thread_state AS B + left join thread as A on B.itid = A.id + left join sched_slice AS C on B.itid = C.itid and B.ts = C.ts + left join trace_section AS TR + left join process AS IP on A.ipid = IP.id +where C.itid is not null +order by B.id) +select * from list; +create index temp_query_cpu_data_idx on temp_query_cpu_data(cpu,startTime); +` + +let temp_query_freq_data = `create table temp_query_freq_data as select cpu,value,ts-tb.start_ts as startNS +from measure c ,trace_section tb +inner join cpu_measure_filter t on c.filter_id = t.id +where (name = 'cpufreq' or name='cpu_frequency') +order by ts; +create index temp_query_freq_data_idx on temp_query_freq_data(cpu); +` + +let temp_query_process_data = `create table temp_query_process_data as select ta.id,type, ts, dur, ta.cpu, itid as utid, state + ,ts-tb.start_ts as startTime,tc.tid,tc.pid,tc.process,tc.thread +from thread_state ta,trace_section tb +left join ( + select it.id,tid,pid,ip.name as process,it.name as thread from thread as it left join process ip on it.ipid = ip.id + ) tc on ta.itid = tc.id +where ta.cpu is not null +order by startTime; +create index temp_query_process_data_idx on temp_query_process_data(pid,startTime); +` +let temp_query_thread_function = `create table temp_query_thread_function as select tid, + A.name as threadName, + is_main_thread, + c.callid as track_id, + c.ts-D.start_ts as startTs, + c.dur, + c.name as funName, + c.parent_id, + c.id, + c.depth, + c.argsetid +from thread A,trace_section D +left join callstack C on A.id = C.callid +where startTs not null and c.cookie is null; +create index temp_query_thread_function_idx on temp_query_thread_function(tid); +`; + +let temp_query_thread_data = `create table temp_query_thread_data as select A.id, A.type, A.tid, A.name, A.start_ts, A.end_ts, A.ipid as upid, A.is_main_thread + , B.cpu, B.ts-TR.start_ts AS startTime,B.dur,B.state,IP.pid,IP.name as processName + from thread_state AS B + left join thread as A on A.id=B.itid + left join trace_section AS TR + left join process AS IP on IP.id=ipid; + create index temp_query_thread_data_idx on temp_query_thread_data(tid);` + +let temp_view = `CREATE VIEW IF NOT EXISTS thread_view AS SELECT id as itid, * FROM thread; +CREATE VIEW IF NOT EXISTS process_view AS SELECT id as ipid, * FROM process; +CREATE VIEW IF NOT EXISTS sched_view AS SELECT *, ts + dur as ts_end FROM sched_slice; +CREATE VIEW IF NOT EXISTS instants_view AS SELECT *, 0.0 as value FROM instant; +CREATE VIEW IF NOT EXISTS trace_section AS select start_ts, end_ts from trace_range;` + +let temp_query_cpu_freq = `create table temp_query_cpu_freq as select cpu from cpu_measure_filter where (name='cpufreq' or name='cpu_frequency') order by cpu;` +let temp_query_cpu_max_freq = `create table temp_query_cpu_max_freq as select max(value) as maxFreq +from measure c +inner join cpu_measure_filter t on c.filter_id = t.id +where (name = 'cpufreq' or name='cpu_frequency');` + +let temp_get_tab_states_group_by_process = `create table temp_get_tab_states_group_by_process as +select IP.name as process,IP.pid as processId, + dur, + Ip.id as id, + (ts - B.start_ts + dur) as end_ts, + (ts - B.start_ts) as start_ts +from thread_state as A,trace_section as B + left join thread as C on A.itid = C.id + left join process AS IP on C.ipid = IP.id +where A.dur > 0 and processId not null and (ts - B.start_ts)>0; +create index temp_get_tab_states_group_by_process_idx on temp_get_tab_states_group_by_process(end_ts,start_ts); +` + +let temp_get_process_thread_state_data = ` create table temp_get_process_thread_state_data as + select IP.name as process, + IP.pid as processId, + A.name as thread, + B.state as state, + A.tid as threadId, + B.dur, + (B.ts - TR.start_ts + B.dur) as end_ts, + (B.ts - TR.start_ts) as start_ts, + B.cpu, + C.priority, + '-' as note +from thread_state as B + left join thread as A on B.itid = A.id + left join process as IP on A.ipid = IP.id + left join trace_section as TR + left join sched_slice as C on B.itid = C.itid and C.ts = B.ts +where + B.dur > 0 and IP.pid not null and (B.ts - TR.start_ts) >= 0; +create index temp_get_process_thread_state_data_idx on temp_get_process_thread_state_data(end_ts,start_ts); +` + +let temp_get_tab_states_group_by_state_pid_tid = ` create table temp_get_tab_states_group_by_state_pid_tid as +select IP.name as process, + IP.pid as processId, + A.name as thread, + B.state as state, + A.tid as threadId, + B.dur as dur, + A.tid as tid, + (B.ts - TR.start_ts + B.dur) as end_ts, + (B.ts - TR.start_ts) as start_ts +from thread_state AS B + left join thread as A on B.itid = A.id + left join process AS IP on A.ipid = IP.id + left join trace_section AS TR +where + B.dur > 0 and IP.pid not null and (B.ts - TR.start_ts > 0); +create index temp_get_tab_states_group_by_state_pid_tid_idx0 on temp_get_tab_states_group_by_state_pid_tid(process,processId,thread,threadId,state); +create index temp_get_tab_states_group_by_state_pid_tid_idx1 on temp_get_tab_states_group_by_state_pid_tid(end_ts,start_ts); +create index temp_get_tab_states_group_by_state_pid_tid_idx3 on temp_get_tab_states_group_by_state_pid_tid(end_ts,start_ts,process,processId,thread,threadId,state); +` +let temp_get_tab_states_group_by_state_pid = `create table temp_get_tab_states_group_by_state_pid as +select IP.name as process, + IP.pid as processId, + B.state as state, + B.dur as dur, + A.tid as tid, + (ts - TR.start_ts + dur) as end_ts, + (ts - TR.start_ts) as start_ts +from thread_state AS B + left join thread as A on B.itid = A.id + left join process AS IP on A.ipid = IP.id + left join trace_section AS TR +where pid not null and + B.dur > 0 and (ts - TR.start_ts > 0); + create index temp_get_tab_states_group_by_state_pid_idx0 on temp_get_tab_states_group_by_state_pid(process,processId,state); + create index temp_get_tab_states_group_by_state_pid_idx1 on temp_get_tab_states_group_by_state_pid(start_ts,end_ts); +` +let temp_get_tab_states_group_by_state = `create table temp_get_tab_states_group_by_state as +select state, + dur, + (ts - B.start_ts + dur) as end_ts, + (ts - B.start_ts) as start_ts + from thread_state as A,trace_section as B + left join thread as C on A.itid = C.id + left join process AS IP on C.ipid = IP.id + where A.dur > 0 and IP.pid not null and (ts - B.start_ts > 0); + create index temp_get_tab_states_group_by_state_idx0 on temp_get_tab_states_group_by_state(state); + create index temp_get_tab_states_group_by_state_idx1 on temp_get_tab_states_group_by_state(start_ts,end_ts); + ` +let temp_get_tab_states_group_by_process_thread = `create table temp_get_tab_states_group_by_process_thread as +select IP.name as process, + IP.pid as processId, + A.name as thread, + a.tid as threadId, + B.dur as dur, + A.tid as tid, + (ts - TR.start_ts + dur) as end_ts, + (ts - TR.start_ts) as start_ts + from thread_state AS B + left join thread as A on B.itid = A.id + left join process AS IP on A.ipid = IP.id + left join trace_section AS TR + where pid not null and + B.dur > 0 and (ts - TR.start_ts)>0; + create index temp_get_tab_states_group_by_process_thread_idx0 on temp_get_tab_states_group_by_process_thread(process,processId,thread,threadId); + create index temp_get_tab_states_group_by_process_thread_idx1 on temp_get_tab_states_group_by_process_thread(start_ts,end_ts); +` + +let temp_get_cpu_rate = `create table temp_get_cpu_rate as +with cpu as ( + select cpu,ts,dur,(case when ro < 99 then ro else 99 end) as ro , + (case when ro < 99 then stime+ro*cell else stime + 99 * cell end) as st, + (case when ro < 99 then stime + (ro+1)*cell else etime end) as et + from ( + select cpu,ts,A.dur,((ts+A.dur)-D.start_ts)/((D.end_ts-D.start_ts)/100) as ro,D.start_ts as stime,D.end_ts etime,(D.end_ts-D.start_ts)/100 as cell + from sched_slice A + left join trace_section D + left join thread B on A.itid = B.id + left join process C on B.ipid = C.id + where tid != 0 and (A.ts) between D.start_ts and D.end_ts)) +select cpu,ro, + sum(case + when ts <= st and ts + dur <= et then (ts + dur - st) + when ts <= st and ts + dur > et then et-st + when ts > st and ts + dur <= et then dur + when ts > st and ts + dur > et then et - ts end)/cast(et-st as float) as rate +from cpu +group by cpu,ro; +` + +let temp_get_tab_thread_states = `create table temp_get_tab_thread_states as + select + IP.name as process, + IP.pid as pid, + A.name as thread, + A.tid as tid, + B.state as state, + B.dur as dur, + (B.ts - TR.start_ts + ifnull(B.dur,0)) as end_ts, + (B.ts - TR.start_ts) as start_ts +from thread_state AS B +left join thread as A on A.id = B.itid +left join trace_section AS TR +left join process AS IP on IP.id=ipid +where (B.ts - TR.start_ts > 0); +create index temp_get_tab_thread_states_idx0 on temp_get_tab_thread_states(process,pid,thread,tid,state); +create index temp_get_tab_thread_states_idx1 on temp_get_tab_thread_states(start_ts,end_ts); +`; + +let temp_get_tab_slices = `create table temp_get_tab_slices as + select + c.name as name, + c.dur as dur, + A.tid as tid, + (C.ts - D.start_ts + C.dur) as end_ts, + (C.ts - D.start_ts) as start_ts +from thread A,trace_section D +left join callstack C on A.id = C.callid +where C.ts not null + and c.dur >= 0 + and (C.ts - D.start_ts > 0); +create index temp_get_tab_slices_idx0 on temp_get_tab_slices(name); +`; + +let delete_callstack_binder_data = `DELETE FROM callstack WHERE dur<0 or name like 'binder%';`; +let temp_init_sql_list = [ + temp_view, + delete_callstack_binder_data, + temp_query_process, + temp_query_cpu_data, + temp_query_process_data, + temp_query_thread_function, + temp_query_thread_data, + temp_get_cpu_rate, +]; diff --git a/host/ide/src/trace/database/TraceWorker.ts b/host/ide/src/trace/database/TraceWorker.ts new file mode 100644 index 0000000..559550f --- /dev/null +++ b/host/ide/src/trace/database/TraceWorker.ts @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +importScripts("trace_streamer_builtin.js", "TempSql.js"); +self.onerror = function (error: any) { +} +let Module: any = null; + +function initWASM() { + return new Promise((resolve, reject) => { + // @ts-ignore + let wasm = trace_streamer_builtin_wasm + Module = wasm({ + locateFile: (s: any) => { + return s + }, + print: (line: any) => { + }, + printErr: (line: any) => { + }, + onRuntimeInitialized: () => { + resolve("ok"); + }, + onAbort: () => { + reject("on abort"); + } + }); + }) +} + +const REQ_BUF_SIZE = 64 * 1024 * 1024; +self.onmessage = async (e: MessageEvent) => { + if (e.data.action === "open") { + await initWASM(); + // @ts-ignore + self.postMessage({id: e.data.id, action: "open", ready: true, index: 0}); + let uint8Array = new Uint8Array(e.data.buffer); + let p = Module._malloc(uint8Array.length); + Module.HEAPU8.set(uint8Array, p); + let r1 = Module._TraceStreamerParseData(p, uint8Array.length); + let r2 = Module._TraceStreamerParseDataOver(); + Module._free(p); + if (r1 == -1) { + // @ts-ignore + self.postMessage({id: e.data.id, action: "open", init: false, msg: "parse data error"}); + return; + } + // @ts-ignore + temp_init_sql_list.forEach((item, index) => { + let r = createView(item); + // @ts-ignore + self.postMessage({id: e.data.id, ready: true, index: index + 1}); + }); + // @ts-ignore + self.postMessage({id: e.data.id, action: "open", init: true, msg: "ok"}); + } else if (e.data.action === "exec") { + let arr = query(e.data.name, e.data.sql, e.data.params); + // @ts-ignore + self.postMessage({id: e.data.id, action: "exec", results: arr}); + } +} + +function createView(sql: string) { + let enc = new TextEncoder(); + let dec = new TextDecoder(); + let sqlPtr = Module._malloc(sql.length); + Module.HEAPU8.set(enc.encode(sql), sqlPtr); + let res = Module._TraceStreamerSqlOperate(sqlPtr, sql.length); + return res; +} + +function query(name: string, sql: string, params: any) { + if (params) { + Reflect.ownKeys(params).forEach((key: any) => { + if (typeof params[key] === "string") { + sql = sql.replace(new RegExp(`\\${key}`, "g"), `'${params[key]}'`); + } else { + sql = sql.replace(new RegExp(`\\${key}`, "g"), params[key]); + } + }); + } + let arr: Array = [] + let enc = new TextEncoder(); + let dec = new TextDecoder(); + let sqlPtr = Module._malloc(sql.length); + let outPtr = Module._malloc(REQ_BUF_SIZE); + Module.HEAPU8.set(enc.encode(sql), sqlPtr); + let a = new Date().getTime(); + let res = Module._TraceStreamerSqlQuery(sqlPtr, sql.length, outPtr, REQ_BUF_SIZE); + let out = Module.HEAPU8.subarray(outPtr, outPtr + res); + let str = dec.decode(out); + Module._free(sqlPtr); + Module._free(outPtr); + str = str.substring(str.indexOf("\n") + 1); + let parse = JSON.parse(str); + let columns = parse.columns; + let values = parse.values; + for (let i = 0; i < values.length; i++) { + let obj: any = {} + for (let j = 0; j < columns.length; j++) { + obj[columns[j]] = values[i][j] + } + arr.push(obj) + } + return arr +} \ No newline at end of file diff --git a/host/ide/src/trace/grpc/HiProfilerClient.ts b/host/ide/src/trace/grpc/HiProfilerClient.ts new file mode 100644 index 0000000..09d6090 --- /dev/null +++ b/host/ide/src/trace/grpc/HiProfilerClient.ts @@ -0,0 +1,56 @@ +import {Address, ProfilerClient} from "./ProfilerClient.js"; + +export class HiProfilerClient { + private _client: ProfilerClient; + private _address: Address; + + get client(): ProfilerClient { + return this._client; + } + + set client(value: ProfilerClient) { + this._client = value; + } + + get address(): Address { + return this._address; + } + + set address(value: Address) { + this._address = value; + } + + public constructor(clients: ProfilerClient, addr: Address) { + this._client = clients; + this._address = addr; + }; + + public getProfilerClient(): ProfilerClient{ + return this._client; + } + + public getCapabilities() { + // this.client.start() + // this.client.getCapabilities( + } + + public createSession() { + // this.client.createSession( + } + + public startSession() { + // this.client.startSession( + } + + public stopSession() { + // this.client.stopSession( + } + + public destroySession() { + // this.client.destroySession( + } + + public keepSession() { + // this.client.keepSession( + } +} \ No newline at end of file diff --git a/host/ide/src/trace/grpc/ProfilerClient.ts b/host/ide/src/trace/grpc/ProfilerClient.ts new file mode 100644 index 0000000..c27e1b6 --- /dev/null +++ b/host/ide/src/trace/grpc/ProfilerClient.ts @@ -0,0 +1,119 @@ +import * as path from 'path'; + +const profilerServicePath = path.join(__dirname,'../proto', 'profiler_service.proto'); + +export class ProfilerClient { + // proto filePaths + private _filePaths: Array | undefined; + // client + private _client: any; + // profiler_proto + private _profiler_proto: any; + // ProfilerClient constructor + public constructor(address: Address) { + // load client port + let clientPort = this.loadAddress(address); + // load proto file + this.start(clientPort, profilerServicePath); + }; + + get filePaths(): Array | undefined { + return this._filePaths; + } + + set filePaths(value: Array | undefined) { + this._filePaths = value; + } + + get client(): any { + return this._client; + } + + set client(value: any) { + this._client = value; + } + + get profiler_proto(): any { + return this._profiler_proto; + } + + set profiler_proto(value: any) { + this._profiler_proto = value; + } + + start(address: string, filePath: string){ + // let loadPackage = proto_load.loadSync( + // filePath, + // { + // keepCase: true, + // longs: String, + // enums: String, + // defaults: true, + // oneofs: true + // } + // ); + // // profiler Proto + // this._profiler_proto = rpc.loadPackageDefinition(loadPackage); + // // get profilerProto service + // let profilerProto = this._profiler_proto.profiler; + // // client + // this._client = new profilerProto.IProfilerService('127.0.0.1:5555', rpc.credentials.createInsecure()); + } + + // Address + loadAddress(clientAddress: Address): string{ + return clientAddress.host + ':' + clientAddress.port; + }; + + public getProfilerClient(callback: any): any{ + return this._client; + }; + + public getCapabilities(callback: any) { + this._client. + this._client.getCapabilities(callback); + callback(); + }; + + public createSession(callback: any) { + this._client.createSession(callback); + callback(); + }; + + public startSession(callback: any) { + this._client.startSession(callback); + callback(); + }; + + public stopSession(callback: any) { + this._client.stopSession(callback); + callback(); + }; + + public destroySession(callback: any) { + this._client.destroySession(callback); + callback(); + }; + + public keepSession(callback: any) { + this._client.keepSession(callback); + callback(); + }; + + public shutdown(): void { + + }; + + public getChannel() { + return this._client.channelInterpretation; + }; + +} + +export interface Address { + // port + port: string | number; + + // host + host?: string | number; +} diff --git a/host/ide/src/trace/grpc/ProfilerController.ts b/host/ide/src/trace/grpc/ProfilerController.ts new file mode 100644 index 0000000..eb927bf --- /dev/null +++ b/host/ide/src/trace/grpc/ProfilerController.ts @@ -0,0 +1,92 @@ +export class ClientContainer { + // private _credentials: rpc.ChannelCredentials | undefined; + // private _clients: { service: any, client?: rpc.Client, target: any }[] = []; + private _port: string | number | undefined; + private _host: string | undefined; + + /* get clients(): { service: any; client?: rpc.Client; target: any }[] { + return this._clients; + } + + set clients(value: { service: any; client?: rpc.Client; target: any }[]) { + this._clients = value; + }*/ + + /* get credentials(): rpc.ChannelCredentials | undefined { + return this._credentials; + } + + set credentials(value: rpc.ChannelCredentials | undefined) { + this._credentials = value; + }*/ + + get port(): string | number | undefined { + return this._port; + } + + set port(value: string | number | undefined) { + this._port = value; + } + + get host(): string | undefined { + return this._host; + } + + set host(value: string | undefined) { + this._host = value; + } + + public registryClient(target: any, path: string) { + // let packageDefinition = proto_load.loadSync(path, { + // keepCase: true, + // longs: String, + // enums: String, + // defaults: true, + // oneofs: true + // }); + // let protoDescriptor = rpc.loadPackageDefinition(packageDefinition); + // + // const packages = Object.keys(protoDescriptor); + // for (let packageKey of packages) { + // for (let key in protoDescriptor[packageKey]) { + // + // } + // } + }; + + public start() { + this.loadSettings(); + this._registryClient(); + } + + private loadSettings() { + let { host, port} = SettingRegistry.settings; + this._host = host; + this._port = port; + } + + private _registryClient() { + // for (let clientContainer of this._clients) { + // let client: rpc.Client = new clientContainer.service( + // `${this.host}:${this.port}`, + // this.credentials + // ); + // clientContainer.client = client; + // } + } +} + + +export class SettingRegistry { + static settings: Settings; + + static registry(settings: Settings) { + this.settings = settings; + } +} + +export interface Settings { + port: string | number; + + host?: string; +} \ No newline at end of file diff --git a/host/ide/src/trace/proto/common_types.proto b/host/ide/src/trace/proto/common_types.proto new file mode 100644 index 0000000..b78ca89 --- /dev/null +++ b/host/ide/src/trace/proto/common_types.proto @@ -0,0 +1,58 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +// option java_package = "ohos.devtools.datasources.transport.grpc.service"; + +// Common message define for profiler tools, imported by profiler and plugin proto file. +message ProfilerPluginConfig { + string name = 1; + string plugin_sha256 = 2; + uint32 sample_interval = 3; + bytes config_data = 4; +} + +message ProfilerPluginState { + string name = 1; + enum State { + INITED = 0; + REGISTERED = 1; // registered to plugin service. + LOADED = 2; // have created session. + IN_SESSION = 3; // have started session. + }; + State state = 2; +} + +// for FetchDataResponse +message ProfilerPluginData { + string name = 1; + uint32 status = 2; + bytes data = 3; + enum ClockId { + CLOCKID_REALTIME = 0; + CLOCKID_REALTIME_ALARM = 1; // since Linux 3.0; Linux-specific + CLOCKID_REALTIME_COARSE = 2; // since Linux 2.6.32; Linux-specific + CLOCKID_TAI = 3; // since Linux 3.10; Linux-specific + CLOCKID_MONOTONIC = 4; + CLOCKID_MONOTONIC_COARSE = 5; // since Linux 2.6.32; Linux-specific + CLOCKID_MONOTONIC_RAW = 6; // since Linux 2.6.28; Linux-specific + CLOCKID_BOOTTIME = 7; // since Linux 2.6.39; Linux-specific + CLOCKID_BOOTTIME_ALARM = 8; // since Linux 3.0; Linux-specific + CLOCKID_PROCESS_CPUTIME_ID = 9; // since Linux 2.6.12 + CLOCKID_THREAD_CPUTIME_ID = 10; // since Linux 2.6.12 + }; + ClockId clock_id = 4; + uint64 tv_sec = 5; + uint64 tv_nsec = 6; +} diff --git a/host/ide/src/trace/proto/profiler_service.proto b/host/ide/src/trace/proto/profiler_service.proto new file mode 100644 index 0000000..b46d7f2 --- /dev/null +++ b/host/ide/src/trace/proto/profiler_service.proto @@ -0,0 +1,47 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +// option java_package = "ohos.devtools.datasources.transport.grpc.service"; + +import "profiler_service_types.proto"; + +// RPC interface between profiler service and host service +// Use protobuf plug-ins to convert proto define to +// source and header files during the build process. + +package profiler; + +service IProfilerService { + // get all plugin infos and capabilities. + rpc GetCapabilities(GetCapabilitiesRequest) returns (GetCapabilitiesResponse); + + // create tracing sesion and pass tracing config to plugins. + rpc CreateSession(CreateSessionRequest) returns (CreateSessionResponse); + + // start tracing session, active server side tracing triggers. + rpc StartSession(StartSessionRequest) returns (StartSessionResponse); + + // get server-side cached tracing data since current session started. + rpc FetchData(FetchDataRequest) returns (stream FetchDataResponse); + + // stop tracing session, deactivate server side tracing triggers. + rpc StopSession(StopSessionRequest) returns (StopSessionResponse); + + // destroy tracing session. + rpc DestroySession(DestroySessionRequest) returns (DestroySessionResponse); + + // keep tracing session alive, call this interface will restart session expire count down task. + rpc KeepSession(KeepSessionRequest) returns (KeepSessionResponse); +} diff --git a/host/ide/src/trace/proto/profiler_service_types.proto b/host/ide/src/trace/proto/profiler_service_types.proto new file mode 100644 index 0000000..8273ace --- /dev/null +++ b/host/ide/src/trace/proto/profiler_service_types.proto @@ -0,0 +1,129 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +// option java_package = "ohos.devtools.datasources.transport.grpc.service"; + +import "common_types.proto"; + +// Message define for profiler service, imported by profiler service proto file. +// for GetCapabilities +message GetCapabilitiesRequest { + uint32 request_id = 1; +} + +message ProfilerPluginCapability { + string path = 1; + string name = 2; +} + +message GetCapabilitiesResponse { + uint32 status = 1; + repeated ProfilerPluginCapability capabilities = 2; +} + + +// for CreateSessionRequest +message ProfilerSessionConfig { + message BufferConfig { + enum Policy { + RECYCLE = 0; + FLATTEN = 1; + }; + uint32 pages = 1; + Policy policy = 2; + } + repeated BufferConfig buffers = 1; + + enum Mode { + OFFLINE = 0; // save all plugin results to result file. + ONLINE = 1; // push all plugin results to host PC with streamed FetchDataResponse. + }; + Mode session_mode = 2; + string result_file = 3; // for OFFLINE mode, result file path + uint32 result_max_size = 4; // for OFFLINE mode, result file max size in KB + uint32 sample_duration = 5; // for OFFLINE mode, sample duration in ms + uint32 keep_alive_time = 6; // if set to non-zero value, session will auto-destroyed after CreateSession in ms +} + +message CreateSessionRequest { + uint32 request_id = 1; + ProfilerSessionConfig session_config = 2; + repeated ProfilerPluginConfig plugin_configs = 3; +} + +message CreateSessionResponse { + uint32 status = 1; + uint32 session_id = 2; + repeated ProfilerPluginState plugin_status = 3; +} + +// for StartSession +message StartSessionRequest { + uint32 request_id = 1; + uint32 session_id = 2; + repeated ProfilerPluginConfig update_configs = 3; +} + +message StartSessionResponse { + uint32 status = 1; + repeated ProfilerPluginState plugin_status = 3; +} + +// for FetchData +message FetchDataRequest { + uint32 request_id = 1; + uint32 session_id = 2; + bytes addtion_data = 3; +} + +message FetchDataResponse { + uint32 status = 1; + uint32 response_id = 2; + bool has_more = 3; + repeated ProfilerPluginData plugin_data = 4; +} + +// for StopSession +message StopSessionRequest { + uint32 request_id = 1; + uint32 session_id = 2; +} + +message StopSessionResponse { + uint32 status = 1; + repeated ProfilerPluginState plugin_status = 3; +} + +// for DestroySession +message DestroySessionRequest { + uint32 request_id = 1; + uint32 session_id = 2; +} + +message DestroySessionResponse { + uint32 status = 1; + repeated ProfilerPluginState plugin_status = 3; +} + +// for KeepSession +message KeepSessionRequest { + uint32 request_id = 1; + uint32 session_id = 2; + uint32 keep_alive_time = 3; +} + +message KeepSessionResponse { + uint32 status = 1; +} diff --git a/host/ide/test/base-ui/button/LitButton.test.ts b/host/ide/test/base-ui/button/LitButton.test.ts new file mode 100644 index 0000000..cf05686 --- /dev/null +++ b/host/ide/test/base-ui/button/LitButton.test.ts @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {LitButton} from "../../../dist/base-ui/button/LitButton.js"; + +describe('button Test', ()=>{ + + it('buttonTest01', function () { + let litButton = new LitButton(); + expect(litButton).not.toBeUndefined() + }); +}) diff --git a/host/ide/test/base-ui/checkbox/LitCheckBox.test.ts b/host/ide/test/base-ui/checkbox/LitCheckBox.test.ts new file mode 100644 index 0000000..3512ce0 --- /dev/null +++ b/host/ide/test/base-ui/checkbox/LitCheckBox.test.ts @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {LitCheckBox} from "../../../dist/base-ui/checkbox/LitCheckBox.js"; + +describe('checkBox Test', ()=>{ + + it('checkBoxTest01', function () { + let litCheckBox = new LitCheckBox(); + expect(litCheckBox).not.toBeUndefined() + expect(litCheckBox).not.toBeNull() + }); + + + it('checkBoxTest02', function () { + let litCheckBox = new LitCheckBox(); + expect(litCheckBox.checked).toBeFalsy(); + }); + + it('checkBoxTest03', function () { + let litCheckBox = new LitCheckBox(); + litCheckBox.checked = true + expect(litCheckBox.checked).toBeTruthy(); + }); + + + it('checkBoxTest04', function () { + let litCheckBox = new LitCheckBox(); + expect(litCheckBox.value).toEqual(""); + }); + + + it('checkBoxTest04', function () { + let litCheckBox = new LitCheckBox(); + litCheckBox.value = "test" + expect(litCheckBox.value).toEqual("test"); + }); + + + it('checkBoxTest05', function () { + document.body.innerHTML = ` + ` + let litCheckBox = new LitCheckBox(); + litCheckBox.checked = false + expect(litCheckBox.checked).toBeFalsy(); + }); +}) \ No newline at end of file diff --git a/host/ide/test/base-ui/checkbox/LitCheckBoxWithText.test.ts b/host/ide/test/base-ui/checkbox/LitCheckBoxWithText.test.ts new file mode 100644 index 0000000..271b243 --- /dev/null +++ b/host/ide/test/base-ui/checkbox/LitCheckBoxWithText.test.ts @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {LitCheckBoxWithText} from "../../../dist/base-ui/checkbox/LitCheckBoxWithText.js"; + +describe('checkBoxWithText Test', ()=>{ + + it('checkBoxWithTextTest01', function () { + let litCheckBoxWithText = new LitCheckBoxWithText(); + expect(litCheckBoxWithText).not.toBeUndefined() + expect(litCheckBoxWithText).not.toBeNull() + }); + + + it('checkBoxWithTextTest02', function () { + let litCheckBoxWithText = new LitCheckBoxWithText(); + expect(litCheckBoxWithText.checked).toBeFalsy(); + }); + + it('checkBoxWithTextTest03', function () { + let litCheckBoxWithText = new LitCheckBoxWithText(); + litCheckBoxWithText.checked = true + expect(litCheckBoxWithText.checked).toBeTruthy(); + }); + + it('checkBoxWithTextTest03', function () { + let litCheckBoxWithText = new LitCheckBoxWithText(); + litCheckBoxWithText.checked = false + expect(litCheckBoxWithText.checked).toBeFalsy(); + }); + + it('checkBoxWithTextTest04', function () { + let litCheckBoxWithText = new LitCheckBoxWithText(); + expect(litCheckBoxWithText.text).toEqual(""); + }); + + it('checkBoxWithTextTest05', function () { + let litCheckBoxWithText = new LitCheckBoxWithText(); + litCheckBoxWithText.text = "test" + expect(litCheckBoxWithText.text).toEqual("test"); + }); + + it('checkBoxWithTextTest05', function () { + let litCheckBoxWithText = new LitCheckBoxWithText(); + expect(litCheckBoxWithText.lowerlimit).toEqual(undefined); + }); + + it('checkBoxWithTextTest05', function () { + let litCheckBoxWithText = new LitCheckBoxWithText(); + litCheckBoxWithText.lowerlimit = "111" + expect(litCheckBoxWithText.lowerlimit).toEqual("111"); + }); + + it('checkBoxWithTextTest05', function () { + let litCheckBoxWithText = new LitCheckBoxWithText(); + litCheckBoxWithText.uplimit = "111" + expect(litCheckBoxWithText.uplimit).toEqual("111"); + }); +}) \ No newline at end of file diff --git a/host/ide/test/base-ui/checkbox/LitCheckGroup.test.ts b/host/ide/test/base-ui/checkbox/LitCheckGroup.test.ts new file mode 100644 index 0000000..57bd9df --- /dev/null +++ b/host/ide/test/base-ui/checkbox/LitCheckGroup.test.ts @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// @ts-ignore +import {LitCheckGroup} from "../../../dist/base-ui/checkbox/LitCheckGroup.js"; + +describe('LitCheckGroup Test', ()=>{ + + it('LitCheckGroupTest01', function () { + let litCheckGroup = new LitCheckGroup(); + expect(litCheckGroup).not.toBeUndefined() + expect(litCheckGroup).not.toBeNull() + }); + + + it('LitCheckGroupTest02', function () { + let litCheckGroup = new LitCheckGroup(); + expect(litCheckGroup.direction).toBeNull(); + }); + + it('LitCheckGroupTest03', function () { + let litCheckGroup = new LitCheckGroup(); + expect(litCheckGroup.value).toEqual([]); + }); + +}) \ No newline at end of file diff --git a/host/ide/test/base-ui/icon/LitIcon.test.ts b/host/ide/test/base-ui/icon/LitIcon.test.ts new file mode 100644 index 0000000..dd3ab13 --- /dev/null +++ b/host/ide/test/base-ui/icon/LitIcon.test.ts @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {LitIcon} from "../../../dist/base-ui/icon/LitIcon.js"; + +describe("testLitIcon Test", () => { + + it('testLitIcon01', () => { + let litIcon = new LitIcon(); + expect(litIcon).not.toBeUndefined() + expect(litIcon).not.toBeNull() + }); + + it('testLitIcon02', () => { + let litIcon = new LitIcon(); + expect(litIcon.path).toBeUndefined() + }); + + it('testLitIcon03', () => { + let litIcon = new LitIcon(); + litIcon.path ="ss" + expect(litIcon.path).toBeUndefined() + }); + + it('testLitIcon04', () => { + let litIcon = new LitIcon(); + expect(litIcon.size).toBe(0) + }); + + it('testLitIcon05', () => { + let litIcon = new LitIcon(); + litIcon.size = 1024 + expect(litIcon.size).toBe(1024) + }); + + it('testLitIcon06', () => { + let litIcon = new LitIcon(); + expect(litIcon.name).toBe("") + }); + + it('testLitIcon07', () => { + let litIcon = new LitIcon(); + litIcon.name = "sss" + expect(litIcon.name).toBe("sss") + }); +}) diff --git a/host/ide/test/base-ui/menu/LitMainMenu.test.ts b/host/ide/test/base-ui/menu/LitMainMenu.test.ts new file mode 100644 index 0000000..dc0fc18 --- /dev/null +++ b/host/ide/test/base-ui/menu/LitMainMenu.test.ts @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {LitMainMenu} from "../../../dist/base-ui/menu/LitMainMenu.js"; +import {MenuItem} from "../../../src/base-ui/menu/LitMainMenu.js"; + +describe("LitMainMenu Test", () => { + + it('LitMainMenu01', () => { + let litMainMenu = new LitMainMenu(); + expect(litMainMenu).not.toBeUndefined() + expect(litMainMenu).not.toBeNull() + }); + + it('LitMainMenu01', () => { + let litMainMenu = new LitMainMenu(); + expect(litMainMenu).not.toBeUndefined() + expect(litMainMenu).not.toBeNull() + }); + + it('LitMainMenu02', () => { + let litMainMenu = new LitMainMenu(); + litMainMenu.menus = [ + { + collapsed: false, + title: 'Navigation', + describe: 'Open or record a new trace', + children: [ + { + title: "Open trace file", + icon: "folder", + fileChoose: true, + fileHandler: function (ev: InputEvent) { + } + + }, + { + title: "Record new trace", icon: "copyhovered", clickHandler: function (item: MenuItem) { + } + } + ] + } + ] + expect(litMainMenu.menus.length).toBe(1) + }); +}) diff --git a/host/ide/test/base-ui/menu/LitMainMenuGroup.test.ts b/host/ide/test/base-ui/menu/LitMainMenuGroup.test.ts new file mode 100644 index 0000000..5c87052 --- /dev/null +++ b/host/ide/test/base-ui/menu/LitMainMenuGroup.test.ts @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {LitMainMenuGroup} from "../../../dist/base-ui/menu/LitMainMenuGroup.js"; + +describe("litMainMenuGroup Test", () => { + + it('litMainMenuGroup01', () => { + let litMainMenuGroup = new LitMainMenuGroup(); + expect(litMainMenuGroup).not.toBeUndefined() + expect(litMainMenuGroup).not.toBeNull() + }); + + it('litMainMenuGroup02', () => { + let litMainMenuGroup = new LitMainMenuGroup(); + expect(litMainMenuGroup.collapsed).toBeFalsy() + }); + + + it('litMainMenuGroup03', () => { + let litMainMenuGroup = new LitMainMenuGroup(); + litMainMenuGroup.collapsed = true + expect(litMainMenuGroup.collapsed).toBeTruthy() + }); + + it('litMainMenuGroup03', () => { + let litMainMenuGroup = new LitMainMenuGroup(); + litMainMenuGroup.collapsed = false + expect(litMainMenuGroup.collapsed).toBeFalsy() + }); +}) diff --git a/host/ide/test/base-ui/menu/LitMainMenuItem.test.ts b/host/ide/test/base-ui/menu/LitMainMenuItem.test.ts new file mode 100644 index 0000000..4dc93a1 --- /dev/null +++ b/host/ide/test/base-ui/menu/LitMainMenuItem.test.ts @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {LitMainMenuItem} from "../../../dist/base-ui/menu/LitMainMenuItem.js"; + +describe("litMainMenuItem Test", () => { + + it('litMainMenuItem01', () => { + let litMainMenuItem = new LitMainMenuItem(); + expect(litMainMenuItem).not.toBeUndefined() + expect(litMainMenuItem).not.toBeNull() + }); + + it('litMainMenuItem02', () => { + let litMainMenuItem = new LitMainMenuItem(); + expect(litMainMenuItem.title).toEqual("") + }); + + it('litMainMenuItem03', () => { + let litMainMenuItem = new LitMainMenuItem(); + litMainMenuItem.title ="test" + expect(litMainMenuItem.title).toEqual("test") + }); + + + it('litMainMenuItem04', () => { + document.body.innerHTML = ` + ` + let litMainMenuItem = new LitMainMenuItem(); + litMainMenuItem.title ="test02" + expect(litMainMenuItem.title).toEqual("test02") + }); + + it('litMainMenuItem05', () => { + document.body.innerHTML = ` + ` + let litMainMenuItem = new LitMainMenuItem(); + litMainMenuItem.title ="test03" + expect(litMainMenuItem.title).toEqual("test03") + }); +}) diff --git a/host/ide/test/base-ui/popover/LitPopContent.test.ts b/host/ide/test/base-ui/popover/LitPopContent.test.ts new file mode 100644 index 0000000..b21ca9b --- /dev/null +++ b/host/ide/test/base-ui/popover/LitPopContent.test.ts @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore + +import {LitPopContent} from "../../../dist/base-ui/popover/LitPopContent.js"; + +describe("LitPopCont Test", () => { + + it('LitPopCont01', () => { + let litPopContent = new LitPopContent(); + expect(litPopContent).not.toBeUndefined() + expect(litPopContent).not.toBeNull() + }); + + it('LitPopCont02', () => { + let litPopContent = new LitPopContent(); + expect(litPopContent.open).toBeFalsy() + }); + + + it('LitPopCont03', () => { + let litPopContent = new LitPopContent(); + litPopContent.open = false + expect(litPopContent.open).toBeFalsy() + }); + + + it('LitPopCont04', () => { + let litPopContent = new LitPopContent(); + litPopContent.open = true + expect(litPopContent.open).toBeTruthy() + }); + + it('LitPopCont04', () => { + let litPopContent = new LitPopContent(); + litPopContent.name = "11" + expect(litPopContent.name).toEqual("11") + }); +}) diff --git a/host/ide/test/base-ui/popover/LitPopover.test.ts b/host/ide/test/base-ui/popover/LitPopover.test.ts new file mode 100644 index 0000000..a8be4f4 --- /dev/null +++ b/host/ide/test/base-ui/popover/LitPopover.test.ts @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {LitPopover} from "../../../dist/base-ui/popover/LitPopover.js"; + +describe("LitPopover Test", () => { + it('LitPopover01', () => { + let litPopover = new LitPopover(); + expect(litPopover).not.toBeUndefined() + expect(litPopover).not.toBeNull() + }); + + it('LitPopover02', () => { + let litPopover = new LitPopover(); + expect(litPopover.open).toBeFalsy() + }); + + it('LitPopover03', () => { + let litPopover = new LitPopover(); + litPopover.open = true + expect(litPopover.open).toBeTruthy() + }); + + it('LitPopover04', () => { + let litPopover = new LitPopover(); + litPopover.open = false + expect(litPopover.open).toBeFalsy() + }); + + + it('LitPopover05', () => { + let litPopover = new LitPopover(); + litPopover.direction = "topleft" + expect(litPopover.direction).toEqual("topleft") + }); + + it('LitPopover06', () => { + let litPopover = new LitPopover(); + expect(litPopover.direction).toEqual("topright") + }); + + it('LitPopover07', () => { + let litPopover = new LitPopover(); + litPopover.type = "multiple" + litPopover.dataSource = [{ + text: "# Samples", + isSelected: true + }] + expect(litPopover.select).toEqual(["# Samples"]) + }); + + it('LitPopover07', () => { + let litPopover = new LitPopover(); + litPopover.type = "radio" + litPopover.dataSource = [{ + text: "# Samples", + isSelected: true + }] + expect(litPopover.select).toEqual(["# Samples"]) + }); + + it('LitPopover08', () => { + let litPopover = new LitPopover(); + litPopover.type = "multiple-text" + litPopover.dataSource = [{ + text: "# Samples", + isSelected: true + }] + expect(litPopover.select).toEqual(["# Samples"]) + }); + + + it('LitPopover09', () => { + let litPopover = new LitPopover(); + litPopover.type = "radio" + litPopover.title = "tee" + litPopover.dataSource = [{ + text: "# Samples", + isSelected: true + }] + expect(litPopover.select).toEqual(["# Samples"]) + }); +}) diff --git a/host/ide/test/base-ui/progress-bar/LitProgressBar.test.ts b/host/ide/test/base-ui/progress-bar/LitProgressBar.test.ts new file mode 100644 index 0000000..0f2f37d --- /dev/null +++ b/host/ide/test/base-ui/progress-bar/LitProgressBar.test.ts @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {LitProgressBar} from "../../../dist/base-ui/progress-bar/LitProgressBar.js"; + +describe('LitProgressBar Test', ()=>{ + let litProgressBar = new LitProgressBar(); + litProgressBar.loading = '' + litProgressBar.loading = 'load' + + it('LitProgressBarTest03', ()=>{ + expect(litProgressBar.loading).toBeTruthy(); + }) +}) diff --git a/host/ide/test/base-ui/radiobox/LitRadioBox.test.ts b/host/ide/test/base-ui/radiobox/LitRadioBox.test.ts new file mode 100644 index 0000000..7bd211a --- /dev/null +++ b/host/ide/test/base-ui/radiobox/LitRadioBox.test.ts @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {LitRadioBox} from "../../../dist/base-ui/radiobox/LitRadioBox.js"; + +// @ts-ignore +import {LitRadioGroup} from "../../../dist/base-ui/radiobox/LitRadioGroup.js"; + +describe('LitRadioBox Test', ()=>{ + let litRadioBox = new LitRadioBox(); + let litRadioGroup = new LitRadioGroup() + + litRadioGroup.layout = 'layout' + + litRadioBox.checked = true + litRadioBox.checked = false + litRadioBox.value = 'value' + litRadioBox.dis = 'dis' + it('LitRadioBoxTest01', ()=>{ + expect(litRadioBox.name).toBeNull(); + }) + + it('LitRadioBoxTest02', ()=>{ + expect(litRadioBox.value).toBe('value'); + }) + + it('litRadioGroupTest01', ()=>{ + let isReturn = litRadioGroup.value.length == 0 + expect(isReturn).toBeTruthy(); + }) +}) diff --git a/host/ide/test/base-ui/slider/LitSlider.test.ts b/host/ide/test/base-ui/slider/LitSlider.test.ts new file mode 100644 index 0000000..d05e457 --- /dev/null +++ b/host/ide/test/base-ui/slider/LitSlider.test.ts @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {LitSlider} from "../../../dist/base-ui/slider/LitSlider.js"; + +describe('LitSlider Test', ()=>{ + let litSliderPanel = new LitSlider(); + + litSliderPanel.disabledX = 'disabledX' + litSliderPanel.customSlider = 'customSlider' + litSliderPanel.customLine = 'customLine' + litSliderPanel.customButton = 'customButton' + litSliderPanel.percent = 'percent' + litSliderPanel.resultUnit = 'resultUnit' + + litSliderPanel.litSlider = jest.fn(()=> true) + + litSliderPanel.sliderStyle = 'sliderStyle' + + it('LitSliderTest01', ()=>{ + expect(litSliderPanel.disabledX).toEqual(''); + }) + + it('LitSliderTest02', ()=>{ + expect(litSliderPanel.customSlider).toEqual(''); + }) + + it('LitSliderTest03', ()=>{ + expect(litSliderPanel.customLine).toEqual('customLine'); + }) + + it('LitSliderTest04', ()=>{ + expect(litSliderPanel.customButton).toEqual('customButton'); + }) + + it('LitSliderTest05', ()=>{ + expect(litSliderPanel.percent).toEqual('percent'); + }) + + it('LitSliderTest06', ()=>{ + expect(litSliderPanel.resultUnit).toEqual('resultUnit'); + }) + + it('LitSliderTest07', ()=>{ + expect(litSliderPanel.formatSeconds(10)).toBe('00:00:10'); + }) + + it('LitSliderTest08', ()=>{ + expect(litSliderPanel.renderDefaultSlider()).toBeUndefined(); + }) + + it('LitSliderTest9', ()=>{ + expect(litSliderPanel.adoptedCallback()).toBeUndefined(); + }) + + it('LitSliderTest10', ()=>{ + litSliderPanel.litSlider.removeEventListener = jest.fn(()=> true) + litSliderPanel.litSlider.removeEventListener = jest.fn(()=> true) + litSliderPanel.litSliderButton = jest.fn(()=> true) + litSliderPanel.litSliderButton.removeEventListener = jest.fn(()=> true) + expect(litSliderPanel.disconnectedCallback()).toBeUndefined(); + }) +}) diff --git a/host/ide/test/base-ui/switch/LitSwitch.test.ts b/host/ide/test/base-ui/switch/LitSwitch.test.ts new file mode 100644 index 0000000..c0094b0 --- /dev/null +++ b/host/ide/test/base-ui/switch/LitSwitch.test.ts @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import LitSwitch from "../../../dist/base-ui/switch/lit-switch"; + +describe('LitSwitch Test', ()=>{ + let litSwitch = new LitSwitch(); + litSwitch.checked = true + litSwitch.checked = false + + it('LitSwitchTest01', ()=>{ + expect(litSwitch.name).toBeNull(); + }) + + it('LitSwitchTest02', ()=>{ + expect(litSwitch.disabled).toBeFalsy(); + }) + + it('LitSwitchTest03', ()=>{ + expect(litSwitch.checked).toBeFalsy(); + }) + + it('LitSwitchTest04', ()=>{ + LitSwitch.switch = document.querySelector("#switch") as HTMLInputElement; + expect(litSwitch.connectedCallback()).toBeUndefined() + }) + + it('LitSwitchTest05', ()=>{ + expect(litSwitch.attributeChangedCallback('disabled', 'disabled', '')).toBeUndefined() + }) + + it('LitSwitchTest06', ()=>{ + expect(litSwitch.attributeChangedCallback('disabled', 'disabled', null)).toBeUndefined() + }) + + it('LitSwitchTest07', ()=>{ + expect(litSwitch.attributeChangedCallback('checked', 'disabled', '')).toBeUndefined() + }) + + it('LitSwitchTest08', ()=>{ + expect(litSwitch.attributeChangedCallback('checked', 'disabled', null)).toBeUndefined() + }) +}) diff --git a/host/ide/test/base-ui/table/LitTable.test.ts b/host/ide/test/base-ui/table/LitTable.test.ts new file mode 100644 index 0000000..b011898 --- /dev/null +++ b/host/ide/test/base-ui/table/LitTable.test.ts @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {LitTable} from "../../../dist/base-ui/table/lit-table.js"; + +describe('LitTable Test', ()=>{ + let litTable = new LitTable(); + litTable.selectable = true + litTable.selectable = false + litTable.scrollY = 'scrollY' + + litTable.dataSource = [] + + litTable.dataSource = [{ + id: 1, + name: 'name' + },{ + id: 2, + name: 'nameValue' + }] + + JSON.parse = jest.fn(()=>[['children', 'father'], ['children', 'father']]) + + litTable.columns = litTable.columns || jest.fn(()=>true) + litTable.ds = jest.fn(()=>[{ + id: 1, + name: 'name' + },{ + id: 2, + name: 'nameValue' + }]) + + litTable.tbodyElement = jest.fn(()=> ({ + innerHTML: '' + })) + + litTable.tableColumns = jest.fn(()=>[]) + + litTable.tableColumns.forEach = jest.fn(()=>[]) + + + it('LitTableTest01', ()=>{ + expect(litTable.adoptedCallback()).toBeUndefined(); + }) + + it('LitTableTest02', ()=>{ + litTable.ds.forEach = jest.fn(()=> true) + expect(litTable.renderTable()).toBeUndefined(); + }) + + // it('LitTableTest03', ()=>{ + // litTable.parentNode = jest.fn(()=> true) + // litTable.parentNode.append = jest.fn(()=> true) + // expect(litTable.renderTreeTable()).toBeUndefined(); + // }) + + it('LitTableTest04', ()=>{ + litTable.switch = document.querySelector("#switch") as HTMLInputElement; + expect(litTable.connectedCallback()).toBeUndefined() + }) + + it('LitTableTest05', ()=>{ + let rowLength = litTable.getCheckRows().length == 0; + expect(rowLength).toBeTruthy() + }) + + it('LitTableTest06', ()=>{ + expect(litTable.deleteRowsCondition(()=>{ + return true + })).toBeUndefined() + }) +}) diff --git a/host/ide/test/base-ui/table/LitTableColumn.test.ts b/host/ide/test/base-ui/table/LitTableColumn.test.ts new file mode 100644 index 0000000..6a1fc57 --- /dev/null +++ b/host/ide/test/base-ui/table/LitTableColumn.test.ts @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {LitTableColumn} from "../../../dist/base-ui/table/lit-table-column.js"; + +describe('LitTableGroup Test', ()=>{ + let litTableColumn = new LitTableColumn(); + litTableColumn.title = 'title' + + it('LitTableGroupTest01', ()=>{ + expect(litTableColumn.adoptedCallback()).toBeUndefined(); + }) + + it('LitTableGroupTest02', ()=>{ + expect(litTableColumn.connectedCallback()).toBeUndefined(); + }) +}) diff --git a/host/ide/test/base-ui/table/LitTableGroup.test.ts b/host/ide/test/base-ui/table/LitTableGroup.test.ts new file mode 100644 index 0000000..64ce85e --- /dev/null +++ b/host/ide/test/base-ui/table/LitTableGroup.test.ts @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {LitTableGroup} from "../../../dist/base-ui/table/lit-table-group.js"; + +describe('LitTableGroup Test', ()=>{ + let litTableGroup = new LitTableGroup(); + litTableGroup.title = 'title' + + it('LitTableGroupTest01', ()=>{ + expect(litTableGroup.adoptedCallback()).toBeUndefined(); + }) + + it('LitTableGroupTest02', ()=>{ + expect(litTableGroup.title).toBe('title'); + }) +}) diff --git a/host/ide/test/base-ui/tabs/LitTabpane.test.ts b/host/ide/test/base-ui/tabs/LitTabpane.test.ts new file mode 100644 index 0000000..2094ef9 --- /dev/null +++ b/host/ide/test/base-ui/tabs/LitTabpane.test.ts @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {LitTabpane} from "../../../dist/base-ui/tabs/lit-tabpane.js"; + +describe('LitTabPane Test', ()=>{ + let litTabPane = new LitTabpane(); + + litTabPane.tab = 'tab' + litTabPane.disabled = 'disabled' + litTabPane.hidden = 'hidden' + litTabPane.closeable = false + litTabPane.key = 'key' + + it('LitTabPaneTest1', ()=>{ + expect(litTabPane.attributeChangedCallback('disabled', 'disabled', '')).toBeUndefined() + }) + + it('LitTabPaneTest2', ()=>{ + expect(litTabPane.tab).toBe('tab'); + }) + + it('LitTabPaneTest3', ()=>{ + expect(litTabPane.icon).toBeNull(); + }) + + it('LitTabPaneTest4', ()=>{ + expect(litTabPane.disabled).toBeTruthy(); + }) + it('LitTabPaneTest5', ()=>{ + expect(litTabPane.hidden).toBeTruthy(); + }) + it('LitTabPaneTest6', ()=>{ + litTabPane.closeable = 'closeable' + expect(litTabPane.closeable).toBeTruthy(); + }) + it('LitTabPaneTest7', ()=>{ + expect(litTabPane.key).toBe('key'); + }) +}) diff --git a/host/ide/test/base-ui/tabs/LitTabs.test.ts b/host/ide/test/base-ui/tabs/LitTabs.test.ts new file mode 100644 index 0000000..06a740d --- /dev/null +++ b/host/ide/test/base-ui/tabs/LitTabs.test.ts @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {LitTabs} from "../../../dist/base-ui/tabs/lit-tabs.js"; + +describe('LitSwitch Test', ()=>{ + let litTabs = new LitTabs(); + + litTabs.position = 'position' + litTabs.mode = 'mode' + litTabs.activekey = 'activekey' + + litTabs.nav = jest.fn(()=>{ + let el = document.createElement('div'); + let htmlDivElement = document.createElement('div'); + htmlDivElement.setAttribute('class', 'nav-item[data-key=\'${key}\']') + + el.appendChild(htmlDivElement) + return el + }) + + LitTabs.nav = jest.fn(()=>{ + return document.createElement('div') as HTMLDivElement + }) + + LitTabs.nav.querySelectorAll = jest.fn(()=>{ + return ['fd'] + }) + + // it('litTabsTest01', ()=>{ + // litTabs.nav = jest.fn(()=> true) + // litTabs.nav.querySelector = jest.fn(()=> { + // return document.createElement('div') as HTMLDivElement + // }) + // litTabs.nav.querySelectorAll = jest.fn(()=> true) + // expect(litTabs.updateLabel('key', 'value')).toBeUndefined(); + // }) + + it('litTabsTest1', ()=>{ + expect(litTabs.activekey).toBe('activekey'); + }) + + it('litTabsTest02', ()=>{ + litTabs.nav = jest.fn(()=> true) + litTabs.nav.querySelector = jest.fn(()=> { + return document.createElement('div') as HTMLDivElement + }) + litTabs.nav.querySelectorAll = jest.fn(()=> true) + expect(litTabs.updateDisabled('key', 'value')).toBeUndefined(); + }) + + it('litTabsTest03', ()=>{ + litTabs.nav = jest.fn(()=> true) + litTabs.nav.querySelector = jest.fn(()=> { + return document.createElement('div') as HTMLDivElement + }) + litTabs.nav.querySelectorAll = jest.fn(()=> true) + expect(litTabs.updateCloseable('key', 'value')).toBeUndefined(); + }) + + it('litTabsTest04', ()=>{ + litTabs.nav = jest.fn(()=> true) + litTabs.nav.querySelector = jest.fn(()=> { + return document.createElement('div') as HTMLDivElement + }) + litTabs.nav.querySelectorAll = jest.fn(()=> true) + + expect(litTabs.updateHidden('key', 'value')).toBeUndefined(); + }) + + it('litTabsTest05', ()=>{ + expect(litTabs.initTabPos()).toBeUndefined(); + }) + + // it('litTabsTest07', ()=>{ + // litTabs.nav.querySelectorAll = jest.fn(()=> true) + // litTabs.nav.querySelectorAll.forEach = jest.fn(()=> true) + // expect(litTabs.activeByKey('newKey')).toBeNull(); + // }) + + it('litTabsTest06', ()=>{ + expect(litTabs.activePane('Key')).toBeFalsy(); + }) + + it('litTabsTest07', ()=>{ + expect(litTabs.connectedCallback()).toBeUndefined() + }) + it('litTabsTest8', ()=>{ + expect(litTabs.attributeChangedCallback('disabled', 'disabled', '')).toBeUndefined() + }) + + it('litTabsTest9', ()=>{ + expect(litTabs.adoptedCallback()).toBeUndefined(); + }) + + it('litTabsTest10', ()=>{ + expect(litTabs.position).toBe('position'); + }) + + it('litTabsTest11', ()=>{ + expect(litTabs.mode).toBe('mode'); + }) +}) diff --git a/host/ide/test/trace/SpApplication.test.ts b/host/ide/test/trace/SpApplication.test.ts new file mode 100644 index 0000000..c4817f6 --- /dev/null +++ b/host/ide/test/trace/SpApplication.test.ts @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {SpApplication} from "../../dist/trace/SpApplication.js"; +import {LitMainMenu} from "../../src/base-ui/menu/LitMainMenu"; + +window.ResizeObserver = window.ResizeObserver || + jest.fn().mockImplementation(() => ({ + disconnect: jest.fn(), + observe: jest.fn(), + unobserve: jest.fn(), + })); + +describe('spApplication Test', ()=>{ + it('spApplicationTest01', function () { + let spApplication = new SpApplication(); + expect(spApplication).not.toBeUndefined() + }); + + + it('spApplicationTest02', function () { + let spApplication = new SpApplication(); + expect(spApplication.sqlite).toBeFalsy() + }); + + + it('spApplicationTest03', function () { + let spApplication = new SpApplication(); + expect(spApplication.wasm).toBeFalsy() + }); + + it('spApplicationTest04', function () { + let spApplication = new SpApplication(); + expect(spApplication.server).toBeFalsy() + }); + + + it('spApplicationTest05', function () { + let spApplication = new SpApplication(); + spApplication.server = true; + expect(spApplication.server).toBeTruthy() + }); + + it('spApplicationTest06', function () { + let spApplication = new SpApplication(); + spApplication.server = false; + expect(spApplication.server).toBeFalsy() + }); + + it('spApplicationTest07', function () { + let spApplication = new SpApplication(); + spApplication.search = false; + expect(spApplication.search).toBeFalsy() + }); + + it('spApplicationTest08', function () { + let spApplication = new SpApplication(); + spApplication.search = true; + expect(spApplication.search).toBeUndefined() + }); + + it('spApplicationTest09', function () { + let spApplication = new SpApplication(); + expect(spApplication.dark).toBeFalsy() + }); + it('spApplicationTest010', function () { + let spApplication = new SpApplication(); + spApplication.dark = true; + expect(spApplication.dark).toBeTruthy() + }); + + it('spApplicationTest11', function () { + let spApplication = new SpApplication(); + spApplication.dark = false; + expect(spApplication.dark).toBeFalsy() + }); + + it('spApplicationTest12', function () { + let spApplication = new SpApplication(); + expect(spApplication.vs).toBeFalsy() + }); + it('spApplicationTest013', function () { + let spApplication = new SpApplication(); + spApplication.vs = true; + expect(spApplication.vs).toBeTruthy() + }); + + it('spApplicationTest14', function () { + let spApplication = new SpApplication(); + spApplication.vs = false; + expect(spApplication.vs).toBeFalsy() + }); + + it('spApplicationTest15',function (){ + let spApplication = new SpApplication(); + expect(spApplication.freshMenuDisable).toBeTruthy() + }) + + +}) diff --git a/host/ide/test/trace/bean/BoxSelection.test.ts b/host/ide/test/trace/bean/BoxSelection.test.ts new file mode 100644 index 0000000..1171184 --- /dev/null +++ b/host/ide/test/trace/bean/BoxSelection.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {SelectionParam, SelectionData, Counter, Fps} from "../../../dist/trace/bean/BoxSelection.js" + +describe('BoxSelection Test', ()=>{ + let selectionParam = new SelectionParam(); + let selectionData = new SelectionData(); + let counter = new Counter(); + let fps = new Fps(); + + it('BoxSelectionTest01', function () { + expect(selectionParam).not.toBeUndefined() + }); + + it('BoxSelectionTest02', function () { + expect(selectionData).not.toBeUndefined() + }); + + it('BoxSelectionTest03', function () { + expect(counter).not.toBeUndefined() + }); + + it('BoxSelectionTest04', function () { + expect(fps).not.toBeUndefined() + }); +}) diff --git a/host/ide/test/trace/bean/CpuFreqStruct.test.ts b/host/ide/test/trace/bean/CpuFreqStruct.test.ts new file mode 100644 index 0000000..1d716aa --- /dev/null +++ b/host/ide/test/trace/bean/CpuFreqStruct.test.ts @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {CpuFreqStruct} from "../../../dist/trace/bean/CpuFreqStruct.js" + +describe('CpuFreqStruct Test', ()=>{ + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + + CpuFreqStruct.hoverCpuFreqStruct = void 0 + const data = { + frame: { + x: 20, + y: 20, + width: 100, + height: 100 + }, + startNS: 200, + value: 50 + } + it('CpuFreqStructTest01', function () { + expect(CpuFreqStruct.draw(ctx, data)).toBeUndefined() + }); +}) diff --git a/host/ide/test/trace/bean/CpuStruct.test.ts b/host/ide/test/trace/bean/CpuStruct.test.ts new file mode 100644 index 0000000..266521c --- /dev/null +++ b/host/ide/test/trace/bean/CpuStruct.test.ts @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {CpuStruct} from "../../../dist/trace/bean/CpuStruct.js" + +describe('CpuStruct Test', ()=>{ + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + + const data = { + frame: { + x: 20, + y: 20, + width: 100, + height: 100 + }, + startNS: 200, + value: 50 + } + it('CpuStructTest01', function () { + expect(CpuStruct.draw(ctx, data)).toBeUndefined() + }); + + it('CpuStructTest02', function () { + expect(CpuStruct.equals({}, data)).toBeTruthy(); + }); +}) diff --git a/host/ide/test/trace/bean/CpuUsage.test.ts b/host/ide/test/trace/bean/CpuUsage.test.ts new file mode 100644 index 0000000..920f4fc --- /dev/null +++ b/host/ide/test/trace/bean/CpuUsage.test.ts @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {CpuUsage, Freq} from "../../../dist/trace/bean/CpuUsage.js" + +describe('CpuUsage Test', ()=>{ + let cpuUsage = new CpuUsage(); + let freq = new Freq(); + + it('CpuUsageTest01', function () { + expect(cpuUsage).not.toBeUndefined() + }); + + it('CpuUsageTest02', function () { + expect(freq).not.toBeUndefined() + }); +}) diff --git a/host/ide/test/trace/bean/FpsStruct.test.ts b/host/ide/test/trace/bean/FpsStruct.test.ts new file mode 100644 index 0000000..568a5a5 --- /dev/null +++ b/host/ide/test/trace/bean/FpsStruct.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {FpsStruct} from "../../../dist/trace/bean/FpsStruct.js" + +describe('FpsStruct Test', ()=>{ + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + + const data = { + frame: { + x: 20, + y: 20, + width: 100, + height: 100 + }, + startNS: 200, + value: 50 + } + it('FpsStructTest01', function () { + expect(FpsStruct.draw(ctx, data)).toBeUndefined() + }); +}) diff --git a/host/ide/test/trace/bean/FuncStruct.test.ts b/host/ide/test/trace/bean/FuncStruct.test.ts new file mode 100644 index 0000000..f2ac0d7 --- /dev/null +++ b/host/ide/test/trace/bean/FuncStruct.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {FuncStruct} from "../../../dist/trace/bean/FuncStruct.js" + +describe('FuncStruct Test', ()=>{ + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + + const dataResource = { + frame: { + x: 20, + y: 20 + } + } + + const durData = { + frame: { + x: 20, + y: 20, + width: 100, + height: 100 + }, + dur: 5 + } + + FuncStruct.isSelected = jest.fn(()=> true) + + it('FuncStructTest01', function () { + expect(FuncStruct.draw(ctx, dataResource)).toBeUndefined() + }); + + it('FuncStructTest02', function () { + expect(FuncStruct.draw(ctx, durData)).toBeUndefined() + }); + it('FuncStructTest03', function () { + expect(FuncStruct.drawString(ctx, 2, durData, durData.frame)).toBeUndefined() + }); + + it('FuncStructTest04', function () { + expect(FuncStruct.isSelected({ + startTs: 10, + dur: 10, + funName: '' + })).toBeTruthy(); + }); + + it('FuncStructTest05', function () { + expect(FuncStruct.isBinder({ + startTs: 10, + dur: 10, + funName: '' + })).toBeFalsy(); + }); +}) diff --git a/host/ide/test/trace/bean/HeapStruct.test.ts b/host/ide/test/trace/bean/HeapStruct.test.ts new file mode 100644 index 0000000..bf92ccf --- /dev/null +++ b/host/ide/test/trace/bean/HeapStruct.test.ts @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {HeapStruct} from "../../../dist/trace/bean/HeapStruct.js" + +describe('HeapStruct Test', () => { + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + + const dataSource = { + frame: { + x: 20, + y: 20, + width: 100, + height: 100 + }, + value: 50, + maxHeapSize: 50, + heapsize: 10 + } + + const reachData = { + frame: { + x: 20, + y: 20, + width: 100, + height: 100 + }, + value: 50, + startTime: 1 + } + + const nodeSource = { + startTime: 10, + dur: 10, + endTime: 20, + frame: { + width: 20 + } + } + + const heapStruct = new HeapStruct(); + + it('HeapStructTest01', function () { + expect(HeapStruct.draw(ctx, dataSource)).toBeUndefined() + }); + + it('HeapStructTest02', function () { + expect(HeapStruct.draw(ctx, reachData)).toBeUndefined() + }); + + it('HeapStructTest03', function () { + expect(HeapStruct.setFrame(nodeSource, 1, 10, 15, 30, nodeSource.frame)).toBeUndefined() + }); + + it('HeapStructTest04', function () { + expect(HeapStruct.setFrame(nodeSource, 1, 15, 20, 30, nodeSource.frame)).toBeUndefined() + }); + + it('HeapStructTest05', function () { + expect(HeapStruct.setFrame(nodeSource, 1, 10, 20, 30, nodeSource.frame)).toBeUndefined() + }); +}) diff --git a/host/ide/test/trace/bean/ProcessMemStruct.test.ts b/host/ide/test/trace/bean/ProcessMemStruct.test.ts new file mode 100644 index 0000000..0dbb4ab --- /dev/null +++ b/host/ide/test/trace/bean/ProcessMemStruct.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {ProcessMemStruct} from "../../../dist/trace/bean/ProcessMemStruct.js" + +describe('ProcessMemStruct Test', ()=>{ + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + + const data = { + frame: { + x: 20, + y: 20, + width: 100, + height: 100 + }, + startNS: 200, + value: 50 + } + it('ProcessMemStructTest01', function () { + expect(ProcessMemStruct.draw(ctx, data)).toBeUndefined() + }); +}) diff --git a/host/ide/test/trace/bean/ProcessStruct.test.ts b/host/ide/test/trace/bean/ProcessStruct.test.ts new file mode 100644 index 0000000..5128692 --- /dev/null +++ b/host/ide/test/trace/bean/ProcessStruct.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {ProcessStruct} from "../../../dist/trace/bean/ProcessStruct.js" + +describe('ProcessStruct Test', ()=>{ + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + + const data = { + frame: { + x: 20, + y: 20, + width: 100, + height: 100 + }, + startNS: 200, + value: 50 + } + it('ProcessStructTest01', function () { + expect(ProcessStruct.draw(ctx, data)).toBeUndefined() + }); +}) diff --git a/host/ide/test/trace/bean/StateProcessThread.test.ts b/host/ide/test/trace/bean/StateProcessThread.test.ts new file mode 100644 index 0000000..83be7ef --- /dev/null +++ b/host/ide/test/trace/bean/StateProcessThread.test.ts @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {StateProcessThread} from "../../../dist/trace/bean/StateProcessThread.js" + +describe('StateProcessThread Test', ()=>{ + let stateProcessThread = new StateProcessThread(); + + it('StateProcessThreadTest01', function () { + expect(stateProcessThread).not.toBeUndefined() + }); +}) diff --git a/host/ide/test/trace/bean/ThreadStruct.test.ts b/host/ide/test/trace/bean/ThreadStruct.test.ts new file mode 100644 index 0000000..82c8abf --- /dev/null +++ b/host/ide/test/trace/bean/ThreadStruct.test.ts @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {ThreadStruct} from "../../../dist/trace/bean/ThreadStruct.js" + +describe('ThreadStruct Test', () => { + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + const dataSource = { + frame: { + x: 20, + y: 20, + width: 100, + height: 100 + }, + startNS: 200, + state: '' + } + const equalsData = { + value: 50, + cpu: 1, + tid: 1, + state: 1, + startTime: 1, + dur: 1 + } + + it('ThreadStructTest01', function () { + expect(ThreadStruct.draw(ctx, dataSource)).toBeUndefined() + }); + + it('ThreadStructTest02', function () { + dataSource.state = "S" + expect(ThreadStruct.draw(ctx, dataSource)).toBeUndefined() + }); + + it('ThreadStructTest03', function () { + dataSource.state = "R" + expect(ThreadStruct.draw(ctx, dataSource)).toBeUndefined() + }); + + it('ThreadStructTest04', function () { + dataSource.state = "D" + expect(ThreadStruct.draw(ctx, dataSource)).toBeUndefined() + }); + + it('ThreadStructTest05', function () { + dataSource.state = "Running" + expect(ThreadStruct.draw(ctx, dataSource)).toBeUndefined() + }); + + it('ThreadStructTest06', function () { + expect(ThreadStruct.drawString(ctx, '', 2, dataSource.frame)).toBeUndefined() + }); + + it('ThreadStructTest07', function () { + dataSource.frame.width = 10000000000000000; + expect(ThreadStruct.drawString(ctx, 'ThreadStructTest07', 1, dataSource.frame)).toBeUndefined() + }); + + it('ThreadStructTest08', function () { + expect(ThreadStruct.equals(equalsData, equalsData)).toBeTruthy() + }); + + it('ThreadStructTest09', function () { + expect(ThreadStruct.equals([], dataSource)).toBeFalsy() + }); + + it('ThreadStructTest10', function () { + dataSource.state = 'ThreadStructTest10' + expect(ThreadStruct.getEndState(1)).toBe('') + }); +}) diff --git a/host/ide/test/trace/component/FrameChart.test.ts b/host/ide/test/trace/component/FrameChart.test.ts new file mode 100644 index 0000000..71c0410 --- /dev/null +++ b/host/ide/test/trace/component/FrameChart.test.ts @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {FrameChart} from "../../../dist/trace/component/FrameChart.js" + + +describe('FrameChart Test', () => { + + let node= [ + {children: ''}, + {children:{length:1}} + ] + + + it('FrameChartTest01', function () { + let frameChart = new FrameChart(); + frameChart.data = false; + expect(frameChart.data).toBeFalsy(); + }); + + it('FrameChartTest02', function () { + let frameChart = new FrameChart(); + expect(frameChart.data).toBeUndefined(); + }); + + it('FrameChartTest03', function () { + let frameChart = new FrameChart(); + frameChart.selectTotalSize = true; + expect(frameChart.selectTotalSize).toBeUndefined(); + }); + + it('FrameChartTest04', function () { + let frameChart = new FrameChart(); + frameChart.maxDepth = true; + expect(frameChart.maxDepth).toBeUndefined(); + }); + + it('FrameChartTest05',function () { + let frameChart = new FrameChart(); + let result = frameChart.cavasContext.lineWidth ; + expect(result).toBe(1); + }) + + it('FrameChartTest06', function () { + let frameChart = new FrameChart(); + expect(frameChart.drawScale()).toBeUndefined(); + }); + + it('FrameChartTest07', function () { + let frameChart = new FrameChart(); + expect(frameChart.calculateChartData()).toBeUndefined(); + }); + + it('FrameChartTest08', function () { + let frameChart = new FrameChart(); + expect(frameChart.darwTypeChart(node)).toBeUndefined(); + }); + + it('FrameChartTest09', function () { + let frameChart = new FrameChart(); + frameChart.mode = true; + expect(frameChart.mode).toBeTruthy(); + }); +}) \ No newline at end of file diff --git a/host/ide/test/trace/component/SpRecordTrace.test.ts b/host/ide/test/trace/component/SpRecordTrace.test.ts new file mode 100644 index 0000000..0a396a7 --- /dev/null +++ b/host/ide/test/trace/component/SpRecordTrace.test.ts @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {SpRecordTrace} from "../../../dist/trace/component/SpRecordTrace.js" +window.ResizeObserver = window.ResizeObserver || + jest.fn().mockImplementation(() => ({ + disconnect: jest.fn(), + observe: jest.fn(), + unobserve: jest.fn(), + })); +describe('SpRecordTrace Test', () => { + + let spRecordTrace = new SpRecordTrace(); + + it('SpRecordTraceTest01', function () { + expect(spRecordTrace.initHtml()).not.toBe('') + }); + it('SpRecordTraceTest02', function () { + expect(spRecordTrace.initElements()).toBeUndefined() + }); + it('SpRecordTraceTest03', function () { + let toReturnWith = spRecordTrace.createFpsPluginConfig(); + expect(toReturnWith.sampleInterval).toBe(1000); + }); + it('SpRecordTraceTest04', function () { + let traceEvents = spRecordTrace.createTraceEvents(['Scheduling details', 'CPU Frequency and idle states', + 'High frequency memory', 'Advanced ftrace config', 'Syscalls']); + expect(traceEvents[0].indexOf('binder/binder_lock')).toBe(-1) + }); + +}) diff --git a/host/ide/test/trace/component/SpRecyclerSystemTrace.test.ts b/host/ide/test/trace/component/SpRecyclerSystemTrace.test.ts new file mode 100644 index 0000000..140f8b6 --- /dev/null +++ b/host/ide/test/trace/component/SpRecyclerSystemTrace.test.ts @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +window.ResizeObserver = window.ResizeObserver || + jest.fn().mockImplementation(() => ({ + disconnect: jest.fn(), + observe: jest.fn(), + unobserve: jest.fn(), + })); + +// @ts-ignore +import {SpRecyclerSystemTrace} from "../../../dist/trace/component/SpRecyclerSystemTrace.js" + +describe('SpRecyclerSystemTrace Test', ()=>{ + + let spRecyclerSystemTrace = new SpRecyclerSystemTrace(); + + spRecyclerSystemTrace.initElements = jest.fn(()=> true) + + + it('SpRecyclerSystemTraceTest01', function () { + expect(spRecyclerSystemTrace.getScrollWidth()).toBe(1) + }); + + it('SpRecyclerSystemTraceTest02', function () { + let resultLength = spRecyclerSystemTrace.getVisibleRows([{}]).length; + expect(resultLength).toBe(0) + }); + + it('SpRecyclerSystemTraceTest03', function () { + expect(spRecyclerSystemTrace.timerShaftELRangeChange('')).toBeUndefined() + }); + + it('SpRecyclerSystemTraceTest04', function () { + expect(spRecyclerSystemTrace.rowsElOnScroll('Scroll')).toBeUndefined() + }); + + it('SpRecyclerSystemTraceTest05', function () { + expect(spRecyclerSystemTrace.documentOnMouseDown('MouseDown')).toBeUndefined() + }); + + it('SpRecyclerSystemTraceTest06', function () { + expect(spRecyclerSystemTrace.documentOnMouseUp('MouseUp')).toBeUndefined() + }); + + it('SpRecyclerSystemTraceTest07', function () { + spRecyclerSystemTrace.rangeSelec = jest.fn(()=>true) + spRecyclerSystemTrace.rangeSelect.mouseMove = jest.fn(()=>true) + expect(spRecyclerSystemTrace.documentOnMouseMove('MouseMove')).toBeUndefined() + }); + + it('SpRecyclerSystemTraceTest08', function () { + expect(spRecyclerSystemTrace.hoverStructNull('')).toBeUndefined() + }); + + it('SpRecyclerSystemTraceTest09', function () { + expect(spRecyclerSystemTrace.selectStructNull('')).toBeUndefined() + }); + + it('SpRecyclerSystemTraceTest10', function () { + spRecyclerSystemTrace.rangeSelec = jest.fn(()=>true) + spRecyclerSystemTrace.rangeSelect.mouseMove = jest.fn(()=>true) + expect(spRecyclerSystemTrace.documentOnClick('OnClick')).toBeUndefined() + }); + + it('SpRecyclerSystemTraceTest11', function () { + expect(spRecyclerSystemTrace.connectedCallback()).toBeUndefined() + }); + + it('SpRecyclerSystemTraceTest12', function () { + expect(spRecyclerSystemTrace.disconnectedCallback()).toBeUndefined() + }); + + it('SpRecyclerSystemTraceTest13', function () { + expect(spRecyclerSystemTrace.init).toBeTruthy() + }); + it('SpRecyclerSystemTraceTest14', function () { + let spRecyclerSystemTrace = new SpRecyclerSystemTrace; + expect(spRecyclerSystemTrace.insertAfter).toBeTruthy() + }); + +}) diff --git a/host/ide/test/trace/component/SpSystemTrace.test.ts b/host/ide/test/trace/component/SpSystemTrace.test.ts new file mode 100644 index 0000000..10e6d11 --- /dev/null +++ b/host/ide/test/trace/component/SpSystemTrace.test.ts @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {SpSystemTrace} from "../../../dist/trace/component/SpSystemTrace.js" + +window.ResizeObserver = window.ResizeObserver || + jest.fn().mockImplementation(() => ({ + disconnect: jest.fn(), + observe: jest.fn(), + unobserve: jest.fn(), + })); + +describe('SpSystemTrace Test', ()=>{ + let spSystemTrace = new SpSystemTrace(); + + spSystemTrace.initElements = jest.fn(()=> true) + + + it('SpSystemTraceTest01', function () { + expect(spSystemTrace.getScrollWidth()).toBe(0) + }); + + it('SpSystemTraceTest02', function () { + let resultLength = spSystemTrace.getVisibleRows([{}]).length; + expect(resultLength).toBe(0) + }); + + it('SpSystemTraceTest03', function () { + expect(spSystemTrace.timerShaftELRangeChange('')).toBeUndefined() + }); + + it('SpSystemTraceTest04', function () { + expect(spSystemTrace.rowsElOnScroll('Scroll')).toBeUndefined() + }); + + it('SpSystemTraceTest05', function () { + expect(spSystemTrace.documentOnMouseDown('MouseDown')).toBeUndefined() + }); + + it('SpSystemTraceTest06', function () { + expect(spSystemTrace.documentOnMouseUp('MouseUp')).toBeUndefined() + }); + + it('SpSystemTraceTest07', function () { + spSystemTrace.rangeSelect = jest.fn(()=>true) + spSystemTrace.rangeSelect.mouseMove = jest.fn(()=>true) + expect(spSystemTrace.documentOnMouseMove('MouseMove')).toBeUndefined() + }); + + it('SpSystemTraceTest08', function () { + expect(spSystemTrace.hoverStructNull('')).toBeUndefined() + }); + + it('SpSystemTraceTest09', function () { + expect(spSystemTrace.selectStructNull('')).toBeUndefined() + }); + + it('SpSystemTraceTest10', function () { + expect(spSystemTrace.documentOnClick('OnClick')).toBeUndefined() + }); + + it('SpSystemTraceTest11', function () { + expect(spSystemTrace.connectedCallback()).toBeUndefined() + }); + + it('SpSystemTraceTest12', function () { + expect(spSystemTrace.disconnectedCallback()).toBeUndefined() + }); + + it('SpSystemTraceTest13', function () { + expect(spSystemTrace.goProcess).toBeTruthy() + }); + + it('SpSystemTraceTest14', function () { + expect(spSystemTrace.loadDatabaseUrl).toBeTruthy() + }); +}) diff --git a/host/ide/test/trace/component/StackBar.test.ts b/host/ide/test/trace/component/StackBar.test.ts new file mode 100644 index 0000000..271c3b5 --- /dev/null +++ b/host/ide/test/trace/component/StackBar.test.ts @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {StackBar} from "../../../dist/trace/component/StackBar.js" + +describe('StackBar Test', () => { + + let stackBar = new StackBar(); + + it('StackBarTest01', function () { + expect(stackBar.initHtml()).not.toBe('') + }); + it('StackBarTest02', function () { + expect(stackBar.initElements()).toBeUndefined() + }); + it('StackBarTest03', function () { + let stateWidth = stackBar.getStateWidth('state'); + let hasWidth = stateWidth > 0; + expect(hasWidth).toBeTruthy(); + }); + it('StackBarTest04', function () { + let htmlDivElement = stackBar.createBarElement({ + state: "", + color: "", + value: 0, + }, 5); + let hasDivEl = htmlDivElement.toLocaleString().length > 5; + expect(hasDivEl).toBeTruthy(); + }); +}) diff --git a/host/ide/test/trace/component/setting/SpAllocations.test.ts b/host/ide/test/trace/component/setting/SpAllocations.test.ts new file mode 100644 index 0000000..b4de0f6 --- /dev/null +++ b/host/ide/test/trace/component/setting/SpAllocations.test.ts @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {SpAllocations} from "../../../../dist/trace/component/setting/SpAllocations.js"; + +describe('SpAllocations Test', ()=>{ + beforeAll(() => { + document.body.innerHTML = ` + + ` + }) + it('new SpAllocations', function () { + expect(new SpAllocations()).not.toBeUndefined(); + }); + + it(' SpAllocations get Default attrValue', function () { + let spEle = document.querySelector("#sp") as SpAllocations + expect(spEle.pid).toEqual(undefined) + expect(spEle.unwind).toEqual(10) + expect(spEle.shared).toEqual(2048) + expect(spEle.filter).toEqual(0) + }); + + it(' SpAllocations set attrValue', function () { + let spEle = document.querySelector("#sp") as SpAllocations + spEle.processId.value ="2" + spEle.unwindEL.value = "111" + spEle.shareMemory.value = "222" + spEle.shareMemoryUnit.value = "MB" + spEle.filterMemory.value = "111" + spEle.filterMemoryUnit.value = "MB" + expect(spEle.pid).toEqual(undefined) + expect(spEle.unwind).toEqual(111) + expect(spEle.shared).toEqual(0) + expect(spEle.filter).toEqual(28416) + }); + + it(' SpAllocations set attrValue', function () { + let spEle = document.querySelector("#sp") as SpAllocations + spEle.processId.value ="3" + spEle.unwindEL.value = "1121" + spEle.shareMemory.value = "222" + spEle.shareMemoryUnit.value = "KB" + spEle.filterMemory.value = "111" + spEle.filterMemoryUnit.value = "KB" + expect(spEle.pid).toEqual(undefined) + expect(spEle.unwind).toEqual(1121) + expect(spEle.shared).toEqual(55) + expect(spEle.filter).toEqual(111) + }); + + it(' SpAllocations set attrValue03', function () { + let spEle = document.querySelector("#sp") as SpAllocations + spEle.processId.value ="3" + spEle.unwindEL.value = "1121" + spEle.shareMemory.value = "222" + spEle.shareMemoryUnit.value = "G" + spEle.filterMemory.value = "111" + spEle.filterMemoryUnit.value = "G" + expect(spEle.pid).toEqual(undefined) + expect(spEle.unwind).toEqual(1121) + expect(spEle.shared).toEqual(0) + expect(spEle.filter).toEqual(111) + }); + // it('CpuStructTest02', function () { + // expect(FpsStruct.equals({}, data)).toBeTruthy(); + // }); +}) \ No newline at end of file diff --git a/host/ide/test/trace/component/setting/SpProbesConfig.test.ts b/host/ide/test/trace/component/setting/SpProbesConfig.test.ts new file mode 100644 index 0000000..c105faf --- /dev/null +++ b/host/ide/test/trace/component/setting/SpProbesConfig.test.ts @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {SpProbesConfig} from "../../../../dist/trace/component/setting/SpProbesConfig.js"; + +describe('SpProbesConfig Test', ()=>{ + beforeAll(() => { + document.body.innerHTML = ` + + ` + }) + it('new SpProbesConfig', function () { + expect(new SpProbesConfig()).not.toBeNull(); + }); + + it(' SpProbesConfig get Default attrValue', function () { + let spEle = document.querySelector("#spconfig") as SpProbesConfig + expect(spEle.traceConfig).toEqual([]) + expect(spEle.traceEvents).toEqual([]) + expect(spEle.memoryConfig).toEqual([]) + }); +}) \ No newline at end of file diff --git a/host/ide/test/trace/component/setting/SpRecordSetting.test.ts b/host/ide/test/trace/component/setting/SpRecordSetting.test.ts new file mode 100644 index 0000000..0832660 --- /dev/null +++ b/host/ide/test/trace/component/setting/SpRecordSetting.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {SpRecordSetting} from "../../../../dist/trace/component/setting/SpRecordSetting.js"; + +describe('SpRecordSetting Test', ()=>{ + beforeAll(() => { + document.body.innerHTML = ` + + ` + }) + it('new SpRecordSetting', function () { + expect(new SpRecordSetting()).not.toBeNull(); + }); + + it(' SpAllocations get Default attrValue', function () { + let spEle = document.querySelector("#setting") as SpRecordSetting + expect(spEle.recordMod).toBeTruthy(); + expect(spEle.bufferSize).toEqual(64) + expect(spEle.maxDur).toEqual(50) + }); + +}) \ No newline at end of file diff --git a/host/ide/test/trace/component/setting/SpTraceCommand.test.ts b/host/ide/test/trace/component/setting/SpTraceCommand.test.ts new file mode 100644 index 0000000..6695556 --- /dev/null +++ b/host/ide/test/trace/component/setting/SpTraceCommand.test.ts @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore + +import {SpTraceCommand} from "../../../../dist/trace/component/setting/SpTraceCommand.js"; + +describe('SPTraceCommand Test', ()=>{ + beforeAll(() => { + document.body.innerHTML = ` + + ` + }) + it('new SPTraceCommand', function () { + expect(new SpTraceCommand()).not.toBeNull(); + }); + + it(' SpAllocations get Default attrValue', function () { + let spEle = document.querySelector("#command") as SpTraceCommand + expect(spEle.hdcCommon).toEqual(""); + }); + + it(' SpAllocations set attrValue', function () { + let spEle = document.querySelector("#command") as SpTraceCommand + spEle.hdcCommon = "aaaaaaaaaa" + expect(spEle.hdcCommon).toEqual("aaaaaaaaaa"); + }); + +}) \ No newline at end of file diff --git a/host/ide/test/trace/component/setting/utils/PluginConvertUtils.test.ts b/host/ide/test/trace/component/setting/utils/PluginConvertUtils.test.ts new file mode 100644 index 0000000..1a3a73a --- /dev/null +++ b/host/ide/test/trace/component/setting/utils/PluginConvertUtils.test.ts @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {PluginConvertUtils} from "../../../../../dist/trace/component/setting/utils/PluginConvertUtils.js"; +// @ts-ignore +import {ProfilerSessionConfigMode} from "../../../../../dist/trace/component/setting/bean/ProfilerServiceTypes"; + +import { + ProfilerSessionConfigBufferConfig, + ProfilerSessionConfigBufferConfigPolicy, TracePluginConfig + // @ts-ignore +} from "../../../../../dist/trace/component/setting/bean/ProfilerServiceTypes.js"; +import { + HilogConfig, + levelFromJSON, + Type + // @ts-ignore +} from "../../../../../dist/trace/component/setting/bean/ProfilerServiceTypes.js"; +import { + MemoryConfig, + sysMeminfoTypeFromJSON, sysVMeminfoTypeFromJSON + // @ts-ignore +} from "../../../../../dist/trace/component/setting/bean/ProfilerServiceTypes.js"; +// @ts-ignore +import {SpRecordTrace} from "../../../../../dist/trace/component/SpRecordTrace.js"; + +describe('PlugConvertUtils Test', ()=>{ + let bufferConfig: ProfilerSessionConfigBufferConfig = { + pages: 1000, + policy: ProfilerSessionConfigBufferConfigPolicy.RECYCLE + } + let sessionConfig = { + buffers: [bufferConfig], + sessionMode: ProfilerSessionConfigMode.OFFLINE, + resultFile: "/data/local/tmp/hiprofiler_data.htrace", + resultMaxSize: 0, + sampleDuration: 1000, + keepAliveTime: 0 + }; + let tracePluginConfig: TracePluginConfig = { + ftraceEvents: [], + bytraceCategories: [], + bytraceApps: [], + bufferSizeKb: 1024, + flushIntervalMs: 1000, + flushThresholdKb: 4096, + parseKsyms: true, + clock: "mono", + tracePeriodMs: 200, + rawDataPrefix: "", + traceDurationMs: 0, + debugOn: false, + } + let hilogConfig: HilogConfig = { + deviceType: Type.HI3516, + logLevel: levelFromJSON("Info"), + needClear: true + } + let memoryconfig: MemoryConfig = { + reportProcessTree: true, + reportSysmemMemInfo: true, + sysMeminfoCounters: [], + reportSysmemVmemInfo: true, + sysVmeminfoCounters: [], + reportProcessMemInfo: true, + reportAppMemInfo: false, + reportAppMemByMemoryService: false, + pid: [] + } + + SpRecordTrace.MEM_INFO.forEach((va: any) => { + memoryconfig.sysMeminfoCounters.push(sysMeminfoTypeFromJSON(va)); + }) + SpRecordTrace.VMEM_INFO.forEach(((me: any) => { + memoryconfig.sysVmeminfoCounters.push(sysVMeminfoTypeFromJSON(me)) + })) + SpRecordTrace.VMEM_INFO_SECOND.forEach(((me: any) => { + memoryconfig.sysVmeminfoCounters.push(sysVMeminfoTypeFromJSON(me)) + })) + SpRecordTrace.VMEM_INFO_THIRD.forEach(((me: any) => { + memoryconfig.sysVmeminfoCounters.push(sysVMeminfoTypeFromJSON(me)) + })) + + let request = { + requestId: 1, + sessionConfig: sessionConfig, + pluginConfigs: [tracePluginConfig, hilogConfig,memoryconfig] + }; + + it('PlugConvertUtils01', function () { + expect(PluginConvertUtils.createHdcCmd("aaaa", 11)).not.toBeNull() + }); + + it('PlugConvertUtils02', function () { + expect(PluginConvertUtils.BeanToCmdTxt(request, true)).not.toBeNull() + }); + + it('PlugConvertUtils03', function () { + expect(PluginConvertUtils.BeanToCmdTxt(request, false)).not.toBeNull() + }); + +}) \ No newline at end of file diff --git a/host/ide/test/trace/component/trace/TimerShaftElement.test.ts b/host/ide/test/trace/component/trace/TimerShaftElement.test.ts new file mode 100644 index 0000000..d4f37f4 --- /dev/null +++ b/host/ide/test/trace/component/trace/TimerShaftElement.test.ts @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TimerShaftElement} from "../../../../dist/trace/component/trace/TimerShaftElement.js" + + +describe('TimerShaftElement Test', () => { + let timerShaftElement = new TimerShaftElement(); + + timerShaftElement.cpuUsage = 'cpuUsage' + + it('TimerShaftElementTest01', function () { + timerShaftElement.rangeRuler = jest.fn(() => true) + timerShaftElement.rangeRuler.cpuUsage = jest.fn(() => true) + expect(timerShaftElement.cpuUsage).toBeUndefined(); + }); + + it('TimerShaftElementTest02', function () { + timerShaftElement.rangeRuler = jest.fn(() => false) + timerShaftElement.loadComplete = jest.fn(() => false) + timerShaftElement.rangeRuler.markA = jest.fn(() => true) + timerShaftElement.rangeRuler.markB = jest.fn(() => true) + timerShaftElement.rangeRuler.markA.frame = jest.fn(() => true) + timerShaftElement.rangeRuler.markB.frame = jest.fn(() => true) + timerShaftElement.rangeRuler.markA.frame.x = jest.fn(() => 0) + timerShaftElement.rangeRuler.markB.frame.x = jest.fn(() => true) + timerShaftElement.rangeRuler.frame = jest.fn(() => true) + timerShaftElement.rangeRuler.frame.width = jest.fn(() => 8) + timerShaftElement.rangeRuler.cpuUsage = jest.fn(() => []) + timerShaftElement.sportRuler = jest.fn(() => true) + timerShaftElement.sportRuler.flagList = jest.fn(() => false) + timerShaftElement.sportRuler.flagList.length = jest.fn(() =>0) + timerShaftElement.totalNS =jest.fn(()=>false); + timerShaftElement.sportRuler.isRangeSelect = jest.fn(() => false) + expect(timerShaftElement.reset()).toBeUndefined(); + }); + + it('spApplicationTest10',function (){ + expect(timerShaftElement.reset()).not.toBeUndefined() + }); + + + it('TimerShaftElementTest03', function () { + timerShaftElement.timeRuler = jest.fn(() => false) + timerShaftElement.sportRuler = jest.fn(() => false) + timerShaftElement.rangeRuler = jest.fn(() => false) + timerShaftElement.timeRuler.frame = jest.fn(() => { + return document.createElement('canvas') as HTMLCanvasElement + }) + + timerShaftElement.sportRuler.frame = jest.fn(() => { + return document.createElement('canvas') as HTMLCanvasElement + }) + + timerShaftElement.rangeRuler.frame = jest.fn(() => { + return document.createElement('canvas') as HTMLCanvasElement + }) + expect(timerShaftElement.connectedCallback()).toBeUndefined(); + }); + + it('TimerShaftElementTest04', function () { + timerShaftElement.canvas = jest.fn(()=> { + return { + width: 20, + height: 20, + style: { + width: 30, + height: 30, + } + } + }) + timerShaftElement.canvas.style = jest.fn(() => true) + timerShaftElement.rangeRuler.fillX = jest.fn(() => true) + timerShaftElement.timeRuler.draw = jest.fn(() => true) + timerShaftElement.rangeRuler.draw = jest.fn(() => true) + timerShaftElement.sportRuler.draw = jest.fn(() => true) + expect(timerShaftElement.updateWidth(2)).toBeUndefined(); + }); + + it('TimerShaftElementTest05', function () { + expect(timerShaftElement.disconnectedCallback()).toBeUndefined(); + }); + + it('TimerShaftElementTest06', function () { + expect(timerShaftElement.totalNS).toBe(10000000000); + }); + it('TimerShaftElementTest10', function () { + timerShaftElement.totalNS = 10000000000; + expect(timerShaftElement.totalNS).toBe(10000000000); + }); + + it('TimerShaftElementTest07', function () { + // timerShaftElement._sportRuler.modifyFlagList = jest.fn(() => true) + expect(timerShaftElement.modifyFlagList()).toBeUndefined(); + }); + + it('TimerShaftElementTest08', function () { + timerShaftElement.startNS = 'startNS' + expect(timerShaftElement.startNS).toBe('startNS'); + }); + + it('TimerShaftElementTest09', function () { + timerShaftElement.endNS = 'endNS' + expect(timerShaftElement.endNS).toBe('endNS'); + }); + + it('TimerShaftElementTest11', function () { + expect(timerShaftElement.render()).toBe(undefined); + }); + + it('TimerShaftElementTest12', function () { + timerShaftElement.ctx = jest.fn(()=>true) + timerShaftElement.ctx.fillStyle = jest.fn(()=>'transparent') + timerShaftElement.ctx.fillRect = jest.fn(()=>true) + expect(timerShaftElement.render()).toBe(undefined); + }); +}) diff --git a/host/ide/test/trace/component/trace/base/ColorUtils.test.ts b/host/ide/test/trace/component/trace/base/ColorUtils.test.ts new file mode 100644 index 0000000..5b49ac2 --- /dev/null +++ b/host/ide/test/trace/component/trace/base/ColorUtils.test.ts @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {ColorUtils} from "../../../../../dist/trace/component/trace/base/ColorUtils.js"; + +describe("testColorUtils Test", () => { + beforeAll(() => { + }) + it('testColorUtils01', () => { + expect(ColorUtils.hash("mm",ColorUtils.MD_PALETTE.length)).toBe(6); + }); + it('testColorUtils02', () => { + // @ts-ignore + expect(ColorUtils.colorForThread(null)).toEqual("#f0f0f0"); + }); + + it('testColorUtils03', () => { + // @ts-ignore + let thread = {processId:1} + expect(ColorUtils.colorForThread(thread)).toEqual("#fbbf00"); + }); + + it('testColorUtils03', () => { + // @ts-ignore + let thread = { + processId:0, + tid:1 + } + expect(ColorUtils.colorForThread(thread)).toEqual("#fbbf00"); + }); + + it('testColorUtils04', () => { + expect(ColorUtils.formatNumberComma(2)).toEqual("2"); + }); + + afterAll(() => { + }) +}) + + diff --git a/host/ide/test/trace/component/trace/base/RangeSelect.test.ts b/host/ide/test/trace/component/trace/base/RangeSelect.test.ts new file mode 100644 index 0000000..2e84cc0 --- /dev/null +++ b/host/ide/test/trace/component/trace/base/RangeSelect.test.ts @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore + + +import {RangeSelect} from "../../../../../dist/trace/component/trace/base/RangeSelect.js"; + +describe("RangeSelect Test", () => { + beforeAll(() => { + }) + + it('Utils Test01', () => { + let rangeSelect = new RangeSelect(); + expect(rangeSelect).not.toBeUndefined(); + }); + + it('Utils Test02', () => { + let rangeSelect = new RangeSelect(); + rangeSelect.rowsEL= document.createElement('div') + let mouseEvent = new MouseEvent("mousedown",{ + button: 0, + buttons: 0, + clientX: 2, + clientY: 100, + screenX: 255, + screenY: 325, + }); + rangeSelect.timerShaftDragEL = jest.fn(()=>true) + rangeSelect.timerShaftDragEL.timerShaftDragEL = jest.fn(()=>true) + expect(rangeSelect.isInRowsEl(mouseEvent)).toBeFalsy(); + }); + it('Utils Test09', () => { + let rangeSelect = new RangeSelect(); + rangeSelect.rowsEL= document.createElement('div') + let mouseEvent = new MouseEvent("mousedown",{ + button: 0, + buttons: 0, + clientX: 2, + clientY: 100, + screenX: 255, + screenY: 325, + }); + rangeSelect.spacerEL = jest.fn(()=>true) + rangeSelect.spacerEL.offsetTop = jest.fn(()=>1) + expect(rangeSelect.isInSpacerEL(mouseEvent)).toBeFalsy(); + }); + + it('Utils Test05', () => { + let rangeSelect = new RangeSelect(); + rangeSelect.isInRowsEl = jest.fn(()=>true) + rangeSelect.rowsEL= { + // offsetTop: 100, + offsetHeight:200, + offsetLeft:0, + offsetWidth:100 + } + let mouseEvent = new MouseEvent("mousedown",{ + // @ts-ignore + offsetY:1, + offsetX:1, + button: 0, + buttons: 0, + clientX: 2, + clientY: 100, + screenX: 255, + screenY: 325 + }); + rangeSelect.spacerEL = jest.fn(()=>true) + rangeSelect.spacerEL.offsetTop = jest.fn(()=>true) + expect(rangeSelect.mouseDown(mouseEvent)).toBeUndefined() + }); + + it('Utils Test07', () => { + let rangeSelect = new RangeSelect(); + rangeSelect.isInRowsEl = jest.fn(()=>true) + rangeSelect.isDrag = jest.fn(()=>true) + + rangeSelect.rowsEL= { + offsetTop: 100, + offsetHeight:200, + offsetLeft:0, + offsetWidth:100 + } + let mouseEvent = new MouseEvent("mousedown",{ + // @ts-ignore + offsetY:1, + offsetX:1, + button: 0, + buttons: 0, + clientX: 2, + clientY: 100, + screenX: 255, + screenY: 325 + }); + rangeSelect.spacerEL = jest.fn(()=>true) + rangeSelect.spacerEL.offsetTop = jest.fn(()=>1) + expect(rangeSelect.mouseUp(mouseEvent)).toBeUndefined(); + }); + + it('Utils Test08', () => { + let rangeSelect = new RangeSelect(); + rangeSelect.isInRowsEl = jest.fn(()=>true) + rangeSelect.isDrag = jest.fn(()=>true) + rangeSelect.isMouseDown = true + let rowsEL= [{ + frame: { + x:1, + width:10, + y:2, + height:10 + }, + offsetTop: 100, + offsetHeight:200, + offsetLeft:0, + offsetWidth:100, + }] + rangeSelect.rowsEL = rowsEL; + let mouseEvent = new MouseEvent("mousedown",{ + // @ts-ignore + offsetY:1, + offsetX:1, + button: 0, + buttons: 0, + clientX: 2, + clientY: 100, + screenX: 255, + screenY: 325 + }); + rangeSelect.timerShaftDragEL = jest.fn(()=>true) + rangeSelect.timerShaftEL= jest.fn(()=>true) + rangeSelect.timerShaftEL.sportRuler = jest.fn(()=>true) + rangeSelect.timerShaftEL.sportRuler.isRangeSelect = jest.fn(()=>true) + rangeSelect.timerShaftEL.sportRuler.draw = jest.fn(()=>true) + rangeSelect.timerShaftDragEL.timerShaftDragEL = jest.fn(()=>0) + rangeSelect.spacerEL = jest.fn(()=>true) + rangeSelect.spacerEL.offsetTop = jest.fn(()=>1) + expect(rangeSelect.mouseMove(rowsEL,mouseEvent)).toBeUndefined(); + }); + + it('Utils Test10', () => { + let rangeSelect = new RangeSelect(); + rangeSelect.isInRowsEl = jest.fn(()=>true) + rangeSelect.isDrag = jest.fn(()=>true) + + rangeSelect.rowsEL= { + offsetTop: 100, + offsetHeight:200, + offsetLeft:0, + offsetWidth:100 + } + let mouseEvent = new MouseEvent("mousedown",{ + // @ts-ignore + offsetY:1, + offsetX:1, + button: 0, + buttons: 0, + clientX: 2, + clientY: 100, + screenX: 255, + screenY: 325 + }); + rangeSelect.timerShaftDragEL = jest.fn(()=>true) + rangeSelect.timerShaftDragEL.timerShaftDragEL = jest.fn(()=>0) + expect(rangeSelect.isTouchMark(mouseEvent)).toBeFalsy(); + }); +}) \ No newline at end of file diff --git a/host/ide/test/trace/component/trace/base/TraceRow.test.ts b/host/ide/test/trace/component/trace/base/TraceRow.test.ts new file mode 100644 index 0000000..da8ecac --- /dev/null +++ b/host/ide/test/trace/component/trace/base/TraceRow.test.ts @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TraceRow} from "../../../../../dist/trace/component/trace/base/TraceRow.js"; +describe("TraceRow Test", () => { + + beforeAll(() => { + }) + it('TraceRow Test01', () => { + let traceRow = new TraceRow(); + expect(traceRow).not.toBeUndefined(); + }); + + it('TraceRow Test02', () => { + let traceRow = new TraceRow(); + expect(traceRow.sleeping).toBeFalsy(); + }); + + it('TraceRow Test03', () => { + let traceRow = new TraceRow(); + traceRow.sleeping = true + expect(traceRow.sleeping).toBeTruthy(); + }); + + it('TraceRow Test04', () => { + let traceRow = new TraceRow(); + traceRow.sleeping = false + expect(traceRow.sleeping).toBeFalsy(); + }); + + it('TraceRow Test05', () => { + let traceRow = new TraceRow(); + expect(traceRow.rangeSelect).toBeFalsy(); + }); + + it('TraceRow Test06', () => { + let traceRow = new TraceRow(); + traceRow.rangeSelect = true + expect(traceRow.rangeSelect).toBeTruthy(); + }); + it('TraceRow Test07', () => { + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + let traceRow = new TraceRow(); + //transferControlToOffscreen() + traceRow.canvas = jest.fn(()=>true) + traceRow.args = jest.fn(()=>true) + traceRow.args.isOffScreen = jest.fn(()=>true) + // @ts-ignore + traceRow.canvas.transferControlToOffscreen = jest.fn(()=>true) + traceRow.dataList = { + supplier:true, + isLoading:false, + } + traceRow.args={ + isOffScreen:true, + } + traceRow.supplier = true; + traceRow.isLoading = false; + traceRow.name = "111" + traceRow.height = 20 + traceRow.height = 30 + expect(traceRow.initCanvas()).toBeUndefined(); + }); + + it('TraceRow Test08', () => { + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + let traceRow = new TraceRow(); + traceRow.dataList = { + supplier:true, + isLoading:false, + } + traceRow.supplier = true; + traceRow.isLoading = false; + traceRow.name = "111" + traceRow.height = 20 + traceRow.height = 30 + expect(traceRow.drawObject()).toBeUndefined(); + }); + + it('TraceRow Test09', () => { + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + let traceRow = new TraceRow(); + traceRow.dataList = { + supplier:true, + isLoading:false, + } + traceRow.supplier = true; + traceRow.isLoading = false; + traceRow.name = "111" + traceRow.height = 20 + traceRow.height = 30 + expect(traceRow.drawObject()).toBeUndefined(); + }); + + it('TraceRow Test10', () => { + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + let traceRow = new TraceRow(); + traceRow.dataList = { + supplier:true, + isLoading:false, + } + traceRow.supplier = true; + traceRow.isLoading = false; + traceRow.name = "111" + traceRow.height = 20 + traceRow.height = 30 + expect(traceRow.clearCanvas()).toBeUndefined(); + }); + + it('TraceRow Test11', () => { + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + let traceRow = new TraceRow(); + traceRow.dataList = { + supplier:true, + isLoading:false, + } + traceRow.supplier = true; + traceRow.isLoading = false; + traceRow.name = "111" + traceRow.height = 20 + traceRow.height = 30 + expect(traceRow.drawLines()).toBeUndefined(); + }); + + it('TraceRow Test12', () => { + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + let traceRow = new TraceRow(); + traceRow.dataList = { + supplier:true, + isLoading:false, + } + traceRow.supplier = true; + traceRow.isLoading = false; + traceRow.name = "111" + traceRow.height = 20 + traceRow.height = 30 + expect(traceRow.drawSelection()).toBeUndefined(); + }); + + it('TraceRow Test13', () => { + let traceRow = new TraceRow(); + expect(traceRow.collect).toBeFalsy(); + }); + + it('TraceRow Test14', () => { + let traceRow = new TraceRow(); + traceRow.collect = true; + expect(traceRow.collect).toBeTruthy(); + }); + + it('TraceRow Test15', () => { + let traceRow = new TraceRow(); + expect(traceRow.rowType).toBeFalsy(); + }); + + it('TraceRow Test16', () => { + let traceRow = new TraceRow(); + traceRow.rowType = true; + expect(traceRow.rowType).toBeTruthy(); + }); + + it('TraceRow Test17', () => { + let traceRow = new TraceRow(); + expect(traceRow.rowId).toBeFalsy(); + }); + + it('TraceRow Test18', () => { + let traceRow = new TraceRow(); + traceRow.rowId = true; + expect(traceRow.rowId).toBeTruthy(); + }); + + it('TraceRow Test19', () => { + let traceRow = new TraceRow(); + expect(traceRow.rowParentId).toBeFalsy(); + }); + + it('TraceRow Test20', () => { + let traceRow = new TraceRow(); + traceRow.rowParentId = true; + expect(traceRow.rowParentId).toBeTruthy(); + }); + + it('TraceRow Test21', () => { + let traceRow = new TraceRow(); + traceRow.rowHidden = true; + expect(traceRow.rowHidden).toBeUndefined(); + }); + + it('TraceRow Test22', () => { + let traceRow = new TraceRow(); + expect(traceRow.name).toBeFalsy(); + }); + + it('TraceRow Test23', () => { + let traceRow = new TraceRow(); + expect(traceRow.folder).toBeFalsy(); + }); + + it('TraceRow Test24', () => { + let traceRow = new TraceRow(); + traceRow.folder = true; + expect(traceRow.folder).toBeTruthy(); + }); + + it('TraceRow Test25', () => { + let traceRow = new TraceRow(); + expect(traceRow.expansion).toBeFalsy(); + }); + + it('TraceRow Test26', () => { + let traceRow = new TraceRow(); + traceRow.expansion = true; + expect(traceRow.expansion).toBeTruthy(); + }); + + it('TraceRow Test27', () => { + let traceRow = new TraceRow(); + traceRow.tip = true; + expect(traceRow.tip).toBeUndefined(); + }); + + it('TraceRow Test28', () => { + let traceRow = new TraceRow(); + expect(traceRow.frame).not.toBeUndefined(); + }); + + it('TraceRow Test29', () => { + let traceRow = new TraceRow(); + traceRow.frame = [0,0,0]; + expect(traceRow.frame).toBeTruthy(); + }); + + it('TraceRow Test30', () => { + let traceRow = new TraceRow(); + expect(traceRow.checkType).not.toBeUndefined(); + }); + + it('TraceRow Test31', () => { + let traceRow = new TraceRow(); + traceRow.checkType = true; + expect(traceRow.checkType).toBeTruthy(); + }); + + it('TraceRow Test32', () => { + let traceRow = new TraceRow(); + expect(traceRow.drawType).toBeUndefined(); + }); + + it('TraceRow Test33', () => { + let traceRow = new TraceRow(); + traceRow.drawType = true; + expect(traceRow.drawType).toBeTruthy(); + }); + + it('TraceRow Test34', () => { + let traceRow = new TraceRow(); + traceRow.args = jest.fn(()=>true) + traceRow.args.isOffScreen = jest.fn(()=>null) + expect(traceRow.updateWidth(1)).toBeUndefined(); + }); + + it('TraceRow Test35', () => { + let traceRow = new TraceRow(); + expect(traceRow.setCheckBox()).toBeUndefined(); + }); + + it('TraceRow Test36', () => { + let traceRow = new TraceRow(); + expect(traceRow.onMouseHover()).toBeFalsy(); + }); + + it('TraceRow Test37', () => { + let traceRow = new TraceRow(); + expect(traceRow.setTipLeft(1,null)).toBeFalsy(); + }); + + it('TraceRow Test38', () => { + let traceRow = new TraceRow(); + expect(traceRow.onMouseLeave(1,1)).toBeFalsy(); + }); + + it('TraceRow Test39', () => { + let traceRow = new TraceRow(); + expect(traceRow.draw(false)).toBeFalsy(); + }); + + +}) \ No newline at end of file diff --git a/host/ide/test/trace/component/trace/base/TraceRowObject.test.ts b/host/ide/test/trace/component/trace/base/TraceRowObject.test.ts new file mode 100644 index 0000000..5014d62 --- /dev/null +++ b/host/ide/test/trace/component/trace/base/TraceRowObject.test.ts @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TraceRowObject} from "../../../../../dist/trace/component/trace/base/TraceRowObject.js"; + +describe("TraceRow Test", () => { + beforeAll(() => { + }) + + it('Utils Test01', () => { + let traceRow = new TraceRowObject(); + expect(traceRow) + }); + +}) \ No newline at end of file diff --git a/host/ide/test/trace/component/trace/base/TraceRowRecyclerView.test.ts b/host/ide/test/trace/component/trace/base/TraceRowRecyclerView.test.ts new file mode 100644 index 0000000..6aa8c61 --- /dev/null +++ b/host/ide/test/trace/component/trace/base/TraceRowRecyclerView.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TraceRowRecyclerView} from "../../../../../dist/trace/component/trace/base/TraceRowRecyclerView.js"; + +describe("TraceRow Test", () => { + beforeAll(() => { + }) + + it('Utils Test01', () => { + let traceRow = new TraceRowRecyclerView(); + expect(traceRow) + }); + + it('Test02', function () { + let traceRow = new TraceRowRecyclerView(); + expect(traceRow.dataSource).toBeTruthy(); + }); + + it('Test03', function () { + let traceRow = new TraceRowRecyclerView(); + traceRow.dataSource=false + expect(traceRow.dataSource).toBeTruthy(); + }); + + it('Test04', function () { + let traceRow = new TraceRowRecyclerView(); + expect(traceRow.renderType).toBeTruthy(); + }); + + it('Test05', function () { + let traceRow = new TraceRowRecyclerView(); + traceRow.renderType=false + expect(traceRow.renderType).toBeFalsy(); + }); + + it('Test06', function () { + let traceRow = new TraceRowRecyclerView(); + expect(traceRow.refreshRow()).toBeUndefined(); + }); + + it('Test07', function () { + let traceRow = new TraceRowRecyclerView(); + traceRow.dataSource = jest.fn(()=>true) + traceRow.dataSource.filter = jest.fn(()=>true) + expect(traceRow.measureHeight()).toBeUndefined(); + }); + + it('Test08', function () { + let traceRow = new TraceRowRecyclerView(); + expect(traceRow.initUI()).toBeUndefined(); + }); + it('Test09', function () { + let traceRow = new TraceRowRecyclerView(); + expect(traceRow.initUI()).toBeUndefined(); + }); +}) \ No newline at end of file diff --git a/host/ide/test/trace/component/trace/base/TraceSheet.test.ts b/host/ide/test/trace/component/trace/base/TraceSheet.test.ts new file mode 100644 index 0000000..39ef4ca --- /dev/null +++ b/host/ide/test/trace/component/trace/base/TraceSheet.test.ts @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TraceSheet} from "../../../../../dist/trace/component/trace/base/TraceSheet.js"; +window.ResizeObserver = window.ResizeObserver || + jest.fn().mockImplementation(() => ({ + disconnect: jest.fn(), + observe: jest.fn(), + unobserve: jest.fn(), + })); + +describe("TraceSheet Test", () => { + beforeAll(() => { + }) +let val=[{ + cpus:{length:1,}, + threadIds:{length: 2}, + funTids:{length: 2}, + trackIds:{length: 2}, + hasFps:0, + heapIds:{length: 0}, + nativeMemory:{length: 1} +}] + it('TraceSheet Test01', () => { + let traceRow = new TraceSheet(); + expect(traceRow).not.toBeUndefined() + }); + + it('TraceSheet Test02', () => { + let traceRow = new TraceSheet(); + expect(traceRow.recoveryBoxSelection).not.toBeUndefined() + }); + + + + it('TraceSheet Test03', () => { + let traceRow = new TraceSheet(); + expect(traceRow.hideBoxTab()).toBeUndefined() + }); + + /* it('TraceSheet Test04', () => { + let traceRow = new TraceSheet(); + expect(traceRow.hideOtherBoxTab("11")).not.toBeUndefined() + }); + + + it('TraceSheet Test05', () => { + let traceRow = new TraceSheet(); + expect(traceRow.hideOtherBoxTab("12")).not.toBeUndefined() + }); + + + it('TraceSheet Test06', () => { + let traceRow = new TraceSheet(); + expect(traceRow.hideOtherBoxTab("13")).not.toBeUndefined() + }); + + it('TraceSheet Test07', () => { + let traceRow = new TraceSheet(); + expect(traceRow.hideOtherBoxTab("14")).not.toBeUndefined() + });*/ + + it('TraceSheet Test08', () => { + let traceRow = new TraceSheet(); + expect(traceRow.connectedCallback()).toBeUndefined() + }); + it('TraceSheet Test09', () => { + let traceRow = new TraceSheet(); + expect(traceRow.loadTabPaneData()).toBeUndefined() + }); + + it('TraceSheet Test10', () => { + let traceRow = new TraceSheet(); + expect(traceRow.clear()).toBeUndefined() + }); + + it('TraceSheet Test11', () => { + let traceRow = new TraceSheet(); + expect(traceRow.boxSelection(val)).toBeUndefined() + }); +}) \ No newline at end of file diff --git a/host/ide/test/trace/component/trace/base/Utils.test.ts b/host/ide/test/trace/component/trace/base/Utils.test.ts new file mode 100644 index 0000000..0b70b4d --- /dev/null +++ b/host/ide/test/trace/component/trace/base/Utils.test.ts @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {Utils} from "../../../../../dist/trace/component/trace/base/Utils.js"; + +describe("Utils Test", () => { + beforeAll(() => { + }) + + it('Utils Test01', () => { + let instance = Utils.getInstance(); + let instance2 = Utils.getInstance(); + expect(instance).toBe(instance2) + }); + + it('Utils Test02', () => { + let instance = Utils.getInstance(); + expect(instance.getStatusMap().get("D")).toBe("Uninterruptible Sleep") + }); + + it('Utils Test03', () => { + expect(Utils.getEndState("D")).toBe("Uninterruptible Sleep") + }); + + it('Utils Test04', () => { + expect(Utils.getEndState("")).toBe("") + }); + + it('Utils Test05', () => { + expect(Utils.getEndState("ggg")).toBe("Unknown State") + }); + + it('Utils Test06', () => { + expect(Utils.getStateColor("D")).toBe("#f19b38") + }); + + it('Utils Test07', () => { + expect(Utils.getStateColor("R")).toBe("#a0b84d") + }); + it('Utils Test08', () => { + expect(Utils.getStateColor("I")).toBe("#673ab7") + }); + + it('Utils Test09', () => { + expect(Utils.getStateColor("Running")).toBe("#467b3b") + }); + + it('Utils Test09', () => { + expect(Utils.getStateColor("S")).toBe("#e0e0e0") + }); + + + it('Utils Test10', () => { + expect(Utils.getTimeString(5900_000_000_000)).toBe("1h 38m ") + }); + + it('Utils Test11', () => { + expect(Utils.getByteWithUnit(1_000_000_001)).toBe("1.00 Gb") + }); + + it('Utils Test12', () => { + expect(Utils.getByteWithUnit(1_000_000_000)).toBe("1000.00 Mb") + }); + + it('Utils Test14', () => { + expect(Utils.getByteWithUnit(1000_000)).toBe("1000.00 kb") + }); + + it('Utils Test13', () => { + expect(Utils.getTimeStringHMS(5900_000_000_000)).toBe("1:38:") + }); + + + afterAll(() => { + // 后处理操作 + }) +}) \ No newline at end of file diff --git a/host/ide/test/trace/component/trace/sheet/TabPaneBoxChild.test.ts b/host/ide/test/trace/component/trace/sheet/TabPaneBoxChild.test.ts new file mode 100644 index 0000000..8304782 --- /dev/null +++ b/host/ide/test/trace/component/trace/sheet/TabPaneBoxChild.test.ts @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +// import { it } from "mocha" +import {TabPaneBoxChild} from "../../../../../dist/trace/component/trace/sheet/TabPaneBoxChild.js" +window.ResizeObserver = window.ResizeObserver || + jest.fn().mockImplementation(() => ({ + disconnect: jest.fn(), + observe: jest.fn(), + unobserve: jest.fn(), + + })); +describe('TabPaneBoxChild Test', () => { + let tabPaneBoxChild = new TabPaneBoxChild(); + tabPaneBoxChild.parentElement= jest.fn(()=> { + return {clientHeight:56} + }) + tabPaneBoxChild.parentElement.clientHeight= jest.fn(()=>100) + tabPaneBoxChild.data = { + cpus: [], + threadIds: [], + trackIds: [], + funTids: [], + heapIds:[], + leftNs: 0, + rightNs: 0, + hasFps: false, + // parentElement:{ + // clientHeight:0, + // } + } + // tabPaneBoxChild.parentElement = { + // clientHeight:0, + // } + + it('TabPaneBoxChildTest01', function () { + + expect(tabPaneBoxChild.sortByColumn({ + key: 'name', + sort: () => { + } + })).toBeUndefined(); + }); + // it('TabPaneBoxChildTest02',function(){ + // TabPaneBoxChild.parentElement= jest.fn(()=> { + // return {clientHeight:56} + // }) + // // TabPaneBoxChild.parentElement.clientHeight= jest.fn(()=>100) + // expect(tabPaneBoxChild.data).toBeUndefined(); + // }) + +}) diff --git a/host/ide/test/trace/component/trace/sheet/TabPaneContextSwitch.test.ts b/host/ide/test/trace/component/trace/sheet/TabPaneContextSwitch.test.ts new file mode 100644 index 0000000..3d3b8e7 --- /dev/null +++ b/host/ide/test/trace/component/trace/sheet/TabPaneContextSwitch.test.ts @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TabPaneContextSwitch} from "../../../../../dist/trace/component/trace/sheet/TabPaneContextSwitch.js" +window.ResizeObserver = window.ResizeObserver || + jest.fn().mockImplementation(() => ({ + disconnect: jest.fn(), + observe: jest.fn(), + unobserve: jest.fn(), + })); +describe('TabPaneContextSwitch Test', () => { + let tabPaneContextSwitch = new TabPaneContextSwitch(); + let dataArray = [{ + id: "", + pid: "", + title: "", + children: [], + process: "", + processId: 0, + thread: "", + threadId: 0, + state: "", + wallDuration: 0, + avgDuration: "", + count: 0, + minDuration: 0, + maxDuration: 0, + stdDuration: "", + }] + + it('TabPaneContextSwitchTest01', function () { + let result = tabPaneContextSwitch.groupByProcessThreadToMap(dataArray); + expect(result.get(0).length).toBeUndefined(); + }); + + it('TabPaneContextSwitchTest02', function () { + let result = tabPaneContextSwitch.groupByProcessToMap(dataArray) + expect(result.get(0).length).toBe(1); + }); + + it('TabPaneContextSwitchTest03', function () { + let result = tabPaneContextSwitch.groupByThreadToMap(dataArray) + expect(result.get(0).length).toBe(1); + }); +}) diff --git a/host/ide/test/trace/component/trace/sheet/TabPaneCounter.test.ts b/host/ide/test/trace/component/trace/sheet/TabPaneCounter.test.ts new file mode 100644 index 0000000..d720649 --- /dev/null +++ b/host/ide/test/trace/component/trace/sheet/TabPaneCounter.test.ts @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TabPaneCounter} from "../../../../../dist/trace/component/trace/sheet/TabPaneCounter.js" + +const sqlit = require("../../../../../dist/trace/database/SqlLite.js") +jest.mock("../../../../../dist/trace/database/SqlLite.js"); +describe('TabPaneCounter Test', () => { + let tabPaneCounter = new TabPaneCounter(); + it('TabPaneCounterTest01', function () { + expect(tabPaneCounter.groupByTrackIdToMap([{ + id: 0, + trackId: 0, + name: "", + value: 0, + startTime: 0, + }])) + }); + + it('TabPaneCounterTest02', function () { + expect(tabPaneCounter.createSelectCounterData([{ + id: 0, + trackId: 0, + name: "", + value: 0, + startTime: 0, + }], 0, 1)) + }); + + it('TabPaneCounterTest03', function () { + expect(tabPaneCounter.sortByColumn({ + key: 'name', + sort: () => { + } + })) + }); + + + it('TabPaneCounterTest04', function () { + let mockgetTabCounters = sqlit.getTabCounters + mockgetTabCounters.mockResolvedValue( + {trackId : 11, + name: "test", + value:111, + startTime:142445, + },{trackId : 11, + name: "test", + value:222, + startTime:142446, + }) + let a = {rightNs: 1, trackIds: [11, 12, 13]} + expect(tabPaneCounter.data = a).toBeTruthy(); + }); + + + it('TabPaneCounterTest05', function () { + let mockgetTabCounters = sqlit.getTabCounters + mockgetTabCounters.mockResolvedValue([] + ) + let a = {rightNs: 1, trackIds: [11, 12, 13]} + expect(tabPaneCounter.data = a).toBeTruthy(); + }); +}) diff --git a/host/ide/test/trace/component/trace/sheet/TabPaneCpu.test.ts b/host/ide/test/trace/component/trace/sheet/TabPaneCpu.test.ts new file mode 100644 index 0000000..fed58e8 --- /dev/null +++ b/host/ide/test/trace/component/trace/sheet/TabPaneCpu.test.ts @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TabPaneCpu} from "../../../../../dist/trace/component/trace/sheet/TabPaneCpu.js" + +describe('TabPaneCpu Test', ()=>{ + + let tabPaneCpu = new TabPaneCpu(); + + it('TabPaneCpuTest01', function () { + expect(tabPaneCpu.initHtml()).not.toBe('') + }); + it('TabPaneCpuTest02', function () { + expect(tabPaneCpu.initElements()).toBeUndefined() + }); +}) \ No newline at end of file diff --git a/host/ide/test/trace/component/trace/sheet/TabPaneCpuByProcess.test.ts b/host/ide/test/trace/component/trace/sheet/TabPaneCpuByProcess.test.ts new file mode 100644 index 0000000..94fb290 --- /dev/null +++ b/host/ide/test/trace/component/trace/sheet/TabPaneCpuByProcess.test.ts @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TabPaneCpuByProcess} from "../../../../../dist/trace/component/trace/sheet/TabPaneCpuByProcess.js" +const sqlit = require("../../../../../dist/trace/database/SqlLite.js") +jest.mock("../../../../../dist/trace/database/SqlLite.js"); +describe('TabPaneCpuByProcess Test', () => { + let tabPaneCpuByProcess = new TabPaneCpuByProcess(); + tabPaneCpuByProcess.sortByColumn = jest.fn(()=> true) + + it('TabPaneCpuByProcessTest01', function () { + expect(tabPaneCpuByProcess.sortByColumn({ + key: 'name', + sort: () => { + } + })).toBeTruthy(); + }); + + it('TabPaneCpuByProcessTest02', function () { + let mockgetTabCpuByProcess = sqlit.getTabCpuByProcess + mockgetTabCpuByProcess.mockResolvedValue([{process : "test", + wallDuration: 10, + occurrences: 10 + }, + {process : "test2", + wallDuration: 11, + occurrences: 11 + }] + ) + let a = {rightNs: 1, cpus: [11, 12, 13]} + expect(tabPaneCpuByProcess.data = a).toBeTruthy(); + }); + + it('TabPaneCpuByProcessTest03', function () { + let mockgetTabCpuByProcess = sqlit.getTabCpuByProcess + mockgetTabCpuByProcess.mockResolvedValue([]) + let a = {rightNs: 1, cpus: [11, 12, 13]} + expect(tabPaneCpuByProcess.data = a).toBeTruthy(); + }); +}) diff --git a/host/ide/test/trace/component/trace/sheet/TabPaneCpuByThread.test.ts b/host/ide/test/trace/component/trace/sheet/TabPaneCpuByThread.test.ts new file mode 100644 index 0000000..d01b6ec --- /dev/null +++ b/host/ide/test/trace/component/trace/sheet/TabPaneCpuByThread.test.ts @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TabPaneCpuByThread} from "../../../../../dist/trace/component/trace/sheet/TabPaneCpuByThread.js" + +describe('TabPaneCpuByThread Test', () => { + let tabPaneCpuByThread = new TabPaneCpuByThread(); + + it('TabPaneCpuByThreadTest01', function () { + expect(tabPaneCpuByThread.sortByColumn({ + key: 'name', + sort: () => { + } + })).toBeUndefined(); + }); +}) diff --git a/host/ide/test/trace/component/trace/sheet/TabPaneCpuUsage.test.ts b/host/ide/test/trace/component/trace/sheet/TabPaneCpuUsage.test.ts new file mode 100644 index 0000000..d70e77f --- /dev/null +++ b/host/ide/test/trace/component/trace/sheet/TabPaneCpuUsage.test.ts @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TabPaneCpuUsage} from "../../../../../dist/trace/component/trace/sheet/TabPaneCpuUsage.js" + +describe('TabPaneCpuUsage Test', () => { + let tabPaneCpuUsage = new TabPaneCpuUsage(); + + it('TabPaneCpuUsageTest02', function () { + expect(tabPaneCpuUsage.sortTable([[1,2,3,9,6,4],[5,2,1,4,9,6]],1,true)).toBeUndefined(); + }); + + it('TabPaneCpuUsageTest03', function () { + expect(tabPaneCpuUsage.sortTable([[1,2,3,9,6,4],[5,2,1,4,9,6]],1,false)).toBeUndefined(); + }); + it('TabPaneCpuUsageTest04', function () { + let result = tabPaneCpuUsage.sortFreq([{ + cpu: 0, + value: 0, + startNs: 0, + dur: 0, + },{ + cpu: 1, + value: 2, + startNs: 2, + dur: 4, + }]); + expect(result[0][0]).toBe(2); + }); + it('TabPaneCpuUsageTest05', function () { + expect(tabPaneCpuUsage.getFreqTop3({ + cpu: 0, + usage: 0, + usageStr: "usage", + top1: 1, + top2: 2, + top3: 3, + top1Percent: 11, + top1PercentStr: "Str1", + top2Percent: 22, + top2PercentStr: "Str2", + top3Percent: 33, + top3PercentStr: "Str3", + }, undefined, undefined, undefined, 1)).toBeUndefined(); + }); + it('TabPaneCpuUsageTest06', function () { + let result = tabPaneCpuUsage.groupByCpuToMap([{ + cpu: 0, + value: 0, + startNs: 0, + dur: 0, + },{ + cpu: 1, + value: 2, + startNs: 2, + dur: 4, + }]) + expect(result.get(0).length).toBe(1); + }); +}) diff --git a/host/ide/test/trace/component/trace/sheet/TabPaneCurrentSelection.test.ts b/host/ide/test/trace/component/trace/sheet/TabPaneCurrentSelection.test.ts new file mode 100644 index 0000000..5ecf5f1 --- /dev/null +++ b/host/ide/test/trace/component/trace/sheet/TabPaneCurrentSelection.test.ts @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TabPaneCurrentSelection, getTimeString} from "../../../../../dist/trace/component/trace/sheet/TabPaneCurrentSelection.js" +import {SpApplication} from "../../../../../src/trace/SpApplication"; +window.ResizeObserver = window.ResizeObserver || + jest.fn().mockImplementation(() => ({ + disconnect: jest.fn(), + observe: jest.fn(), + unobserve: jest.fn(), + + })); +describe('TabPaneCurrentSelection Test', () => { + let tabPaneCurrentSelection = new TabPaneCurrentSelection(); + + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + + let cpuData = [{ + cpu: 1, + dur: 1, + end_state: 'string', + id: 12, + name: 'name', + priority: 11, + processCmdLine: 'processCmdLine', + processId: 111, + processName: 'processName', + schedId: 22, + startTime: 0, + tid: 100, + type: 'type', + }] + let functionData = [{ + argsetid: 53161, + depth: 0, + dur: 570000, + funName: "binder transaction", + id: 92749, + is_main_thread: 0, + parent_id: null, + startTs: 9729867000, + threadName: "Thread-15", + tid: 2785, + }] + let memData = [{ + trackId: 100, + processName:'processName', + pid: 11, + upid:1, + trackName:'trackName', + type:'type', + track_id: 'track_id', + value: 111, + startTime:0, + duration:1000, + maxValue:4000, + delta: 2, + }] + let threadData = [{ + hasSched: 14724852000, + pid: 2519, + processName: null, + threadName: "ACCS0", + tid: 2716, + upid: 1, + utid: 1, + cpu: null, + dur: 405000, + end_ts: null, + id: 1, + is_main_thread: 0, + name: "ACCS0", + startTime: 58000, + start_ts: null, + state: "S", + type: "thread", + }] + let wakeupBean = [{ + wakeupTime:0, + cpu:1, + process:'process', + pid:11, + thread:'thread', + tid:22, + schedulingLatency:33, + schedulingDesc:'schedulingDesc', + + }] + + let queryData = [{ + id:1, + startTime:0, + hasSched: 14724852000, + pid: 2519, + processName: null, + threadName: "ACCS0", + tid: 2716, + upid: 1, + utid: 1, + cpu: null, + dur: 405000, + end_ts: null, + is_main_thread: 0, + name: "ACCS0", + start_ts: null, + state: "S", + type: "thread", + + }] + + tabPaneCurrentSelection.queryWakeUpData = jest.fn(()=> 'WakeUpData') + tabPaneCurrentSelection.queryWakeUpData.wb = jest.fn(()=>null) + + + + it('TabPaneCurrentSelectionTest01', function () { + let result = tabPaneCurrentSelection.setFunctionData(functionData) + expect(result).toBeUndefined(); + }); + + it('TabPaneCurrentSelectionTest02', function () { + let result = tabPaneCurrentSelection.setMemData(memData) + expect(result).toBeUndefined(); + }); + + it('TabPaneCurrentSelectionTest12', function () { + let result = tabPaneCurrentSelection.queryWakeUpData(queryData) + expect(result).toBeTruthy(); + }); + + it('TabPaneCurrentSelectionTest03', function () { + let result = tabPaneCurrentSelection.setThreadData(threadData) + expect(result).toBeUndefined(); + }); + + it('TabPaneCurrentSelectionTest04', function () { + let result = tabPaneCurrentSelection.drawRight(canvas, wakeupBean) + expect(result).toBeUndefined(); + }); + + it('TabPaneCurrentSelectionTest06', function () { + let result = getTimeString(3600000000001) + expect(result).toBe('1h 1ns '); + }); + + it('TabPaneCurrentSelectionTest07', function () { + let result = getTimeString(60000000001) + expect(result).toBe('1m 1ns '); + }); + + it('TabPaneCurrentSelectionTest08', function () { + let result = getTimeString(1000000001) + expect(result).toBe('1s 1ns '); + }); + + it('TabPaneCurrentSelectionTest9', function () { + let result = getTimeString(1000001) + expect(result).toBe('1ms 1ns '); + }); + + it('TabPaneCurrentSelectionTest10', function () { + let result = getTimeString(1001) + expect(result).toBe('1μs 1ns '); + }); + + it('TabPaneCurrentSelectionTest11', function () { + let result = getTimeString(101) + expect(result).toBe('101ns '); + }); + +}) diff --git a/host/ide/test/trace/component/trace/sheet/TabPanePTS.test.ts b/host/ide/test/trace/component/trace/sheet/TabPanePTS.test.ts new file mode 100644 index 0000000..76a843c --- /dev/null +++ b/host/ide/test/trace/component/trace/sheet/TabPanePTS.test.ts @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TabPanePTS} from "../../../../../dist/trace/component/trace/sheet/TabPanePTS.js" +window.ResizeObserver = window.ResizeObserver ||jest.fn().mockImplementation(() => ({ + disconnect: jest.fn(), + observe: jest.fn(), + unobserve: jest.fn(), +})); +describe('TabPanePTS Test', () => { + let tabPanePTS = new TabPanePTS(); + + + let dataArray = [{ + id: "", + pid: "", + title: "", + children: [], + process: "", + processId: 0, + thread: "", + threadId: 0, + state: "", + wallDuration: 0, + avgDuration: "", + count: 0, + minDuration: 0, + maxDuration: 0, + stdDuration: "", + }] + + it('TabPanePTSTest01', function () { + let result = tabPanePTS.groupByProcessThreadToMap(dataArray); + expect(result.get(0).length).toBeUndefined(); + }); + + it('TabPanePTSTest02', function () { + let result = tabPanePTS.groupByProcessToMap(dataArray) + expect(result.get(0).length).toBe(1); + }); + + it('TabPanePTSTest03', function () { + let result = tabPanePTS.groupByThreadToMap(dataArray) + expect(result.get(0).length).toBe(1); + }); +}) diff --git a/host/ide/test/trace/component/trace/sheet/TabPaneSPT.test.ts b/host/ide/test/trace/component/trace/sheet/TabPaneSPT.test.ts new file mode 100644 index 0000000..e6cdc3d --- /dev/null +++ b/host/ide/test/trace/component/trace/sheet/TabPaneSPT.test.ts @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TabPaneSPT} from "../../../../../dist/trace/component/trace/sheet/TabPaneSPT.js" +window.ResizeObserver = window.ResizeObserver || + jest.fn().mockImplementation(() => ({ + disconnect: jest.fn(), + observe: jest.fn(), + unobserve: jest.fn(), + })); +describe('TabPaneSPT Test', () => { + let tabPaneSPT = new TabPaneSPT(); + + let dataArray = [{ + id: "", + pid: "", + title: "", + children: [], + process: "", + processId: 0, + thread: "", + threadId: 0, + state: "", + wallDuration: 0, + avgDuration: "", + count: 0, + minDuration: 0, + maxDuration: 0, + stdDuration: "", + }] + + it('TabPaneSPTTest01', function () { + let result = tabPaneSPT.groupByStateToMap(dataArray); + expect(result.get('').length).toBe(1); + }); + + it('TabPaneSPTTest02', function () { + let result = tabPaneSPT.groupByProcessToMap(dataArray) + expect(result.get(0).length).toBe(1); + }); + + it('TabPaneSPTTest03', function () { + let result = tabPaneSPT.groupByStateProcessToMap(dataArray) + expect(result.get('').get(0).length).toBe(1); + }); +}) diff --git a/host/ide/test/trace/component/trace/sheet/TabPaneSlices.test.ts b/host/ide/test/trace/component/trace/sheet/TabPaneSlices.test.ts new file mode 100644 index 0000000..d00e3b5 --- /dev/null +++ b/host/ide/test/trace/component/trace/sheet/TabPaneSlices.test.ts @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TabPaneSlices} from "../../../../../dist/trace/component/trace/sheet/TabPaneSlices.js" + +const sqlit = require("../../../../../dist/trace/database/SqlLite.js") +jest.mock("../../../../../dist/trace/database/SqlLite.js"); +describe('TabPaneSlices Test', () => { + let tabPaneSlices = new TabPaneSlices(); + + tabPaneSlices.sortByColumn = jest.fn(()=> true) + + it('TabPaneSlicesTest01', function () { + expect(tabPaneSlices.sortByColumn({ + key: 'name', + sort: () => { + } + })).toBeTruthy(); + }); + + it('TabPaneSlicesTest02', function () { + let mockgetTabThreadStates = sqlit.getTabSlices + mockgetTabThreadStates.mockResolvedValue([{name : "11", + wallDuration: 10, + occurrences: 10, + },{name : "22", + wallDuration: 20, + occurrences: 20, + }] + ) + let a = {rightNs: 1, leftNs: 0, funTids: [11, 12, 13]} + expect(tabPaneSlices.data = a).toBeTruthy(); + }); + + it('TabPaneSlicesTest03', function () { + let mockgetTabThreadStates = sqlit.getTabSlices + mockgetTabThreadStates.mockResolvedValue([]) + let a = {rightNs: 1, leftNs: 0, funTids: [11, 12, 13]} + expect(tabPaneSlices.data = a).toBeTruthy(); + }); +}) diff --git a/host/ide/test/trace/component/trace/sheet/TabPaneThreadStates.test.ts b/host/ide/test/trace/component/trace/sheet/TabPaneThreadStates.test.ts new file mode 100644 index 0000000..e2dcc09 --- /dev/null +++ b/host/ide/test/trace/component/trace/sheet/TabPaneThreadStates.test.ts @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TabPaneThreadStates} from "../../../../../dist/trace/component/trace/sheet/TabPaneThreadStates.js" + +const sqlit = require("../../../../../dist/trace/database/SqlLite.js") +jest.mock("../../../../../dist/trace/database/SqlLite.js"); +describe('TabPaneThreadStates Test', () => { + let tabPaneThreadStates = new TabPaneThreadStates(); + + tabPaneThreadStates.sortByColumn = jest.fn(() => true) + + it('TabPaneThreadStatesTest01', function () { + expect(tabPaneThreadStates.sortByColumn({ + key: 'name', + sort: () => { + } + })).toBeTruthy(); + }); + + + it('TabPaneThreadStatesTest02', function () { + // @ts-ignore + let mockgetTabThreadStates = sqlit.getTabThreadStates + mockgetTabThreadStates.mockResolvedValue( + [{process: "11", + thread: "222", + wallDuration: 10, + occurrences: 10, + state: "sss", + stateJX: "mm" + },{process: "11", + thread: "222", + wallDuration: 10, + occurrences: 10, + state: "sss", + stateJX: "mm" + }] + ) + let a = {rightNs: 1, leftNs: 0, threadIds: [11, 12, 13]} + expect(tabPaneThreadStates.data = a).toBeTruthy(); + }); + + it('TabPaneThreadStatesTest03', function () { + // @ts-ignore + let mockgetTabThreadStates = sqlit.getTabThreadStates + mockgetTabThreadStates.mockResolvedValue([]) + let a = {rightNs: 1, leftNs: 0, threadIds: [11, 12, 13]} + expect(tabPaneThreadStates.data = a).toBeTruthy(); + }); +}) diff --git a/host/ide/test/trace/component/trace/sheet/TabPaneThreadSwitch.test.ts b/host/ide/test/trace/component/trace/sheet/TabPaneThreadSwitch.test.ts new file mode 100644 index 0000000..cd1fce5 --- /dev/null +++ b/host/ide/test/trace/component/trace/sheet/TabPaneThreadSwitch.test.ts @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TabPaneThreadSwitch} from "../../../../../dist/trace/component/trace/sheet/TabPaneThreadSwitch.js" +window.ResizeObserver = window.ResizeObserver || + jest.fn().mockImplementation(() => ({ + disconnect: jest.fn(), + observe: jest.fn(), + unobserve: jest.fn(), + })); +describe('TabPaneContextSwitch Test', () => { + let tabPaneThreadSwitch = new TabPaneThreadSwitch(); + let dataArray = [{ + id: "", + pid: "", + title: "", + children: [], + process: "", + processId: 0, + thread: "", + threadId: 0, + state: "", + wallDuration: 0, + avgDuration: "", + count: 0, + minDuration: 0, + maxDuration: 0, + stdDuration: "", + }] +let val={ + leftNs:1, + rightNs:1, + +} + it('TabPaneThreadSwitchTest01', function () { + let result = tabPaneThreadSwitch.groupByStateToMap(dataArray); + expect(result.get('').length).toBe(1); + }); + + it('TabPaneThreadSwitchTest02', function () { + let result = tabPaneThreadSwitch.groupByProcessToMap(dataArray) + expect(result.get(0).length).toBe(1); + }); + + it('TabPaneThreadSwitchTest03', function () { + let result = tabPaneThreadSwitch.groupByStateProcessToMap(dataArray) + expect(result.get('').get(0).length).toBe(1); + }); + + it('TabPaneThreadSwitchTest04', function () { + expect(tabPaneThreadSwitch.data).toBeUndefined(); + }); + + it('TabPaneThreadSwitchTest05', function () { + expect(tabPaneThreadSwitch.queryDataByCacheData(val)).toBeUndefined(); + }); + + + +}) diff --git a/host/ide/test/trace/component/trace/timer-shaft/RangeRuler.test.ts b/host/ide/test/trace/component/trace/timer-shaft/RangeRuler.test.ts new file mode 100644 index 0000000..99775c9 --- /dev/null +++ b/host/ide/test/trace/component/trace/timer-shaft/RangeRuler.test.ts @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {RangeRuler} from "../../../../../dist/trace/component/trace/timer-shaft/RangeRuler.js" +// @ts-ignore +import {Mark} from "../../../../../dist/trace/component/trace/timer-shaft/RangeRuler.js"; + +describe('RangeRuler Test', ()=>{ + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + + let rangeRuler = new RangeRuler(canvas , ctx, { + x: 20, + y: 20, + width: 100, + height: 100 + }, { + startX: 10, + endX: 30 + }); + let mark = new Mark(canvas , ctx, '', { + x: 20, + y: 20, + width: 100, + height: 100 + }); + + rangeRuler.cpuUsage = ([{ + cpu: 1, + ro: 2, + rate: 2 + }]) + + mark.isHover = true + + it('RangeRulerTest01', function () { + expect(rangeRuler.drawCpuUsage()).toBeUndefined(); + }); + + it('RangeRulerTest02', function () { + expect(rangeRuler.fillX()).toBeUndefined(); + }); + + it('RangeRulerTest03', function () { + expect(rangeRuler.keyPress({ + key: 'w' + })).toBeUndefined(); + }); + + it('RangeRulerTest04', function () { + expect(rangeRuler.keyPress({ + key: 's' + })).toBeUndefined(); + }); + + it('RangeRulerTest05', function () { + expect(rangeRuler.keyPress({ + key: 'a' + })).toBeUndefined(); + }); + + it('RangeRulerTest06', function () { + expect(rangeRuler.keyPress({ + key: 'd' + })).toBeUndefined(); + }); + + it('RangeRulerTest07', function () { + expect(rangeRuler.keyUp({ + key: 'w' + })).toBeUndefined(); + }); + + it('RangeRulerTest08', function () { + expect(rangeRuler.keyUp({ + key: 's' + })).toBeUndefined(); + }); + + it('RangeRulerTest09', function () { + expect(rangeRuler.keyUp({ + key: 'a' + })).toBeUndefined(); + }); + + it('RangeRulerTest10', function () { + expect(rangeRuler.keyUp({ + key: 'd' + })).toBeUndefined(); + }); + + it('RangeRulerTest11', function () { + expect(rangeRuler.mouseUp({ + key: '' + })).toBeUndefined(); + }); + + it('RangeRulerTest12', function () { + expect(rangeRuler.mouseOut({ + key: '' + })).toBeUndefined(); + }); + + it('RangeRulerTest13', function () { + rangeRuler.markA = jest.fn(()=>true) + rangeRuler.rangeRect = jest.fn(()=>true) + rangeRuler.rangeRect.containsWithPadding = jest.fn(()=>true) + + rangeRuler.markA = jest.fn(()=> { + return { + frame: { + x: 20 + } + } + }) + rangeRuler.markA.isHover = jest.fn(()=> true) + rangeRuler.markA.frame = jest.fn(()=> []) + rangeRuler.markA.frame.x = jest.fn(()=>true) + + expect(rangeRuler.mouseDown({ + key: '' + })).toBeUndefined(); + }); + + it('RangeRulerTest14', function () { + rangeRuler.markA = jest.fn(()=>true) + rangeRuler.rangeRect = jest.fn(()=>true) + rangeRuler.rangeRect.containsWithPadding = jest.fn(()=>false) + rangeRuler.frame = jest.fn(()=>false) + rangeRuler.frame.containsWithMargin = jest.fn(()=> true) + rangeRuler.rangeRect.containsWithMargin = jest.fn(()=> false) + rangeRuler.markB.isHover = jest.fn(()=> true) + rangeRuler.markB.frame = jest.fn(()=> true) + rangeRuler.markB.frame.x = jest.fn(()=>true) + expect(rangeRuler.mouseDown({ + key: '' + })).toBeUndefined(); + }); + + it('RangeRulerTest15', function () { + rangeRuler.markA = jest.fn(()=>true) + rangeRuler.markA.inspectionFrame = jest.fn(()=>true) + rangeRuler.markA.inspectionFrame.contains = jest.fn(()=>true) + rangeRuler.markA.frame = jest.fn(()=> true) + rangeRuler.markA.frame.x = jest.fn(()=>true) + rangeRuler.markA.draw = jest.fn(()=>true) + expect(rangeRuler.mouseMove({ + key: '' + })).toBeUndefined(); + }); + + it('RangeRulerTest16', ()=> { + rangeRuler.markA = jest.fn(()=>false) + rangeRuler.markA.draw = jest.fn(()=>true) + rangeRuler.markA.frame = jest.fn(()=> true) + rangeRuler.markA.frame.x = jest.fn(()=>true) + rangeRuler.markA.inspectionFrame = jest.fn(()=>false) + rangeRuler.markA.inspectionFrame.contains = jest.fn(()=>false) + rangeRuler.movingMark = jest.fn(()=>false) + rangeRuler.movingMark.frame = jest.fn(()=> false) + rangeRuler.movingMark.frame.x = jest.fn(()=>false) + rangeRuler.rangeRect = jest.fn(()=>true) + rangeRuler.rangeRect.containsWithPadding = jest.fn(()=>true) + rangeRuler.movingMark.inspectionFrame = jest.fn(()=>false) + rangeRuler.movingMark.inspectionFrame.x = jest.fn(()=>false) + expect(rangeRuler.mouseMove({ + key: '' + })).toBeUndefined(); + }); + it('RangeRulerTest17', ()=> { + rangeRuler.notifyHandler = jest.fn(()=>true) + rangeRuler.movingMark.inspectionFrame.x = jest.fn(()=>false) + rangeRuler.c = jest.fn(()=>true) + rangeRuler.frame = jest.fn(()=>true) + rangeRuler.frame.x = jest.fn(()=>true) + rangeRuler.frame.y = jest.fn(()=>true) + rangeRuler.c.clearRect = jest.fn(()=>true) + expect(rangeRuler.draw()).toBeUndefined(); + }); + + it('RangeRulerTest18', function () { + expect(mark.isHover).toBeTruthy(); + }); +}) diff --git a/host/ide/test/trace/component/trace/timer-shaft/Rect.test.ts b/host/ide/test/trace/component/trace/timer-shaft/Rect.test.ts new file mode 100644 index 0000000..82d7175 --- /dev/null +++ b/host/ide/test/trace/component/trace/timer-shaft/Rect.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {Rect, Point} from "../../../../../dist/trace/component/trace/timer-shaft/Rect.js" + +describe('Rect Test', ()=>{ + let rect = new Rect(20, 20, 100, 100); + let point = new Point() + + it('RectTest01', function () { + expect(rect.contains(4,5)).toBeFalsy(); + }); + + it('RectTest02', function () { + expect(Rect.contains(rect, 4, 4)).toBeFalsy(); + }); + + it('RectTest03', function () { + expect(rect.containsWithPadding(4,5, 2, 2)).toBeFalsy(); + }); + + it('RectTest04', function () { + expect(Rect.containsWithPadding(rect, 4, 4, 2, 2)).toBeFalsy(); + }); + + it('RectTest05', function () { + expect(rect.containsWithMargin(1,2,1,2,4,5,)).toBeFalsy(); + }); + + it('RectTest06', function () { + expect(Rect.containsWithMargin(1,2,1,2,4,5,)).toBeFalsy(); + }); + + it('RectTest07', function () { + expect(rect.intersect([])).toBeFalsy(); + }); +}) diff --git a/host/ide/test/trace/component/trace/timer-shaft/SportRuler.test.ts b/host/ide/test/trace/component/trace/timer-shaft/SportRuler.test.ts new file mode 100644 index 0000000..41d3e83 --- /dev/null +++ b/host/ide/test/trace/component/trace/timer-shaft/SportRuler.test.ts @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {SportRuler} from "../../../../../dist/trace/component/trace/timer-shaft/SportRuler.js" + +describe('SportRuler Test', ()=>{ + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + + let sportRuler = new SportRuler(canvas, ctx, { + x: 20, + y: 20, + width: 100, + height: 100, + }); + + sportRuler.range = { + totalNS: 20, + startX: 0, + endX: 10, + startNS: 10, + endNS: 20, + xs: [], + xsTxt: [], + } + + it('SportRulerTest01', function () { + expect(sportRuler.drawTheFlag(2, '#999999', false, 'text')).toBeUndefined(); + }); + + it('SportRulerTest02', function () { + let randomRgbColor = sportRuler.randomRgbColor(); + let isColor = randomRgbColor.length > 4; + expect(isColor).toBeTruthy() + }); + + it('SportRulerTest03', function () { + expect(sportRuler.onFlagRangeEvent({ + x: 0, + y: 0, + width: 0, + height: 0, + time: 0, + color: "", + selected: false, + text: "", + }, 2)).toBeUndefined(); + }); + + it('SportRulerTest04', function () { + expect(sportRuler.mouseMove({ + offsetY: 20, + offsetX: 20, + })).toBeUndefined(); + }); + + it('SportRulerTest05', function () { + let ranges = sportRuler.range; + expect(ranges.endNS).toBe(20); + }) + + it('SportRulerTest06', function () { + sportRuler.flagListIdx = jest.fn(()=>"flagListIdx") + sportRuler.flagList = jest.fn(()=>true) + expect(sportRuler.modifyFlagList('amend', {})).toBeUndefined(); + }) + + it('SportRulerTest07', function () { + sportRuler.flagList.splice = jest.fn(()=>true) + expect(sportRuler.modifyFlagList('remove', {})).toBeUndefined(); + }) + + it('SportRulerTest08', function () { + expect(sportRuler.draw()).toBeUndefined(); + }) + + it('SportRulerTest09', function () { + expect(sportRuler.mouseUp()).toBeUndefined(); + }) + + it('SportRulerTest10', function () { + sportRuler.draw = jest.fn(()=>true) + expect(sportRuler.mouseMove({ + offsetX: 10000, + offsetY: 10000 + })).toBeUndefined(); + }); + +}) diff --git a/host/ide/test/trace/component/trace/timer-shaft/TabPaneFlag.test.ts b/host/ide/test/trace/component/trace/timer-shaft/TabPaneFlag.test.ts new file mode 100644 index 0000000..88198fa --- /dev/null +++ b/host/ide/test/trace/component/trace/timer-shaft/TabPaneFlag.test.ts @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TabPaneFlag} from "../../../../../dist/trace/component/trace/timer-shaft/TabPaneFlag.js" + +describe('TabPaneFlag Test', ()=>{ + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + + let tabPaneFlag = new TabPaneFlag(canvas , ctx, { + x: 20, + y: 20, + width: 100, + height: 100 + }, 10000000000); + + it('TabPaneFlagTest01', function () { + expect(tabPaneFlag.initElements()).toBeUndefined(); + }); + + it('TabPaneFlagTest01', function () { + expect(tabPaneFlag.initHtml()).not.toBe('') + }); + + it('TabPaneFlagTest01', function () { + expect(tabPaneFlag.setFlagObj({ + x: 0, + y: 0, + width: 0, + height: 0, + time: 0, + color: "", + selected: false, + text: "", + }, 5)).toBeUndefined(); + }); +}) diff --git a/host/ide/test/trace/component/trace/timer-shaft/TimeRuler.test.ts b/host/ide/test/trace/component/trace/timer-shaft/TimeRuler.test.ts new file mode 100644 index 0000000..cb3ae26 --- /dev/null +++ b/host/ide/test/trace/component/trace/timer-shaft/TimeRuler.test.ts @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {TimeRuler} from "../../../../../dist/trace/component/trace/timer-shaft/TimeRuler.js" + +describe('TimeRuler Test', ()=>{ + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + + let timeRuler = new TimeRuler(canvas , ctx, { + x: 20, + y: 20, + width: 100, + height: 100 + }, 10000000000); + + it('TimeRulerTest01', function () { + expect(timeRuler.draw()).toBeUndefined(); + }); +}) diff --git a/host/ide/test/trace/database/Procedure.test.ts b/host/ide/test/trace/database/Procedure.test.ts new file mode 100644 index 0000000..c3cb4c9 --- /dev/null +++ b/host/ide/test/trace/database/Procedure.test.ts @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// @ts-ignore +import {procedurePool} from "../../../dist/trace/database/Procedure.js" + +describe('procedure Test', () => { + // let procedure = new procedurePool(); + it('ProfilerClientTest01', function () { + expect(procedurePool.uuid).toBeUndefined(); + }); + + }) diff --git a/host/ide/test/trace/grpc/HiProfilerClient.test.ts b/host/ide/test/trace/grpc/HiProfilerClient.test.ts new file mode 100644 index 0000000..cfe9dce --- /dev/null +++ b/host/ide/test/trace/grpc/HiProfilerClient.test.ts @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {HiProfilerClient} from "../../../dist/trace/grpc/HiProfilerClient.js" + +describe('HiProfilerClient Test', ()=>{ + let hiProfilerClient = new HiProfilerClient(); + it('HiProfilerClientTest01', function () { + expect(hiProfilerClient.address).toBeUndefined(); + }); + + it('HiProfilerClientTest02', function () { + hiProfilerClient.address=true; + expect(hiProfilerClient.address).toBeTruthy(); + }); + + it('HiProfilerClientTest03', function () { + expect(hiProfilerClient.client).toBeUndefined(); + }); + + it('HiProfilerClientTest04', function () { + hiProfilerClient.client=true; + expect(hiProfilerClient.client).toBeTruthy(); + }); + + it('HiProfilerClientTest05', function () { + expect(hiProfilerClient.getProfilerClient()).toBeUndefined(); + }); + +}) \ No newline at end of file diff --git a/host/ide/test/trace/grpc/ProfilerClient.test.ts b/host/ide/test/trace/grpc/ProfilerClient.test.ts new file mode 100644 index 0000000..a4e00e7 --- /dev/null +++ b/host/ide/test/trace/grpc/ProfilerClient.test.ts @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-ignore +import {ProfilerClient} from "../../../dist/trace/grpc/ProfilerClient.js" + +describe('HiProfilerClient Test', ()=>{ + + + it('ProfilerClientTest01', function () { + expect(ProfilerClient.client).toBeUndefined(); + }); + it('ProfilerClientTest01', function () { + ProfilerClient.client = true; + expect(ProfilerClient.client).toBeTruthy(); + }); + +}) \ No newline at end of file diff --git a/host/ide/tsconfig.json b/host/ide/tsconfig.json new file mode 100644 index 0000000..57e2db3 --- /dev/null +++ b/host/ide/tsconfig.json @@ -0,0 +1,71 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ +// "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ + "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + }, + "include": ["src"] +} diff --git a/host/trace_streamer/.gn b/host/trace_streamer/.gn new file mode 100644 index 0000000..8141909 --- /dev/null +++ b/host/trace_streamer/.gn @@ -0,0 +1,14 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +buildconfig = "//gn/CONFIG.gn" diff --git a/host/trace_streamer/BUILD.gn b/host/trace_streamer/BUILD.gn new file mode 100644 index 0000000..092bb98 --- /dev/null +++ b/host/trace_streamer/BUILD.gn @@ -0,0 +1,23 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +group("trace_streamer") { + if (use_wasm) { + deps = [ "src:trace_streamer_builtin" ] + } else if (is_test) { + deps = [ "test:unittest" ] + testonly = true + } else { + deps = [ "src:trace_streamer" ] + } +} diff --git a/host/trace_streamer/README.md b/host/trace_streamer/README.md new file mode 100644 index 0000000..eb0deec --- /dev/null +++ b/host/trace_streamer/README.md @@ -0,0 +1,309 @@ +# trace_streamer工具说明 +trace_streamer工具可以2种方式使用 +1. 可以将系统离线trace文件解析并转为db,此工具支持基于文本的trace和基于proto的trace。 +2. trace_streamer工具还可以WebAssembly的方式在浏览器中运行,需暴露相关接口给js文件。 + +## 关于trace解析工具的使用说明: +### 导出db模式 +在导出db模式下,trace_streamer.exe trace文件路径名 -e 导出db路径名.db +此命令可以将trace文件转为db +本应用支持在ohos, linux, windows, mac使用。 +关于db文件的说明: +使用db查看工具查看stat表,可以浏览当前数据一共有多少类数据,各类数据都收到多少条,数据是否正常等情况。在meta表会记录数据库导出时的一些系统信息,比如导入和导出的文件全路径,解析时间等信息。 +meta表可以选择不导出(有些情况下会暴露系统敏感信息),在导出时添加 -nm选项即可。 +在数据导出之后,会在本地目录下生成一个trace_streamer.log文件,在导出db的目录下生成一个数据库文件同名,.db.ohos.ts后缀的文件 +文件内容如下: +时间戳:执行结果(数字) +应用运行时间 + +执行结果解释如下:0 代表执行成功 1 表示输入文件不匹配, 2 表示解析错误, 3其他错误 +### 内置浏览器方式 +trace_streamer可以WebAssembly方式在浏览器中运行,暴露如下接口给js +``` +extern "C" { +/* 上传trace数据 + * + * @data: 数据的缓冲区 + * @dataLen: 数据长度 + * + * return: 0:成功; -1:失败 +*/ +EMSCRIPTEN_KEEPALIVE int TraceStreamerParseData(const uint8_t* data, int dataLen); + +/* 通知TS上传trace数据结束 + * + * return: 0:成功; -1:失败 +*/ +EMSCRIPTEN_KEEPALIVE int TraceStreamerParseDataOver(); + +/* 通过sql语句操作数据库 + * + * @sql: sql语句 + * @sqlLen: sql语句长度 + * + * return: 0:成功; -1:失败 +*/ +EMSCRIPTEN_KEEPALIVE int TraceStreamerSqlOperate(const uint8_t* sql, int sqlLen); + +/* 通过sql语句查询数据库 + * + * @sql: sql语句 + * @sqlLen: sql语句长度 + * @out: 查询结果的缓冲区,查询结果为json + * @outLen: 缓冲区长度 + * + * return: >0:查询成功,返回查询结果数据长度; -1:失败 +*/ +EMSCRIPTEN_KEEPALIVE int TraceStreamerSqlQuery(const uint8_t* sql, int sqlLen, uint8_t* out, int outLen); + +} // extern "C" +``` + +### 你也可以执行如下命令查看应用帮助 +./trace_streamer --help +-i 选项可查看应用支持的事件源和具体的事件名列表 + +#### trace_streamer支持的事件解析 +本工具支持基于文本的trace(# TRACE)和基于proto的二进制日志文件的解析,支持的事件列表如下: +##### ftrace事件 +``` +binder_transaction +binder_transaction_received +binder_transaction_alloc_buf +binder_transaction_lock +binder_transaction_locked +binder_transaction_unlock +sched_switch +task_rename +task_newtask +tracing_mark_write +print +sched_wakeup +sched_waking +cpu_idle +cpu_frequency +suspend_resume +workqueue_execute_start +workqueue_execute_end +clock_set_rate +clock_enable +clock_disable +clk_set_rate +clk_enable +clk_disable +sys_enter +sys_exit +regulator_set_voltage +regulator_set_voltage_complete +regulator_disable +regulator_disable_complete +ipi_entry +ipi_exit +irq_handler_entry +irq_handler_exit +softirq_raise +softirq_entry +softirq_exit +sched_wakeup_new +sched_process_exit +trace_event_clock_sync +``` +##### 内存事件 +``` +mem.vm.size +mem.rss +mem.rss.anon +mem.rss.file +mem.rss.schem +mem.swap +mem.locked +mem.hwm +mem.oom_score_adj +``` +##### 系统内存事件 +``` +sys.mem.unspecified +sys.mem.total +sys.mem.free +sys.mem.avaiable +sys.mem.buffers +sys.mem.cached +sys.mem.swap.chard +sys.mem.active +sys.mem.inactive +sys.mem.active.anon +sys.mem.inactive.anon +sys.mem.active_file +sys.mem.inactive_file +sys.mem.unevictable +sys.mem.mlocked +sys.mem.swap.total +sys.mem.swap.free +sys.mem.dirty +sys.mem.writeback +sys.mem.anon.pages +sys.mem.mapped +sys.mem.shmem +sys.mem.slab +sys.mem.slab.reclaimable +sys.mem.slab.unreclaimable +sys.mem.kernel.stack +sys.mem.page.tables +sys.mem.commit.limit +sys.mem.commited.as +sys.mem.vmalloc.total +sys.mem.vmalloc.used +sys.mem.vmalloc.chunk +sys.mem.cma.total +sys.mem.cma.free +``` +##### 系统虚拟内存事件 +``` +sys.virtual.mem.unspecified +sys.virtual.mem.nr.free.pages +sys.virtual.mem.nr.alloc.batch +sys.virtual.mem.nr.inactive.anon +sys.virtual.mem.nr.active_anon +sys.virtual.mem.nr.inactive.file +sys.virtual.mem.nr.active_file +sys.virtual.mem.nr.unevictable +sys.virtual.mem.nr.mlock +sys.virtual.mem.anon.pages +sys.virtual.mem.nr.mapped +sys.virtual.mem.nr.file.pages +sys.virtual.mem.nr.dirty +sys.virtual.mem.nr.writeback +sys.virtual.mem.nr.slab.reclaimable +sys.virtual.mem.nr.slab.unreclaimable +sys.virtual.mem.nr.page_table.pages +sys.virtual.mem.nr_kernel.stack +sys.virtual.mem.nr.overhead +sys.virtual.mem.nr.unstable +sys.virtual.mem.nr.bounce +sys.virtual.mem.nr.vmscan.write +sys.virtual.mem.nr.vmscan.immediate.reclaim +sys.virtual.mem.nr.writeback_temp +sys.virtual.mem.nr.isolated_anon +sys.virtual.mem.nr.isolated_file +sys.virtual.mem.nr.shmem +sys.virtual.mem.nr.dirtied +sys.virtual.mem.nr.written +sys.virtual.mem.nr.pages.scanned +sys.virtual.mem.workingset.refault +sys.virtual.mem.workingset.activate +sys.virtual.mem.workingset_nodereclaim +sys.virtual.mem.nr_anon.transparent.hugepages +sys.virtual.mem.nr.free_cma +sys.virtual.mem.nr.swapcache +sys.virtual.mem.nr.dirty.threshold +sys.virtual.mem.nr.dirty.background.threshold +sys.virtual.mem.vmeminfo.pgpgin +sys.virtual.mem.pgpgout +sys.virtual.mem.pgpgoutclean +sys.virtual.mem.pswpin +sys.virtual.mem.pswpout +sys.virtual.mem.pgalloc.dma +sys.virtual.mem.pgalloc.normal +sys.virtual.mem.pgalloc.movable +sys.virtual.mem.pgfree +sys.virtual.mem.pgactivate +sys.virtual.mem.pgdeactivate +sys.virtual.mem.pgfault +sys.virtual.mem.pgmajfault +sys.virtual.mem.pgrefill.dma +sys.virtual.mem.pgrefill.normal +sys.virtual.mem.pgrefill.movable +sys.virtual.mem.pgsteal.kswapd.dma +sys.virtual.mem.pgsteal.kswapd.normal +sys.virtual.mem.pgsteal.kswapd.movable +sys.virtual.mem.pgsteal.direct.dma +sys.virtual.mem.pgsteal.direct.normal +sys.virtual.mem.pgsteal_direct.movable +sys.virtual.mem.pgscan.kswapd.dma +sys.virtual.mem.pgscan_kswapd.normal +sys.virtual.mem.pgscan.kswapd.movable +sys.virtual.mem.pgscan.direct.dma +sys.virtual.mem.pgscan.direct.normal +sys.virtual.mem.pgscan.direct.movable +sys.virtual.mem.pgscan.direct.throttle +sys.virtual.mem.pginodesteal +sys.virtual.mem.slabs_scanned +sys.virtual.mem.kswapd.inodesteal +sys.virtual.mem.kswapd.low.wmark.hit.quickly +sys.virtual.mem.high.wmark.hit.quickly +sys.virtual.mem.pageoutrun +sys.virtual.mem.allocstall +sys.virtual.mem.pgrotated +sys.virtual.mem.drop.pagecache +sys.virtual.mem.drop.slab +sys.virtual.mem.pgmigrate.success +sys.virtual.mem.pgmigrate.fail +sys.virtual.mem.compact.migrate.scanned +sys.virtual.mem.compact.free.scanned +sys.virtual.mem.compact.isolated +sys.virtual.mem.compact.stall +sys.virtual.mem.compact.fail +sys.virtual.mem.compact.success +sys.virtual.mem.compact.daemon.wake +sys.virtual.mem.unevictable.pgs.culled +sys.virtual.mem.unevictable.pgs.scanned +sys.virtual.mem.unevictable.pgs.rescued +sys.virtual.mem.unevictable.pgs.mlocked +sys.virtual.mem.unevictable.pgs.munlocked +sys.virtual.mem.unevictable.pgs.cleared +sys.virtual.mem.unevictable.pgs.stranded +sys.virtual.mem.nr.zspages +sys.virtual.mem.nr.ion.heap +sys.virtual.mem.nr.gpu.heap +sys.virtual.mem.allocstall.dma +sys.virtual.mem.allocstall.movable +sys.virtual.mem.allocstall.normal +sys.virtual.mem.compact_daemon.free.scanned +sys.virtual.mem.compact.daemon.migrate.scanned +sys.virtual.mem.nr.fastrpc +sys.virtual.mem.nr.indirectly.reclaimable +sys.virtual.mem.nr_ion_heap_pool +sys.virtual.mem.nr.kernel_misc.reclaimable +sys.virtual.mem.nr.shadow_call.stack_bytes +sys.virtual.mem.nr.shmem.hugepages +sys.virtual.mem.nr.shmem.pmdmapped +sys.virtual.mem.nr.unreclaimable.pages +sys.virtual.mem.nr.zone.active.anon +sys.virtual.mem.nr.zone.active.file +ys.virtual.mem.nr.zone.inactive_anon +sys.virtual.mem.nr.zone.inactive_file +sys.virtual.mem.nr.zone.unevictable +sys.virtual.mem.nr.zone.write_pending +sys.virtual.mem.oom.kill +sys.virtual.mem.pglazyfree +sys.virtual.mem.pglazyfreed +sys.virtual.mem.pgrefill +sys.virtual.mem.pgscan.direct +sys.virtual.mem.pgscan.kswapd +sys.virtual.mem.pgskip.dma +sys.virtual.mem.pgskip.movable +sys.virtual.mem.pgskip.normal +sys.virtual.mem.pgsteal.direct +sys.virtual.mem.pgsteal.kswapd +sys.virtual.mem.swap.ra +sys.virtual.mem.swap.ra.hit +``` + +## trace_streamer开发环境搭建和编译运行指引 + +本应用使用gn作为构建工具,支持在linux环境同时编译linux,windows和mac使用QtCreator作为开发IDE +### 1、开发环境 +ubuntu使用vscode,windows和mac使用QtCreator +# 对外部的依赖 +本应用依赖与sqlite,protobuf(htrace解析部分依赖) + +本应用同时依赖于src/protos目录下文件来生成相关pb.h,pb.cc文件 + +### 2.1、 编译linux版应用 +在根目录下执行相关命令进行编译 + +### 2.2、编译Windows版和Mac应用 +在项目目录下有pro文件,为QtCreator的工程文件,但部分内容赖在于上面所添加的外部依赖,如果要编译相关平台应用,开发者需自行补充相关工程文件,或者在论坛留言 + +### 2.3、开始编译 +具体方法可参考《compile_trace_streamer.md》 \ No newline at end of file diff --git a/host/trace_streamer/build.sh b/host/trace_streamer/build.sh new file mode 100644 index 0000000..9e183ee --- /dev/null +++ b/host/trace_streamer/build.sh @@ -0,0 +1,119 @@ +#! /bin/bash +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +PARAMS=$* +echo $PARAMS +echo "begin to check input" +target_os='linux' +is_debug='false' +is_clean='false' +if [ "$#" -ne "0" ];then + if [ $1 == "wasm" ];then + if [ ! -d "prebuilts/emsdk" ];then + echo "you need emsdk to compile wasm" + mv emsdk.tar.gz prebuilts/ + mv ../emsdk.tar.gz prebuilts/ + if [ ! -f "prebuilts/emsdk.tar.gz" ];then + # consider + # you need to get emsdk.tar.gz some where + mv emsdk.tar.gz prebuilts/ + tar -zxvf prebuilts/emsdk.tar.gz -C prebuilts/ + else + tar -zxvf prebuilts/emsdk.tar.gz -C prebuilts/ + fi + fi + target_os='wasm' + fi + if [ $1 == "test" ];then + target_os='test' + fi +fi +if [ "$#" -eq "2" ];then + if [ "$1" != 'windows' ] && [ $1 != "linux" ] && [ $1 != "wasm" ] && [ $1 != "test" ];then + echo "failed" + echo "Usage: `basename $0` windows/linux/wasm/test debug/release/clean" + exit + fi + if [ $2 != "debug" -a $2 != "release" -a $2 != "clean" ];then + echo "failed" + echo "Usage: `basename $0` windows/linux debug/release/clean" + exit + fi + if [ $2 == "debug" ];then + is_debug='true' + elif [ $2 == "clean" ];then + is_clean='true' + else + is_debug='false' + fi + target_os=$1 + if [ $target_os == "windows" ];then + echo "gn only support linux and wasm build currently" + mkdir out/windows + touch out/windows/trace_streamer.exe + exit + fi + echo "platform is $target_os" + echo "isdebug: $is_debug" +else + echo "Usage: `basename $0` windows/linux/wasm debug/release wasm[optional]" + echo "You provided $# parameters,but 2 are required." + echo "use default input paramter" + echo "platform is $target_os" + echo "is_debug:$is_debug" +fi +echo "gen ..." +ext="" +if [ "$is_debug" != 'false' ];then + ext="_debug" +fi +#exec "protogen.sh" +echo "the output file will be at ""$prefix""$target_os" +echo "" +echo "" +echo "-------------tips-------------" +echo "" +echo "if you are compiling first time, or your proto has changed, you need to run ./src/protos/protogen.sh" +echo "" +echo "" +echo +#./src/protos/protogen.sh +mkdir prebuilts/$target_os +if [ ! -f "prebuilts/$target_os/gn" ];then + echo "you may get gn for $target_os and place it in prebuilts/$target_os" + ehco "the file can be get at https://gitee.com/su_fu/public_tools/raw/master/gn/$target_os/gn, you need to download it manually" + #wget https://gitee.com/su_fu/public_tools/raw/master/gn/$target_os/gn + #mv gn prebuilts/$target_os/ + #chmod +x prebuilts/$target_os/gn + exit +fi +if [ ! -f "prebuilts/$target_os/ninja" ];then + echo "you may get ninja for $target_os and place it in prebuilts/$target_os" + ehco "the file can be get at https://gitee.com/su_fu/public_tools/raw/master/gn/$target_os/ninja, you need to download it manually" + #wget "https://gitee.com/su_fu/public_tools/raw/master/gn/$target_os/ninja" + #wget https://gitee.com/su_fu/public_tools/raw/master/gn/$target_os/ninja + #mv ninja prebuilts/$target_os/ + #chmod +x prebuilts/$target_os/* + exit +fi +echo "$is_clean" +if [ "$is_clean" == 'true' ];then + prebuilts/$target_os/gn gen out/"$target_os""$ext" --clean + prebuilts/$target_os/ninja -C out/"$target_os""$ext" -t clean +else + prebuilts/$target_os/gn gen out/"$target_os""$ext" --args='is_debug='"$is_debug"' target_os="'"$target_os"'"' + echo "begin to build ..." + mkdir -p out/windows + touch out/windows/trace_streamer.exe + prebuilts/$target_os/ninja -v -C out/"$target_os""$ext" +fi diff --git a/host/trace_streamer/build/ohos.gni b/host/trace_streamer/build/ohos.gni new file mode 100644 index 0000000..2f3a1d5 --- /dev/null +++ b/host/trace_streamer/build/ohos.gni @@ -0,0 +1,118 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +template("ohos_source_set") { + source_set(target_name) { + sources = invoker.sources + if (defined(invoker.configs)) { + configs = invoker.configs + } + if (defined(invoker.cflags)) { + cflags = invoker.cflags + } + if (defined(invoker.include_dirs)) { + include_dirs = invoker.include_dirs + } + if (defined(invoker.deps)) { + deps = invoker.deps + } + if (defined(invoker.cflags_cc)) { + cflags_cc = invoker.cflags_cc + } + if (defined(invoker.ldflags)) { + ldflags = invoker.ldflags + } + if (defined(invoker.public_deps)) { + public_deps = invoker.public_deps + } + if (defined(invoker.public_configs)) { + public_configs = invoker.public_configs + } + } +} +template("ohos_shared_library") { + shared_library(target_name) { + sources = invoker.sources + if (defined(invoker.configs)) { + configs = invoker.configs + } + public_configs = invoker.public_configs + if (defined(invoker.defines)) { + defines = invoker.defines + } + if (defined(invoker.cflags)) { + cflags = invoker.cflags + } + if (defined(invoker.include_dirs)) { + include_dirs = invoker.include_dirs + } + if (defined(invoker.cflags_cc)) { + cflags_cc = invoker.cflags_cc + } + if (defined(invoker.deps)) { + deps = invoker.deps + } + } +} +template("ohos_static_library") { + static_library(target_name) { + sources = invoker.sources + if (defined(invoker.configs)) { + configs = invoker.configs + } + public_configs = invoker.public_configs + if (defined(invoker.defines)) { + defines = invoker.defines + } + if (defined(invoker.cflags)) { + cflags = invoker.cflags + } + if (defined(invoker.include_dirs)) { + include_dirs = invoker.include_dirs + } + if (defined(invoker.cflags_cc)) { + cflags_cc = invoker.cflags_cc + } + if (defined(invoker.deps)) { + deps = invoker.deps + } + } +} +template("ohos_executable") { + executable(target_name) { + if (defined(invoker.include_dirs)) { + include_dirs = invoker.include_dirs + } + sources = invoker.sources + if (defined(invoker.defines)) { + defines = invoker.defines + } + if (defined(invoker.cflags)) { + cflags = invoker.cflags + } + if (defined(invoker.deps)) { + deps = invoker.deps + } + if (defined(invoker.cflags_cc)) { + cflags_cc = invoker.cflags_cc + } + if (defined(invoker.output_name)) { + output_name = invoker.output_name + } + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + if (defined(invoker.ohos_test)) { + ohos_test = invoker.ohos_test + } + } +} diff --git a/host/trace_streamer/build/protoc.sh b/host/trace_streamer/build/protoc.sh new file mode 100644 index 0000000..8012b22 --- /dev/null +++ b/host/trace_streamer/build/protoc.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e +#echo "begin to protoc--------------" +LIBCXX_X64_OUT="//out/linux" +PROTOC="//out/linux/protoc" +PARAMS=$* +PARAMS_FILTER="$1 $2" +#echo "EXEC: LD_LIBRARY_PATH=$LIBCXX_X64_OUT:$PROTOC ${PARAMS[@]:${#PARAMS_FILTER}}" +LD_LIBRARY_PATH=$LIBCXX_X64_OUT exec $PROTOC ${PARAMS[@]:${#PARAMS_FILTER}} diff --git a/host/trace_streamer/build/test.gni b/host/trace_streamer/build/test.gni new file mode 100644 index 0000000..6912354 --- /dev/null +++ b/host/trace_streamer/build/test.gni @@ -0,0 +1,25 @@ +#!/bin/bash +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +template("ohos_unittest") { + if (defined(invoker.ldflags)) { + print(invoker.ldflags) + } + executable(target_name) { + forward_variables_from(invoker, "*") + testonly = true + if (defined(invoker.ldflags)) { + print(invoker.ldflags) + } + } +} diff --git a/host/trace_streamer/doc/README.md b/host/trace_streamer/doc/README.md new file mode 100644 index 0000000..eb0deec --- /dev/null +++ b/host/trace_streamer/doc/README.md @@ -0,0 +1,309 @@ +# trace_streamer工具说明 +trace_streamer工具可以2种方式使用 +1. 可以将系统离线trace文件解析并转为db,此工具支持基于文本的trace和基于proto的trace。 +2. trace_streamer工具还可以WebAssembly的方式在浏览器中运行,需暴露相关接口给js文件。 + +## 关于trace解析工具的使用说明: +### 导出db模式 +在导出db模式下,trace_streamer.exe trace文件路径名 -e 导出db路径名.db +此命令可以将trace文件转为db +本应用支持在ohos, linux, windows, mac使用。 +关于db文件的说明: +使用db查看工具查看stat表,可以浏览当前数据一共有多少类数据,各类数据都收到多少条,数据是否正常等情况。在meta表会记录数据库导出时的一些系统信息,比如导入和导出的文件全路径,解析时间等信息。 +meta表可以选择不导出(有些情况下会暴露系统敏感信息),在导出时添加 -nm选项即可。 +在数据导出之后,会在本地目录下生成一个trace_streamer.log文件,在导出db的目录下生成一个数据库文件同名,.db.ohos.ts后缀的文件 +文件内容如下: +时间戳:执行结果(数字) +应用运行时间 + +执行结果解释如下:0 代表执行成功 1 表示输入文件不匹配, 2 表示解析错误, 3其他错误 +### 内置浏览器方式 +trace_streamer可以WebAssembly方式在浏览器中运行,暴露如下接口给js +``` +extern "C" { +/* 上传trace数据 + * + * @data: 数据的缓冲区 + * @dataLen: 数据长度 + * + * return: 0:成功; -1:失败 +*/ +EMSCRIPTEN_KEEPALIVE int TraceStreamerParseData(const uint8_t* data, int dataLen); + +/* 通知TS上传trace数据结束 + * + * return: 0:成功; -1:失败 +*/ +EMSCRIPTEN_KEEPALIVE int TraceStreamerParseDataOver(); + +/* 通过sql语句操作数据库 + * + * @sql: sql语句 + * @sqlLen: sql语句长度 + * + * return: 0:成功; -1:失败 +*/ +EMSCRIPTEN_KEEPALIVE int TraceStreamerSqlOperate(const uint8_t* sql, int sqlLen); + +/* 通过sql语句查询数据库 + * + * @sql: sql语句 + * @sqlLen: sql语句长度 + * @out: 查询结果的缓冲区,查询结果为json + * @outLen: 缓冲区长度 + * + * return: >0:查询成功,返回查询结果数据长度; -1:失败 +*/ +EMSCRIPTEN_KEEPALIVE int TraceStreamerSqlQuery(const uint8_t* sql, int sqlLen, uint8_t* out, int outLen); + +} // extern "C" +``` + +### 你也可以执行如下命令查看应用帮助 +./trace_streamer --help +-i 选项可查看应用支持的事件源和具体的事件名列表 + +#### trace_streamer支持的事件解析 +本工具支持基于文本的trace(# TRACE)和基于proto的二进制日志文件的解析,支持的事件列表如下: +##### ftrace事件 +``` +binder_transaction +binder_transaction_received +binder_transaction_alloc_buf +binder_transaction_lock +binder_transaction_locked +binder_transaction_unlock +sched_switch +task_rename +task_newtask +tracing_mark_write +print +sched_wakeup +sched_waking +cpu_idle +cpu_frequency +suspend_resume +workqueue_execute_start +workqueue_execute_end +clock_set_rate +clock_enable +clock_disable +clk_set_rate +clk_enable +clk_disable +sys_enter +sys_exit +regulator_set_voltage +regulator_set_voltage_complete +regulator_disable +regulator_disable_complete +ipi_entry +ipi_exit +irq_handler_entry +irq_handler_exit +softirq_raise +softirq_entry +softirq_exit +sched_wakeup_new +sched_process_exit +trace_event_clock_sync +``` +##### 内存事件 +``` +mem.vm.size +mem.rss +mem.rss.anon +mem.rss.file +mem.rss.schem +mem.swap +mem.locked +mem.hwm +mem.oom_score_adj +``` +##### 系统内存事件 +``` +sys.mem.unspecified +sys.mem.total +sys.mem.free +sys.mem.avaiable +sys.mem.buffers +sys.mem.cached +sys.mem.swap.chard +sys.mem.active +sys.mem.inactive +sys.mem.active.anon +sys.mem.inactive.anon +sys.mem.active_file +sys.mem.inactive_file +sys.mem.unevictable +sys.mem.mlocked +sys.mem.swap.total +sys.mem.swap.free +sys.mem.dirty +sys.mem.writeback +sys.mem.anon.pages +sys.mem.mapped +sys.mem.shmem +sys.mem.slab +sys.mem.slab.reclaimable +sys.mem.slab.unreclaimable +sys.mem.kernel.stack +sys.mem.page.tables +sys.mem.commit.limit +sys.mem.commited.as +sys.mem.vmalloc.total +sys.mem.vmalloc.used +sys.mem.vmalloc.chunk +sys.mem.cma.total +sys.mem.cma.free +``` +##### 系统虚拟内存事件 +``` +sys.virtual.mem.unspecified +sys.virtual.mem.nr.free.pages +sys.virtual.mem.nr.alloc.batch +sys.virtual.mem.nr.inactive.anon +sys.virtual.mem.nr.active_anon +sys.virtual.mem.nr.inactive.file +sys.virtual.mem.nr.active_file +sys.virtual.mem.nr.unevictable +sys.virtual.mem.nr.mlock +sys.virtual.mem.anon.pages +sys.virtual.mem.nr.mapped +sys.virtual.mem.nr.file.pages +sys.virtual.mem.nr.dirty +sys.virtual.mem.nr.writeback +sys.virtual.mem.nr.slab.reclaimable +sys.virtual.mem.nr.slab.unreclaimable +sys.virtual.mem.nr.page_table.pages +sys.virtual.mem.nr_kernel.stack +sys.virtual.mem.nr.overhead +sys.virtual.mem.nr.unstable +sys.virtual.mem.nr.bounce +sys.virtual.mem.nr.vmscan.write +sys.virtual.mem.nr.vmscan.immediate.reclaim +sys.virtual.mem.nr.writeback_temp +sys.virtual.mem.nr.isolated_anon +sys.virtual.mem.nr.isolated_file +sys.virtual.mem.nr.shmem +sys.virtual.mem.nr.dirtied +sys.virtual.mem.nr.written +sys.virtual.mem.nr.pages.scanned +sys.virtual.mem.workingset.refault +sys.virtual.mem.workingset.activate +sys.virtual.mem.workingset_nodereclaim +sys.virtual.mem.nr_anon.transparent.hugepages +sys.virtual.mem.nr.free_cma +sys.virtual.mem.nr.swapcache +sys.virtual.mem.nr.dirty.threshold +sys.virtual.mem.nr.dirty.background.threshold +sys.virtual.mem.vmeminfo.pgpgin +sys.virtual.mem.pgpgout +sys.virtual.mem.pgpgoutclean +sys.virtual.mem.pswpin +sys.virtual.mem.pswpout +sys.virtual.mem.pgalloc.dma +sys.virtual.mem.pgalloc.normal +sys.virtual.mem.pgalloc.movable +sys.virtual.mem.pgfree +sys.virtual.mem.pgactivate +sys.virtual.mem.pgdeactivate +sys.virtual.mem.pgfault +sys.virtual.mem.pgmajfault +sys.virtual.mem.pgrefill.dma +sys.virtual.mem.pgrefill.normal +sys.virtual.mem.pgrefill.movable +sys.virtual.mem.pgsteal.kswapd.dma +sys.virtual.mem.pgsteal.kswapd.normal +sys.virtual.mem.pgsteal.kswapd.movable +sys.virtual.mem.pgsteal.direct.dma +sys.virtual.mem.pgsteal.direct.normal +sys.virtual.mem.pgsteal_direct.movable +sys.virtual.mem.pgscan.kswapd.dma +sys.virtual.mem.pgscan_kswapd.normal +sys.virtual.mem.pgscan.kswapd.movable +sys.virtual.mem.pgscan.direct.dma +sys.virtual.mem.pgscan.direct.normal +sys.virtual.mem.pgscan.direct.movable +sys.virtual.mem.pgscan.direct.throttle +sys.virtual.mem.pginodesteal +sys.virtual.mem.slabs_scanned +sys.virtual.mem.kswapd.inodesteal +sys.virtual.mem.kswapd.low.wmark.hit.quickly +sys.virtual.mem.high.wmark.hit.quickly +sys.virtual.mem.pageoutrun +sys.virtual.mem.allocstall +sys.virtual.mem.pgrotated +sys.virtual.mem.drop.pagecache +sys.virtual.mem.drop.slab +sys.virtual.mem.pgmigrate.success +sys.virtual.mem.pgmigrate.fail +sys.virtual.mem.compact.migrate.scanned +sys.virtual.mem.compact.free.scanned +sys.virtual.mem.compact.isolated +sys.virtual.mem.compact.stall +sys.virtual.mem.compact.fail +sys.virtual.mem.compact.success +sys.virtual.mem.compact.daemon.wake +sys.virtual.mem.unevictable.pgs.culled +sys.virtual.mem.unevictable.pgs.scanned +sys.virtual.mem.unevictable.pgs.rescued +sys.virtual.mem.unevictable.pgs.mlocked +sys.virtual.mem.unevictable.pgs.munlocked +sys.virtual.mem.unevictable.pgs.cleared +sys.virtual.mem.unevictable.pgs.stranded +sys.virtual.mem.nr.zspages +sys.virtual.mem.nr.ion.heap +sys.virtual.mem.nr.gpu.heap +sys.virtual.mem.allocstall.dma +sys.virtual.mem.allocstall.movable +sys.virtual.mem.allocstall.normal +sys.virtual.mem.compact_daemon.free.scanned +sys.virtual.mem.compact.daemon.migrate.scanned +sys.virtual.mem.nr.fastrpc +sys.virtual.mem.nr.indirectly.reclaimable +sys.virtual.mem.nr_ion_heap_pool +sys.virtual.mem.nr.kernel_misc.reclaimable +sys.virtual.mem.nr.shadow_call.stack_bytes +sys.virtual.mem.nr.shmem.hugepages +sys.virtual.mem.nr.shmem.pmdmapped +sys.virtual.mem.nr.unreclaimable.pages +sys.virtual.mem.nr.zone.active.anon +sys.virtual.mem.nr.zone.active.file +ys.virtual.mem.nr.zone.inactive_anon +sys.virtual.mem.nr.zone.inactive_file +sys.virtual.mem.nr.zone.unevictable +sys.virtual.mem.nr.zone.write_pending +sys.virtual.mem.oom.kill +sys.virtual.mem.pglazyfree +sys.virtual.mem.pglazyfreed +sys.virtual.mem.pgrefill +sys.virtual.mem.pgscan.direct +sys.virtual.mem.pgscan.kswapd +sys.virtual.mem.pgskip.dma +sys.virtual.mem.pgskip.movable +sys.virtual.mem.pgskip.normal +sys.virtual.mem.pgsteal.direct +sys.virtual.mem.pgsteal.kswapd +sys.virtual.mem.swap.ra +sys.virtual.mem.swap.ra.hit +``` + +## trace_streamer开发环境搭建和编译运行指引 + +本应用使用gn作为构建工具,支持在linux环境同时编译linux,windows和mac使用QtCreator作为开发IDE +### 1、开发环境 +ubuntu使用vscode,windows和mac使用QtCreator +# 对外部的依赖 +本应用依赖与sqlite,protobuf(htrace解析部分依赖) + +本应用同时依赖于src/protos目录下文件来生成相关pb.h,pb.cc文件 + +### 2.1、 编译linux版应用 +在根目录下执行相关命令进行编译 + +### 2.2、编译Windows版和Mac应用 +在项目目录下有pro文件,为QtCreator的工程文件,但部分内容赖在于上面所添加的外部依赖,如果要编译相关平台应用,开发者需自行补充相关工程文件,或者在论坛留言 + +### 2.3、开始编译 +具体方法可参考《compile_trace_streamer.md》 \ No newline at end of file diff --git a/host/trace_streamer/doc/des_compile_trace_streamer.md b/host/trace_streamer/doc/des_compile_trace_streamer.md new file mode 100644 index 0000000..dd1f00f --- /dev/null +++ b/host/trace_streamer/doc/des_compile_trace_streamer.md @@ -0,0 +1,133 @@ +# 如何独立编译Trace_streamer +尽管本工具(trace_streamer)是在ohos工具箱中的一员,但你依然可以独立编译此工具。 + +本工具可以编译linux, mac, windows, WebAssembly版本。 + +本工具默认编译方式是使用gn ++ 编译方式 +``` +./build.sh linux/wasm +``` +如果需要编译WebAssembly版本,您需要在prebuilts/目录下安装emsdk +``` +git clone https://github.com/juj/emsdk.git --depth=1 +cd emsdk +git pull +./emsdk update # this may not work, ignore it +./emsdk install latest +./emsdk activate latest +安装之后,您需要将upstream目录复制到prebuilts/emsdk/emsdk,node复制到prebuilts/emsdk/node +``` +安装之后,目录结构当如: +``` +prebuilts/emsdk +├── prebuilts/emsdk/emsdk +│ ├── prebuilts/emsdk/emsdk/bin +│ ├── prebuilts/emsdk/emsdk/emscripten +│ │ ├── prebuilts/emsdk/emsdk/emscripten/cache +│ │ ├── prebuilts/emsdk/emsdk/emscripten/cmake +│ │ ├── prebuilts/emsdk/emsdk/emscripten/docs +│ │ ├── prebuilts/emsdk/emsdk/emscripten/media +│ │ ├── prebuilts/emsdk/emsdk/emscripten/node_modules +│ │ ├── prebuilts/emsdk/emsdk/emscripten/__pycache__ +│ │ ├── prebuilts/emsdk/emsdk/emscripten/src +│ │ ├── prebuilts/emsdk/emsdk/emscripten/system +│ │ ├── prebuilts/emsdk/emsdk/emscripten/tests +│ │ ├── prebuilts/emsdk/emsdk/emscripten/third_party +│ │ └── prebuilts/emsdk/emsdk/emscripten/tools +│ ├── prebuilts/emsdk/emsdk/include +│ │ └── prebuilts/emsdk/emsdk/include/c++ +│ └── prebuilts/emsdk/emsdk/lib +│ └── prebuilts/emsdk/emsdk/lib/clang +└── prebuilts/emsdk/node + └── prebuilts/emsdk/node/14.18.2_64bit + ├── prebuilts/emsdk/node/14.18.2_64bit/bin + ├── prebuilts/emsdk/node/14.18.2_64bit/include + ├── prebuilts/emsdk/node/14.18.2_64bit/lib + └── prebuilts/emsdk/node/14.18.2_64bit/share +``` +之后调用 +``` +./build.sh wasm进行编译,您需要将sh脚本进行部分修改,因为这个脚本内置了一些库的下载和解析方式 +``` +本工具还支持使用QtCreator来编译。 + +src/trace_streamer.pro 是工程文件,编译本工具需要依赖Sqlite库和一些基于proto的pb.h文件 +## 2 准备工程 +### 2.1 基于proto文件生成pb文件 +您需要自行下载并编译一个当前系统(linux)可用的proobuf/protoc程序,此全路径为位于out/linux/protoc +src/protos目录下有一个protogen.sh文件,运行该文件可以在third_party/protogen目录下生成项目需要的pb相关文件 +序列化二进制的解析依赖于基于proto生成的.pb.cc文件。 +在执行protogen.sh脚本之后 +你的目录结构当类似如下结构: +``` +third_party/protogen/types/plugins/ftrace_data/*.pb.cc +third_party/sqlite/*. +third_party/protobuf/* +``` +### 2.2 获取第三方依赖库 +从 +https://gitee.com/openharmony/third_party_sqlite +获取sqlite3目录到代码根目录的third_party目录 +从 +https://gitee.com/openharmony/third_party_protobuf +获取protobuf目录到代码根目录的third_party目录 +之后,你的目录当如下所示 +trace_streamer/third_party/protobuf +trace_streamer/third_party/sqlite +# 3 (linux和ohos平台)使用gn编译TraceStreamer +在编译WebAssembly目标时,需要将sqlite3和protobuf里面相关的ohos_xxx_library统一修改为source_set +## 3.2 准备gn +在自己的项目中使用gn,必须遵循以下要求: +在根目录创建.gn文件,该文件用于指定CONFIG.gn文件的位置; +在BUILDCONFIG.gn中指定编译时使用的编译工具链; +在独立的gn文件中定义编译使用的工具链; +在项目根目录下创建BUILD.gn文件,指定编译的目标。 +``` +cp prebuilts/gn ./ +``` +不同的操作系统下,你需要获取不同的gn +## 3.3 执行编译 +./build.sh linux debug +或./build.sh linux debug +./build.sh将直接编译linux的release版本 +build.sh wasm 命令将可以编译WebAssembly版本 +特别说明:编译WebAssembly版本需要emSDK支持,你需要将build.sh里面的相关路径做更改,以保证编译时必须的文件是存在的 +# 4 编译Windows版本或Mac版本 +## 4.1 编译依赖文件 +### 4.1.1 编译SqliteLib +使用QtCreator打开prebuiltsprebuilts/buildprotobuf/sqlite.pro +### 4.1.2 编译ProtobufLib +使用QtCreator打开prebuilts/buildprotobuf/protobuf.pro +编译之后,文件结构当如下所示: +``` +lib +├── linux +│ ├── libdl.so +│ └── libsqlite.a +├── linux_debug +│ ├── libprotobuf.a +│ └── libsqlite.a +├── macx +│ ├── libprotobuf.a +│ └── libsqlite.a +├── macx_debug +│ ├── libprotobuf.a +│ └── libsqlite.a +├── windows +│ ├── libprotobuf.a +│ └── libsqlite.a +└── windows_debug + ├── libprotobuf.a + └── libsqlite.a +``` +## 4.2 编译TraceStreamer +之后,使用QtCreator打开src/trace_streamer.pro,选择合适的构建工具,执行 Ctrl + b 即可编译 + +编译之后的可执行文件位于out目录 +``` +- out +---- linux (Linux平台下QtCreator或gn生成) +---- macx (mac平台下QtCreator或gn生成) +---- windows (windows平台下QtCreator或gn生成) +``` \ No newline at end of file diff --git a/host/trace_streamer/doc/des_stat.md b/host/trace_streamer/doc/des_stat.md new file mode 100644 index 0000000..e1048f5 --- /dev/null +++ b/host/trace_streamer/doc/des_stat.md @@ -0,0 +1,413 @@ +# TraceStreamer 解析数据状态表 +TraceStreamer使用stat表统计解析trace数据源过程遇到的重要事件状态。通过stat表可以对trace数据源中各个类型事件的数据有一个基本了解。 +## stat表支持统计的事件列表如下: +|event_name | +| ---- | +|binder_transaction | +|binder_transaction_alloc_buf | +|binder_transaction_lock | +|binder_transaction_locked | +|binder_transaction_received | +|binder_transaction_unlock | +|clk_disable | +|clk_enable | +|clk_set_rate | +|clock_disable | +|clock_enable | +|clock_set_rate | +|cpu_frequency | +|cpu_idle | +|hidump_fps | +|hilog | +|ipi_entry | +|ipi_exit | +|irq_handler_entry | +|irq_handler_exit | +|memory | +|native_hook_free | +|native_hook_malloc | +|oom_score_adj_update | +|other | +|print | +|regulator_disable | +|regulator_disable_complete | +|regulator_set_voltage | +|regulator_set_voltage_complete | +|sched_process_exit | +|sched_process_free | +|sched_switch | +|sched_wakeup | +|sched_wakeup_new | +|sched_waking | +|signal_deliver | +|signal_generate | +|softirq_entry | +|softirq_exit | +|softirq_raise | +|suspend_resume | +|sys_enter | +|sys_exit | +|sys_memory | +|sys_virtual_memory | +|task_newtask | +|task_rename | +|trace_bblock_bio_queue | +|trace_block_bio_backmerge | +|trace_block_bio_bounce | +|trace_block_bio_complete | +|trace_block_bio_frontmerge | +|trace_block_bio_remap | +|trace_block_dirty_buffer | +|trace_block_getrq | +|trace_block_plug | +|trace_block_rq_complete | +|trace_block_rq_insert | +|trace_block_rq_issue | +|trace_block_rq_remap | +|trace_event_clock_sync | +|tracing_mark_write | +|workqueue_execute_end | +|workqueue_execute_start | + +## 事件对应解析状态: +每种事件解析数据都有5种状态,描述如下表: +|stat_type|description| +|---- |---- | +|received | 统计trace数据源中总共有多少该事件。| +|data_lost | 统计TraceStreamer解析过程中发现丢失数据条数。 | +|not_match | 统计有多少数据与上下文其他数据不匹配。 | +|not_supported | 统计有多少暂不支持解析该事件(一个事件可能包含多种类型的子事件, TraceStreamer可能支持该事件的一部分子事件)。| +|invalid_data | 统计收到多少条该事件的非法数据。| + +## 数据状态级别 +数据状态级别总共有4种,分别是:info, warn, error,fatal。由于数据的重要性不同,不同事件的同一种状态可能对应不同的级别。 +例如binder_transaction_received的 not_supported状态的数据为info级别,而binder_transaction_alloc_buf的not_supported状态数据为warn级别。 + +## 事件,状态与级别对应关系 +| event_name | stat_type | serverity | +|---- |---- |---- | +| binder_transaction | received | info | +| binder_transaction | data_lost | error | +| binder_transaction | not_match | info | +| binder_transaction | not_supported | info | +| binder_transaction | invalid_data | error | +| binder_transaction_received | received | info | +| binder_transaction_received | data_lost | error | +| binder_transaction_received | not_match | info | +| binder_transaction_received | not_supported | info | +| binder_transaction_received | invalid_data | error | +| binder_transaction_alloc_buf | received | info | +| binder_transaction_alloc_buf | data_lost | error | +| binder_transaction_alloc_buf | not_match | info | +| binder_transaction_alloc_buf | not_supported | warn | +| binder_transaction_alloc_buf | invalid_data | error | +| binder_transaction_lock | received | info | +| binder_transaction_lock | data_lost | error | +| binder_transaction_lock | not_match | info | +| binder_transaction_lock | not_supported | warn | +| binder_transaction_lock | invalid_data | error | +| binder_transaction_locked | received | info | +| binder_transaction_locked | data_lost | error | +| binder_transaction_locked | not_match | info | +| binder_transaction_locked | not_supported | warn | +| binder_transaction_locked | invalid_data | error | +| binder_transaction_unlock | received | info | +| binder_transaction_unlock | data_lost | error | +| binder_transaction_unlock | not_match | info | +| binder_transaction_unlock | not_supported | warn | +| binder_transaction_unlock | invalid_data | error | +| sched_switch | received | info | +| sched_switch | data_lost | error | +| sched_switch | not_match | info | +| sched_switch | not_supported | info | +| sched_switch | invalid_data | error | +| task_rename | received | info | +| task_rename | data_lost | error | +| task_rename | not_match | info | +| task_rename | not_supported | info | +| task_rename | invalid_data | error | +| task_newtask | received | info | +| task_newtask | data_lost | error | +| task_newtask | not_match | info | +| task_newtask | not_supported | info | +| task_newtask | invalid_data | error | +| tracing_mark_write | received | info | +| tracing_mark_write | data_lost | error | +| tracing_mark_write | not_match | info | +| tracing_mark_write | not_supported | info | +| tracing_mark_write | invalid_data | error | +| print | received | info | +| print | data_lost | error | +| print | not_match | info | +| print | not_supported | info | +| print | invalid_data | error | +| sched_wakeup | received | info | +| sched_wakeup | data_lost | error | +| sched_wakeup | not_match | info | +| sched_wakeup | not_supported | info | +| sched_wakeup | invalid_data | error | +| sched_waking | received | info | +| sched_waking | data_lost | error | +| sched_waking | not_match | info | +| sched_waking | not_supported | info | +| sched_waking | invalid_data | error | +| cpu_idle | received | info | +| cpu_idle | data_lost | error | +| cpu_idle | not_match | info | +| cpu_idle | not_supported | info | +| cpu_idle | invalid_data | error | +| cpu_frequency | received | info | +| cpu_frequency | data_lost | error | +| cpu_frequency | not_match | info | +| cpu_frequency | not_supported | info | +| cpu_frequency | invalid_data | error | +| suspend_resume | received | info | +| suspend_resume | data_lost | error | +| suspend_resume | not_match | info | +| suspend_resume | not_supported | info | +| suspend_resume | invalid_data | error | +| workqueue_execute_start | received | info | +| workqueue_execute_start | data_lost | error | +| workqueue_execute_start | not_match | info | +| workqueue_execute_start | not_supported | info | +| workqueue_execute_start | invalid_data | error | +| workqueue_execute_end | received | info | +| workqueue_execute_end | data_lost | error | +| workqueue_execute_end | not_match | info | +| workqueue_execute_end | not_supported | warn | +| workqueue_execute_end | invalid_data | error | +| clock_set_rate | received | info | +| clock_set_rate | data_lost | error | +| clock_set_rate | not_match | info | +| clock_set_rate | not_supported | warn | +| clock_set_rate | invalid_data | error | +| clock_enable | received | info | +| clock_enable | data_lost | error | +| clock_enable | not_match | info | +| clock_enable | not_supported | warn | +| clock_enable | invalid_data | error | +| clock_disable | received | info | +| clock_disable | data_lost | error | +| clock_disable | not_match | info | +| clock_disable | not_supported | warn | +| clock_disable | invalid_data | error | +| clk_set_rate | received | info | +| clk_set_rate | data_lost | error | +| clk_set_rate | not_match | info | +| clk_set_rate | not_supported | warn | +| clk_set_rate | invalid_data | error | +| clk_enable | received | info | +| clk_enable | data_lost | error | +| clk_enable | not_match | info | +| clk_enable | not_supported | warn | +| clk_enable | invalid_data | error | +| clk_disable | received | info | +| clk_disable | data_lost | error | +| clk_disable | not_match | info | +| clk_disable | not_supported | warn | +| clk_disable | invalid_data | error | +| sys_enter | received | info | +| sys_enter | data_lost | error | +| sys_enter | not_match | info | +| sys_enter | not_supported | warn | +| sys_enter | invalid_data | error | +| sys_exit | received | info | +| sys_exit | data_lost | error | +| sys_exit | not_match | info | +| sys_exit | not_supported | warn | +| sys_exit | invalid_data | error | +| regulator_set_voltage | received | info | +| regulator_set_voltage | data_lost | error | +| regulator_set_voltage | not_match | info | +| regulator_set_voltage | not_supported | warn | +| regulator_set_voltage | invalid_data | error | +| regulator_set_voltage_complete | received | info | +| regulator_set_voltage_complete | data_lost | error | +| regulator_set_voltage_complete | not_match | info | +| regulator_set_voltage_complete | not_supported | warn | +| regulator_set_voltage_complete | invalid_data | error | +| regulator_disable | received | info | +| regulator_disable | data_lost | error | +| regulator_disable | not_match | info | +| regulator_disable | not_supported | warn | +| regulator_disable | invalid_data | error | +| regulator_disable_complete | received | info | +| regulator_disable_complete | data_lost | error | +| regulator_disable_complete | not_match | info | +| regulator_disable_complete | not_supported | warn | +| regulator_disable_complete | invalid_data | error | +| ipi_entry | received | info | +| ipi_entry | data_lost | error | +| ipi_entry | not_match | info | +| ipi_entry | not_supported | warn | +| ipi_entry | invalid_data | error | +| ipi_exit | received | info | +| ipi_exit | data_lost | error | +| ipi_exit | not_match | info | +| ipi_exit | not_supported | warn | +| ipi_exit | invalid_data | error | +| irq_handler_entry | received | info | +| irq_handler_entry | data_lost | error | +| irq_handler_entry | not_match | info | +| irq_handler_entry | not_supported | warn | +| irq_handler_entry | invalid_data | error | +| irq_handler_exit | received | info | +| irq_handler_exit | data_lost | error | +| irq_handler_exit | not_match | info | +| irq_handler_exit | not_supported | warn | +| irq_handler_exit | invalid_data | error | +| softirq_raise | received | info | +| softirq_raise | data_lost | error | +| softirq_raise | not_match | info | +| softirq_raise | not_supported | warn | +| softirq_raise | invalid_data | error | +| softirq_entry | received | info | +| softirq_entry | data_lost | error | +| softirq_entry | not_match | info | +| softirq_entry | not_supported | warn | +| softirq_entry | invalid_data | error | +| softirq_exit | received | info | +| softirq_exit | data_lost | error | +| softirq_exit | not_match | info | +| softirq_exit | not_supported | warn | +| softirq_exit | invalid_data | error | +| oom_score_adj_update | received | info | +| oom_score_adj_update | data_lost | error | +| oom_score_adj_update | not_match | info | +| oom_score_adj_update | not_supported | warn | +| oom_score_adj_update | invalid_data | error | +| sched_wakeup_new | received | info | +| sched_wakeup_new | data_lost | error | +| sched_wakeup_new | not_match | info | +| sched_wakeup_new | not_supported | warn | +| sched_wakeup_new | invalid_data | error | +| sched_process_exit | received | info | +| sched_process_exit | data_lost | error | +| sched_process_exit | not_match | info | +| sched_process_exit | not_supported | warn | +| sched_process_exit | invalid_data | error | +| sched_process_free | received | info | +| sched_process_free | data_lost | error | +| sched_process_free | not_match | info | +| sched_process_free | not_supported | warn | +| sched_process_free | invalid_data | error | +| trace_event_clock_sync | received | info | +| trace_event_clock_sync | data_lost | error | +| trace_event_clock_sync | not_match | info | +| trace_event_clock_sync | not_supported | warn | +| trace_event_clock_sync | invalid_data | error | +| memory | received | info | +| memory | data_lost | error | +| memory | not_match | info | +| memory | not_supported | warn | +| memory | invalid_data | error | +| hilog | received | info | +| hilog | data_lost | error | +| hilog | not_match | info | +| hilog | not_supported | warn | +| hilog | invalid_data | error | +| hidump_fps | received | info | +| hidump_fps | data_lost | error | +| hidump_fps | not_match | info | +| hidump_fps | not_supported | warn | +| hidump_fps | invalid_data | error | +| native_hook_malloc | received | info | +| native_hook_malloc | data_lost | error | +| native_hook_malloc | not_match | info | +| native_hook_malloc | not_supported | warn | +| native_hook_malloc | invalid_data | error | +| native_hook_free | received | info | +| native_hook_free | data_lost | error | +| native_hook_free | not_match | info | +| native_hook_free | not_supported | warn | +| native_hook_free | invalid_data | error | +| sys_memory | received | info | +| sys_memory | data_lost | error | +| sys_memory | not_match | info | +| sys_memory | not_supported | warn | +| sys_memory | invalid_data | error | +| sys_virtual_memory | received | info | +| sys_virtual_memory | data_lost | error | +| sys_virtual_memory | not_match | info | +| sys_virtual_memory | not_supported | warn | +| sys_virtual_memory | invalid_data | error | +| signal_generate | received | info | +| signal_generate | data_lost | error | +| signal_generate | not_match | info | +| signal_generate | not_supported | warn | +| signal_generate | invalid_data | error | +| signal_deliver | received | info | +| signal_deliver | data_lost | error | +| signal_deliver | not_match | info | +| signal_deliver | not_supported | warn | +| signal_deliver | invalid_data | error | +| trace_block_bio_backmerge | received | info | +| trace_block_bio_backmerge | data_lost | error | +| trace_block_bio_backmerge | not_match | info | +| trace_block_bio_backmerge | not_supported | warn | +| trace_block_bio_backmerge | invalid_data | error | +| trace_block_bio_bounce | received | info | +| trace_block_bio_bounce | data_lost | error | +| trace_block_bio_bounce | not_match | info | +| trace_block_bio_bounce | not_supported | warn | +| trace_block_bio_bounce | invalid_data | error | +| trace_block_bio_complete | received | info | +| trace_block_bio_complete | data_lost | error | +| trace_block_bio_complete | not_match | info | +| trace_block_bio_complete | not_supported | warn | +| trace_block_bio_complete | invalid_data | error | +| trace_block_bio_frontmerge | received | info | +| trace_block_bio_frontmerge | data_lost | error | +| trace_block_bio_frontmerge | not_match | info | +| trace_block_bio_frontmerge | not_supported | warn | +| trace_block_bio_frontmerge | invalid_data | error | +| trace_bblock_bio_queue | received | info | +| trace_bblock_bio_queue | data_lost | error | +| trace_bblock_bio_queue | not_match | info | +| trace_bblock_bio_queue | not_supported | warn | +| trace_bblock_bio_queue | invalid_data | error | +| trace_block_bio_remap | received | info | +| trace_block_bio_remap | data_lost | error | +| trace_block_bio_remap | not_match | info | +| trace_block_bio_remap | not_supported | warn | +| trace_block_bio_remap | invalid_data | error | +| trace_block_dirty_buffer | received | info | +| trace_block_dirty_buffer | data_lost | error | +| trace_block_dirty_buffer | not_match | info | +| trace_block_dirty_buffer | not_supported | warn | +| trace_block_dirty_buffer | invalid_data | error | +| trace_block_getrq | received | info | +| trace_block_getrq | data_lost | error | +| trace_block_getrq | not_match | info | +| trace_block_getrq | not_supported | warn | +| trace_block_getrq | invalid_data | error | +| trace_block_plug | received | info | +| trace_block_plug | data_lost | error | +| trace_block_plug | not_match | info | +| trace_block_plug | not_supported | warn | +| trace_block_plug | invalid_data | error | +| trace_block_rq_complete | received | info | +| trace_block_rq_complete | data_lost | error | +| trace_block_rq_complete | not_match | info | +| trace_block_rq_complete | not_supported | warn | +| trace_block_rq_complete | invalid_data | error | +| trace_block_rq_insert | received | info | +| trace_block_rq_insert | data_lost | error | +| trace_block_rq_insert | not_match | info | +| trace_block_rq_insert | not_supported | warn | +| trace_block_rq_insert | invalid_data | error | +| trace_block_rq_remap | received | info | +| trace_block_rq_remap | data_lost | error | +| trace_block_rq_remap | not_match | info | +| trace_block_rq_remap | not_supported | warn | +| trace_block_rq_remap | invalid_data | error | +| trace_block_rq_issue | received | info | +| trace_block_rq_issue | data_lost | error | +| trace_block_rq_issue | not_match | info | +| trace_block_rq_issue | not_supported | warn | +| trace_block_rq_issue | invalid_data | error | +| other | received | info | +| other | data_lost | error | +| other | not_match | info | +| other | not_supported | warn | +| other | invalid_data | error | diff --git a/host/trace_streamer/doc/des_support_eventlist.md b/host/trace_streamer/doc/des_support_eventlist.md new file mode 100644 index 0000000..bde55dc --- /dev/null +++ b/host/trace_streamer/doc/des_support_eventlist.md @@ -0,0 +1,224 @@ +# TraceStreamer支持解析事件列表 +## ftrace事件 +``` +binder_transaction +binder_transaction_received +binder_transaction_alloc_buf +binder_transaction_lock +binder_transaction_locked +binder_transaction_unlock +sched_switch +task_rename +task_newtask +tracing_mark_write +print +sched_wakeup +sched_waking +cpu_idle +cpu_frequency +suspend_resume +workqueue_execute_start +workqueue_execute_end +clock_set_rate +clock_enable +clock_disable +clk_set_rate +clk_enable +clk_disable +sys_enter +sys_exit +regulator_set_voltage +regulator_set_voltage_complete +regulator_disable +regulator_disable_complete +ipi_entry +ipi_exit +irq_handler_entry +irq_handler_exit +softirq_raise +softirq_entry +softirq_exit +sched_wakeup_new +sched_process_exit +trace_event_clock_sync +``` +## 内存事件 +``` +mem.vm.size +mem.rss +mem.rss.anon +mem.rss.file +mem.rss.schem +mem.swap +mem.locked +mem.hwm +mem.oom_score_adj +``` +## 系统内存事件 +``` +sys.mem.unspecified +sys.mem.total +sys.mem.free +sys.mem.avaiable +sys.mem.buffers +sys.mem.cached +sys.mem.swap.chard +sys.mem.active +sys.mem.inactive +sys.mem.active.anon +sys.mem.inactive.anon +sys.mem.active_file +sys.mem.inactive_file +sys.mem.unevictable +sys.mem.mlocked +sys.mem.swap.total +sys.mem.swap.free +sys.mem.dirty +sys.mem.writeback +sys.mem.anon.pages +sys.mem.mapped +sys.mem.shmem +sys.mem.slab +sys.mem.slab.reclaimable +sys.mem.slab.unreclaimable +sys.mem.kernel.stack +sys.mem.page.tables +sys.mem.commit.limit +sys.mem.commited.as +sys.mem.vmalloc.total +sys.mem.vmalloc.used +sys.mem.vmalloc.chunk +sys.mem.cma.total +sys.mem.cma.free +``` +## 系统虚拟内存事件 +``` +sys.virtual.mem.unspecified +sys.virtual.mem.nr.free.pages +sys.virtual.mem.nr.alloc.batch +sys.virtual.mem.nr.inactive.anon +sys.virtual.mem.nr.active_anon +sys.virtual.mem.nr.inactive.file +sys.virtual.mem.nr.active_file +sys.virtual.mem.nr.unevictable +sys.virtual.mem.nr.mlock +sys.virtual.mem.anon.pages +sys.virtual.mem.nr.mapped +sys.virtual.mem.nr.file.pages +sys.virtual.mem.nr.dirty +sys.virtual.mem.nr.writeback +sys.virtual.mem.nr.slab.reclaimable +sys.virtual.mem.nr.slab.unreclaimable +sys.virtual.mem.nr.page_table.pages +sys.virtual.mem.nr_kernel.stack +sys.virtual.mem.nr.overhead +sys.virtual.mem.nr.unstable +sys.virtual.mem.nr.bounce +sys.virtual.mem.nr.vmscan.write +sys.virtual.mem.nr.vmscan.immediate.reclaim +sys.virtual.mem.nr.writeback_temp +sys.virtual.mem.nr.isolated_anon +sys.virtual.mem.nr.isolated_file +sys.virtual.mem.nr.shmem +sys.virtual.mem.nr.dirtied +sys.virtual.mem.nr.written +sys.virtual.mem.nr.pages.scanned +sys.virtual.mem.workingset.refault +sys.virtual.mem.workingset.activate +sys.virtual.mem.workingset_nodereclaim +sys.virtual.mem.nr_anon.transparent.hugepages +sys.virtual.mem.nr.free_cma +sys.virtual.mem.nr.swapcache +sys.virtual.mem.nr.dirty.threshold +sys.virtual.mem.nr.dirty.background.threshold +sys.virtual.mem.vmeminfo.pgpgin +sys.virtual.mem.pgpgout +sys.virtual.mem.pgpgoutclean +sys.virtual.mem.pswpin +sys.virtual.mem.pswpout +sys.virtual.mem.pgalloc.dma +sys.virtual.mem.pgalloc.normal +sys.virtual.mem.pgalloc.movable +sys.virtual.mem.pgfree +sys.virtual.mem.pgactivate +sys.virtual.mem.pgdeactivate +sys.virtual.mem.pgfault +sys.virtual.mem.pgmajfault +sys.virtual.mem.pgrefill.dma +sys.virtual.mem.pgrefill.normal +sys.virtual.mem.pgrefill.movable +sys.virtual.mem.pgsteal.kswapd.dma +sys.virtual.mem.pgsteal.kswapd.normal +sys.virtual.mem.pgsteal.kswapd.movable +sys.virtual.mem.pgsteal.direct.dma +sys.virtual.mem.pgsteal.direct.normal +sys.virtual.mem.pgsteal_direct.movable +sys.virtual.mem.pgscan.kswapd.dma +sys.virtual.mem.pgscan_kswapd.normal +sys.virtual.mem.pgscan.kswapd.movable +sys.virtual.mem.pgscan.direct.dma +sys.virtual.mem.pgscan.direct.normal +sys.virtual.mem.pgscan.direct.movable +sys.virtual.mem.pgscan.direct.throttle +sys.virtual.mem.pginodesteal +sys.virtual.mem.slabs_scanned +sys.virtual.mem.kswapd.inodesteal +sys.virtual.mem.kswapd.low.wmark.hit.quickly +sys.virtual.mem.high.wmark.hit.quickly +sys.virtual.mem.pageoutrun +sys.virtual.mem.allocstall +sys.virtual.mem.pgrotated +sys.virtual.mem.drop.pagecache +sys.virtual.mem.drop.slab +sys.virtual.mem.pgmigrate.success +sys.virtual.mem.pgmigrate.fail +sys.virtual.mem.compact.migrate.scanned +sys.virtual.mem.compact.free.scanned +sys.virtual.mem.compact.isolated +sys.virtual.mem.compact.stall +sys.virtual.mem.compact.fail +sys.virtual.mem.compact.success +sys.virtual.mem.compact.daemon.wake +sys.virtual.mem.unevictable.pgs.culled +sys.virtual.mem.unevictable.pgs.scanned +sys.virtual.mem.unevictable.pgs.rescued +sys.virtual.mem.unevictable.pgs.mlocked +sys.virtual.mem.unevictable.pgs.munlocked +sys.virtual.mem.unevictable.pgs.cleared +sys.virtual.mem.unevictable.pgs.stranded +sys.virtual.mem.nr.zspages +sys.virtual.mem.nr.ion.heap +sys.virtual.mem.nr.gpu.heap +sys.virtual.mem.allocstall.dma +sys.virtual.mem.allocstall.movable +sys.virtual.mem.allocstall.normal +sys.virtual.mem.compact_daemon.free.scanned +sys.virtual.mem.compact.daemon.migrate.scanned +sys.virtual.mem.nr.fastrpc +sys.virtual.mem.nr.indirectly.reclaimable +sys.virtual.mem.nr_ion_heap_pool +sys.virtual.mem.nr.kernel_misc.reclaimable +sys.virtual.mem.nr.shadow_call.stack_bytes +sys.virtual.mem.nr.shmem.hugepages +sys.virtual.mem.nr.shmem.pmdmapped +sys.virtual.mem.nr.unreclaimable.pages +sys.virtual.mem.nr.zone.active.anon +sys.virtual.mem.nr.zone.active.file +ys.virtual.mem.nr.zone.inactive_anon +sys.virtual.mem.nr.zone.inactive_file +sys.virtual.mem.nr.zone.unevictable +sys.virtual.mem.nr.zone.write_pending +sys.virtual.mem.oom.kill +sys.virtual.mem.pglazyfree +sys.virtual.mem.pglazyfreed +sys.virtual.mem.pgrefill +sys.virtual.mem.pgscan.direct +sys.virtual.mem.pgscan.kswapd +sys.virtual.mem.pgskip.dma +sys.virtual.mem.pgskip.movable +sys.virtual.mem.pgskip.normal +sys.virtual.mem.pgsteal.direct +sys.virtual.mem.pgsteal.kswapd +sys.virtual.mem.swap.ra +sys.virtual.mem.swap.ra.hit +``` \ No newline at end of file diff --git a/host/trace_streamer/doc/des_tables.md b/host/trace_streamer/doc/des_tables.md new file mode 100644 index 0000000..efe0a6f --- /dev/null +++ b/host/trace_streamer/doc/des_tables.md @@ -0,0 +1,306 @@ +# ___概述TraceStreamer生成的数据库___ +``` + TraceStreamer虽然对外提供了各种各样的使用方式,但核心的业务仍是将trace数据源转化为易于理解和使用的数据库。用户可以通过SmartPerf界面直观的研究系统跟踪数据,也可在理解TraceStreamer生成的数据库的基础上,在TraceStreamer的交互模式或者Smartperf的数据库查询模式下,使用SQL查询语句自由组装查看用户关心的数据。 下文将对TraceStreamer生成的数据库进行详细描述,给用户使用SQL查询系统跟踪数据提供帮助。 +``` + +## ___TraceStreamer输出数据库包含以下表格___ +* trace_range : 记录ftrace数据与其他类型数据的时间交集,供前端展示数据时使用。 +* process : 记录进程信息。 +* thread : 记录线程信息。 +* thread_state : 记录线程状态信息。 +* instant : 记录Sched_waking, sched_wakeup事件, 用作ThreadState表的上下文使用。 +* raw : 此数据结构主要作为ThreadState的上下文使用,这张表是sched_waking,sched_wakup, cpu_idle事件的原始记录。 +* callstack : 记录调用堆栈和异步调用,其中depth,stack_id和parent_stack_id仅在非异步调用中有效。当cookid不为空时,为异步调用,此时callid为进程唯一号,否则为线程唯一号。 +* irq : 记录中断相关事件。 +* measure : 记录所有的计量值。 +* log : 记录hilog打印日志数据。 +* heap : 记录堆内存申请与释放相关的数据。 +* heap_frame : 记录堆内存申请与释放相关的调用栈。 +* hidump : 记录FPS(Frame Per Second)数据。 +* symbols : 记录系统调用名称和其函数指针的对应关系,trace中用addr来映射function_name来节省存储空间 +* syscall : 记录用户空间函数与内核空间函数相互调用记录 +* args : 记录方法参数集合 +* sys_event_filter : 记录所有的filter +* clk_event_filter : 记录时钟事件 +* cpu_measure_filter : cpu事件过滤器表。 +* measure_filter : 记录一个递增的filterid队列,所有其他的filter类型在获取过程中,均从此数据列表中获取下一个可用的filter_id并做记录。 +* process_measure_filter : 将进程ID作为key1,进程的内存,界面刷新,屏幕亮度等信息作为key2,唯一确定一个filter_id +* data_type : 记录数据类型和typeId的关联关系。 +* data_dict : 记录常用的字符串,将字符串和索引关联,降低程序运行的内存占用,用作辅助数据。 +* meta : 记录执行解析操作相关的基本信息。 + +## ___表格关系图___ + +### 进程表与线程表关系图: +![GitHub Logo](../figures/process_thread.png) +### 描述: +当一个进程或者线程结束后,系统可能再次将该进程号或者线程号分配给其他进程或者线程,造成一个进程号或线程号代表多个进程或线程的情况。 +Process和Thread表中的id字段可以唯一标识进程和线程。process表中的id在其他表中用作ipid字段。thread表中的id在其他表中用作itid字段。 +thread表通过ipid字段关联process表的id字段,可以查询线程归属进程。 +### 举例: +已知pid = 123,查看当前进程下的所有线程信息,可以使用如下SQL语句: +select thread.* from thread, process where process.pid = 123 and thread.ipid = process.id + +### 线程表与线程运行状态表关系图 +![GitHub Logo](../figures/thread_state.png) +### 描述: +thread_state表记录所有线程的运行状态信息,包含ts(状态起始时间),dur(状态持续时间),cpu, itid, state(线程状态)。 thread表的id字段与thread_state表的itid字段相关联。 +### 举例: +已知tid = 123, 查看当前线程的所有运行状态信息,可以使用如下SQL语句: +select thread_state.* from thread, thread_state where thread.tid = 123 and thread.id = thread_state.itid + +### 堆内存数据变化表关系图 +![GitHub Logo](../figures/dump_and_mem.png) +### 描述: +heap表记录堆内存申请(AllocEvent)和释放(FreeEvent)数据。heap表通过ipid和itid字段分别与process和thread表的id字段关联,通过eventId与heap_frame表的eventId字段相关联。 +heap表字段解释如下: + eventId: 唯一标识一次堆内存申请或释放, 通过与heap_frame表关联可以拿到当前申请或释放的函数调用堆栈。 + addr: 堆内存申请/释放的地址 + heap_size: 堆内存申请/释放的大小 +heap_frame表记录内存申请/释放的调用堆栈。通过eventId区分一组调用堆栈,depth为堆栈深度,depth为0时,表示当前行为栈顶数据。 +### 举例: +已知tid = 123, 查看当前线程的所有堆内存变化信息,可以使用如下SQL语句: +select heap.* from thread, heap where thread.tid = 123 and thread.id = heap.itid +已知eventid = 0, 查看当前内存变化调用堆栈 +select * from heap_frame where eventId = 0 + +### 日志表与进程线程表关系图 +![GitHub Logo](../figures/log.png) +### 描述: +log表记录日志信息。可以根据seq字段的连续性,来判断是否存在日志丢失的情况。 +### 举例: +已知tid = 123, 查看当前线程的所有error级别的日志,可以使用如下SQL语句: +select * from log where tid = 123 and level = "error" + + +## TraceStreamer输出数据库表格详细介绍 +### trace_range表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|start_ts |NUM | +|end_ts |INT | +#### 关键字段描述: +start_ts: trace的开始时间,纳秒为单位 +end_ts: trace的结束时间,纳秒为单位 +### process表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |NUM | +|type |INT | +|pid |NUM | +|name |INT | +|start_ts |NUM | +#### 关键字段描述: +id: 进程在数据库重新重新定义的id,从0开始序列增长 +pid: 进程的真实id +name: 进程名字 +### thread表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |INT | +|type |NUM | +|tid |INT | +|name |NUM | +|start_ts |INT | +|end_ts |INT | +|ipid |INT | +|is_main_thread|INT | +#### 字段详细描述: +id: 线程在数据库重新重新定义的id,从0开始序列增长 +ipid: 线程所属的进程id, 关联进程表中的ID +name: 线程名字 +is_main_thread: 是否主线程,主线程即该线程实际就是进程本身 + +### thread_state表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |INT | +|type |NUM | +|ts |INT | +|dur |INT | +|cpu |INT | +|itid |INT | +|state |NUM | +#### 字段详细描述: +id: 线程状态在数据库中的id,从0开始序列增长 +ts: 该线程状态的起始时间 +dur: 该线程状态的持续时间 +cpu: 该线程在哪个cpu上执行(针对running状态的线程) +itid: 该状态所属的线程所属的进程id, 关联进程表中的ID +state: 线程实际的的状态值 +``` +'R', Runnable状态 +'S', interruptible sleep +'D', uninterruptible sleep +'T', Stoped +'t', Traced +'X', ExitedDead +'Z', ExitZombie +'x', TaskDead +'I', TaskDead +'K', WakeKill +'P', Parked +'N', NoLoad +``` +### instant表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|ts |INT | +|name |NUM | +|ref |INT | +|ref_type |NUM | +#### 表描述: +记录了系统中的waking和wakeup事件。 +### raw表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |INT | +|type |NUM | +|ts |INT | +|name |NUM | +|cpu |INT | +|itid |INT | +#### 表描述: +记录了系统中的waking、wakup、cpu_idel、cpu_frequency数据。 + +### callstack表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |INT | +|ts |INT | +|dur |INT | +|callid |INT | +|cat |NUM | +|name |NUM | +|depth |INT | +|cookie |INT | +|parent_id |INT | +|argsetid |INT | +|chainId |NUM | +|spanId |NUM | +|parentSpanId |NUM | +|flag |NUM | +|args |NUM | +#### 字段详细描述: +dur: 调用时长 +callid: 调用者的ID,比如针对线程表里面的id +name: 调用名称 +depth: 调用深度 +parent_id: 父调用的id + +### measure表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|type |NUM | +|ts |INT | +|value |INT | +|filter_id |INT | +#### 字段详细描述: + +### heap表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|eventId |INT | +|ipid |INT | +|itid |INT | +|event_type |NUM | +|start_ts |INT | +|end_ts |INT | +|dur |INT | +|addr |INT | +|heap_size |INT | +|all_heap_size |INT | +#### 字段详细描述: + +### heap_frame表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|eventId |INT | +|depth |INT | +|ip |INT | +|sp |INT | +|symbol_name |NUM | +|file_path |NUM | +|offset |INT | +|symbol_offset |INT | +#### 表描述: +记录了内存的申请和释放的堆栈。 +### hidump表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|ts |INT | +|fps |INT | +#### 表描述: +此表记录了设备的帧率信息,fps。 +### symbols表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |INT | +|funcname |NUM | +|addr |INT | +#### 表描述: +此表记录了数值和函数调用名称的映射关系。 + +### measure_filter表 +记录一个递增的filterid队列,所有其他的filter类型在获取过程中,均从此数据列表中获取下一个可用的filter_id并做记录。 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |NUM | +|type |NUM | +|name |INT | +|source_arg_set_id |INT | + +#### 字段详细描述: +过滤分类(type),过滤名称(key2),数据ID(key1)。 +数据ID在process_measure_filter, sys_event_filter中作为id。 +### process_measure_filter表 +将进程ID作为key1,进程的内存,界面刷新,屏幕亮度等信息作为key2,唯一确定一个filter_id, filter_id同时被记录在measure_filter表中。 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |INT | +|type |NUM | +|name |NUM | +|ipid |INT | +#### 字段详细描述: +filterid: 来自measure_filter表 +name: cpu状态名 +ipid: 进程内部编号 +### data_type表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |INT | +|typeId |INT | +|desc |NUM | +#### 表描述: +此表记录了一个数据类型ID和数据描述的映射。 +### data_dict表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|id |INT | +|data |NUM | +#### 表描述: +此表记录了一个数据类型ID和字符串的映射。 +### meta表 +#### 表结构: +| Columns Name | SQL TYPE | +|---- |---- | +|name |NUM | +|value |NUM | +#### 表描述: +此表记录了数据解析或导出时的一些现场数据,比如使用的trace_streamer版本, 工具的发布时间,数据解析的时间,数据的持续时长,以及原始数据的格式。 diff --git a/host/trace_streamer/figures/cpu_frequency.png b/host/trace_streamer/figures/cpu_frequency.png new file mode 100644 index 0000000000000000000000000000000000000000..a18715fc696b3231b94425e4acd6aaf319bf399f GIT binary patch literal 36246 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 E4=MAVv;Y7A literal 0 HcmV?d00001 diff --git a/host/trace_streamer/figures/filters.png b/host/trace_streamer/figures/filters.png new file mode 100644 index 0000000000000000000000000000000000000000..a02d9416f08382ff7a03e176e37e6479f5922c08 GIT binary patch literal 49833 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 literal 0 HcmV?d00001 diff --git a/host/trace_streamer/figures/log.png b/host/trace_streamer/figures/log.png new file mode 100644 index 0000000000000000000000000000000000000000..dbe3780eb647493dec252d69f23e71cf66ac15a8 GIT binary patch literal 4037 zcmZ`+2UJr_w>}_9l`5!!NQa1`(g}i6LnxtLLQ_CGO0UwTDMflH3IT;AfOJ866V!k} zP@42m&85bRKVduzS*zqkH>);j0RIx~CknZ4)x_Ur^B1ndGM4C>GuZvLEc{Kqoc)6y`8olno^IZc{QR9B zULwo_0IL;TQ|+&(`J1z$`lcjax?eEQFa0=z2V!kpEHG)+(b~?%rp!rVj2$AzQpAom z%~o5oU${Jg92tFN!N)A)5ov0VXmuWWr4Qj1ys4=B&wI-NhcxZ_mOv|<-Zuj@3;X`b zcatr~kSWSH)xyXdw@OH13oA-Pp)A;x^{uV*v21!mI&}9XFq*+cRb z)fBPWb0ROQyLTt5#~WA})7LUcG78E+(gG({nW6A$Vn1@vHH6kl!7+8`-6{0x)ehxtE+Bs1Hj$qvRQKsfB?i{)PxwXmrkdP z=92gzY)BnwFmzby!V8qZ3y=#uwaxc7(uxu3L5}|R zuKt$K?SKx*_vVXQ0W7#G=~?VwSMmV>DN}k?A01OBSzOT(yjh1W*75G+Kd!!0u``e! z#2EIQ`Wy*?Ekv7>?AG|0i(Pjwy)=X5JaGt@vFP*5qk5k2S^}?fAFOg*CYlZq2s0Jhu^8kBM;)oj=j(#0T?3Sl zycpUpFm4z}i9*)h6iqA}zAd#Z^Ip3hv{q18a$hj>S^FIGyzG^MwvDd^B}`7dm>z`e z{h}&oM2CU;GPlah{#7bS z*R$qz1>st1K?^iLkji9#&`o7{?Qv0@G;SahgR-WgNfgrl$ZH?VCxsjpW{9m7z~p`{ zNLh9oUq!1Vd4aKUrg)U?HUTs-lxXVmu@#!AXuA1TWvHumh$sp>V??ZcpXSWo%&L1Y+QjJ&IVaYKBdutFic$NxVgzA zY6GLqK5O}bm9vbplHM1$)`cJH$Lc+K&p4u>6P^SOj~H^Q8*#kND)ZeLO;*V6RuG-r z=Dn-VLKlKUWjsR&XFLfZ@2YTMtMqe9$};MKIqUoL51Y!{Bxw?a9@;gqPGy+n7$cpY zbuiNa0_oA(B0*HlO$k5fuz|}_IyoXB_8)~9GA(+PI_~Q>Kltmr3`>tey?{Z%G8*P) zgT4ryPi2!K9BtV{Advk68nqgw;!*or8j^)tp(%ORFGEsF===cW!0ej(Tbe&;@tKD* z#?k9;+Ud9mu!jD7FcI53nth7wI{H*3x8~Y;xwyDEIW?fY5+H18e_J>_K`2B2G%-ZJ z4z%}g=G+k
      w6VmI(#OH)&>KidxG^7GnBcV?NNY40t{t)Tb z!Mog|7yGPEAc2-4v%E<6rqPK8`dp`Ze{WdWA&FByfyx5j$_?^qZQR z{-}DW2i3ESb57mPQw?visgB%gCd~ztuD<9|gCcUF;fMbA_KWyyk$l@LG=jw9si)$GK4x2qPF>-HXGW=*SorOb0aIW~+nUH_5`@{C$?x?BstBu9aNtmVB zq?wr+0)g-gB2Hh(zd`{Oev?I`P^i2-(7c3li>6|c5zvCHEW}9^z9Afh#aYhMQ%_zxquOz0j4e%)!p6g zQV=ZW9?5w*Q01!^xG-2MzlWEXZJl#6ekpi|^ILI!LxV1MczJob26PUIe5&H`-8X6jQ*MFRWo5L)4zlLEF_MNaka(H%==$^ZQfkAGYzdw;obbFz}w zR)ufUXZu>z@7cFVlLa&5h9VSSG4IS5KJw~6K8E?Nft$Jr1VTrr#;P)kp#L?QS7CT~ zn1h33^zp81p(L{GQwPk_GOV%HOKb9_cB5i{5YLi}Q^F*DzaXg0J_~y{iS_u8iJ~>- zoMKfU90oJ78JO9JF)5?Il#6n}r?ek!wOUom-@LimM;UL#%y=sNIuHy~;j-b9tH48j zv^6zRSy?wNYV1yr55y7q^sZfY*Kgjetg2EC+?YtZ)v^Ccnb$%sO!eBKLM2-a7ws=c zal+{6=;7gE3?1uZS)ObM+F7vo{{4IBfm~w~lWmcs+AHCDZjIkv&($`O8FX)OQzGV& z7kwMWYKnk@{ZC5ZscR3VbetZpCnO}47#2FqSh7uS?$I)FU*zVlvTodB2omLvp%lsD zd(C%kZO+A;tytO(!2h(`1hcn+SGlyH_iGN4N^D^qNuP}zWm`U59v5(uN?o8TD&ecc zSXx@z4<9~U3*TW6O^FOb-spHidHAf3n@JhJ9RF+G*uHim)Vi7& z5`*R>E%#+oHm51u{kO!$#S4{#QnK3A-WW-MoDMRLAyh|4;H10z+vxcC-dreobLvBq ze;Uly&CRmL?qdR{_{6l+iFki@xXIAm=@*{kqf+4TXP#Hb;aYh{Mn(zO( z*PAXJ(jL(r%K(xFOGRl{OT-D(V1$8zA@wY=scWLaV|ujAw9#W)8>&;v(79eZ|N4%@ z^Y5vmW#8cWl%lc6S6N;l%P-6b8re=1#|N)unO~Qb^dfe& zd(O5z`B6o?$d)JX!E-O-Znmir`^ky)+Sb;Vr>Cb)M_BWBS4W>h&}Y`_>fK;IvzQU* zVR*3Xqvhu2=I7@J=G2|lkwR~C?c!P-?)jtK-d-K#`?u*miHm8rktbQWRd62z0|WRu zxSASOD!zYc$mPyu9;LRF#WaPvr^R4SqYxt_&rT>_vn_H{C9Shtjl>9LIXSNW1d-u> zglz0ciDA;`MPXRVK8#R!FLJ&ss$=^2ghB+Dy&Gv^Y%G}B=Q-2F-iC6Om?&D~Z3SJnv*SPfi2c8%Kzi;3 zl`Y{@8yg$Xb&Zvqx0BhLg2*=HJS}2t5czO-eIKf_)y4lgz`k~WZ*%&TwFH5FET4Bt z>cPb1t=*2}rW@`*8a-Z$iYvM{16aS(N1oP58$pMhG? z>zm@Asf0`u&;B^?#i zCjGDgr#nmyV5N#HbfX2+S&TQFNSpAyvs3ixSY8{8r;VrVuzO z=O$)8P=gNLrQx6-y*uCZhc{Igq0k<1S+_6sgU_|Fwer z7eCq%XB3V1+xk@0{FXGq(7>C{ycFnbnf{Le?(Cf0Q@IZkYTIC;4ghd1gl3hxUG#qe DzZtlV literal 0 HcmV?d00001 diff --git a/host/trace_streamer/figures/mem_usage.png b/host/trace_streamer/figures/mem_usage.png new file mode 100644 index 0000000000000000000000000000000000000000..f4ebd6e272c424d6861e2e8150c72c1f4de9802a GIT binary patch literal 30381 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 literal 0 HcmV?d00001 diff --git a/host/trace_streamer/figures/thread_state.png b/host/trace_streamer/figures/thread_state.png new file mode 100644 index 0000000000000000000000000000000000000000..186ffb16905c7cfa0178f7c14a0189223f6c9e22 GIT binary patch literal 1906 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 literal 0 HcmV?d00001 diff --git a/host/trace_streamer/gn/.emscripten b/host/trace_streamer/gn/.emscripten new file mode 100644 index 0000000..0ed7e1d --- /dev/null +++ b/host/trace_streamer/gn/.emscripten @@ -0,0 +1,32 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from platform import system +import os +import sys + +thisFile = os.getenv('EM_CONFIG') +if thisFile is None: + sys.stderr.write('No EM_CONFIG in .emscripten file\n') + sys.exit(-1) + +rootDir = os.path.dirname(os.path.dirname(thisFile)) +emsdkPath = os.path.join(rootDir, 'prebuilts/emsdk/emsdk') +nodePath = os.path.join(rootDir, 'prebuilts/emsdk/node/14.18.2_64bit') + +LLVM_ROOT = os.path.join(emsdkPath, 'bin') +NODE_JS = os.path.join(nodePath, 'bin/node') +EMSCRIPTEN_ROOT = os.path.join(emsdkPath, 'emscripten') +COMPILER_ENGINE = NODE_JS +JS_ENGINES = [NODE_JS] +BINARYEN_ROOT = emsdkPath diff --git a/host/trace_streamer/gn/BUILD.gn b/host/trace_streamer/gn/BUILD.gn new file mode 100644 index 0000000..a44389b --- /dev/null +++ b/host/trace_streamer/gn/BUILD.gn @@ -0,0 +1,126 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +print("target_os", target_os) +group("default_deps") { + public_configs = [ ":default_config" ] + public_deps = [] +} +config("default_config") { + include_dirs = [ + "..", + "../include", + ] +} +config("trace_cfg") { + cflags_cc = [ + "-std=c++17", + "-fno-rtti", + "-fno-exceptions", + "-fvisibility=hidden", + ] +} + +config("visibility_hidden") { + cflags = [ "-fvisibility=hidden" ] +} + +config("default") { + cflags_c = [] + cflags_cc = [] + libs = [] + + cflags = [ + "-fstrict-aliasing", + "-fPIC", + "-g", + "-Wformat", + ] + + if (is_linux) { + cflags += [ + "-Wa,--noexecstack", + "-fcolor-diagnostics", + "-fdiagnostics-show-template-tree", + "-ftrapv", + ] + if (!use_wasm) { + cflags += [ + "-fPIE", + "-fstack-protector-strong", + "-fstack-protector-all", + "-D_FORTIFY_SOURCE=2 -O2", + ] + } + libs += [ + "pthread", + "rt", + ] + if (is_debug) { + libs += [ "dl" ] + } + } +} + +config("symbols") { + cflags = [ "-O0" ] + if (is_linux) { + cflags += [ "-funwind-tables" ] + } +} + +config("release") { + cflags = [ + "-fdata-sections", + "-ffunction-sections", + ] + + cflags += [ "-O3" ] + ldflags = [ + "-fuse-ld=gold", + "-fstack-protector", + "-Wl,--gc-sections", + "-Wl,-O1", + "-fpie", + "-pie", + ] + defines = [ "NDEBUG" ] +} + +config("shared_library") { + ldflags = [ "-fPIC" ] +} + +config("executable") { + print("use_wasm", use_wasm) + ldflags = [] + + if (is_linux && !use_wasm) { + ldflags += [ + # "-Wl,-rpath=\$ORIGIN/.", + "-Wl,-z,now", + + # "-Wl,-rpath-link=.", + "-Wl,-z,relro", + "-lrt", + "-fpie", + "-pie", + "-Wl,-z,noexecstack", + "-Wl,--disable-new-dtags", + + # "-s", # delete sambols + ] + } + if (!is_debug) { + ldflags += [ "-s" ] + } +} diff --git a/host/trace_streamer/gn/CONFIG.gn b/host/trace_streamer/gn/CONFIG.gn new file mode 100644 index 0000000..babf975 --- /dev/null +++ b/host/trace_streamer/gn/CONFIG.gn @@ -0,0 +1,72 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +is_win = false +is_linux = false +declare_args() { + ar = "ar" + is_debug = true + use_wasm = false + is_test = false +} +if (target_os == "linux") { + is_win = false + is_linux = true + is_test = false +} else if (target_os == "windows") { + is_win = true + is_linux = false + is_test = false +} else if (target_os == "wasm") { + is_win = false + is_linux = true + use_wasm = true + is_test = false +} else if (target_os == "test") { + is_win = false + is_linux = true + use_wasm = false + is_test = true +} else { + print("unknown platform " + target_os) + exit(-1) +} + +print("platform " + target_os) +default_configs = [ + "//gn:symbols", + "//gn:default", + "//gn:trace_cfg", +] + +set_defaults("static_library") { + configs = default_configs +} +if (!is_debug) { + default_configs -= [ "//gn:symbols" ] + default_configs += [ "//gn:release" ] +} + +set_defaults("ohos_source_set") { + configs = default_configs +} + +set_defaults("executable") { + configs = default_configs + configs += [ "//gn:executable" ] +} +if (use_wasm) { + set_default_toolchain("//gn/toolchain:wasm") +} else { + print(use_wasm) + set_default_toolchain("//gn/toolchain:gcc_like") +} diff --git a/host/trace_streamer/gn/toolchain/BUILD.gn b/host/trace_streamer/gn/toolchain/BUILD.gn new file mode 100644 index 0000000..e1b24fd --- /dev/null +++ b/host/trace_streamer/gn/toolchain/BUILD.gn @@ -0,0 +1,178 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//gn/wasm.gni") +declare_args() { + if (target_os == "linux" || target_os == "wasm" || target_os == "test") { + cc = "/usr/bin/clang" + cxx = "/usr/bin/clang++" + } else if (target_os == "windows") { + cc = "~/mingw-w64/ohos/linux-x86_64/clang-mingw/bin/clang" + cxx = "~/mingw-w64/ohos/linux-x86_64/clang-mingw/bin/clang++" + } + if (use_wasm == true) { + print("make_wasm") + } else if (use_wasm == false) { + print("no make_wasm") + } + cc_wrapper = "" + is_mac = false +} +toolchain("wasm") { + # emsdk_dir and em_config are defined in wasm.gni. + print("use gcc_like_chain wasm") + ar = "$emsdk_dir/emscripten/emar --em-config $em_config" + cc = "$emsdk_dir/emscripten/emcc --em-config $em_config" + cxx = "$emsdk_dir/emscripten/em++ --em-config $em_config" + + lib_switch = "-l" + ld_arg = "" + lib_dir_switch = "-L" + external_cxxflags = "" + external_cflags = "" + external_ldflags = "" + if (defined(linker) && linker != "") { + ld_arg = "-fuse-ld=$_invoker_linker" + _invoker_linker = linker + } + if (defined(sysroot) && sysroot != "") { + _invoker_sysroot = sysroot + cxx = "$cxx --sysroot=$_invoker_sysroot" + cc = "$cc --sysroot=$_invoker_sysroot" + } + if (defined(gcc_toolchain) && gcc_toolchain != "") { + ld_arg = "$ld_arg --gcc-toolchain=$gcc_toolchain" + } + if (defined(external_cxxflags)) { + print("defined external_cxxflags") + external_cxxflags = external_cxxflags + } + if (defined(external_cflags)) { + external_cflags = external_cflags + } + + tool("cc") { + depfiles = "{{output}}.d" + command = "$cc_wrapper $cc -MMD -MF $depfiles {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} ${external_cflags} -c {{source}} -o {{output}}" + outputfiles = + "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" + outputs = [ outputfiles ] + description = "step: compile {{source}}" + } + + tool("cxx") { + depfiles = "{{output}}.d" # must be defined + command = "$cc_wrapper $cxx -MMD -MF $depfiles {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} ${external_cflags} ${external_cxxflags} -c {{source}} -o {{output}}" + + outputfiles = + "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" + outputs = [ outputfiles ] + description = "step: compile {{source}}" + } + tool("alink") { + rspfile = "{{output}}.rsp" # must be defined + rspfile_content = "{{inputs}}" + command = "rm -rf {{output}} && $ar rcsD {{output}} @$rspfile" + outputfiles = "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" + outputs = [ outputfiles ] + output_prefix = "lib" + default_output_extension = ".a" + description = "step: link {{output}}" + } + tool("solink") { + sonames = "{{target_output_name}}{{output_extension}}" + outputfiles = "{{root_out_dir}}/$sonames" + unstripped_so = outputfiles + rpath = "" + outputs = [ outputfiles ] + command = "$cc_wrapper $cxx $ld_arg -shared {{ldflags}} ${external_ldflags} {{inputs}} {{solibs}} {{libs}} $rpath -o {{output}}" + output_prefix = "lib" + description = "step: link $unstripped_so" + default_output_extension = ".so" + } + + tool("link") { + outputfiles = "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" + outputs = [ outputfiles ] + command = "$cc_wrapper $cxx $ld_arg {{ldflags}} ${external_ldflags} {{inputs}} {{solibs}} {{libs}} -o {{output}}" + description = "step:link {{output}}" + } + + tool("stamp") { + description = "step: stamp {{output}}" + command = "touch {{output}}" + } + + tool("copy") { + description = "step: COPY files from {{source}} to {{output}}" + command = "cp -arf {{source}} {{output}}" + } +} +toolchain("gcc_like") { + lib_switch = "-l" + lib_dir_switch = "-L" + + tool("cxx") { + depfile = "{{output}}.d" # must be defined + command = "$cxx -o {{output}} -MMD -MF $depfile {{defines}} -fPIC {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}}" + outputfiles = + "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" + outputs = [ outputfiles ] + description = "step: compile {{source}}" + } + + tool("cc") { + depfile = "{{output}}.d" + command = "$cc -o {{output}} -MMD -MF $depfile {{defines}} -fPIC {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}}" + outputfiles = + "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" + outputs = [ outputfiles ] + description = "step: compile {{source}}" + } + tool("alink") { + rspfile = "{{output}}.rsp" # this must be defined + rspfile_content = "{{inputs}}" # this must be defined + command = "rm -f {{output}} && $ar rcsD {{output}} @$rspfile" + outputsfiles = "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" + outputs = [ outputsfiles ] + default_output_extension = ".a" + output_prefix = "lib" + description = "step: link {{output}}" + } + + tool("link") { + command = "$cxx -o {{output}} {{ldflags}} {{inputs}} {{solibs}} {{libs}}" + outputsfiles = "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" + outputs = [ outputsfiles ] + description = "step: link {{output}}" + } + tool("solink") { + default_output_extension = ".so" + command = + "$cxx -o {{output}} {{ldflags}} {{inputs}} {{solibs}} {{libs}} -shared" + outputsfiles = "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" + outputs = [ outputsfiles ] + description = "step: solink {{output}}" + } + + tool("stamp") { + print("need do nothing") + description = "step: touch {{output}}" + command = "touch {{output}}" + } + + tool("copy") { + description = "COPY files from {{source}} to {{output}}" + command = "cp -arf {{source}} {{output}}" + } +} diff --git a/host/trace_streamer/gn/wasm.gni b/host/trace_streamer/gn/wasm.gni new file mode 100644 index 0000000..7209ff6 --- /dev/null +++ b/host/trace_streamer/gn/wasm.gni @@ -0,0 +1,78 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("./wasm_vars.gni") + +em_config = rebase_path(".emscripten", "") +emsdk_dir = rebase_path("//prebuilts/emsdk/emsdk", "") + +template("wasm_lib") { + _exports = "['ccall', 'callMain', 'addFunction', 'FS']" + print(invoker.name) + assert(defined(invoker.name)) + + # If the name is trace_sreamer the target_name must be trace_sreamer_wasm. + assert(invoker.name + "_wasm" == target_name) + _target_ldflags = [ + "-s", + "DISABLE_EXCEPTION_CATCHING=1", + "-s", + "WASM=1", + "-s", + "NO_DYNAMIC_EXECUTION=1", + "-s", + "ALLOW_MEMORY_GROWTH=1", + "-s", + "INITIAL_MEMORY=33554432", + "-s", + "ALLOW_TABLE_GROWTH=1", + "-s", + "MEMFS_APPEND_TO_TYPED_ARRAYS=1", + "-s", + "WASM_ASYNC_COMPILATION=0", + "-s", + "EXPORTED_RUNTIME_METHODS=" + _exports, + "-s", + "EXPORT_NAME=${target_name}", + "-s", + "MODULARIZE=1", + ] + _lib_name = invoker.name + if (is_debug) { + _target_ldflags += [ + "-s", + "ASSERTIONS=2", + "-s", + "STACK_OVERFLOW_CHECK=1", + "-s", + "SAFE_HEAP=1", + "-g4", + "-O0", + ] + } else { + _target_ldflags += [ + "-g2", # Required for getting C++ symbol names. + "-O3", + "-s", + "ASSERTIONS=1", + ] + } + + _vars_to_forward = [ "deps" ] + + executable("${_lib_name}.js") { + ldflags = _target_ldflags + output_extension = "" + forward_variables_from(invoker, _vars_to_forward) + } +} diff --git a/host/trace_streamer/gn/wasm_vars.gni b/host/trace_streamer/gn/wasm_vars.gni new file mode 100644 index 0000000..8b858fd --- /dev/null +++ b/host/trace_streamer/gn/wasm_vars.gni @@ -0,0 +1,15 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +wasm_toolchain = "//gn/toolchain:wasm" +is_wasm = current_toolchain == wasm_toolchain diff --git a/host/trace_streamer/prebuilts/buildprotobuf/libprotobuf_lite_la_SOURCES.pri b/host/trace_streamer/prebuilts/buildprotobuf/libprotobuf_lite_la_SOURCES.pri new file mode 100644 index 0000000..39c051f --- /dev/null +++ b/host/trace_streamer/prebuilts/buildprotobuf/libprotobuf_lite_la_SOURCES.pri @@ -0,0 +1,64 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + message("qmake" $${PROTOBUDIR}"/src") +win32 { +SOURCES += $${PROTOBUDIR}/src/google/protobuf/io/io_win32.cc +} +SOURCES += \ + $${PROTOBUDIR}/src/google/protobuf/stubs/bytestream.cc \ + $${PROTOBUDIR}/src/google/protobuf/stubs/common.cc \ + $${PROTOBUDIR}/src/google/protobuf/stubs/int128.cc \ +# $${PROTOBUDIR}/src/google/protobuf/stubs/once.cc \ + $${PROTOBUDIR}/src/google/protobuf/stubs/int128.h \ +# $${PROTOBUDIR}/src/google/protobuf/io/io_win32.cc \ + $${PROTOBUDIR}/src/google/protobuf/stubs/status.cc \ + $${PROTOBUDIR}/src/google/protobuf/stubs/statusor.cc \ + $${PROTOBUDIR}/src/google/protobuf/stubs/statusor.h \ + $${PROTOBUDIR}/src/google/protobuf/stubs/stringpiece.cc \ + $${PROTOBUDIR}/src/google/protobuf/stubs/stringprintf.cc \ + $${PROTOBUDIR}/src/google/protobuf/stubs/structurally_valid.cc \ + $${PROTOBUDIR}/src/google/protobuf/stubs/strutil.cc \ + $${PROTOBUDIR}/src/google/protobuf/stubs/time.cc \ +# $${PROTOBUDIR}/src/google/protobuf/any_lite.cc \ + $${PROTOBUDIR}/src/google/protobuf/arena.cc \ +# $${PROTOBUDIR}/src/google/protobuf/arenastring.cc \ + $${PROTOBUDIR}/src/google/protobuf/extension_set.cc \ + $${PROTOBUDIR}/src/google/protobuf/generated_enum_util.cc \ + $${PROTOBUDIR}/src/google/protobuf/generated_message_util.cc \ +# $${PROTOBUDIR}/src/google/protobuf/generated_message_table_driven_lite.cc \ + $${PROTOBUDIR}/src/google/protobuf/implicit_weak_message.cc \ + $${PROTOBUDIR}/src/google/protobuf/message_lite.cc \ + $${PROTOBUDIR}/src/google/protobuf/parse_context.cc \ + $${PROTOBUDIR}/src/google/protobuf/repeated_field.cc \ +# $${PROTOBUDIR}/src/google/protobuf/stubs/atomicops_internals_x86_gcc.cc \ +# $${PROTOBUDIR}/src/google/protobuf/stubs/atomicops_internals_x86_msvc.cc \ + $${PROTOBUDIR}/src/google/protobuf/wire_format_lite.cc \ + $${PROTOBUDIR}/src/google/protobuf/io/coded_stream.cc \ + $${PROTOBUDIR}/src/google/protobuf/io/strtod.cc \ + $${PROTOBUDIR}/src/google/protobuf/io/zero_copy_stream.cc \ + $${PROTOBUDIR}/src/google/protobuf/io/zero_copy_stream_impl.cc \ + $${PROTOBUDIR}/src/google/protobuf/io/zero_copy_stream_impl_lite.cc + +HEADERS += \ + $${PROTOBUDIR}/src/google/protobuf/stubs/bytestream.h \ + $${PROTOBUDIR}/src/google/protobuf/stubs/hash.h \ + $${PROTOBUDIR}/src/google/protobuf/io/coded_stream_inl.h \ +# $${PROTOBUDIR}/src/google/protobuf/generated_message_table_driven_lite.h \ + $${PROTOBUDIR}/src/google/protobuf/stubs/time.h \ + $${PROTOBUDIR}/src/google/protobuf/stubs/stringprintf.h \ + $${PROTOBUDIR}/src/google/protobuf/stubs/stringpiece.h \ + $${PROTOBUDIR}/src/google/protobuf/stubs/status.h \ + $${PROTOBUDIR}/src/google/protobuf/stubs/status_macros.h \ +# $${PROTOBUDIR}/src/google/protobuf/io/io_win32.h \ + $${PROTOBUDIR}/src/google/protobuf/stubs/map_util.h \ + $${PROTOBUDIR}/src/google/protobuf/stubs/mathutil.h diff --git a/host/trace_streamer/prebuilts/buildprotobuf/libprotoc_la_SOURCES.pri b/host/trace_streamer/prebuilts/buildprotobuf/libprotoc_la_SOURCES.pri new file mode 100644 index 0000000..ed1da78 --- /dev/null +++ b/host/trace_streamer/prebuilts/buildprotobuf/libprotoc_la_SOURCES.pri @@ -0,0 +1,173 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + message("qmake" $${PROTOBUDIR}"/src/google/protobuf/compiler/") +SOURCES += \ + $${PROTOBUDIR}/src/google/protobuf/compiler/code_generator.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/command_line_interface.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/plugin.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/plugin.pb.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/subprocess.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/zip_writer.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_enum.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_enum_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_extension.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_file.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_generator.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_helpers.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_map_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_message.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_message_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_padding_optimizer.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_primitive_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_service.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_string_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_context.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_enum.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_enum_lite.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_enum_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_enum_field_lite.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_extension.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_extension_lite.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_file.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_generator.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_generator_factory.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_helpers.cc \ +# $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_lazy_message_field.cc \ +# $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_lazy_message_field_lite.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_map_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_map_field_lite.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_message_lite.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_message_builder.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_message_builder_lite.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_message_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_message_field_lite.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_name_resolver.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_primitive_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_primitive_field_lite.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_shared_code_generator.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_service.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_string_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_string_field_lite.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_doc_comment.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/js/js_generator.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/js/well_known_types_embed.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_enum.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_enum_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_extension.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_file.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_generator.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_map_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_message.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_message_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_oneof.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_primitive_field.cc \ +# $${PROTOBUDIR}/src/google/protobuf/compiler/php/php_generator.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/python/python_generator.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/ruby/ruby_generator.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_doc_comment.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_enum.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_enum_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_field_base.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_generator.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_message.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_helpers.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_map_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_message.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_message_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_primitive_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_reflection_class.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_repeated_enum_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_repeated_message_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_repeated_primitive_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_source_generator_base.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_wrapper_field.cc \ + +HEADERS += \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_padding_optimizer.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_primitive_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_options.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_service.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_string_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_enum_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_enum_field_lite.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_context.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_enum.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_enum_lite.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_extension.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_map_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_map_field_lite.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_helpers.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_file.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_message_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_generator_factory.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_extension_lite.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_message_field_lite.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_message.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_message_lite.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_message_builder.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_message_builder_lite.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_name_resolver.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_options.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_primitive_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_primitive_field_lite.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_shared_code_generator.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_service.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_string_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_enum.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_string_field_lite.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/scc.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/subprocess.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/zip_writer.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_enum.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_enum_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_extension.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_file.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_helpers.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_map_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_message.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_message_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/cpp/cpp_message_layout_helper.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/java/java_doc_comment.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_enum_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_extension.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_file.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_helpers.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_map_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_message.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_message_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_nsobject_methods.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_oneof.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/objectivec/objectivec_primitive_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_doc_comment.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_wrapper_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_repeated_enum_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_repeated_message_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_repeated_primitive_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_source_generator_base.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_message_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_options.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_primitive_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_reflection_class.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_enum.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_enum_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_field_base.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_helpers.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_map_field.h \ + $${PROTOBUDIR}/src/google/protobuf/compiler/csharp/csharp_message.h \ diff --git a/host/trace_streamer/prebuilts/buildprotobuf/protobuf.pri b/host/trace_streamer/prebuilts/buildprotobuf/protobuf.pri new file mode 100644 index 0000000..1bfc084 --- /dev/null +++ b/host/trace_streamer/prebuilts/buildprotobuf/protobuf.pri @@ -0,0 +1,69 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +PROTOBUDIR = $$PWD/../../third_party/protobuf + message("qmake" $${PROTOBUDIR}"/src/google/protobuf/") +SOURCES += \ +$${PROTOBUDIR}/src/google/protobuf/any.cc \ +$${PROTOBUDIR}/src/google/protobuf/any_lite.cc \ + $${PROTOBUDIR}/src/google/protobuf/any.pb.cc \ + $${PROTOBUDIR}/src/google/protobuf/api.pb.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/importer.cc \ + $${PROTOBUDIR}/src/google/protobuf/compiler/parser.cc \ + $${PROTOBUDIR}/src/google/protobuf/descriptor.cc \ + $${PROTOBUDIR}/src/google/protobuf/descriptor.pb.cc \ + $${PROTOBUDIR}/src/google/protobuf/descriptor_database.cc \ + $${PROTOBUDIR}/src/google/protobuf/duration.pb.cc \ + $${PROTOBUDIR}/src/google/protobuf/dynamic_message.cc \ + $${PROTOBUDIR}/src/google/protobuf/empty.pb.cc \ + $${PROTOBUDIR}/src/google/protobuf/extension_set_heavy.cc \ + $${PROTOBUDIR}/src/google/protobuf/field_mask.pb.cc \ + $${PROTOBUDIR}/src/google/protobuf/generated_message_reflection.cc \ + $${PROTOBUDIR}/src/google/protobuf/generated_message_table_driven.cc \ + $${PROTOBUDIR}/src/google/protobuf/io/gzip_stream.cc \ + $${PROTOBUDIR}/src/google/protobuf/io/printer.cc \ + $${PROTOBUDIR}/src/google/protobuf/io/tokenizer.cc \ + $${PROTOBUDIR}/src/google/protobuf/map_field.cc \ + $${PROTOBUDIR}/src/google/protobuf/message.cc \ + $${PROTOBUDIR}/src/google/protobuf/reflection_ops.cc \ + $${PROTOBUDIR}/src/google/protobuf/service.cc \ + $${PROTOBUDIR}/src/google/protobuf/source_context.pb.cc \ + $${PROTOBUDIR}/src/google/protobuf/struct.pb.cc \ +# $${PROTOBUDIR}/src/google/protobuf/stubs/mathlimits.cc \ + $${PROTOBUDIR}/src/google/protobuf/stubs/substitute.cc \ + $${PROTOBUDIR}/src/google/protobuf/text_format.cc \ + $${PROTOBUDIR}/src/google/protobuf/timestamp.pb.cc \ + $${PROTOBUDIR}/src/google/protobuf/type.pb.cc \ + $${PROTOBUDIR}/src/google/protobuf/unknown_field_set.cc \ +# $${PROTOBUDIR}/src/google/protobuf/util/delimited_message_util.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/field_comparator.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/field_mask_util.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/internal/datapiece.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/internal/default_value_objectwriter.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/internal/error_listener.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/internal/field_mask_utility.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/internal/json_escaping.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/internal/json_objectwriter.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/internal/json_stream_parser.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/internal/object_writer.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/internal/proto_writer.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/internal/protostream_objectsource.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/internal/protostream_objectwriter.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/internal/type_info.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/internal/type_info_test_helper.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/internal/utility.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/json_util.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/message_differencer.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/time_util.cc \ + $${PROTOBUDIR}/src/google/protobuf/util/type_resolver_util.cc \ + $${PROTOBUDIR}/src/google/protobuf/wire_format.cc \ + $${PROTOBUDIR}/src/google/protobuf/wrappers.pb.cc diff --git a/host/trace_streamer/prebuilts/buildprotobuf/protobuf.pro b/host/trace_streamer/prebuilts/buildprotobuf/protobuf.pro new file mode 100644 index 0000000..16afcce --- /dev/null +++ b/host/trace_streamer/prebuilts/buildprotobuf/protobuf.pro @@ -0,0 +1,35 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +QT -= gui core +TEMPLATE = lib +#TEMPLATE = app +CONFIG += c++14 lib +#CONFIG += c++14 +TARGET = protobuf + +DEFINES += HAVE_PTHREAD + +DEFINES += HAVE_PTHREAD +PROTOBUDIR = $$PWD/../../third_party/protobuf +ROOTSRCDIR = $$PWD/../../src/ +include($$PWD/../../src/multi_platform/global.pri) + +LIBS += -L$$DESTDIR/ -lstdc++ + +#INCLUDEPATH += $$PWD/$${PROTOBUDIR}/src +INCLUDEPATH += $$PWD/../../third_party/protobuf/src + +message("includepath is:"$$INCLUDEPATH) +include($$PWD/protobuf.pri) +include($$PWD/libprotobuf_lite_la_SOURCES.pri) +include($$PWD/libprotoc_la_SOURCES.pri) diff --git a/host/trace_streamer/prebuilts/buildsqlite/sqlite.pro b/host/trace_streamer/prebuilts/buildsqlite/sqlite.pro new file mode 100644 index 0000000..50d8082 --- /dev/null +++ b/host/trace_streamer/prebuilts/buildsqlite/sqlite.pro @@ -0,0 +1,31 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +QT -= gui core +TEMPLATE = lib +#TEMPLATE = app +CONFIG += c++14 lib +#CONFIG += c++14 +TARGET = sqlite + +DEFINES += HAVE_PTHREAD +LIBDIR = $$PWD/../../third_party/sqlite +ROOTSRCDIR = $$PWD/../../src/ +include($$PWD/../../src/multi_platform/global.pri) + +LIBS += -L$$DESTDIR/ -lstdc++ + +#INCLUDEPATH += $$PWD/$${PROTOBUDIR}/src +INCLUDEPATH += $$PWD/../../third_party/sqlite/include + +message("includepath is:"$$INCLUDEPATH) +SOURCES += $${LIBDIR}/src/sqlite3.c diff --git a/host/trace_streamer/prebuilts/protos/BUILD.gn b/host/trace_streamer/prebuilts/protos/BUILD.gn new file mode 100644 index 0000000..760af44 --- /dev/null +++ b/host/trace_streamer/prebuilts/protos/BUILD.gn @@ -0,0 +1,100 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR = "//third_party/protobuf" +proto_dir = "//third_party/protogen" +services_dir = "$proto_dir/services" +ftrace_data_dir = "$proto_dir/types/plugins/ftrace_data" +memory_data_dir = "$proto_dir/types/plugins/memory_data" +hilog_data_dir = "$proto_dir/types/plugins/hilog_data" +native_hook_dir = "$proto_dir/types/plugins/native_hook" +hidump_data_dir = "$proto_dir/types/plugins/hidump_data" + +config("ts_proto_include_config") { + include_dirs = [ + "$ftrace_data_dir", + "$memory_data_dir", + "$hilog_data_dir", + "$native_hook_dir", + "$hidump_data_dir", + ] +} +source_set("ts_proto_data_cpp") { + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + ] + cflags = [ + "-fPIC", + "-ftrapv", + "-fPIE", + "-D_FORTIFY_SOURCE=2 -O2", + "-Wno-zero-length-array", + + # "-Wl,--disable-new-dtags,--rpath,/libpath1:/libpath2" + ] + if (!use_wasm) { + cflags += [ + "-fstack-protector-strong", # + "-fstack-protector-all", + ] + } + + public_configs = [ ":ts_proto_include_config" ] + sources = [ + "$ftrace_data_dir/binder.pb.cc", + "$ftrace_data_dir/block.pb.cc", + "$ftrace_data_dir/cgroup.pb.cc", + "$ftrace_data_dir/clk.pb.cc", + "$ftrace_data_dir/compaction.pb.cc", + "$ftrace_data_dir/cpuhp.pb.cc", + "$ftrace_data_dir/dma_fence.pb.cc", + "$ftrace_data_dir/ext4.pb.cc", + "$ftrace_data_dir/filelock.pb.cc", + "$ftrace_data_dir/filemap.pb.cc", + "$ftrace_data_dir/ftrace.pb.cc", + "$ftrace_data_dir/ftrace_event.pb.cc", + "$ftrace_data_dir/gpio.pb.cc", + "$ftrace_data_dir/i2c.pb.cc", + "$ftrace_data_dir/ipi.pb.cc", + "$ftrace_data_dir/irq.pb.cc", + "$ftrace_data_dir/kmem.pb.cc", + "$ftrace_data_dir/net.pb.cc", + "$ftrace_data_dir/oom.pb.cc", + "$ftrace_data_dir/pagemap.pb.cc", + "$ftrace_data_dir/power.pb.cc", + "$ftrace_data_dir/printk.pb.cc", + "$ftrace_data_dir/raw_syscalls.pb.cc", + "$ftrace_data_dir/rcu.pb.cc", + "$ftrace_data_dir/sched.pb.cc", + "$ftrace_data_dir/signal.pb.cc", + "$ftrace_data_dir/sunrpc.pb.cc", + "$ftrace_data_dir/task.pb.cc", + "$ftrace_data_dir/timer.pb.cc", + "$ftrace_data_dir/trace_plugin_result.pb.cc", + "$ftrace_data_dir/v4l2.pb.cc", + "$ftrace_data_dir/vmscan.pb.cc", + "$ftrace_data_dir/workqueue.pb.cc", + "$ftrace_data_dir/writeback.pb.cc", + "$hidump_data_dir/hidump_plugin_config.pb.cc", + "$hidump_data_dir/hidump_plugin_result.pb.cc", + "$hilog_data_dir/hilog_plugin_result.pb.cc", + "$memory_data_dir/memory_plugin_common.pb.cc", + "$memory_data_dir/memory_plugin_config.pb.cc", + "$memory_data_dir/memory_plugin_result.pb.cc", + "$native_hook_dir/native_hook_config.pb.cc", + "$native_hook_dir/native_hook_result.pb.cc", + "${services_dir}/common_types.pb.cc", + ] +} diff --git a/host/trace_streamer/src/BUILD.gn b/host/trace_streamer/src/BUILD.gn new file mode 100644 index 0000000..3abb145 --- /dev/null +++ b/host/trace_streamer/src/BUILD.gn @@ -0,0 +1,228 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("ts.gni") +if (use_wasm) { + import("//gn/wasm.gni") +} +if (use_wasm) { + ohos_source_set("trace_streamer_builtin") { + sources = [] + include_dirs = [] + deps = [] + public_deps = [] + } +} +ohos_source_set("lib") { + sources = [ "main.cpp" ] + deps = [ ":trace_streamer_source" ] + include_dirs = [ + "base", + "..", + "trace_streamer", + "filter", + "table", + "trace_data", + "include", + "rpc", + "./", + "parser", + "cfg", + "parser/htrace_parser", + "parser/htrace_parser/htrace_event_parser", + "parser/htrace_parser/htrace_cpu_parser", + "//third_party/sqlite/include", + "${OHOS_PROTO_GEN}/types/plugins/memory_data", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data", + "${OHOS_PROTO_GEN}/types/plugins/hilog_data", + "${OHOS_PROTO_GEN}/types/plugins/native_hook", + "${OHOS_PROTO_GEN}/types/plugins/hidump_data", + "${OHOS_PROTO_GEN}", + "//third_party/protobuf/src", + ] + public_deps = [] +} +ohos_source_set("trace_streamer_source") { + sources = [ + "cfg/trace_streamer_config.cpp", + "cfg/trace_streamer_config.h", + "filter/args_filter.cpp", + "filter/args_filter.h", + "filter/binder_filter.cpp", + "filter/binder_filter.h", + "filter/clock_filter.cpp", + "filter/clock_filter.h", + "filter/cpu_filter.cpp", + "filter/filter_base.cpp", + "filter/filter_base.h", + "filter/filter_filter.cpp", + "filter/filter_filter.h", + "filter/irq_filter.cpp", + "filter/irq_filter.h", + "filter/measure_filter.cpp", + "filter/measure_filter.h", + "filter/process_filter.cpp", + "filter/process_filter.h", + "filter/slice_filter.cpp", + "filter/slice_filter.h", + "filter/stat_filter.cpp", + "filter/stat_filter.h", + "filter/symbols_filter.cpp", + "filter/symbols_filter.h", + "filter/system_event_measure_filter.cpp", + "filter/system_event_measure_filter.h", + "parser/bytrace_parser/bytrace_event_parser.cpp", + "parser/bytrace_parser/bytrace_event_parser.h", + "parser/bytrace_parser/bytrace_parser.cpp", + "parser/bytrace_parser/bytrace_parser.h", + "parser/common_types.h", + "parser/event_parser_base.cpp", + "parser/event_parser_base.h", + "parser/print_event_parser.cpp", + "parser/print_event_parser.h", + "parser/thread_state.cpp", + "parser/thread_state.h", + "rpc/http_server.cpp", + "rpc/http_socket.cpp", + "rpc/rpc_server.cpp", + "table/args_table.cpp", + "table/args_table.h", + "table/callstack_table.cpp", + "table/callstack_table.h", + "table/clk_event_filter_table.cpp", + "table/clk_event_filter_table.h", + "table/clock_event_filter_table.cpp", + "table/clock_event_filter_table.h", + "table/cpu_measure_filter_table.cpp", + "table/cpu_measure_filter_table.h", + "table/data_dict_table.cpp", + "table/data_dict_table.h", + "table/data_type_table.cpp", + "table/data_type_table.h", + "table/filter_table.cpp", + "table/filter_table.h", + "table/heap_frame_table.cpp", + "table/heap_frame_table.h", + "table/heap_table.cpp", + "table/heap_table.h", + "table/hidump_table.cpp", + "table/hidump_table.h", + "table/instants_table.cpp", + "table/instants_table.h", + "table/irq_table.cpp", + "table/log_table.cpp", + "table/log_table.h", + "table/measure_filter_table.cpp", + "table/measure_filter_table.h", + "table/measure_table.cpp", + "table/measure_table.h", + "table/meta_table.cpp", + "table/meta_table.h", + "table/process_filter_table.cpp", + "table/process_filter_table.h", + "table/process_measure_filter_table.cpp", + "table/process_measure_filter_table.h", + "table/process_table.cpp", + "table/process_table.h", + "table/range_table.cpp", + "table/raw_table.cpp", + "table/raw_table.h", + "table/sched_slice_table.cpp", + "table/sched_slice_table.h", + "table/stat_table.cpp", + "table/stat_table.h", + "table/symbols_table.cpp", + "table/symbols_table.h", + "table/system_call_table.cpp", + "table/system_call_table.h", + "table/system_event_filter_table.cpp", + "table/table_base.cpp", + "table/thread_filter_table.cpp", + "table/thread_state_table.cpp", + "table/thread_table.cpp", + "trace_data/trace_data_cache.cpp", + "trace_data/trace_data_cache.h", + "trace_data/trace_data_cache_base.cpp", + "trace_data/trace_data_cache_base.h", + "trace_data/trace_data_cache_reader.cpp", + "trace_data/trace_data_cache_reader.h", + "trace_data/trace_data_cache_writer.cpp", + "trace_data/trace_data_cache_writer.h", + "trace_data/trace_data_db.cpp", + "trace_data/trace_data_db.h", + "trace_data/trace_stdtype.cpp", + "trace_data/trace_stdtype.h", + "trace_streamer/trace_streamer_filters.cpp", + "trace_streamer/trace_streamer_filters.h", + "trace_streamer/trace_streamer_selector.cpp", + "trace_streamer/trace_streamer_selector.h", + ] + include_dirs = [ + "base", + "..", + "trace_streamer", + "filter", + "table", + "trace_data", + "include", + "rpc", + "./", + "parser", + "cfg", + "parser/htrace_parser", + "parser/htrace_parser/htrace_event_parser", + "parser/htrace_parser/htrace_cpu_parser", + "//third_party/sqlite/include", + "${OHOS_PROTO_GEN}/types/plugins/memory_data", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data", + "${OHOS_PROTO_GEN}/types/plugins/hilog_data", + "${OHOS_PROTO_GEN}/types/plugins/native_hook", + "${OHOS_PROTO_GEN}/types/plugins/hidump_data", + "${OHOS_PROTO_GEN}", + "//third_party/protobuf/src", + ] + deps = [ + "base:base", + "ext:sqliteext", + "include:ibase", + "parser/htrace_parser:htrace_parser", + "//third_party/sqlite:sqlite", + ] + + if (use_wasm) { + sources += [ "rpc/wasm_func.cpp" ] + } + if (enable_ts_utest && !use_wasm) { + cflags = [ + "-fprofile-arcs", + "-ftest-coverage", + ] + ldflags = [ + "-fprofile-arcs", + "-ftest-coverage", + "--coverage", + ] + } + public_deps = [] +} +if (use_wasm) { + wasm_lib("trace_streamer_builtin_wasm") { + name = "trace_streamer_builtin" + deps = [ ":lib" ] + } +} else { + executable("trace_streamer") { + deps = [ ":lib" ] + } +} diff --git a/host/trace_streamer/src/base/BUILD.gn b/host/trace_streamer/src/base/BUILD.gn new file mode 100644 index 0000000..110bed4 --- /dev/null +++ b/host/trace_streamer/src/base/BUILD.gn @@ -0,0 +1,37 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../ts.gni") +ohos_source_set("base") { + deps = [] + public_deps = [ "../include:ibase" ] + include_dirs = [ "../include" ] + sources = [ + "codec_cov.cpp", + "file.cpp", + "log.cpp", + "parting_string.cpp", + ] + if (enable_ts_utest && !use_wasm) { + cflags = [ + "-fprofile-arcs", + "-ftest-coverage", + ] + ldflags = [ + "-fprofile-arcs", + "-ftest-coverage", + "--coverage", + ] + } +} diff --git a/host/trace_streamer/src/base/args_set.h b/host/trace_streamer/src/base/args_set.h new file mode 100644 index 0000000..9a506be --- /dev/null +++ b/host/trace_streamer/src/base/args_set.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_BASE_ARGS_SET_H +#define SRC_TRACE_BASE_ARGS_SET_H + +#include +#include +#include "ts_common.h" +namespace SysTuning { +namespace TraceStreamer { +class ArgsSet { +public: + ArgsSet() {} + ~ArgsSet() {} + ArgsSet& operator=(const ArgsSet& other) + { + this->valuesMap_ = other.valuesMap_; + return *this; + } + void AppendArg(DataIndex dataIndex, BaseDataType datatype, uint64_t value) + { + ArgsData data; + data.type = datatype; + data.value = value; + valuesMap_.emplace(dataIndex, data); + } + std::map valuesMap_; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif diff --git a/host/trace_streamer/src/base/base.pri b/host/trace_streamer/src/base/base.pri new file mode 100644 index 0000000..e972a92 --- /dev/null +++ b/host/trace_streamer/src/base/base.pri @@ -0,0 +1,23 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDEPATH +=$$PWD +HEADERS += \ + $$PWD/ts_common.h \ + $$PWD/double_map.h + +SOURCES += \ + $$PWD/codec_cov.cpp \ + $$PWD/file.cpp \ + $$PWD/parting_string.cpp \ + $$PWD/log.cpp diff --git a/host/trace_streamer/src/base/codec_cov.cpp b/host/trace_streamer/src/base/codec_cov.cpp new file mode 100644 index 0000000..5a1251c --- /dev/null +++ b/host/trace_streamer/src/base/codec_cov.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "codec_cov.h" + +#include +#ifdef _WIN32 +#include +#endif + +namespace SysTuning { +namespace base { +int PreNum(unsigned char byte) +{ + constexpr uint32_t BITS = 8; + unsigned char mask = 0x80; + int num = 0; + for (uint32_t i = 0; i < BITS; i++) { + if ((byte & mask) == mask) { + mask = mask >> 1; + num++; + } else { + break; + } + } + return num; +} + +bool IsUTF8(const uint8_t* data, int len) +{ + constexpr uint8_t MASK = 0x80; + constexpr uint8_t FIRST_BYTE = 0xc0; + constexpr int TARGET = 2; + int num = 0; + int i = 0; + while (i < len) { + if ((data[i] & MASK) == 0x00) { + i++; + continue; + } + if ((num = PreNum(data[i])) <= TARGET) { + return false; + } + i++; + for (int j = 0; j < num - 1; j++) { + if ((data[i] & FIRST_BYTE) != MASK) { + return false; + } + i++; + } + } + return true; +} + +bool IsGBK(const uint8_t* data, int len) +{ + constexpr int STEP = 2; + constexpr uint8_t ASCII_END = 0x7f; + constexpr uint8_t FIRST_BYTE = 0x81; + constexpr uint8_t FIRST_BYTE_END = 0xfe; + constexpr uint8_t SECOND_BYTE_ONE = 0x40; + constexpr uint8_t SECOND_BYTE_TWO_END = 0xfe; + constexpr uint8_t GBK_MASK = 0xf7; + int i = 0; + while (i < len) { + if (data[i] <= ASCII_END) { + i++; + continue; + } else { + if (data[i] >= FIRST_BYTE && data[i] <= FIRST_BYTE_END && data[i + 1] >= SECOND_BYTE_ONE && + data[i + 1] <= SECOND_BYTE_TWO_END && data[i + 1] != GBK_MASK) { + i += STEP; + continue; + } else { + return false; + } + } + } + return true; +} + +CODING GetCoding(const uint8_t* data, int len) +{ + CODING coding; + if (IsUTF8(data, len)) { + coding = UTF8; + } else if (IsGBK(data, len)) { + coding = GBK; + } else { + coding = UNKOWN; + } + return coding; +} + +#ifdef _WIN32 +std::string GbkToUtf8(const char* srcStr) +{ + int len = MultiByteToWideChar(CP_ACP, 0, srcStr, -1, NULL, 0); + std::unique_ptr wstr = std::make_unique(len + 1); + MultiByteToWideChar(CP_ACP, 0, srcStr, -1, wstr.get(), len); + len = WideCharToMultiByte(CP_UTF8, 0, wstr.get(), -1, NULL, 0, NULL, NULL); + std::unique_ptr str = std::make_unique(len + 1); + WideCharToMultiByte(CP_UTF8, 0, wstr.get(), -1, str.get(), len, NULL, NULL); + return std::string(str.get()); +} +#endif +} // namespace base +} // namespace SysTuning diff --git a/host/trace_streamer/src/base/double_map.h b/host/trace_streamer/src/base/double_map.h new file mode 100644 index 0000000..921a10c --- /dev/null +++ b/host/trace_streamer/src/base/double_map.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_BASE_DOUBLEMAP_H +#define SRC_TRACE_BASE_DOUBLEMAP_H + +#include + +template +class DoubleMap { +public: + DoubleMap(T3 invalidValue) + { + invalidValue_ = invalidValue; + } + void SetInvalidRet(T3 invalidValue) + { + invalidValue_ = invalidValue; + } + void Insert(T1 t1, T2 t2, T3 t3) + { + auto streamIdHookidMap = internalMap_.find(t1); + if (streamIdHookidMap != internalMap_.end()) { + auto hookId = (*streamIdHookidMap).second.find(t2); + if (hookId == (*streamIdHookidMap).second.end()) { + (*streamIdHookidMap).second.insert(std::make_pair(t2, t3)); + } else { + (*streamIdHookidMap).second.at(t2) = t3; + } + } else { + std::map mm = { + {t2, t3} + }; + internalMap_.insert(std::make_pair(t1, mm)); + } + } + T3 Find(T1 t1, T2 t2) + { + auto streamIdHookidMap = internalMap_.find(t1); + if (streamIdHookidMap != internalMap_.end()) { + auto hookId = (*streamIdHookidMap).second.find(t2); + if (hookId == (*streamIdHookidMap).second.end()) { + return invalidValue_; + } else { + return hookId->second; + } + } else { + return invalidValue_; + } + } + void Erase(T1 t1) + { + auto streamIdHookidMap = internalMap_.find(t1); + if (streamIdHookidMap != internalMap_.end()) { + internalMap_.erase(streamIdHookidMap); + } + } + void Erase(T1 t1, T2 t2) + { + auto streamIdHookidMap = internalMap_.find(t1); + if (streamIdHookidMap != internalMap_.end()) { + auto hookId = (*streamIdHookidMap).second.find(t2); + if (hookId != (*streamIdHookidMap).second.end()) { + (*streamIdHookidMap).second.erase(hookId); + } + } + } + +private: + std::map> internalMap_; + T3 invalidValue_; +}; + +#endif // DOUBLEMAP_H diff --git a/host/trace_streamer/src/base/file.cpp b/host/trace_streamer/src/base/file.cpp new file mode 100644 index 0000000..1d8bd18 --- /dev/null +++ b/host/trace_streamer/src/base/file.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "file.h" +#include +#include +#include +#include +#include + +#include "log.h" +#if defined(_WIN32) +#include +#include +#include +#endif + +namespace SysTuning { +namespace base { +static TraceParserStatus g_status = TRACE_PARSER_ABNORMAL; + +void SetAnalysisResult(TraceParserStatus stat) +{ + g_status = stat; +} +TraceParserStatus GetAnalysisResult() +{ + return g_status; +} + +ssize_t Read(int fd, uint8_t* dst, size_t dstSize) +{ +#if defined(_WIN32) + return _read(fd, dst, static_cast(dstSize)); +#else + ssize_t ret = -1; + do { + ret = read(fd, dst, dstSize); + } while (ret == -1 && errno == EINTR); + return ret; +#endif +} +int OpenFile(const std::string& path, int flags, uint32_t mode) +{ + TS_ASSERT((flags & O_CREAT) == 0 || mode != kFileModeInvalid); +#if defined(_WIN32) + int fd(_open(path.c_str(), flags | O_BINARY, mode)); +#else + int fd(open(path.c_str(), flags | O_CLOEXEC, mode)); +#endif + return fd; +} + +std::string GetExecutionDirectoryPath() +{ + char currPath[1024] = {0}; +#if defined(_WIN32) + ::GetModuleFileNameA(NULL, currPath, MAX_PATH); + (strrchr(currPath, '\\'))[1] = 0; +#else + readlink("/proc/self/exe", currPath, sizeof(currPath) - 1); +#endif + std::string str(currPath); + str = str.substr(0, str.find_last_of('/')); + return str; +} +} // namespace base +} // namespace SysTuning diff --git a/host/trace_streamer/src/base/log.cpp b/host/trace_streamer/src/base/log.cpp new file mode 100644 index 0000000..7a751c8 --- /dev/null +++ b/host/trace_streamer/src/base/log.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "log.h" + +// namespace SysTuning { +// namespace base { +bool g_cleanMode = false; +// } // namespace base +// } // namespace SysTuning diff --git a/host/trace_streamer/src/base/parting_string.cpp b/host/trace_streamer/src/base/parting_string.cpp new file mode 100644 index 0000000..adff0f9 --- /dev/null +++ b/host/trace_streamer/src/base/parting_string.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "parting_string.h" +namespace SysTuning { +namespace base { +PartingString::PartingString(std::string str, char delimiter) : str_(std::move(str)), delimiter_(delimiter) +{ + begin_ = str_.begin(); + end_ = str_.end(); + cur_ = nullptr; +} + +bool PartingString::Next() +{ + while (begin_ != end_) { + if (*begin_ == delimiter_) { + begin_++; + continue; + } + + cur_ = begin_.base(); + do { + if (*begin_ == delimiter_) { + *(begin_++) = '\0'; + break; + } + if (*begin_ == '\0') { + begin_ = end_; + break; + } + } while (begin_++ != end_); + + if (*cur_) { + return true; + } + + begin_++; + } + + cur_ = nullptr; + return false; +} +} // namespace base +} // namespace SysTuning diff --git a/host/trace_streamer/src/base/triple_map.h b/host/trace_streamer/src/base/triple_map.h new file mode 100644 index 0000000..c856d73 --- /dev/null +++ b/host/trace_streamer/src/base/triple_map.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_BASE_TRIPLEMAP_H +#define SRC_TRACE_BASE_TRIPLEMAP_H + +#include "double_map.h" + +template +class TripleMap { +public: + TripleMap(T4 invalidValue) + { + invalidValue_ = invalidValue; + } + void SetInvalidRet(T4 invalidValue) + { + invalidValue_ = invalidValue; + } + void Insert(T1 t1, T2 t2, T3 t3, T4 t4) + { + auto streamIdHookidMap = internalMap_.find(t1); + if (streamIdHookidMap != internalMap_.end()) { + (*streamIdHookidMap).second.Insert(t2, t3, t4); + } else { + DoubleMap mm(invalidValue_); + mm.Insert(t2, t3, t4); + internalMap_.insert(std::make_pair(t1, mm)); + } + } + T4 Find(T1 t1, T2 t2, T3 t3) + { + auto streamIdHookidMap = internalMap_.find(t1); + if (streamIdHookidMap != internalMap_.end()) { + return (*streamIdHookidMap).second.Find(t2, t3); + } else { + return invalidValue_; + } + } + void Erase(T1 t1) + { + auto streamIdHookidMap = internalMap_.find(t1); + if (streamIdHookidMap != internalMap_.end()) { + internalMap_.erase(streamIdHookidMap); + } + } + void Erase(T1 t1, T2 t2) + { + auto streamIdHookidMap = internalMap_.find(t1); + if (streamIdHookidMap != internalMap_.end()) { + (*streamIdHookidMap).second.Erase(t2); + } + } + void Erase(T1 t1, T2 t2, T3 t3) + { + auto streamIdHookidMap = internalMap_.find(t1); + if (streamIdHookidMap != internalMap_.end()) { + (*streamIdHookidMap).second.Erase(t2, t3); + } + } + +private: + std::map> internalMap_; + T4 invalidValue_; +}; + +#endif // SRC_TRACE_BASE_TRIPLEMAP_H diff --git a/host/trace_streamer/src/base/ts_common.h b/host/trace_streamer/src/base/ts_common.h new file mode 100644 index 0000000..57ada72 --- /dev/null +++ b/host/trace_streamer/src/base/ts_common.h @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_TRACE_BASE_TS_COMMON_H +#define SRC_TRACE_BASE_TS_COMMON_H + +#include +#include +#include +#include + +const uint64_t INVALID_UTID = std::numeric_limits::max(); +const uint64_t INVALID_UINT64 = std::numeric_limits::max(); +const uint64_t MAX_UINT32 = std::numeric_limits::max(); +const uint64_t MAX_UINT64 = std::numeric_limits::max(); +const uint32_t INVALID_UINT32 = std::numeric_limits::max(); +const uint32_t INVALID_INT32 = std::numeric_limits::max(); +const uint64_t INVALID_DATAINDEX = std::numeric_limits::max(); +const size_t MAX_SIZE_T = std::numeric_limits::max(); +const uint32_t INVALID_ID = std::numeric_limits::max(); +const uint64_t SEC_TO_NS = 1000 * 1000 * 1000; +const int STR_DEFAULT_LEN = -1; +enum BuiltinClocks { + TS_CLOCK_UNKNOW = 0, + TS_CLOCK_BOOTTIME = 1, + TS_CLOCK_REALTIME = 2, + TS_CLOCK_REALTIME_COARSE = 3, + TS_MONOTONIC = 4, + TS_MONOTONIC_COARSE = 5, + TS_MONOTONIC_RAW = 6, +}; + +enum RefType { + K_REF_NO_REF = 0, + K_REF_ITID = 1, + K_REF_CPUID = 2, + K_REF_IRQ = 3, + K_REF_SOFT_IRQ = 4, + K_REF_IPID = 5, + K_REF_ITID_LOOKUP_IPID = 6, + K_REF_MAX +}; + +enum EndState { + // (R) ready state or running state, the process is ready to run, but not necessarily occupying the CPU + TASK_RUNNABLE = 0, + // (S) Indicates that the process is in light sleep, waiting for the resource state, and can respond to the signal. + // Generally, the process actively sleeps into 'S' state. + TASK_INTERRUPTIBLE = 1, + // (D) Indicates that the process is in deep sleep, waiting for resources, and does not respond to signals. + // Typical scenario: process acquisition semaphore blocking. + TASK_UNINTERRUPTIBLE = 2, + // (Running) Indicates that the thread is running + TASK_RUNNING = 3, + // (I) Thread in interrupt state + TASK_INTERRUPTED = 4, + // (T) Task being traced + TASK_TRACED = 8, + // (X) Exit status, the process is about to be destroyed. + TASK_EXIT_DEAD = 16, + // (Z) Zombie state + TASK_ZOMBIE = 32, + // (I) clone thread + TASK_CLONE = 64, + // (K) Process killed + TASK_KILLED = 128, + // (DK) + TASK_DK = 130, + // the process is being debug now + TASK_TRACED_KILL = 136, + // (W) The process is in a deep sleep state and will be killed directly after waking up + TASK_WAKEKILL = 256, + // (R+) Process groups in the foreground + TASK_FOREGROUND = 2048, + TASK_MAX = 4096, + TASK_INVALID = 9999 +}; +enum TSLogLevel { + TS_DEBUG = 68, // Debug + TS_ERROR = 69, // Error + TS_INFO = 73, // Info + TS_VERBOSE = 86, // Verbose + TS_WARN = 87 // Warn +}; +enum SchedWakeType { + SCHED_WAKING = 0, // sched_waking + SCHED_WAKEUP = 1, // sched_wakeup +}; +using DataIndex = uint64_t; +using TableRowId = uint64_t; +using InternalPid = uint32_t; +using InternalTid = uint32_t; +using InternalTime = uint64_t; +using FilterId = uint32_t; + +enum BaseDataType { + BASE_DATA_TYPE_INT, + BASE_DATA_TYPE_STRING, + BASE_DATA_TYPE_DOUBLE, + BASE_DATA_TYPE_BOOLEAN +}; +namespace SysTuning { +namespace TraceStreamer { +struct ArgsData { + BaseDataType type; + int64_t value; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif diff --git a/host/trace_streamer/src/cfg/cfg.pri b/host/trace_streamer/src/cfg/cfg.pri new file mode 100644 index 0000000..7833002 --- /dev/null +++ b/host/trace_streamer/src/cfg/cfg.pri @@ -0,0 +1,19 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDEPATH +=$$PWD +HEADERS += \ + $$PWD/trace_streamer_config.h + +SOURCES += \ + $$PWD/trace_streamer_config.cpp diff --git a/host/trace_streamer/src/cfg/trace_streamer_config.cpp b/host/trace_streamer/src/cfg/trace_streamer_config.cpp new file mode 100644 index 0000000..e72cf60 --- /dev/null +++ b/host/trace_streamer/src/cfg/trace_streamer_config.cpp @@ -0,0 +1,989 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "trace_streamer_config.h" +#include "log.h" +namespace SysTuning { +namespace TraceCfg { +TraceStreamerConfig::TraceStreamerConfig() +{ + InitEventNameMap(); + eventErrorDescMap_ = { + {STAT_EVENT_RECEIVED, TRACE_STAT_TYPE_RECEIVED_DESC}, + {STAT_EVENT_DATA_LOST, TRACE_STAT_TYPE_LOST_DESC}, + {STAT_EVENT_NOTMATCH, TRACE_STAT_TYPE_NOTMATCH_DESC}, + {STAT_EVENT_NOTSUPPORTED, TRACE_STAT_TYPE_NOTSUPPORTED_DESC}, + {STAT_EVENT_DATA_INVALID, TRACE_STAT_TYPE_DATA_INVALID_DESC}, + }; + serverityLevelDescMap_ = { + {STAT_SEVERITY_LEVEL_INFO, STAT_SEVERITY_LEVEL_INFO_DESC}, + {STAT_SEVERITY_LEVEL_WARN, STAT_SEVERITY_LEVEL_WARN_DESC}, + {STAT_SEVERITY_LEVEL_ERROR, STAT_SEVERITY_LEVEL_ERROR_DESC}, + {STAT_SEVERITY_LEVEL_FATAL, STAT_SEVERITY_LEVEL_FATAL_DESC}, + }; + memNameMap_ = { + {MEM_VM_SIZE, MEM_INFO_VM_SIZE_DESC}, {MEM_VM_LOCKED, MEM_INFO_LOCKED_DESC}, + {MEM_VM_RSS, MEM_INFO_RSS_DESC}, {MEM_VM_ANON, MEM_INFO_RSS_ANON_DESC}, + {MEM_RSS_FILE, MEM_INFO_RSS_FILE_DESC}, {MEM_RSS_SHMEM, MEM_INFO_RSS_SCHEM_DESC}, + {MEM_VM_SWAP, MEM_INFO_SWAP_DESC}, {MEM_VM_LOCKED, MEM_INFO_VIRT_DESC}, + {MEM_VM_HWM, MEM_INFO_HWM_DESC}, {MEM_OOM_SCORE_ADJ, MEM_INFO_SCORE_ADJ_DESC}, + }; + + InitSysMemMap(); + InitSysVmemMap(); + InitSecurityMap(); + if (eventNameMap_.size() != TRACE_EVENT_MAX) { + TS_LOGF("eventNameMap_.size() max be %d, logic error", TRACE_EVENT_MAX); + } + if (eventErrorDescMap_.size() != STAT_EVENT_MAX) { + TS_LOGF("eventErrorDescMap_.size() max be %d, logic error", STAT_EVENT_MAX); + } + if (serverityLevelDescMap_.size() != STAT_SEVERITY_LEVEL_MAX) { + TS_LOGF("serverityLevelDescMap_.size() max be %d, logic error", STAT_SEVERITY_LEVEL_MAX); + } + if (eventParserStatSeverityDescMap_.size() != TRACE_EVENT_MAX) { + TS_LOGF("eventParserStatSeverityDescMap_.size() max be %d, logic error", TRACE_EVENT_MAX); + } + if (memNameMap_.size() != MEM_MAX) { + TS_LOGF("memNameMap_.size() max be %d, logic error", MEM_MAX); + } + for (int i = TRACE_EVENT_START; i < TRACE_EVENT_MAX; i++) { + if (eventParserStatSeverityDescMap_.at(static_cast(i)).size() != STAT_EVENT_MAX) { + TS_LOGF("every item in eventParserStatSeverityDescMap_ max be %d, logic error", STAT_EVENT_MAX); + } + } +} + +void TraceStreamerConfig::PrintInfo() const +{ + printf("---all kind of trace event info---\n"); + for (auto itor = eventNameMap_.begin(); itor != eventNameMap_.end(); itor++) { + printf("%s\n", itor->second.c_str()); + } + printf("\n"); + printf("---subdir of process mem info---\n"); + for (auto itor = memNameMap_.begin(); itor != memNameMap_.end(); itor++) { + printf("%s\n", itor->second.c_str()); + } + printf("\n"); + printf("---subdir of sys mem info---\n"); + for (auto itor = sysMemNameMap_.begin(); itor != sysMemNameMap_.end(); itor++) { + printf("%s\n", itor->second.c_str()); + } + printf("\n"); + printf("---subdir of sys vmem info---\n"); + for (auto itor = sysVirtualMemNameMap_.begin(); itor != sysVirtualMemNameMap_.end(); itor++) { + printf("%s\n", itor->second.c_str()); + } + printf("\n"); +} +void TraceStreamerConfig::InitEventNameMap() +{ + eventNameMap_ = {{TRACE_EVENT_BINDER_TRANSACTION, TRACE_ACTION_BINDER_TRANSACTION}, + {TRACE_EVENT_BINDER_TRANSACTION_RECEIVED, TRACE_ACTION_BINDER_TRANSACTION_RECEIVED}, + {TRACE_EVENT_BINDER_TRANSACTION_ALLOC_BUF, TRACE_ACTION_BINDER_TRANSACTION_ALLOC_BUF}, + {TRACE_EVENT_BINDER_TRANSACTION_LOCK, TRACE_ACTION_BINDER_TRANSACTION_LOCK}, + {TRACE_EVENT_BINDER_TRANSACTION_LOCKED, TRACE_ACTION_BINDER_TRANSACTION_LOCKED}, + {TRACE_EVENT_BINDER_TRANSACTION_UNLOCK, TRACE_ACTION_BINDER_TRANSACTION_UNLOCK}, + {TRACE_EVENT_SCHED_SWITCH, TRACE_ACTION_SCHED_SWITCH}, + {TRACE_EVENT_TASK_RENAME, TRACE_ACTION_TASK_RENAME}, + {TRACE_EVENT_TASK_NEWTASK, TRACE_ACTION_TASK_NEWTASK}, + {TRACE_EVENT_TRACING_MARK_WRITE, TRACE_ACTION_TRACING_MARK_WRITE}, + {TRACE_EVENT_PRINT, TRACE_ACTION_PRINT}, + {TRACE_EVENT_SCHED_WAKEUP, TRACE_ACTION_SCHED_WAKEUP}, + {TRACE_EVENT_SCHED_WAKING, TRACE_ACTION_SCHED_WAKING}, + {TRACE_EVENT_CPU_IDLE, TRACE_ACTION_CPU_IDLE}, + {TRACE_EVENT_CPU_FREQUENCY, TRACE_ACTION_CPU_FREQUENCY}, + {TRACE_EVENT_SUSPEND_RESUME, TRACE_ACTION_SUSPEND_RESUME}, + {TRACE_EVENT_WORKQUEUE_EXECUTE_START, TRACE_ACTION_WORKQUEUE_EXECUTE_START}, + {TRACE_EVENT_WORKQUEUE_EXECUTE_END, TRACE_ACTION_WORKQUEUE_EXECUTE_END}, + {TRACE_EVENT_CLOCK_SET_RATE, TRACE_ACTION_CLOCK_SET_RATE}, + {TRACE_EVENT_CLOCK_ENABLE, TRACE_ACTION_CLOCK_ENABLE}, + {TRACE_EVENT_CLOCK_DISABLE, TRACE_ACTION_CLOCK_DISABLE}, + {TRACE_EVENT_CLK_SET_RATE, TRACE_ACTION_CLK_SET_RATE}, + {TRACE_EVENT_CLK_ENABLE, TRACE_ACTION_CLK_ENABLE}, + {TRACE_EVENT_CLK_DISABLE, TRACE_ACTION_CLK_DISABLE}, + {TRACE_EVENT_SYS_ENTRY, TRACE_ACTION_SYS_ENTRY}, + {TRACE_EVENT_SYS_EXIT, TRACE_ACTION_SYS_EXIT}, + {TRACE_EVENT_OOM_SCORE_ADJ_UPDATE, TRACE_ACTION_OOM_SCORE_ADJ_UPDATE}, + {TRACE_EVENT_REGULATOR_SET_VOLTAGE, TRACE_ACTION_REGULATOR_SET_VOLTAGE}, + {TRACE_EVENT_REGULATOR_SET_VOLTAGE_COMPLETE, TRACE_ACTION_REGULATOR_SET_VOLTAGE_COMPLETE}, + {TRACE_EVENT_REGULATOR_DISABLE, TRACE_ACTION_REGULATOR_DISABLE}, + {TRACE_EVENT_REGULATOR_DISABLE_COMPLETE, TRACE_ACTION_REGULATOR_DISABLE_COMPLETE}, + {TRACE_EVENT_IPI_ENTRY, TRACE_ACTION_IPI_ENTRY}, + {TRACE_EVENT_IPI_EXIT, TRACE_ACTION_IPI_EXIT}, + {TRACE_EVENT_IRQ_HANDLER_ENTRY, TRACE_ACTION_IRQ_HANDLER_ENTRY}, + {TRACE_EVENT_IRQ_HANDLER_EXIT, TRACE_ACTION_IRQ_HANDLER_EXIT}, + {TRACE_EVENT_SOFTIRQ_RAISE, TRACE_ACTION_SOFTIRQ_RAISE}, + {TRACE_EVENT_SOFTIRQ_ENTRY, TRACE_ACTION_SOFTIRQ_ENTRY}, + {TRACE_EVENT_SOFTIRQ_EXIT, TRACE_ACTION_SOFTIRQ_EXIT}, + {TRACE_EVENT_SCHED_WAKEUP_NEW, TRACE_ACTION_SCHED_WAKEUP_NEW}, + {TRACE_EVENT_PROCESS_EXIT, TRACE_ACTION_PROCESS_EXIT}, + {TRACE_EVENT_PROCESS_FREE, TRACE_ACTION_PROCESS_FREE}, + {TRACE_EVENT_CLOCK_SYNC, TRACE_ACTION_CLOCK_SYNC}, + {TRACE_MEMORY, TRACE_ACTION_MEMORY}, + {TRACE_SYS_MEMORY, TRACE_ACTION_SYS_MEMORY}, + {TRACE_SYS_VIRTUAL_MEMORY, TRACE_ACTION_SYS_VIRTUAL_MEMORY}, + {TRACE_EVENT_SIGNAL_GENERATE, TRACE_ACTION_SIGNAL_GENERATE}, + {TRACE_EVENT_SIGNAL_DELIVER, TRACE_ACTION_SIGNAL_DELIVER}, + {TRACE_EVENT_BLOCK_BIO_BACKMERGE, TRACE_ACTION_BLOCK_BIO_BACKMERGE}, + {TRACE_EVENT_BLOCK_BIO_BOUNCE, TRACE_ACTION_BLOCK_BIO_BOUNCE}, + {TRACE_EVENT_BLOCK_BIO_COMPLETE, TRACE_ACTION_BLOCK_BIO_COMPLETE}, + {TRACE_EVENT_BLOCK_BIO_FRONTMERGE, TRACE_ACTION_BLOCK_BIO_FRONTMERGE}, + {TRACE_EVENT_BLOCK_BIO_QUEUE, TRACE_ACTION_BLOCK_BIO_QUEUE}, + {TRACE_EVENT_BLOCK_BIO_REMAP, TRACE_ACTION_BLOCK_BIO_REMAP}, + {TRACE_EVENT_BLOCK_DIRTY_BUFFER, TRACE_ACTION_BLOCK_DIRTY_BUFFER}, + {TRACE_EVENT_BLOCK_GETRQ, TRACE_ACTION_BLOCK_GETRQ}, + {TRACE_EVENT_BLOCK_PLUG, TRACE_ACTION_BLOCK_PLUG}, + {TRACE_EVENT_BLOCK_RQ_COMPLETE, TRACE_ACTION_BLOCK_RQ_COMPLETE}, + {TRACE_EVENT_BLOCK_RQ_INSERT, TRACE_ACTION_BLOCK_RQ_INSERT}, + {TRACE_EVENT_BLOCK_RQ_REMAP, TRACE_ACTION_BLOCK_RQ_REMAP}, + {TRACE_EVENT_BLOCK_RQ_ISSUE, TRACE_ACTION_BLOCK_RQ_ISSUE}, + {TRACE_EVENT_OTHER, TRACE_ACTION_OTHER}, + {TRACE_HILOG, TRACE_ACTION_HILOG}, + {TRACE_HIDUMP_FPS, TRACE_ACTION_HIDUMP_FPS}, + {TRACE_NATIVE_HOOK_MALLOC, TRACE_ACTION_NATIVE_HOOK_MALLOC}, + {TRACE_NATIVE_HOOK_FREE, TRACE_ACTION_NATIVE_HOOK_FREE}}; +} +void TraceStreamerConfig::InitSysMemMap() +{ + sysMemNameMap_ = { + {SysMeminfoType::PMEM_UNSPECIFIED, SYS_MEMINFO_UNSPECIFIED_DESC}, + {SysMeminfoType::PMEM_MEM_TOTAL, SYS_MEMINFO_MEM_TOTAL_DESC}, + {SysMeminfoType::PMEM_MEM_FREE, SYS_MEMINFO_MEM_FREE_DESC}, + {SysMeminfoType::PMEM_MEM_AVAILABLE, SYS_MEMINFO_MEM_AVAILABLE_DESC}, + {SysMeminfoType::PMEM_BUFFERS, SYS_MEMINFO_BUFFERS_DESC}, + {SysMeminfoType::PMEM_CACHED, SYS_MEMINFO_CACHED_DESC}, + {SysMeminfoType::PMEM_SWAP_CACHED, SYS_MEMINFO_SWAP_CACHED_DESC}, + {SysMeminfoType::PMEM_ACTIVE, SYS_MEMINFO_ACTIVE_DESC}, + {SysMeminfoType::PMEM_INACTIVE, SYS_MEMINFO_INACTIVE_DESC}, + {SysMeminfoType::PMEM_ACTIVE_ANON, SYS_MEMINFO_ACTIVE_ANON_DESC}, + {SysMeminfoType::PMEM_INACTIVE_ANON, SYS_MEMINFO_INACTIVE_ANON_DESC}, + {SysMeminfoType::PMEM_ACTIVE_FILE, SYS_MEMINFO_ACTIVE_FILE_DESC}, + {SysMeminfoType::PMEM_INACTIVE_FILE, SYS_MEMINFO_INACTIVE_FILE_DESC}, + {SysMeminfoType::PMEM_UNEVICTABLE, SYS_MEMINFO_UNEVICTABLE_DESC}, + {SysMeminfoType::PMEM_MLOCKED, SYS_MEMINFO_MLOCKED_DESC}, + {SysMeminfoType::PMEM_SWAP_TOTAL, SYS_MEMINFO_SWAP_TOTAL_DESC}, + {SysMeminfoType::PMEM_SWAP_FREE, SYS_MEMINFO_SWAP_FREE_DESC}, + {SysMeminfoType::PMEM_DIRTY, SYS_MEMINFO_DIRTY_DESC}, + {SysMeminfoType::PMEM_WRITEBACK, SYS_MEMINFO_WRITEBACK_DESC}, + {SysMeminfoType::PMEM_ANON_PAGES, SYS_MEMINFO_ANON_PAGES_DESC}, + {SysMeminfoType::PMEM_MAPPED, SYS_MEMINFO_MAPPED_DESC}, + {SysMeminfoType::PMEM_SHMEM, SYS_MEMINFO_SHMEM_DESC}, + {SysMeminfoType::PMEM_SLAB, SYS_MEMINFO_SLAB_DESC}, + {SysMeminfoType::PMEM_SLAB_RECLAIMABLE, SYS_MEMINFO_SLAB_RECLAIMABLE_DESC}, + {SysMeminfoType::PMEM_SLAB_UNRECLAIMABLE, SYS_MEMINFO_SLAB_UNRECLAIMABLE_DESC}, + {SysMeminfoType::PMEM_KERNEL_STACK, SYS_MEMINFO_KERNEL_STACK_DESC}, + {SysMeminfoType::PMEM_PAGE_TABLES, SYS_MEMINFO_PAGE_TABLES_DESC}, + {SysMeminfoType::PMEM_COMMIT_LIMIT, SYS_MEMINFO_COMMIT_LIMIT_DESC}, + {SysMeminfoType::PMEM_COMMITED_AS, SYS_MEMINFO_COMMITED_AS_DESC}, + {SysMeminfoType::PMEM_VMALLOC_TOTAL, SYS_MEMINFO_VMALLOC_TOTAL_DESC}, + {SysMeminfoType::PMEM_VMALLOC_USED, SYS_MEMINFO_VMALLOC_USED_DESC}, + {SysMeminfoType::PMEM_VMALLOC_CHUNK, SYS_MEMINFO_VMALLOC_CHUNK_DESC}, + {SysMeminfoType::PMEM_CMA_TOTAL, SYS_MEMINFO_CMA_TOTAL_DESC}, + {SysMeminfoType::PMEM_CMA_FREE, SYS_MEMINFO_CMA_FREE_DESC}, + }; +} + +void TraceStreamerConfig::InitSysVmemMap() +{ + sysVirtualMemNameMap_ = { + {SysVMeminfoType::VMEMINFO_UNSPECIFIED, SYS_VMEMINFO_UNSPECIFIED_DESC}, + {SysVMeminfoType::VMEMINFO_NR_FREE_PAGES, SYS_VMEMINFO_NR_FREE_PAGES_DESC}, + {SysVMeminfoType::VMEMINFO_NR_ALLOC_BATCH, SYS_VMEMINFO_NR_ALLOC_BATCH_DESC}, + {SysVMeminfoType::VMEMINFO_NR_INACTIVE_ANON, SYS_VMEMINFO_NR_INACTIVE_ANON_DESC}, + {SysVMeminfoType::VMEMINFO_NR_ACTIVE_ANON, SYS_VMEMINFO_NR_ACTIVE_ANON_DESC}, + {SysVMeminfoType::VMEMINFO_NR_INACTIVE_FILE, SYS_VMEMINFO_NR_INACTIVE_FILE_DESC}, + {SysVMeminfoType::VMEMINFO_NR_ACTIVE_FILE, SYS_VMEMINFO_NR_ACTIVE_FILE_DESC}, + {SysVMeminfoType::VMEMINFO_NR_UNEVICTABLE, SYS_VMEMINFO_NR_UNEVICTABLE_DESC}, + {SysVMeminfoType::VMEMINFO_NR_MLOCK, SYS_VMEMINFO_NR_MLOCK_DESC}, + {SysVMeminfoType::VMEMINFO_NR_ANON_PAGES, SYS_VMEMINFO_NR_ANON_PAGES_DESC}, + {SysVMeminfoType::VMEMINFO_NR_MAPPED, SYS_VMEMINFO_NR_MAPPED_DESC}, + {SysVMeminfoType::VMEMINFO_NR_FILE_PAGES, SYS_VMEMINFO_NR_FILE_PAGES_DESC}, + {SysVMeminfoType::VMEMINFO_NR_DIRTY, SYS_VMEMINFO_NR_DIRTY_DESC}, + {SysVMeminfoType::VMEMINFO_NR_WRITEBACK, SYS_VMEMINFO_NR_WRITEBACK_DESC}, + {SysVMeminfoType::VMEMINFO_NR_SLAB_RECLAIMABLE, SYS_VMEMINFO_NR_SLAB_RECLAIMABLE_DESC}, + {SysVMeminfoType::VMEMINFO_NR_SLAB_UNRECLAIMABLE, SYS_VMEMINFO_NR_SLAB_UNRECLAIMABLE_DESC}, + {SysVMeminfoType::VMEMINFO_NR_PAGE_TABLE_PAGES, SYS_VMEMINFO_NR_PAGE_TABLE_PAGES_DESC}, + {SysVMeminfoType::VMEMINFO_NR_KERNEL_STACK, SYS_VMEMINFO_NR_KERNEL_STACK_DESC}, + {SysVMeminfoType::VMEMINFO_NR_OVERHEAD, SYS_VMEMINFO_NR_OVERHEAD_DESC}, + {SysVMeminfoType::VMEMINFO_NR_UNSTABLE, SYS_VMEMINFO_NR_UNSTABLE_DESC}, + {SysVMeminfoType::VMEMINFO_NR_BOUNCE, SYS_VMEMINFO_NR_BOUNCE_DESC}, + {SysVMeminfoType::VMEMINFO_NR_VMSCAN_WRITE, SYS_VMEMINFO_NR_VMSCAN_WRITE_DESC}, + {SysVMeminfoType::VMEMINFO_NR_VMSCAN_IMMEDIATE_RECLAIM, SYS_VMEMINFO_NR_VMSCAN_IMMEDIATE_RECLAIM_DESC}, + {SysVMeminfoType::VMEMINFO_NR_WRITEBACK_TEMP, SYS_VMEMINFO_NR_WRITEBACK_TEMP_DESC}, + {SysVMeminfoType::VMEMINFO_NR_ISOLATED_ANON, SYS_VMEMINFO_NR_ISOLATED_ANON_DESC}, + {SysVMeminfoType::VMEMINFO_NR_ISOLATED_FILE, SYS_VMEMINFO_NR_ISOLATED_FILE_DESC}, + {SysVMeminfoType::VMEMINFO_NR_SHMEM, SYS_VMEMINFO_NR_SHMEM_DESC}, + {SysVMeminfoType::VMEMINFO_NR_DIRTIED, SYS_VMEMINFO_NR_DIRTIED_DESC}, + {SysVMeminfoType::VMEMINFO_NR_WRITTEN, SYS_VMEMINFO_NR_WRITTEN_DESC}, + {SysVMeminfoType::VMEMINFO_NR_PAGES_SCANNED, SYS_VMEMINFO_NR_PAGES_SCANNED_DESC}, + {SysVMeminfoType::VMEMINFO_WORKINGSET_REFAULT, SYS_VMEMINFO_WORKINGSET_REFAULT_DESC}, + {SysVMeminfoType::VMEMINFO_WORKINGSET_ACTIVATE, SYS_VMEMINFO_WORKINGSET_ACTIVATE_DESC}, + {SysVMeminfoType::VMEMINFO_WORKINGSET_NODERECLAIM, SYS_VMEMINFO_WORKINGSET_NODERECLAIM_DESC}, + {SysVMeminfoType::VMEMINFO_NR_ANON_TRANSPARENT_HUGEPAGES, SYS_VMEMINFO_NR_ANON_TRANSPARENT_HUGEPAGES_DESC}, + {SysVMeminfoType::VMEMINFO_NR_FREE_CMA, SYS_VMEMINFO_NR_FREE_CMA_DESC}, + {SysVMeminfoType::VMEMINFO_NR_SWAPCACHE, SYS_VMEMINFO_NR_SWAPCACHE_DESC}, + {SysVMeminfoType::VMEMINFO_NR_DIRTY_THRESHOLD, SYS_VMEMINFO_NR_DIRTY_THRESHOLD_DESC}, + {SysVMeminfoType::VMEMINFO_NR_DIRTY_BACKGROUND_THRESHOLD, SYS_VMEMINFO_NR_DIRTY_BACKGROUND_THRESHOLD_DESC}, + {SysVMeminfoType::VMEMINFO_PGPGIN, SYS_VMEMINFO_PGPGIN_DESC}, + {SysVMeminfoType::VMEMINFO_PGPGOUT, SYS_VMEMINFO_PGPGOUT_DESC}, + {SysVMeminfoType::VMEMINFO_PGPGOUTCLEAN, SYS_VMEMINFO_PGPGOUTCLEAN_DESC}, + {SysVMeminfoType::VMEMINFO_PSWPIN, SYS_VMEMINFO_PSWPIN_DESC}, + {SysVMeminfoType::VMEMINFO_PSWPOUT, SYS_VMEMINFO_PSWPOUT_DESC}, + {SysVMeminfoType::VMEMINFO_PGALLOC_DMA, SYS_VMEMINFO_PGALLOC_DMA_DESC}, + {SysVMeminfoType::VMEMINFO_PGALLOC_NORMAL, SYS_VMEMINFO_PGALLOC_NORMAL_DESC}, + {SysVMeminfoType::VMEMINFO_PGALLOC_MOVABLE, SYS_VMEMINFO_PGALLOC_MOVABLE_DESC}, + {SysVMeminfoType::VMEMINFO_PGFREE, SYS_VMEMINFO_PGFREE_DESC}, + {SysVMeminfoType::VMEMINFO_PGACTIVATE, SYS_VMEMINFO_PGACTIVATE_DESC}, + {SysVMeminfoType::VMEMINFO_PGDEACTIVATE, SYS_VMEMINFO_PGDEACTIVATE_DESC}, + {SysVMeminfoType::VMEMINFO_PGFAULT, SYS_VMEMINFO_PGFAULT_DESC}, + {SysVMeminfoType::VMEMINFO_PGMAJFAULT, SYS_VMEMINFO_PGMAJFAULT_DESC}, + {SysVMeminfoType::VMEMINFO_PGREFILL_DMA, SYS_VMEMINFO_PGREFILL_DMA_DESC}, + {SysVMeminfoType::VMEMINFO_PGREFILL_NORMAL, SYS_VMEMINFO_PGREFILL_NORMAL_DESC}, + {SysVMeminfoType::VMEMINFO_PGREFILL_MOVABLE, SYS_VMEMINFO_PGREFILL_MOVABLE_DESC}, + {SysVMeminfoType::VMEMINFO_PGSTEAL_KSWAPD_DMA, SYS_VMEMINFO_PGSTEAL_KSWAPD_DMA_DESC}, + {SysVMeminfoType::VMEMINFO_PGSTEAL_KSWAPD_NORMAL, SYS_VMEMINFO_PGSTEAL_KSWAPD_NORMAL_DESC}, + {SysVMeminfoType::VMEMINFO_PGSTEAL_KSWAPD_MOVABLE, SYS_VMEMINFO_PGSTEAL_KSWAPD_MOVABLE_DESC}, + {SysVMeminfoType::VMEMINFO_PGSTEAL_DIRECT_DMA, SYS_VMEMINFO_PGSTEAL_DIRECT_DMA_DESC}, + {SysVMeminfoType::VMEMINFO_PGSTEAL_DIRECT_NORMAL, SYS_VMEMINFO_PGSTEAL_DIRECT_NORMAL_DESC}, + {SysVMeminfoType::VMEMINFO_PGSTEAL_DIRECT_MOVABLE, SYS_VMEMINFO_PGSTEAL_DIRECT_MOVABLE_DESC}, + {SysVMeminfoType::VMEMINFO_PGSCAN_KSWAPD_DMA, SYS_VMEMINFO_PGSCAN_KSWAPD_DMA_DESC}, + {SysVMeminfoType::VMEMINFO_PGSCAN_KSWAPD_NORMAL, SYS_VMEMINFO_PGSCAN_KSWAPD_NORMAL_DESC}, + {SysVMeminfoType::VMEMINFO_PGSCAN_KSWAPD_MOVABLE, SYS_VMEMINFO_PGSCAN_KSWAPD_MOVABLE_DESC}, + {SysVMeminfoType::VMEMINFO_PGSCAN_DIRECT_DMA, SYS_VMEMINFO_PGSCAN_DIRECT_DMA_DESC}, + {SysVMeminfoType::VMEMINFO_PGSCAN_DIRECT_NORMAL, SYS_VMEMINFO_PGSCAN_DIRECT_NORMAL_DESC}, + {SysVMeminfoType::VMEMINFO_PGSCAN_DIRECT_MOVABLE, SYS_VMEMINFO_PGSCAN_DIRECT_MOVABLE_DESC}, + {SysVMeminfoType::VMEMINFO_PGSCAN_DIRECT_THROTTLE, SYS_VMEMINFO_PGSCAN_DIRECT_THROTTLE_DESC}, + {SysVMeminfoType::VMEMINFO_PGINODESTEAL, SYS_VMEMINFO_PGINODESTEAL_DESC}, + {SysVMeminfoType::VMEMINFO_SLABS_SCANNED, SYS_VMEMINFO_SLABS_SCANNED_DESC}, + {SysVMeminfoType::VMEMINFO_KSWAPD_INODESTEAL, SYS_VMEMINFO_KSWAPD_INODESTEAL_DESC}, + {SysVMeminfoType::VMEMINFO_KSWAPD_LOW_WMARK_HIT_QUICKLY, SYS_VMEMINFO_KSWAPD_LOW_WMARK_HIT_QUICKLY_DESC}, + {SysVMeminfoType::VMEMINFO_KSWAPD_HIGH_WMARK_HIT_QUICKLY, SYS_VMEMINFO_KSWAPD_HIGH_WMARK_HIT_QUICKLY_DESC}, + {SysVMeminfoType::VMEMINFO_PAGEOUTRUN, SYS_VMEMINFO_PAGEOUTRUN_DESC}, + {SysVMeminfoType::VMEMINFO_ALLOCSTALL, SYS_VMEMINFO_ALLOCSTALL_DESC}, + {SysVMeminfoType::VMEMINFO_PGROTATED, SYS_VMEMINFO_PGROTATED_DESC}, + {SysVMeminfoType::VMEMINFO_DROP_PAGECACHE, SYS_VMEMINFO_DROP_PAGECACHE_DESC}, + {SysVMeminfoType::VMEMINFO_DROP_SLAB, SYS_VMEMINFO_DROP_SLAB_DESC}, + {SysVMeminfoType::VMEMINFO_PGMIGRATE_SUCCESS, SYS_VMEMINFO_PGMIGRATE_SUCCESS_DESC}, + {SysVMeminfoType::VMEMINFO_PGMIGRATE_FAIL, SYS_VMEMINFO_PGMIGRATE_FAIL_DESC}, + {SysVMeminfoType::VMEMINFO_COMPACT_MIGRATE_SCANNED, SYS_VMEMINFO_COMPACT_MIGRATE_SCANNED_DESC}, + {SysVMeminfoType::VMEMINFO_COMPACT_FREE_SCANNED, SYS_VMEMINFO_COMPACT_FREE_SCANNED_DESC}, + {SysVMeminfoType::VMEMINFO_COMPACT_ISOLATED, SYS_VMEMINFO_COMPACT_ISOLATED_DESC}, + {SysVMeminfoType::VMEMINFO_COMPACT_STALL, SYS_VMEMINFO_COMPACT_STALL_DESC}, + {SysVMeminfoType::VMEMINFO_COMPACT_FAIL, SYS_VMEMINFO_COMPACT_FAIL_DESC}, + {SysVMeminfoType::VMEMINFO_COMPACT_SUCCESS, SYS_VMEMINFO_COMPACT_SUCCESS_DESC}, + {SysVMeminfoType::VMEMINFO_COMPACT_DAEMON_WAKE, SYS_VMEMINFO_COMPACT_DAEMON_WAKE_DESC}, + {SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_CULLED, SYS_VMEMINFO_UNEVICTABLE_PGS_CULLED_DESC}, + {SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_SCANNED, SYS_VMEMINFO_UNEVICTABLE_PGS_SCANNED_DESC}, + {SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_RESCUED, SYS_VMEMINFO_UNEVICTABLE_PGS_RESCUED_DESC}, + {SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_MLOCKED, SYS_VMEMINFO_UNEVICTABLE_PGS_MLOCKED_DESC}, + {SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_MUNLOCKED, SYS_VMEMINFO_UNEVICTABLE_PGS_MUNLOCKED_DESC}, + {SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_CLEARED, SYS_VMEMINFO_UNEVICTABLE_PGS_CLEARED_DESC}, + {SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_STRANDED, SYS_VMEMINFO_UNEVICTABLE_PGS_STRANDED_DESC}, + {SysVMeminfoType::VMEMINFO_NR_ZSPAGES, SYS_VMEMINFO_NR_ZSPAGES_DESC}, + {SysVMeminfoType::VMEMINFO_NR_ION_HEAP, SYS_VMEMINFO_NR_ION_HEAP_DESC}, + {SysVMeminfoType::VMEMINFO_NR_GPU_HEAP, SYS_VMEMINFO_NR_GPU_HEAP_DESC}, + {SysVMeminfoType::VMEMINFO_ALLOCSTALL_DMA, SYS_VMEMINFO_ALLOCSTALL_DMA_DESC}, + {SysVMeminfoType::VMEMINFO_ALLOCSTALL_MOVABLE, SYS_VMEMINFO_ALLOCSTALL_MOVABLE_DESC}, + {SysVMeminfoType::VMEMINFO_ALLOCSTALL_NORMAL, SYS_VMEMINFO_ALLOCSTALL_NORMAL_DESC}, + {SysVMeminfoType::VMEMINFO_COMPACT_DAEMON_FREE_SCANNED, SYS_VMEMINFO_COMPACT_DAEMON_FREE_SCANNED_DESC}, + {SysVMeminfoType::VMEMINFO_COMPACT_DAEMON_MIGRATE_SCANNED, SYS_VMEMINFO_COMPACT_DAEMON_MIGRATE_SCANNED_DESC}, + {SysVMeminfoType::VMEMINFO_NR_FASTRPC, SYS_VMEMINFO_NR_FASTRPC_DESC}, + {SysVMeminfoType::VMEMINFO_NR_INDIRECTLY_RECLAIMABLE, SYS_VMEMINFO_NR_INDIRECTLY_RECLAIMABLE_DESC}, + {SysVMeminfoType::VMEMINFO_NR_ION_HEAP_POOL, SYS_VMEMINFO_NR_ION_HEAP_POOL_DESC}, + {SysVMeminfoType::VMEMINFO_NR_KERNEL_MISC_RECLAIMABLE, SYS_VMEMINFO_NR_KERNEL_MISC_RECLAIMABLE_DESC}, + {SysVMeminfoType::VMEMINFO_NR_SHADOW_CALL_STACK_BYTES, SYS_VMEMINFO_NR_SHADOW_CALL_STACK_BYTES_DESC}, + {SysVMeminfoType::VMEMINFO_NR_SHMEM_HUGEPAGES, SYS_VMEMINFO_NR_SHMEM_HUGEPAGES_DESC}, + {SysVMeminfoType::VMEMINFO_NR_SHMEM_PMDMAPPED, SYS_VMEMINFO_NR_SHMEM_PMDMAPPED_DESC}, + {SysVMeminfoType::VMEMINFO_NR_UNRECLAIMABLE_PAGES, SYS_VMEMINFO_NR_UNRECLAIMABLE_PAGES_DESC}, + {SysVMeminfoType::VMEMINFO_NR_ZONE_ACTIVE_ANON, SYS_VMEMINFO_NR_ZONE_ACTIVE_ANON_DESC}, + {SysVMeminfoType::VMEMINFO_NR_ZONE_ACTIVE_FILE, SYS_VMEMINFO_NR_ZONE_ACTIVE_FILE_DESC}, + {SysVMeminfoType::VMEMINFO_NR_ZONE_INACTIVE_ANON, SYS_VMEMINFO_NR_ZONE_INACTIVE_ANON_DESC}, + {SysVMeminfoType::VMEMINFO_NR_ZONE_INACTIVE_FILE, SYS_VMEMINFO_NR_ZONE_INACTIVE_FILE_DESC}, + {SysVMeminfoType::VMEMINFO_NR_ZONE_UNEVICTABLE, SYS_VMEMINFO_NR_ZONE_UNEVICTABLE_DESC}, + {SysVMeminfoType::VMEMINFO_NR_ZONE_WRITE_PENDING, SYS_VMEMINFO_NR_ZONE_WRITE_PENDING_DESC}, + {SysVMeminfoType::VMEMINFO_OOM_KILL, SYS_VMEMINFO_OOM_KILL_DESC}, + {SysVMeminfoType::VMEMINFO_PGLAZYFREE, SYS_VMEMINFO_PGLAZYFREE_DESC}, + {SysVMeminfoType::VMEMINFO_PGLAZYFREED, SYS_VMEMINFO_PGLAZYFREED_DESC}, + {SysVMeminfoType::VMEMINFO_PGREFILL, SYS_VMEMINFO_PGREFILL_DESC}, + {SysVMeminfoType::VMEMINFO_PGSCAN_DIRECT, SYS_VMEMINFO_PGSCAN_DIRECT_DESC}, + {SysVMeminfoType::VMEMINFO_PGSCAN_KSWAPD, SYS_VMEMINFO_PGSCAN_KSWAPD_DESC}, + {SysVMeminfoType::VMEMINFO_PGSKIP_DMA, SYS_VMEMINFO_PGSKIP_DMA_DESC}, + {SysVMeminfoType::VMEMINFO_PGSKIP_MOVABLE, SYS_VMEMINFO_PGSKIP_MOVABLE_DESC}, + {SysVMeminfoType::VMEMINFO_PGSKIP_NORMAL, SYS_VMEMINFO_PGSKIP_NORMAL_DESC}, + {SysVMeminfoType::VMEMINFO_PGSTEAL_DIRECT, SYS_VMEMINFO_PGSTEAL_DIRECT_DESC}, + {SysVMeminfoType::VMEMINFO_PGSTEAL_KSWAPD, SYS_VMEMINFO_PGSTEAL_KSWAPD_DESC}, + {SysVMeminfoType::VMEMINFO_SWAP_RA, SYS_VMEMINFO_SWAP_RA_DESC}, + {SysVMeminfoType::VMEMINFO_SWAP_RA_HIT, SYS_VMEMINFO_SWAP_RA_HIT_DESC}, + {SysVMeminfoType::VMEMINFO_WORKINGSET_RESTORE, SYS_VMEMINFO_WORKINGSET_RESTORE_DESC}}; +} +void TraceStreamerConfig::InitSecurityMap() +{ + eventParserStatSeverityDescMap_ = { + { + TRACE_EVENT_BINDER_TRANSACTION, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BINDER_TRANSACTION_RECEIVED, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BINDER_TRANSACTION_ALLOC_BUF, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BINDER_TRANSACTION_LOCK, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BINDER_TRANSACTION_LOCKED, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BINDER_TRANSACTION_UNLOCK, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_SCHED_SWITCH, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_TASK_RENAME, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_TASK_NEWTASK, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_TRACING_MARK_WRITE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_PRINT, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_SCHED_WAKEUP, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_SCHED_WAKING, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_CPU_IDLE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_CPU_FREQUENCY, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_SUSPEND_RESUME, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_WORKQUEUE_EXECUTE_START, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_WORKQUEUE_EXECUTE_END, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_CLOCK_SET_RATE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_CLOCK_ENABLE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_CLOCK_DISABLE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_CLK_SET_RATE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_CLK_ENABLE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_CLK_DISABLE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_REGULATOR_SET_VOLTAGE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_REGULATOR_SET_VOLTAGE_COMPLETE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_REGULATOR_DISABLE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_REGULATOR_DISABLE_COMPLETE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_IPI_ENTRY, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_IPI_EXIT, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_IRQ_HANDLER_ENTRY, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_IRQ_HANDLER_EXIT, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_SOFTIRQ_RAISE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_SOFTIRQ_ENTRY, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_SOFTIRQ_EXIT, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_SCHED_WAKEUP_NEW, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_PROCESS_EXIT, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_PROCESS_FREE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_CLOCK_SYNC, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_SYS_ENTRY, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_OOM_SCORE_ADJ_UPDATE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_SYS_EXIT, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_MEMORY, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_HILOG, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_HIDUMP_FPS, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_NATIVE_HOOK_MALLOC, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_NATIVE_HOOK_FREE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_SYS_MEMORY, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_SYS_VIRTUAL_MEMORY, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_SIGNAL_GENERATE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_SIGNAL_DELIVER, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BLOCK_BIO_BACKMERGE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BLOCK_BIO_BOUNCE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BLOCK_BIO_COMPLETE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BLOCK_BIO_FRONTMERGE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BLOCK_BIO_QUEUE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BLOCK_BIO_REMAP, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BLOCK_DIRTY_BUFFER, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BLOCK_GETRQ, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BLOCK_PLUG, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BLOCK_RQ_COMPLETE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BLOCK_RQ_INSERT, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BLOCK_RQ_REMAP, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_BLOCK_RQ_ISSUE, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + { + TRACE_EVENT_OTHER, + { + {STAT_EVENT_RECEIVED, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_DATA_LOST, STAT_SEVERITY_LEVEL_ERROR}, + {STAT_EVENT_NOTMATCH, STAT_SEVERITY_LEVEL_INFO}, + {STAT_EVENT_NOTSUPPORTED, STAT_SEVERITY_LEVEL_WARN}, + {STAT_EVENT_DATA_INVALID, STAT_SEVERITY_LEVEL_ERROR}, + }, + }, + }; +} +} // namespace TraceCfg +} // namespace SysTuning diff --git a/host/trace_streamer/src/cfg/trace_streamer_config.h b/host/trace_streamer/src/cfg/trace_streamer_config.h new file mode 100644 index 0000000..3e71a1a --- /dev/null +++ b/host/trace_streamer/src/cfg/trace_streamer_config.h @@ -0,0 +1,423 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TRACE_STREAMER_CONFIG_H +#define TRACE_STREAMER_CONFIG_H +#include +#include +#include "memory_plugin_common.pb.h" +namespace SysTuning { +namespace TraceCfg { +// all supported events should be defined here +enum SupportedTraceEventType { + TRACE_EVENT_START = 0, + TRACE_EVENT_BINDER_TRANSACTION = TRACE_EVENT_START, + TRACE_EVENT_BINDER_TRANSACTION_RECEIVED, + TRACE_EVENT_BINDER_TRANSACTION_ALLOC_BUF, + TRACE_EVENT_BINDER_TRANSACTION_LOCK, + TRACE_EVENT_BINDER_TRANSACTION_LOCKED, + TRACE_EVENT_BINDER_TRANSACTION_UNLOCK, + TRACE_EVENT_SCHED_SWITCH, + TRACE_EVENT_TASK_RENAME, + TRACE_EVENT_TASK_NEWTASK, + TRACE_EVENT_TRACING_MARK_WRITE, + TRACE_EVENT_PRINT, + TRACE_EVENT_SCHED_WAKEUP, + TRACE_EVENT_SCHED_WAKING, + TRACE_EVENT_CPU_IDLE, + TRACE_EVENT_CPU_FREQUENCY, + TRACE_EVENT_SUSPEND_RESUME, + TRACE_EVENT_WORKQUEUE_EXECUTE_START, + TRACE_EVENT_WORKQUEUE_EXECUTE_END, + TRACE_EVENT_CLOCK_SET_RATE, + TRACE_EVENT_CLOCK_ENABLE, + TRACE_EVENT_CLOCK_DISABLE, + TRACE_EVENT_CLK_SET_RATE, + TRACE_EVENT_CLK_ENABLE, + TRACE_EVENT_CLK_DISABLE, + TRACE_EVENT_SYS_ENTRY, + TRACE_EVENT_SYS_EXIT, + TRACE_EVENT_REGULATOR_SET_VOLTAGE, + TRACE_EVENT_REGULATOR_SET_VOLTAGE_COMPLETE, + TRACE_EVENT_REGULATOR_DISABLE, + TRACE_EVENT_REGULATOR_DISABLE_COMPLETE, + TRACE_EVENT_IPI_ENTRY, + TRACE_EVENT_IPI_EXIT, + TRACE_EVENT_IRQ_HANDLER_ENTRY, + TRACE_EVENT_IRQ_HANDLER_EXIT, + TRACE_EVENT_SOFTIRQ_RAISE, + TRACE_EVENT_SOFTIRQ_ENTRY, + TRACE_EVENT_SOFTIRQ_EXIT, + TRACE_EVENT_OOM_SCORE_ADJ_UPDATE, + TRACE_EVENT_SCHED_WAKEUP_NEW, + TRACE_EVENT_PROCESS_EXIT, + TRACE_EVENT_PROCESS_FREE, + TRACE_EVENT_CLOCK_SYNC, + TRACE_MEMORY, + TRACE_HILOG, + TRACE_HIDUMP_FPS, + TRACE_NATIVE_HOOK_MALLOC, + TRACE_NATIVE_HOOK_FREE, + TRACE_SYS_MEMORY, + TRACE_SYS_VIRTUAL_MEMORY, + TRACE_EVENT_SIGNAL_GENERATE, + TRACE_EVENT_SIGNAL_DELIVER, + TRACE_EVENT_BLOCK_BIO_BACKMERGE, + TRACE_EVENT_BLOCK_BIO_BOUNCE, + TRACE_EVENT_BLOCK_BIO_COMPLETE, + TRACE_EVENT_BLOCK_BIO_FRONTMERGE, + TRACE_EVENT_BLOCK_BIO_QUEUE, + TRACE_EVENT_BLOCK_BIO_REMAP, + TRACE_EVENT_BLOCK_DIRTY_BUFFER, + TRACE_EVENT_BLOCK_GETRQ, + TRACE_EVENT_BLOCK_PLUG, + TRACE_EVENT_BLOCK_RQ_COMPLETE, + TRACE_EVENT_BLOCK_RQ_INSERT, + TRACE_EVENT_BLOCK_RQ_REMAP, + TRACE_EVENT_BLOCK_RQ_ISSUE, + TRACE_EVENT_OTHER, + TRACE_EVENT_MAX +}; +enum MemInfoType { + MEM_VM_SIZE, + MEM_VM_RSS, + MEM_VM_ANON, + MEM_RSS_FILE, + MEM_RSS_SHMEM, + MEM_VM_SWAP, + MEM_VM_LOCKED, + MEM_VM_HWM, + MEM_OOM_SCORE_ADJ, + MEM_MAX +}; +enum StatType { + STAT_EVENT_START = 0, + STAT_EVENT_RECEIVED = STAT_EVENT_START, + STAT_EVENT_DATA_LOST, + STAT_EVENT_NOTMATCH, + STAT_EVENT_NOTSUPPORTED, + STAT_EVENT_DATA_INVALID, + STAT_EVENT_MAX +}; + +// there maybe some error while parser trace msgs, here defined the error levels +enum StatSeverityLevel { + STAT_SEVERITY_LEVEL_START = 0, + STAT_SEVERITY_LEVEL_INFO = STAT_SEVERITY_LEVEL_START, + STAT_SEVERITY_LEVEL_WARN, + STAT_SEVERITY_LEVEL_ERROR, + STAT_SEVERITY_LEVEL_FATAL, + STAT_SEVERITY_LEVEL_MAX +}; + +// the supported metadata +enum MetaDataItem { + METADATA_ITEM_START = 0, + METADATA_ITEM_DATASIZE = METADATA_ITEM_START, + METADATA_ITEM_PARSETOOL_NAME, + METADATA_ITEM_PARSERTOOL_VERSION, + METADATA_ITEM_PARSERTOOL_PUBLISH_DATETIME, + METADATA_ITEM_SOURCE_FILENAME, + METADATA_ITEM_OUTPUT_FILENAME, + METADATA_ITEM_PARSERTIME, // the data time while the data parsed + METADATA_ITEM_TRACE_DURATION, + METADATA_ITEM_SOURCE_DATETYPE, // proto-based-trace or txt-based-trace + METADATA_ITEM_MAX +}; + +class TraceStreamerConfig { +public: + TraceStreamerConfig(); + ~TraceStreamerConfig() = default; + void PrintInfo() const; + +public: + std::map eventNameMap_ = {}; + std::map eventErrorDescMap_ = {}; + std::map serverityLevelDescMap_ = {}; + // different msg may have STAT_EVENT_MAX types of exception when parse, and they have different error level + // if you think some error level should be improve or depress, you can edit this map + std::map> eventParserStatSeverityDescMap_ = {}; + // process mem info desc + std::map memNameMap_ = {}; + // sys memorty info desc + std::map sysMemNameMap_ = {}; + // sys virtual memorty info desc + std::map sysVirtualMemNameMap_ = {}; + +private: + void InitEventNameMap(); + void InitSysMemMap(); + void InitSysVmemMap(); + void InitSecurityMap(); + // all supported events should be defined here, these str can be find in text-based trace + const std::string TRACE_ACTION_BINDER_TRANSACTION = "binder_transaction"; + const std::string TRACE_ACTION_BINDER_TRANSACTION_RECEIVED = "binder_transaction_received"; + const std::string TRACE_ACTION_SCHED_SWITCH = "sched_switch"; + const std::string TRACE_ACTION_TASK_RENAME = "task_rename"; + const std::string TRACE_ACTION_TASK_NEWTASK = "task_newtask"; + const std::string TRACE_ACTION_TRACING_MARK_WRITE = "tracing_mark_write"; + const std::string TRACE_ACTION_PRINT = "print"; + const std::string TRACE_ACTION_SCHED_WAKEUP = "sched_wakeup"; + const std::string TRACE_ACTION_SCHED_WAKING = "sched_waking"; + const std::string TRACE_ACTION_CPU_IDLE = "cpu_idle"; + const std::string TRACE_ACTION_CPU_FREQUENCY = "cpu_frequency"; + const std::string TRACE_ACTION_SUSPEND_RESUME = "suspend_resume"; + const std::string TRACE_ACTION_WORKQUEUE_EXECUTE_START = "workqueue_execute_start"; + const std::string TRACE_ACTION_WORKQUEUE_EXECUTE_END = "workqueue_execute_end"; + + const std::string TRACE_ACTION_CLOCK_SET_RATE = "clock_set_rate"; + const std::string TRACE_ACTION_CLOCK_ENABLE = "clock_enable"; + const std::string TRACE_ACTION_CLOCK_DISABLE = "clock_disable"; + const std::string TRACE_ACTION_CLK_SET_RATE = "clk_set_rate"; + const std::string TRACE_ACTION_CLK_ENABLE = "clk_enable"; + const std::string TRACE_ACTION_CLK_DISABLE = "clk_disable"; + const std::string TRACE_ACTION_SYS_ENTRY = "sys_enter"; + const std::string TRACE_ACTION_SYS_EXIT = "sys_exit"; + const std::string TRACE_ACTION_OOM_SCORE_ADJ_UPDATE = "oom_score_adj_update"; + const std::string TRACE_ACTION_REGULATOR_SET_VOLTAGE = "regulator_set_voltage"; + const std::string TRACE_ACTION_REGULATOR_SET_VOLTAGE_COMPLETE = "regulator_set_voltage_complete"; + const std::string TRACE_ACTION_REGULATOR_DISABLE = "regulator_disable"; + const std::string TRACE_ACTION_REGULATOR_DISABLE_COMPLETE = "regulator_disable_complete"; + const std::string TRACE_ACTION_IPI_ENTRY = "ipi_entry"; + const std::string TRACE_ACTION_IPI_EXIT = "ipi_exit"; + const std::string TRACE_ACTION_IRQ_HANDLER_ENTRY = "irq_handler_entry"; + const std::string TRACE_ACTION_IRQ_HANDLER_EXIT = "irq_handler_exit"; + const std::string TRACE_ACTION_SOFTIRQ_RAISE = "softirq_raise"; + const std::string TRACE_ACTION_SOFTIRQ_ENTRY = "softirq_entry"; + const std::string TRACE_ACTION_SOFTIRQ_EXIT = "softirq_exit"; + const std::string TRACE_ACTION_BINDER_TRANSACTION_ALLOC_BUF = "binder_transaction_alloc_buf"; + const std::string TRACE_ACTION_BINDER_TRANSACTION_LOCK = "binder_transaction_lock"; + const std::string TRACE_ACTION_BINDER_TRANSACTION_LOCKED = "binder_transaction_locked"; + const std::string TRACE_ACTION_BINDER_TRANSACTION_UNLOCK = "binder_transaction_unlock"; + const std::string TRACE_ACTION_SCHED_WAKEUP_NEW = "sched_wakeup_new"; + const std::string TRACE_ACTION_PROCESS_EXIT = "sched_process_exit"; + const std::string TRACE_ACTION_PROCESS_FREE = "sched_process_free"; + const std::string TRACE_ACTION_CLOCK_SYNC = "trace_event_clock_sync"; + const std::string TRACE_ACTION_MEMORY = "memory"; + const std::string TRACE_ACTION_HILOG = "hilog"; + const std::string TRACE_ACTION_HIDUMP_FPS = "hidump_fps"; + const std::string TRACE_ACTION_NATIVE_HOOK_MALLOC = "native_hook_malloc"; + const std::string TRACE_ACTION_NATIVE_HOOK_FREE = "native_hook_free"; + const std::string TRACE_ACTION_SIGNAL_GENERATE = "signal_generate"; + const std::string TRACE_ACTION_SIGNAL_DELIVER = "signal_deliver"; + const std::string TRACE_ACTION_BLOCK_BIO_BACKMERGE = "trace_block_bio_backmerge"; + const std::string TRACE_ACTION_BLOCK_BIO_BOUNCE = "trace_block_bio_bounce"; + const std::string TRACE_ACTION_BLOCK_BIO_COMPLETE = "trace_block_bio_complete"; + const std::string TRACE_ACTION_BLOCK_BIO_FRONTMERGE = "trace_block_bio_frontmerge"; + const std::string TRACE_ACTION_BLOCK_BIO_QUEUE = "trace_bblock_bio_queue"; + const std::string TRACE_ACTION_BLOCK_BIO_REMAP = "trace_block_bio_remap"; + const std::string TRACE_ACTION_BLOCK_DIRTY_BUFFER = "trace_block_dirty_buffer"; + const std::string TRACE_ACTION_BLOCK_GETRQ = "trace_block_getrq"; + const std::string TRACE_ACTION_BLOCK_PLUG = "trace_block_plug"; + const std::string TRACE_ACTION_BLOCK_RQ_COMPLETE = "trace_block_rq_complete"; + const std::string TRACE_ACTION_BLOCK_RQ_INSERT = "trace_block_rq_insert"; + const std::string TRACE_ACTION_BLOCK_RQ_REMAP = "trace_block_rq_remap"; + const std::string TRACE_ACTION_BLOCK_RQ_ISSUE = "trace_block_rq_issue"; + + const std::string TRACE_ACTION_SYS_MEMORY = "sys_memory"; + const std::string TRACE_ACTION_SYS_VIRTUAL_MEMORY = "sys_virtual_memory"; + const std::string TRACE_ACTION_OTHER = "other"; + + const std::string MEM_INFO_VM_SIZE_DESC = "mem.vm.size"; + const std::string MEM_INFO_LOCKED_DESC = "mem.locked"; + const std::string MEM_INFO_RSS_DESC = "mem.rss"; + const std::string MEM_INFO_RSS_ANON_DESC = "mem.rss.anon"; + const std::string MEM_INFO_RSS_FILE_DESC = "mem.rss.file"; + const std::string MEM_INFO_RSS_SCHEM_DESC = "mem.rss.schem"; + const std::string MEM_INFO_SWAP_DESC = "mem.swap"; + const std::string MEM_INFO_VIRT_DESC = "mem.virt"; + const std::string MEM_INFO_HWM_DESC = "mem.hwm"; + const std::string MEM_INFO_SCORE_ADJ_DESC = "mm.oom_score_adj"; + + const std::string SYS_MEMINFO_UNSPECIFIED_DESC = "sys.mem.unspecified"; + const std::string SYS_MEMINFO_MEM_TOTAL_DESC = "sys.mem.total"; + const std::string SYS_MEMINFO_MEM_FREE_DESC = "sys.mem.free"; + const std::string SYS_MEMINFO_MEM_AVAILABLE_DESC = "sys.mem.avaiable"; + const std::string SYS_MEMINFO_BUFFERS_DESC = "sys.mem.buffers"; + const std::string SYS_MEMINFO_CACHED_DESC = "sys.mem.cached"; + const std::string SYS_MEMINFO_SWAP_CACHED_DESC = "sys.mem.swap.chard"; + const std::string SYS_MEMINFO_ACTIVE_DESC = "sys.mem.active"; + const std::string SYS_MEMINFO_INACTIVE_DESC = "sys.mem.inactive"; + const std::string SYS_MEMINFO_ACTIVE_ANON_DESC = "sys.mem.active.anon"; + const std::string SYS_MEMINFO_INACTIVE_ANON_DESC = "sys.mem.inactive.anon"; + const std::string SYS_MEMINFO_ACTIVE_FILE_DESC = "sys.mem.active_file"; + const std::string SYS_MEMINFO_INACTIVE_FILE_DESC = "sys.mem.inactive_file"; + const std::string SYS_MEMINFO_UNEVICTABLE_DESC = "sys.mem.unevictable"; + const std::string SYS_MEMINFO_MLOCKED_DESC = "sys.mem.mlocked"; + const std::string SYS_MEMINFO_SWAP_TOTAL_DESC = "sys.mem.swap.total"; + const std::string SYS_MEMINFO_SWAP_FREE_DESC = "sys.mem.swap.free"; + const std::string SYS_MEMINFO_DIRTY_DESC = "sys.mem.dirty"; + const std::string SYS_MEMINFO_WRITEBACK_DESC = "sys.mem.writeback"; + const std::string SYS_MEMINFO_ANON_PAGES_DESC = "sys.mem.anon.pages"; + const std::string SYS_MEMINFO_MAPPED_DESC = "sys.mem.mapped"; + const std::string SYS_MEMINFO_SHMEM_DESC = "sys.mem.shmem"; + const std::string SYS_MEMINFO_SLAB_DESC = "sys.mem.slab"; + const std::string SYS_MEMINFO_SLAB_RECLAIMABLE_DESC = "sys.mem.slab.reclaimable"; + const std::string SYS_MEMINFO_SLAB_UNRECLAIMABLE_DESC = "sys.mem.slab.unreclaimable"; + const std::string SYS_MEMINFO_KERNEL_STACK_DESC = "sys.mem.kernel.stack"; + const std::string SYS_MEMINFO_PAGE_TABLES_DESC = "sys.mem.page.tables"; + const std::string SYS_MEMINFO_COMMIT_LIMIT_DESC = "sys.mem.commit.limit"; + const std::string SYS_MEMINFO_COMMITED_AS_DESC = "sys.mem.commited.as"; + const std::string SYS_MEMINFO_VMALLOC_TOTAL_DESC = "sys.mem.vmalloc.total"; + const std::string SYS_MEMINFO_VMALLOC_USED_DESC = "sys.mem.vmalloc.used"; + const std::string SYS_MEMINFO_VMALLOC_CHUNK_DESC = "sys.mem.vmalloc.chunk"; + const std::string SYS_MEMINFO_CMA_TOTAL_DESC = "sys.mem.cma.total"; + const std::string SYS_MEMINFO_CMA_FREE_DESC = "sys.mem.cma.free"; + const std::string SYS_VMEMINFO_UNSPECIFIED_DESC = "sys.virtual.mem.unspecified"; + const std::string SYS_VMEMINFO_NR_FREE_PAGES_DESC = "sys.virtual.mem.nr.free.pages"; + const std::string SYS_VMEMINFO_NR_ALLOC_BATCH_DESC = "sys.virtual.mem.nr.alloc.batch"; + const std::string SYS_VMEMINFO_NR_INACTIVE_ANON_DESC = "sys.virtual.mem.nr.inactive.anon"; + const std::string SYS_VMEMINFO_NR_ACTIVE_ANON_DESC = "sys.virtual.mem.nr.active_anon"; + const std::string SYS_VMEMINFO_NR_INACTIVE_FILE_DESC = "sys.virtual.mem.nr.inactive.file"; + const std::string SYS_VMEMINFO_NR_ACTIVE_FILE_DESC = "sys.virtual.mem.nr.active_file"; + const std::string SYS_VMEMINFO_NR_UNEVICTABLE_DESC = "sys.virtual.mem.nr.unevictable"; + const std::string SYS_VMEMINFO_NR_MLOCK_DESC = "sys.virtual.mem.nr.mlock"; + const std::string SYS_VMEMINFO_NR_ANON_PAGES_DESC = "sys.virtual.mem.anon.pages"; + const std::string SYS_VMEMINFO_NR_MAPPED_DESC = "sys.virtual.mem.nr.mapped"; + const std::string SYS_VMEMINFO_NR_FILE_PAGES_DESC = "sys.virtual.mem.nr.file.pages"; + const std::string SYS_VMEMINFO_NR_DIRTY_DESC = "sys.virtual.mem.nr.dirty"; + const std::string SYS_VMEMINFO_NR_WRITEBACK_DESC = "sys.virtual.mem.nr.writeback"; + const std::string SYS_VMEMINFO_NR_SLAB_RECLAIMABLE_DESC = "sys.virtual.mem.nr.slab.reclaimable"; + const std::string SYS_VMEMINFO_NR_SLAB_UNRECLAIMABLE_DESC = "sys.virtual.mem.nr.slab.unreclaimable"; + const std::string SYS_VMEMINFO_NR_PAGE_TABLE_PAGES_DESC = "sys.virtual.mem.nr.page_table.pages"; + const std::string SYS_VMEMINFO_NR_KERNEL_STACK_DESC = "sys.virtual.mem.nr_kernel.stack"; + const std::string SYS_VMEMINFO_NR_OVERHEAD_DESC = "sys.virtual.mem.nr.overhead"; + const std::string SYS_VMEMINFO_NR_UNSTABLE_DESC = "sys.virtual.mem.nr.unstable"; + const std::string SYS_VMEMINFO_NR_BOUNCE_DESC = "sys.virtual.mem.nr.bounce"; + const std::string SYS_VMEMINFO_NR_VMSCAN_WRITE_DESC = "sys.virtual.mem.nr.vmscan.write"; + const std::string SYS_VMEMINFO_NR_VMSCAN_IMMEDIATE_RECLAIM_DESC = "sys.virtual.mem.nr.vmscan.immediate.reclaim"; + const std::string SYS_VMEMINFO_NR_WRITEBACK_TEMP_DESC = "sys.virtual.mem.nr.writeback_temp"; + const std::string SYS_VMEMINFO_NR_ISOLATED_ANON_DESC = "sys.virtual.mem.nr.isolated_anon"; + const std::string SYS_VMEMINFO_NR_ISOLATED_FILE_DESC = "sys.virtual.mem.nr.isolated_file"; + const std::string SYS_VMEMINFO_NR_SHMEM_DESC = "sys.virtual.mem.nr.shmem"; + const std::string SYS_VMEMINFO_NR_DIRTIED_DESC = "sys.virtual.mem.nr.dirtied"; + const std::string SYS_VMEMINFO_NR_WRITTEN_DESC = "sys.virtual.mem.nr.written"; + const std::string SYS_VMEMINFO_NR_PAGES_SCANNED_DESC = "sys.virtual.mem.nr.pages.scanned"; + const std::string SYS_VMEMINFO_WORKINGSET_REFAULT_DESC = "sys.virtual.mem.workingset.refault"; + const std::string SYS_VMEMINFO_WORKINGSET_ACTIVATE_DESC = "sys.virtual.mem.workingset.activate"; + const std::string SYS_VMEMINFO_WORKINGSET_NODERECLAIM_DESC = "sys.virtual.mem.workingset_nodereclaim"; + const std::string SYS_VMEMINFO_NR_ANON_TRANSPARENT_HUGEPAGES_DESC = "sys.virtual.mem.nr_anon.transparent.hugepages"; + const std::string SYS_VMEMINFO_NR_FREE_CMA_DESC = "sys.virtual.mem.nr.free_cma"; + const std::string SYS_VMEMINFO_NR_SWAPCACHE_DESC = "sys.virtual.mem.nr.swapcache"; + const std::string SYS_VMEMINFO_NR_DIRTY_THRESHOLD_DESC = "sys.virtual.mem.nr.dirty.threshold"; + const std::string SYS_VMEMINFO_NR_DIRTY_BACKGROUND_THRESHOLD_DESC = "sys.virtual.mem.nr.dirty.background.threshold"; + const std::string SYS_VMEMINFO_PGPGIN_DESC = "sys.virtual.mem.vmeminfo.pgpgin"; + const std::string SYS_VMEMINFO_PGPGOUT_DESC = "sys.virtual.mem.pgpgout"; + const std::string SYS_VMEMINFO_PGPGOUTCLEAN_DESC = "sys.virtual.mem.pgpgoutclean"; + const std::string SYS_VMEMINFO_PSWPIN_DESC = "sys.virtual.mem.pswpin"; + const std::string SYS_VMEMINFO_PSWPOUT_DESC = "sys.virtual.mem.pswpout"; + const std::string SYS_VMEMINFO_PGALLOC_DMA_DESC = "sys.virtual.mem.pgalloc.dma"; + const std::string SYS_VMEMINFO_PGALLOC_NORMAL_DESC = "sys.virtual.mem.pgalloc.normal"; + const std::string SYS_VMEMINFO_PGALLOC_MOVABLE_DESC = "sys.virtual.mem.pgalloc.movable"; + const std::string SYS_VMEMINFO_PGFREE_DESC = "sys.virtual.mem.pgfree"; + const std::string SYS_VMEMINFO_PGACTIVATE_DESC = "sys.virtual.mem.pgactivate"; + const std::string SYS_VMEMINFO_PGDEACTIVATE_DESC = "sys.virtual.mem.pgdeactivate"; + const std::string SYS_VMEMINFO_PGFAULT_DESC = "sys.virtual.mem.pgfault"; + const std::string SYS_VMEMINFO_PGMAJFAULT_DESC = "sys.virtual.mem.pgmajfault"; + const std::string SYS_VMEMINFO_PGREFILL_DMA_DESC = "sys.virtual.mem.pgrefill.dma"; + const std::string SYS_VMEMINFO_PGREFILL_NORMAL_DESC = "sys.virtual.mem.pgrefill.normal"; + const std::string SYS_VMEMINFO_PGREFILL_MOVABLE_DESC = "sys.virtual.mem.pgrefill.movable"; + const std::string SYS_VMEMINFO_PGSTEAL_KSWAPD_DMA_DESC = "sys.virtual.mem.pgsteal.kswapd.dma"; + const std::string SYS_VMEMINFO_PGSTEAL_KSWAPD_NORMAL_DESC = "sys.virtual.mem.pgsteal.kswapd.normal"; + const std::string SYS_VMEMINFO_PGSTEAL_KSWAPD_MOVABLE_DESC = "sys.virtual.mem.pgsteal.kswapd.movable"; + const std::string SYS_VMEMINFO_PGSTEAL_DIRECT_DMA_DESC = "sys.virtual.mem.pgsteal.direct.dma"; + const std::string SYS_VMEMINFO_PGSTEAL_DIRECT_NORMAL_DESC = "sys.virtual.mem.pgsteal.direct.normal"; + const std::string SYS_VMEMINFO_PGSTEAL_DIRECT_MOVABLE_DESC = "sys.virtual.mem.pgsteal_direct.movable"; + const std::string SYS_VMEMINFO_PGSCAN_KSWAPD_DMA_DESC = "sys.virtual.mem.pgscan.kswapd.dma"; + const std::string SYS_VMEMINFO_PGSCAN_KSWAPD_NORMAL_DESC = "sys.virtual.mem.pgscan_kswapd.normal"; + const std::string SYS_VMEMINFO_PGSCAN_KSWAPD_MOVABLE_DESC = "sys.virtual.mem.pgscan.kswapd.movable"; + const std::string SYS_VMEMINFO_PGSCAN_DIRECT_DMA_DESC = "sys.virtual.mem.pgscan.direct.dma"; + const std::string SYS_VMEMINFO_PGSCAN_DIRECT_NORMAL_DESC = "sys.virtual.mem.pgscan.direct.normal"; + const std::string SYS_VMEMINFO_PGSCAN_DIRECT_MOVABLE_DESC = "sys.virtual.mem.pgscan.direct.movable"; + const std::string SYS_VMEMINFO_PGSCAN_DIRECT_THROTTLE_DESC = "sys.virtual.mem.pgscan.direct.throttle"; + const std::string SYS_VMEMINFO_PGINODESTEAL_DESC = "sys.virtual.mem.pginodesteal"; + const std::string SYS_VMEMINFO_SLABS_SCANNED_DESC = "sys.virtual.mem.slabs_scanned"; + const std::string SYS_VMEMINFO_KSWAPD_INODESTEAL_DESC = "sys.virtual.mem.kswapd.inodesteal"; + const std::string SYS_VMEMINFO_KSWAPD_LOW_WMARK_HIT_QUICKLY_DESC = "sys.virtual.mem.kswapd.low.wmark.hit.quickly"; + const std::string SYS_VMEMINFO_KSWAPD_HIGH_WMARK_HIT_QUICKLY_DESC = "sys.virtual.mem.high.wmark.hit.quickly"; + const std::string SYS_VMEMINFO_PAGEOUTRUN_DESC = "sys.virtual.mem.pageoutrun"; + const std::string SYS_VMEMINFO_ALLOCSTALL_DESC = "sys.virtual.mem.allocstall"; + const std::string SYS_VMEMINFO_PGROTATED_DESC = "sys.virtual.mem.pgrotated"; + const std::string SYS_VMEMINFO_DROP_PAGECACHE_DESC = "sys.virtual.mem.drop.pagecache"; + const std::string SYS_VMEMINFO_DROP_SLAB_DESC = "sys.virtual.mem.drop.slab"; + const std::string SYS_VMEMINFO_PGMIGRATE_SUCCESS_DESC = "sys.virtual.mem.pgmigrate.success"; + const std::string SYS_VMEMINFO_PGMIGRATE_FAIL_DESC = "sys.virtual.mem.pgmigrate.fail"; + const std::string SYS_VMEMINFO_COMPACT_MIGRATE_SCANNED_DESC = "sys.virtual.mem.compact.migrate.scanned"; + const std::string SYS_VMEMINFO_COMPACT_FREE_SCANNED_DESC = "sys.virtual.mem.compact.free.scanned"; + const std::string SYS_VMEMINFO_COMPACT_ISOLATED_DESC = "sys.virtual.mem.compact.isolated"; + const std::string SYS_VMEMINFO_COMPACT_STALL_DESC = "sys.virtual.mem.compact.stall"; + const std::string SYS_VMEMINFO_COMPACT_FAIL_DESC = "sys.virtual.mem.compact.fail"; + const std::string SYS_VMEMINFO_COMPACT_SUCCESS_DESC = "sys.virtual.mem.compact.success"; + const std::string SYS_VMEMINFO_COMPACT_DAEMON_WAKE_DESC = "sys.virtual.mem.compact.daemon.wake"; + const std::string SYS_VMEMINFO_UNEVICTABLE_PGS_CULLED_DESC = "sys.virtual.mem.unevictable.pgs.culled"; + const std::string SYS_VMEMINFO_UNEVICTABLE_PGS_SCANNED_DESC = "sys.virtual.mem.unevictable.pgs.scanned"; + const std::string SYS_VMEMINFO_UNEVICTABLE_PGS_RESCUED_DESC = "sys.virtual.mem.unevictable.pgs.rescued"; + const std::string SYS_VMEMINFO_UNEVICTABLE_PGS_MLOCKED_DESC = "sys.virtual.mem.unevictable.pgs.mlocked"; + const std::string SYS_VMEMINFO_UNEVICTABLE_PGS_MUNLOCKED_DESC = "sys.virtual.mem.unevictable.pgs.munlocked"; + const std::string SYS_VMEMINFO_UNEVICTABLE_PGS_CLEARED_DESC = "sys.virtual.mem.unevictable.pgs.cleared"; + const std::string SYS_VMEMINFO_UNEVICTABLE_PGS_STRANDED_DESC = "sys.virtual.mem.unevictable.pgs.stranded"; + const std::string SYS_VMEMINFO_NR_ZSPAGES_DESC = "sys.virtual.mem.nr.zspages"; + const std::string SYS_VMEMINFO_NR_ION_HEAP_DESC = "sys.virtual.mem.nr.ion.heap"; + const std::string SYS_VMEMINFO_NR_GPU_HEAP_DESC = "sys.virtual.mem.nr.gpu.heap"; + const std::string SYS_VMEMINFO_ALLOCSTALL_DMA_DESC = "sys.virtual.mem.allocstall.dma"; + const std::string SYS_VMEMINFO_ALLOCSTALL_MOVABLE_DESC = "sys.virtual.mem.allocstall.movable"; + const std::string SYS_VMEMINFO_ALLOCSTALL_NORMAL_DESC = "sys.virtual.mem.allocstall.normal"; + const std::string SYS_VMEMINFO_COMPACT_DAEMON_FREE_SCANNED_DESC = "sys.virtual.mem.compact_daemon.free.scanned"; + const std::string SYS_VMEMINFO_COMPACT_DAEMON_MIGRATE_SCANNED_DESC = + "sys.virtual.mem.compact.daemon.migrate.scanned"; + const std::string SYS_VMEMINFO_NR_FASTRPC_DESC = "sys.virtual.mem.nr.fastrpc"; + const std::string SYS_VMEMINFO_NR_INDIRECTLY_RECLAIMABLE_DESC = "sys.virtual.mem.nr.indirectly.reclaimable"; + const std::string SYS_VMEMINFO_NR_ION_HEAP_POOL_DESC = "sys.virtual.mem.nr_ion_heap_pool"; + const std::string SYS_VMEMINFO_NR_KERNEL_MISC_RECLAIMABLE_DESC = "sys.virtual.mem.nr.kernel_misc.reclaimable"; + const std::string SYS_VMEMINFO_NR_SHADOW_CALL_STACK_BYTES_DESC = "sys.virtual.mem.nr.shadow_call.stack_bytes"; + const std::string SYS_VMEMINFO_NR_SHMEM_HUGEPAGES_DESC = "sys.virtual.mem.nr.shmem.hugepages"; + const std::string SYS_VMEMINFO_NR_SHMEM_PMDMAPPED_DESC = "sys.virtual.mem.nr.shmem.pmdmapped"; + const std::string SYS_VMEMINFO_NR_UNRECLAIMABLE_PAGES_DESC = "sys.virtual.mem.nr.unreclaimable.pages"; + const std::string SYS_VMEMINFO_NR_ZONE_ACTIVE_ANON_DESC = "sys.virtual.mem.nr.zone.active.anon"; + const std::string SYS_VMEMINFO_NR_ZONE_ACTIVE_FILE_DESC = "sys.virtual.mem.nr.zone.active.file"; + const std::string SYS_VMEMINFO_NR_ZONE_INACTIVE_ANON_DESC = "sys.virtual.mem.nr.zone.inactive_anon"; + const std::string SYS_VMEMINFO_NR_ZONE_INACTIVE_FILE_DESC = "sys.virtual.mem.nr.zone.inactive_file"; + const std::string SYS_VMEMINFO_NR_ZONE_UNEVICTABLE_DESC = "sys.virtual.mem.nr.zone.unevictable"; + const std::string SYS_VMEMINFO_NR_ZONE_WRITE_PENDING_DESC = "sys.virtual.mem.nr.zone.write_pending"; + const std::string SYS_VMEMINFO_OOM_KILL_DESC = "sys.virtual.mem.oom.kill"; + const std::string SYS_VMEMINFO_PGLAZYFREE_DESC = "sys.virtual.mem.pglazyfree"; + const std::string SYS_VMEMINFO_PGLAZYFREED_DESC = "sys.virtual.mem.pglazyfreed"; + const std::string SYS_VMEMINFO_PGREFILL_DESC = "sys.virtual.mem.pgrefill"; + const std::string SYS_VMEMINFO_PGSCAN_DIRECT_DESC = "sys.virtual.mem.pgscan.direct"; + const std::string SYS_VMEMINFO_PGSCAN_KSWAPD_DESC = "sys.virtual.mem.pgscan.kswapd"; + const std::string SYS_VMEMINFO_PGSKIP_DMA_DESC = "sys.virtual.mem.pgskip.dma"; + const std::string SYS_VMEMINFO_PGSKIP_MOVABLE_DESC = "sys.virtual.mem.pgskip.movable"; + const std::string SYS_VMEMINFO_PGSKIP_NORMAL_DESC = "sys.virtual.mem.pgskip.normal"; + const std::string SYS_VMEMINFO_PGSTEAL_DIRECT_DESC = "sys.virtual.mem.pgsteal.direct"; + const std::string SYS_VMEMINFO_PGSTEAL_KSWAPD_DESC = "sys.virtual.mem.pgsteal.kswapd"; + const std::string SYS_VMEMINFO_SWAP_RA_DESC = "sys.virtual.mem.swap.ra"; + const std::string SYS_VMEMINFO_SWAP_RA_HIT_DESC = "sys.virtual.mem.swap.ra.hit"; + const std::string SYS_VMEMINFO_WORKINGSET_RESTORE_DESC = "sys.virtual.mem.workingset.restore"; + + const std::string TRACE_STAT_TYPE_RECEIVED_DESC = "received"; + const std::string TRACE_STAT_TYPE_LOST_DESC = "data_lost"; + const std::string TRACE_STAT_TYPE_NOTMATCH_DESC = "not_match"; + const std::string TRACE_STAT_TYPE_NOTSUPPORTED_DESC = "not_supported"; + const std::string TRACE_STAT_TYPE_DATA_INVALID_DESC = "invalid_data"; + + const std::string STAT_SEVERITY_LEVEL_INFO_DESC = "info"; + const std::string STAT_SEVERITY_LEVEL_WARN_DESC = "warn"; + const std::string STAT_SEVERITY_LEVEL_ERROR_DESC = "error"; + const std::string STAT_SEVERITY_LEVEL_FATAL_DESC = "fatal"; +}; +} // namespace TRACE_STREAMER_CONFIG_H +} // namespace SysTuning +#endif diff --git a/host/trace_streamer/src/ext/BUILD.gn b/host/trace_streamer/src/ext/BUILD.gn new file mode 100644 index 0000000..0964302 --- /dev/null +++ b/host/trace_streamer/src/ext/BUILD.gn @@ -0,0 +1,25 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +config("sqlite_config") { + include_dirs = [ + "../../third_party/sqlite/include", + "../include", + ] + cflags = [ "-Wno-writable-strings" ] +} +source_set("sqliteext") { + sources = [ "sqlite_ext_funcs.cpp" ] + public_configs = [ ":sqlite_config" ] +} diff --git a/host/trace_streamer/src/ext/sqlite_ext.pri b/host/trace_streamer/src/ext/sqlite_ext.pri new file mode 100644 index 0000000..6d1a82c --- /dev/null +++ b/host/trace_streamer/src/ext/sqlite_ext.pri @@ -0,0 +1,16 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +SOURCES += $$PWD/sqlite_ext_funcs.cpp +HEADERS += $$PWD/sqlite_ext_funcs.h +INCLUDEPATH += $$PWD/../../third_party/sqlite/include, + $$PWD/../include \ No newline at end of file diff --git a/host/trace_streamer/src/ext/sqlite_ext_funcs.cpp b/host/trace_streamer/src/ext/sqlite_ext_funcs.cpp new file mode 100644 index 0000000..680e61e --- /dev/null +++ b/host/trace_streamer/src/ext/sqlite_ext_funcs.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "sqlite_ext_funcs.h" +#include +#include "log.h" +#include "sqlite3.h" +/* +** Return a stdev value +*/ +static void sqliteExtStdevFinalize(sqlite3_context* context) +{ + StdevCtx* ptr = static_cast(sqlite3_aggregate_context(context, 0)); + if (ptr && ptr->cntValue > 1) { + sqlite3_result_double(context, sqrt(ptr->rSValue / (ptr->cntValue - 1))); + } else { + sqlite3_result_double(context, 0.0); + } +} +/* +** called each value received during a calculation of stdev or variance +*/ +static void sqliteExtStdevNextStep(sqlite3_context* context, int argc, sqlite3_value** argv) +{ + double deltaValue; + double x; + + TS_ASSERT(argc == 1); + StdevCtx* ptr = static_cast(sqlite3_aggregate_context(context, sizeof(StdevCtx))); + if (SQLITE_NULL != sqlite3_value_numeric_type(argv[0])) { + ptr->cntValue++; + x = sqlite3_value_double(argv[0]); + deltaValue = (x - ptr->rMValue); + ptr->rMValue += deltaValue / ptr->cntValue; + ptr->rSValue += deltaValue * (x - ptr->rMValue); + } +} + +void CreateExtendFunction(sqlite3* db) +{ + sqlite3_create_function(db, "stdev", 1, SQLITE_UTF8, nullptr, 0, sqliteExtStdevNextStep, + sqliteExtStdevFinalize); +} \ No newline at end of file diff --git a/host/trace_streamer/src/ext/sqlite_ext_funcs.h b/host/trace_streamer/src/ext/sqlite_ext_funcs.h new file mode 100644 index 0000000..b75925f --- /dev/null +++ b/host/trace_streamer/src/ext/sqlite_ext_funcs.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef SQLITE_EXT_FUNCS_H +#define SQLITE_EXT_FUNCS_H 1 +#include +#include "sqlite3.h" + +typedef struct StdevCtx StdevCtx; +struct StdevCtx { + double rMValue; + double rSValue; + int64_t cntValue; +}; +void CreateExtendFunction(sqlite3* db); +#endif diff --git a/host/trace_streamer/src/filter/args_filter.cpp b/host/trace_streamer/src/filter/args_filter.cpp new file mode 100644 index 0000000..6ad2e2e --- /dev/null +++ b/host/trace_streamer/src/filter/args_filter.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "args_filter.h" +#include "filter_filter.h" +#include "log.h" +#include "ts_common.h" + +namespace SysTuning { +namespace TraceStreamer { +ArgsFilter::ArgsFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter) : FilterBase(dataCache, filter) +{ + traceDataCache_->GetDataTypeData()->AppendNewDataType(BASE_DATA_TYPE_INT, traceDataCache_->GetDataIndex("int")); + traceDataCache_->GetDataTypeData()->AppendNewDataType(BASE_DATA_TYPE_STRING, + traceDataCache_->GetDataIndex("string")); + traceDataCache_->GetDataTypeData()->AppendNewDataType(BASE_DATA_TYPE_DOUBLE, + traceDataCache_->GetDataIndex("double")); + traceDataCache_->GetDataTypeData()->AppendNewDataType(BASE_DATA_TYPE_BOOLEAN, + traceDataCache_->GetDataIndex("boolean")); +} + +ArgsFilter::~ArgsFilter() {} + +uint32_t ArgsFilter::NewArgs(const ArgsSet& args) +{ + auto argSet = traceDataCache_->GetArgSetData(); + for (auto it = args.valuesMap_.begin(); it != args.valuesMap_.end(); it++) { + argSet->AppendNewArg(it->first, it->second.type, it->second.value, count_); + } + count_++; + return count_ - 1; +} +uint32_t ArgsFilter::AppendArgs(const ArgsSet& args, const size_t argSetId) +{ + auto argSet = traceDataCache_->GetArgSetData(); + for (auto it = args.valuesMap_.begin(); it != args.valuesMap_.end(); it++) { + argSet->AppendNewArg(it->first, it->second.type, it->second.value, argSetId); + } + return count_ - 1; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/filter/args_filter.h b/host/trace_streamer/src/filter/args_filter.h new file mode 100644 index 0000000..1b87d43 --- /dev/null +++ b/host/trace_streamer/src/filter/args_filter.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ARGS_FILTER_H +#define ARGS_FILTER_H + +#include +#include +#include + +#include "args_set.h" +#include "double_map.h" +#include "filter_base.h" +#include "trace_data_cache.h" +#include "trace_streamer_filters.h" + +namespace SysTuning { +namespace TraceStreamer { +class ArgsFilter : private FilterBase { +public: + ArgsFilter(TraceDataCache*, const TraceStreamerFilters*); + ArgsFilter(const ArgsFilter&) = delete; + ArgsFilter& operator=(const ArgsFilter&) = delete; + ~ArgsFilter() override; + uint32_t NewArgs(const ArgsSet& args); + uint32_t AppendArgs(const ArgsSet& args, const size_t argSetId); + int count_ = 0; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // ARGS_FILTER_H diff --git a/host/trace_streamer/src/filter/binder_filter.cpp b/host/trace_streamer/src/filter/binder_filter.cpp new file mode 100644 index 0000000..b13ad8e --- /dev/null +++ b/host/trace_streamer/src/filter/binder_filter.cpp @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "binder_filter.h" +#include "measure_filter.h" +#include "process_filter.h" +#include "slice_filter.h" +#include "stat_filter.h" +#include "string_to_numerical.h" +namespace SysTuning { +namespace TraceStreamer { +BinderFilter::BinderFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter) + : FilterBase(dataCache, filter) +{ + binderFlagDescs_ = {{noReturnMsgFlag_, " this is a one-way call: async, no return; "}, + {rootObjectMsgFlag_, " contents are the components root object; "}, + {statusCodeMsgFlag_, " contents are a 32-bit status code; "}, + {acceptFdsMsgFlag_, " allow replies with file descriptors; "}, + {noFlagsMsgFlag_, " No Flags Set"}}; +} +BinderFilter::~BinderFilter() = default; + +std::string BinderFilter::GetBinderFlagsDesc(uint32_t flag) +{ + std::string str; + if (flag & noReturnMsgFlag_) { + str += binderFlagDescs_.at(noReturnMsgFlag_); + } + if (flag & rootObjectMsgFlag_) { + str += binderFlagDescs_.at(rootObjectMsgFlag_); + } + if (flag & statusCodeMsgFlag_) { + str += binderFlagDescs_.at(statusCodeMsgFlag_); + } + if (flag & acceptFdsMsgFlag_) { + str += binderFlagDescs_.at(acceptFdsMsgFlag_); + } + if (flag == noFlagsMsgFlag_) { + str += binderFlagDescs_.at(noFlagsMsgFlag_); + } + return str; +} +void BinderFilter::MaybeDealEvent() +{ + if (tsBinderEventQueue_.size() > MAX_CACHE_SIZE) { + DealEvent(tsBinderEventQueue_.begin()->second.get()); + tsBinderEventQueue_.erase(tsBinderEventQueue_.begin()); + } +} + +void BinderFilter::FinishBinderEvent() +{ + for (auto it = tsBinderEventQueue_.begin(); it != tsBinderEventQueue_.end(); it++) { + DealEvent(it->second.get()); + } + tsBinderEventQueue_.clear(); +} + +void BinderFilter::SendTraction(int64_t ts, + uint32_t tid, + uint64_t transactionId, + int32_t destNode, + int32_t destTgid, + int32_t destTid, + bool isReply, + int32_t flags, + int32_t code) +{ + auto sendTractionEvent = std::make_unique(ts, tid, transactionId, destNode, + destTgid, destTid, isReply, flags, code); + auto binderEvent = std::make_unique(); + binderEvent->type_ = TS_EVENT_BINDER_SEND; + binderEvent->senderBinderEvent_ = std::move(sendTractionEvent); + tsBinderEventQueue_.insert(std::make_pair(ts, std::move(binderEvent))); + MaybeDealEvent(); +} +void BinderFilter::ReceiveTraction(int64_t ts, uint32_t pid, uint64_t transactionId) +{ + auto receiveTractionEvent = std::make_unique(ts, pid, transactionId); + auto binderEvent = std::make_unique(); + binderEvent->type_ = TS_EVENT_BINDER_RECIVED; + binderEvent->receivedBinderEvent_ = std::move(receiveTractionEvent); + tsBinderEventQueue_.insert(std::make_pair(ts, + std::move(binderEvent))); + MaybeDealEvent(); +} +void BinderFilter::TransactionAllocBuf(int64_t ts, uint32_t pid, uint64_t dataSize, uint64_t offsetsSize) +{ + auto tractionAllocBufEvent = std::make_unique(ts, pid, dataSize, offsetsSize); + auto binderEvent = std::make_unique(); + binderEvent->type_ = TS_EVENT_BINDER_ALLOC_BUF; + binderEvent->binderAllocBufEvent_ = std::move(tractionAllocBufEvent); + tsBinderEventQueue_.insert(std::make_pair(ts, + std::move(binderEvent))); + MaybeDealEvent(); +} +void BinderFilter::TractionLock(int64_t ts, uint32_t pid, const std::string& tag) +{ + auto tractionLockEvent = std::make_unique(ts, pid, tag); + auto binderEvent = std::make_unique(); + binderEvent->type_ = TS_EVENT_BINDER_LOCK; + binderEvent->binderLockEvent_ = std::move(tractionLockEvent); + tsBinderEventQueue_.insert(std::make_pair(ts, + std::move(binderEvent))); + MaybeDealEvent(); +} +void BinderFilter::TractionLocked(int64_t ts, uint32_t pid, const std::string& tag) +{ + auto tractionLockedEvent = std::make_unique(ts, pid, tag); + auto binderEvent = std::make_unique(); + binderEvent->type_ = TS_EVENT_BINDER_LOCKED; + binderEvent->binderLockedEvent_ = std::move(tractionLockedEvent); + tsBinderEventQueue_.insert(std::make_pair(ts, + std::move(binderEvent))); + MaybeDealEvent(); +} +void BinderFilter::TractionUnlock(int64_t ts, uint32_t pid, const std::string& tag) +{ + auto tractionUnlockEvent = std::make_unique(ts, pid, tag); + auto binderEvent = std::make_unique(); + binderEvent->type_ = TS_EVENT_BINDER_UNLOCK; + binderEvent->binderUnlockEvent_ = std::move(tractionUnlockEvent); + tsBinderEventQueue_.insert(std::make_pair(ts, + std::move(binderEvent))); + MaybeDealEvent(); +} +void BinderFilter::DealEvent(const TSBinderEvent* event) +{ + switch (static_cast(event->type_)) { + case TS_EVENT_BINDER_SEND: + ExecSendTraction(event->senderBinderEvent_->ts_, event->senderBinderEvent_->tid_, + event->senderBinderEvent_->transactionId_, event->senderBinderEvent_->destNode_, + event->senderBinderEvent_->destTgid_, event->senderBinderEvent_->destTid_, + event->senderBinderEvent_->isReply_, event->senderBinderEvent_->flags_, + event->senderBinderEvent_->code_); + break; + case TS_EVENT_BINDER_RECIVED: + ExecReceiveTraction(event->receivedBinderEvent_->ts_, event->receivedBinderEvent_->pid_, + event->receivedBinderEvent_->transactionId_); + break; + case TS_EVENT_BINDER_ALLOC_BUF: + ExecTransactionAllocBuf(event->binderAllocBufEvent_->ts_, event->binderAllocBufEvent_->pid_, + event->binderAllocBufEvent_->dataSize_, event->binderAllocBufEvent_->offsetsSize_); + break; + case TS_EVENT_BINDER_LOCK: + ExecTractionLock(event->binderLockEvent_->ts_, event->binderLockEvent_->pid_, + event->binderLockEvent_->tag_); + break; + case TS_EVENT_BINDER_LOCKED: + ExecTractionLocked(event->binderLockedEvent_->ts_, event->binderLockedEvent_->pid_, + event->binderLockedEvent_->tag_); + break; + case TS_EVENT_BINDER_UNLOCK: + ExecTractionUnlock(event->binderUnlockEvent_->ts_, event->binderUnlockEvent_->pid_, + event->binderUnlockEvent_->tag_); + break; + default: + break; + } +} +void BinderFilter::ExecSendTraction(int64_t ts, + uint32_t tid, + uint64_t transactionId, + int32_t destNode, + int32_t destTgid, + int32_t destTid, + bool isReply, + int32_t flags, + int32_t code) +{ + auto flagsStr = traceDataCache_->GetDataIndex("0x" + base::number(flags, base::INTEGER_RADIX_TYPE_HEX) + + GetBinderFlagsDesc(flags)); + DataIndex codeStr = traceDataCache_->GetDataIndex("0x" + base::number(code, base::INTEGER_RADIX_TYPE_HEX) + + " Java Layer Dependent"); + ArgsSet argsSend; + argsSend.AppendArg(transId_, BASE_DATA_TYPE_INT, transactionId); + argsSend.AppendArg(destNodeId_, BASE_DATA_TYPE_INT, destNode); + argsSend.AppendArg(destProcessId_, BASE_DATA_TYPE_INT, destTgid); + argsSend.AppendArg(isReplayId_, BASE_DATA_TYPE_BOOLEAN, isReply); + argsSend.AppendArg(flagsId_, BASE_DATA_TYPE_STRING, flagsStr); + argsSend.AppendArg(codeId_, BASE_DATA_TYPE_STRING, codeStr); + argsSend.AppendArg(callingTid_, BASE_DATA_TYPE_INT, tid); + + if (isReply) { + // Add dest information to Reply slices, the Begin msg is from TAG-2 + InternalTid dstItid = streamFilters_->processFilter_->UpdateOrCreateThread(ts, destTid); + const auto destThreadName = traceDataCache_->GetConstThreadData(dstItid).nameIndex_; + argsSend.AppendArg(destThreadId_, BASE_DATA_TYPE_INT, destTid); + argsSend.AppendArg(destThreadNameId_, BASE_DATA_TYPE_STRING, destThreadName); + streamFilters_->sliceFilter_->EndBinder(ts, tid, nullStringId_, nullStringId_, argsSend); + transReplyWaitingReply_.insert(transactionId); + return; + } else { + bool needReply = !isReply && !(flags & noReturnMsgFlag_); + if (needReply) { + // transaction needs reply TAG-1 + streamFilters_->sliceFilter_->BeginBinder(ts, tid, binderCatalogId_, transSliceId_, argsSend); + transWaitingRcv_[transactionId] = tid; + } else { + // transaction not need reply + streamFilters_->sliceFilter_->BeginAsyncBinder(ts, tid, binderCatalogId_, transAsyncId_, argsSend); + transNoNeedReply_[transactionId] = argsSend; + } + } +} +void BinderFilter::ExecReceiveTraction(int64_t ts, uint32_t pid, uint64_t transactionId) +{ + InternalTid internalTid = streamFilters_->processFilter_->UpdateOrCreateThread(ts, pid); + const auto threadName = traceDataCache_->GetConstThreadData(internalTid).nameIndex_; + if (transReplyWaitingReply_.count(transactionId)) { + streamFilters_->sliceFilter_->EndBinder(ts, pid); + transReplyWaitingReply_.erase(transactionId); + return; + } + + if (transWaitingRcv_.count(transactionId)) { + // First, begin the reply, the reply will be end in "SendTraction" func, and the isReply will be true, TAG-2 + auto replySliceid = streamFilters_->sliceFilter_->BeginBinder(ts, pid, binderCatalogId_, replyId_); + // Add dest info to the reply + ArgsSet args; + args.AppendArg(destThreadId_, BASE_DATA_TYPE_INT, pid); + args.AppendArg(destThreadNameId_, BASE_DATA_TYPE_STRING, threadName); + if (IsValidUint32(static_cast(replySliceid))) { + args.AppendArg(destSliceId_, BASE_DATA_TYPE_INT, replySliceid); + } + // Add dest args + auto transSliceId = streamFilters_->sliceFilter_->AddArgs(transWaitingRcv_[transactionId], binderCatalogId_, + transSliceId_, args); + + // remeber dest slice-id to the argset form "SendTraction" TAG-1 + ArgsSet replyDestInserter; + if (IsValidUint32(transSliceId)) { + replyDestInserter.AppendArg(destSliceId_, BASE_DATA_TYPE_INT, transSliceId); + } + streamFilters_->sliceFilter_->AddArgs(pid, binderCatalogId_, replyId_, replyDestInserter); + transWaitingRcv_.erase(transactionId); + return; + } + // the code below can be hard to understand, may be a EndBinder will be better + // this problem cna be test after the IDE is finished + if (transNoNeedReply_.count(transactionId)) { + auto args = transNoNeedReply_[transactionId]; + streamFilters_->sliceFilter_->BeginAsyncBinder(ts, pid, binderCatalogId_, asyncRcvId_, args); + transNoNeedReply_.erase(transactionId); + return; + } +} +void BinderFilter::ExecTransactionAllocBuf(int64_t ts, uint32_t pid, uint64_t dataSize, uint64_t offsetsSize) +{ + ArgsSet args; + args.AppendArg(dataSizeId_, BASE_DATA_TYPE_INT, dataSize); + args.AppendArg(dataOffsetSizeId_, BASE_DATA_TYPE_INT, offsetsSize); + streamFilters_->sliceFilter_->AddArgs(pid, binderCatalogId_, transSliceId_, args); + UNUSED(ts); +} +void BinderFilter::ExecTractionLock(int64_t ts, uint32_t pid, const std::string& tag) +{ + lastEventTs_[pid] = ts; + streamFilters_->sliceFilter_->BeginBinder(ts, pid, binderCatalogId_, lockTryId_); +} +void BinderFilter::ExecTractionLocked(int64_t ts, uint32_t pid, const std::string& tag) +{ + if (!lastEventTs_.count(pid)) { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_BINDER_TRANSACTION_LOCKED, STAT_EVENT_NOTMATCH); + return; + } + streamFilters_->sliceFilter_->EndBinder(ts, pid); + streamFilters_->sliceFilter_->BeginBinder(ts, pid, binderCatalogId_, lockHoldId_); + lastEventTs_.erase(pid); + lastEventTs_[pid] = ts; +} +void BinderFilter::ExecTractionUnlock(int64_t ts, uint32_t pid, const std::string& tag) +{ + if (!lastEventTs_.count(pid)) { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_BINDER_TRANSACTION_UNLOCK, STAT_EVENT_NOTMATCH); + return; + } + streamFilters_->sliceFilter_->EndBinder(ts, pid); + lastEventTs_.erase(pid); + lastEventTs_[pid] = ts; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/filter/binder_filter.h b/host/trace_streamer/src/filter/binder_filter.h new file mode 100644 index 0000000..1833384 --- /dev/null +++ b/host/trace_streamer/src/filter/binder_filter.h @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BINDER_FILTER_H +#define BINDER_FILTER_H + +#include +#include "args_set.h" +#include "filter_base.h" +#include "trace_data_cache.h" +#include "trace_streamer_filters.h" +namespace SysTuning { +namespace TraceStreamer { +class BinderFilter : private FilterBase { +public: + BinderFilter(TraceDataCache*, const TraceStreamerFilters*); + BinderFilter(const BinderFilter&) = delete; + BinderFilter& operator=(const BinderFilter&) = delete; + ~BinderFilter() override; + +public: + void SendTraction(int64_t ts, + uint32_t tid, + uint64_t transactionId, + int32_t destNode, + int32_t destTgid, + int32_t destTid, + bool isReply, + int32_t flags, + int32_t code); + void ReceiveTraction(int64_t ts, uint32_t pid, uint64_t transactionId); + void TransactionAllocBuf(int64_t ts, uint32_t pid, uint64_t dataSize, uint64_t offsetsSize); + void TractionLock(int64_t ts, uint32_t pid, const std::string& tag); + void TractionLocked(int64_t ts, uint32_t pid, const std::string& tag); + void TractionUnlock(int64_t ts, uint32_t pid, const std::string& tag); + void FinishBinderEvent(); + +private: + void MaybeDealEvent(); + + class TSSendTractionEvent { + public: + TSSendTractionEvent(int64_t ts, + uint32_t tid, + uint64_t transactionId, + int32_t destNode, + int32_t destTgid, + int32_t destTid, + bool isReply, + int32_t flags, + int32_t code) + : ts_(ts), + tid_(tid), + transactionId_(transactionId), + destNode_(destNode), + destTgid_(destTgid), + destTid_(destTid), + isReply_(isReply), + flags_(flags), + code_(code) + { + } + ~TSSendTractionEvent() {} + int64_t ts_; + uint32_t tid_; + uint64_t transactionId_; + int32_t destNode_; + int32_t destTgid_; + int32_t destTid_; + bool isReply_; + int32_t flags_; + int32_t code_; + }; + class TSReceiveTractionEvent { + public: + TSReceiveTractionEvent(int64_t ts, uint32_t pid, uint64_t transactionId) + : ts_(ts), pid_(pid), transactionId_(transactionId) + { + } + ~TSReceiveTractionEvent() {} + uint64_t ts_; + uint32_t pid_; + uint64_t transactionId_; + }; + class TSTractionLockEvent { + public: + TSTractionLockEvent(int64_t ts, uint32_t pid, const std::string& tag) : ts_(ts), pid_(pid), tag_(tag) {} + ~TSTractionLockEvent() {} + uint64_t ts_; + uint32_t pid_; + const std::string tag_; + }; + class TSTransactionAllocBufEvent { + public: + TSTransactionAllocBufEvent(int64_t ts, uint32_t pid, uint64_t dataSize, uint64_t offsetsSize) + : ts_(ts), pid_(pid), dataSize_(dataSize), offsetsSize_(offsetsSize) + { + } + ~TSTransactionAllocBufEvent() {} + uint64_t ts_; + uint32_t pid_; + uint64_t dataSize_; + uint64_t offsetsSize_; + }; + enum TSBinderEventType { + TS_EVENT_BINDER_SEND, + TS_EVENT_BINDER_RECIVED, + TS_EVENT_BINDER_ALLOC_BUF, + TS_EVENT_BINDER_LOCK, + TS_EVENT_BINDER_LOCKED, + TS_EVENT_BINDER_UNLOCK + }; + class TSBinderEvent { + public: + TSBinderEvent() {} + ~TSBinderEvent() {} + TSBinderEventType type_; + // us union below will be a good choice + // but union with unique_ptr can bring about runtime error on windows and mac,only work well on linux + std::unique_ptr senderBinderEvent_ = {}; + std::unique_ptr receivedBinderEvent_ = {}; + std::unique_ptr binderAllocBufEvent_ = {}; + std::unique_ptr binderLockEvent_ = {}; + std::unique_ptr binderLockedEvent_ = {}; + std::unique_ptr binderUnlockEvent_ = {}; + }; + void DealEvent(const TSBinderEvent* event); + + void ExecSendTraction(int64_t ts, + uint32_t tid, + uint64_t transactionId, + int32_t destNode, + int32_t destTgid, + int32_t destTid, + bool isReply, + int32_t flags, + int32_t code); + void ExecReceiveTraction(int64_t ts, uint32_t pid, uint64_t transactionId); + void ExecTransactionAllocBuf(int64_t ts, uint32_t pid, uint64_t dataSize, uint64_t offsetsSize); + void ExecTractionLock(int64_t ts, uint32_t pid, const std::string& tag); + void ExecTractionLocked(int64_t ts, uint32_t pid, const std::string& tag); + void ExecTractionUnlock(int64_t ts, uint32_t pid, const std::string& tag); + std::string GetBinderFlagsDesc(uint32_t flag); + bool IsValidUint32(uint32_t value) const + { + return (value != INVALID_UINT32); + } + uint32_t noReturnMsgFlag_ = 0x01; + uint32_t rootObjectMsgFlag_ = 0x04; + uint32_t statusCodeMsgFlag_ = 0x08; + uint32_t acceptFdsMsgFlag_ = 0x10; + uint32_t noFlagsMsgFlag_ = 0; + DataIndex binderCatalogId_ = traceDataCache_->GetDataIndex("binder"); + DataIndex replyId_ = traceDataCache_->GetDataIndex("binder reply"); + DataIndex isReplayId_ = traceDataCache_->GetDataIndex("reply transaction?"); + DataIndex flagsId_ = traceDataCache_->GetDataIndex("flags"); + DataIndex transSliceId_ = traceDataCache_->GetDataIndex("binder transaction"); + DataIndex transId_ = traceDataCache_->GetDataIndex("transaction id"); + DataIndex asyncRcvId_ = traceDataCache_->GetDataIndex("binder async rcv"); + DataIndex codeId_ = traceDataCache_->GetDataIndex("code"); + DataIndex callingTid_ = traceDataCache_->GetDataIndex("calling tid"); + DataIndex destNodeId_ = traceDataCache_->GetDataIndex("destination node"); + DataIndex destThreadId_ = traceDataCache_->GetDataIndex("destination thread"); + DataIndex destThreadNameId_ = traceDataCache_->GetDataIndex("destination name"); + DataIndex destSliceId_ = traceDataCache_->GetDataIndex("destination slice id"); + DataIndex destProcessId_ = traceDataCache_->GetDataIndex("destination process"); + DataIndex transAsyncId_ = traceDataCache_->GetDataIndex("binder transaction async"); + DataIndex lockTryId_ = traceDataCache_->GetDataIndex("binder lock waiting"); + DataIndex lockHoldId_ = traceDataCache_->GetDataIndex("binder lock held"); + DataIndex dataSizeId_ = traceDataCache_->GetDataIndex("data size"); + DataIndex dataOffsetSizeId_ = traceDataCache_->GetDataIndex("offsets size"); + DataIndex nullStringId_ = traceDataCache_->GetDataIndex("null"); + std::unordered_map lastEventTs_ = {}; + std::unordered_set transReplyWaitingReply_ = {}; + std::unordered_map transWaitingRcv_ = {}; + std::unordered_map transNoNeedReply_ = {}; + std::unordered_map binderFlagDescs_ = {}; + std::multimap> tsBinderEventQueue_; + // timestamp of ftrace events from different cpu can be outof order + // keep a cache of ftrace events in memory and keep msg in order + // the value below is the count of msg, maybe you can change it + const size_t MAX_CACHE_SIZE = 10000; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // BINDER_FILTER_H diff --git a/host/trace_streamer/src/filter/clock_filter.cpp b/host/trace_streamer/src/filter/clock_filter.cpp new file mode 100644 index 0000000..392bae5 --- /dev/null +++ b/host/trace_streamer/src/filter/clock_filter.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "clock_filter.h" +#include +#include + +namespace SysTuning { +namespace TraceStreamer { +ClockFilter::ClockFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter) + : FilterBase(dataCache, filter), primaryClock_(BuiltinClocks::TS_CLOCK_BOOTTIME) +{ +} + +ClockFilter::~ClockFilter() {} + +std::string ClockFilter::GenClockKey(ClockId srcClockId, ClockId desClockId) const +{ + std::string ret; + ret += std::to_string(srcClockId); + ret += ","; + ret += std::to_string(desClockId); + return ret; +} + +uint64_t ClockFilter::ToPrimaryTraceTime(ClockId srcClockId, uint64_t srcTs) const +{ + if (srcClockId == primaryClock_) { + return srcTs; + } + return Convert(srcClockId, srcTs, primaryClock_); +} + +uint64_t ClockFilter::Convert(ClockId srcClockId, uint64_t srcTs, ClockId desClockId) const +{ + std::string&& clockKey = GenClockKey(srcClockId, desClockId); + auto keyIt = clockMaps_.find(clockKey); + if (keyIt == clockMaps_.end()) { + return srcTs; + } + + auto tsIt = keyIt->second.upper_bound(srcTs); + if (tsIt != keyIt->second.begin()) { + tsIt--; + } + + if (tsIt->second >= 0) { + return srcTs + static_cast(tsIt->second); + } else { + return srcTs - static_cast(0 - tsIt->second); + } +} + +void ClockFilter::AddConvertClockMap(ClockId srcClockId, ClockId dstClockId, uint64_t srcTs, uint64_t dstTs) +{ + std::string&& clockKey = GenClockKey(srcClockId, dstClockId); + auto keyIt = clockMaps_.find(clockKey); + if (keyIt == clockMaps_.end()) { + ConvertClockMap newConvertMap = {{srcTs, dstTs - srcTs}}; + clockMaps_[clockKey] = newConvertMap; + } else { + clockMaps_[clockKey].insert(std::make_pair(srcTs, dstTs - srcTs)); + } +} + +void ClockFilter::AddClockSnapshot(const std::vector& snapShot) +{ + ClockId srcId, desId; + for (srcId = 0; srcId < snapShot.size() - 1; srcId++) { + for (desId = srcId + 1; desId < snapShot.size(); desId++) { + ClockId srcClockId = snapShot[srcId].clockId; + ClockId desClockId = snapShot[desId].clockId; + uint64_t srcTs = snapShot[srcId].ts; + uint64_t desTs = snapShot[desId].ts; + + AddConvertClockMap(srcClockId, desClockId, srcTs, desTs); + AddConvertClockMap(desClockId, srcClockId, desTs, srcTs); + } + } +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/filter/clock_filter.h b/host/trace_streamer/src/filter/clock_filter.h new file mode 100644 index 0000000..6d8e29d --- /dev/null +++ b/host/trace_streamer/src/filter/clock_filter.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CLOCK_FILTER_H +#define CLOCK_FILTER_H + +#include +#include +#include +#include +#include "filter_base.h" +#include "trace_data_cache.h" +#include "trace_streamer_filters.h" +#include "ts_common.h" + +namespace SysTuning { +namespace TraceStreamer { +/* + * TS_REALTIME: A settable system-wide clock that measures real time. Its time represents seconds and nanoseconds + * since the Epoch. + * TS_REALTIME_COARSE: A faster but less precise version of TS_REALTIME. This clock is not settable. + * TS_MONOTONIC: The number of seconds that the system has been running since it was booted.The CLOCK_MONOTONIC + * clock is not affected by discontinuous jumps in the system time ,but is affected by the incremental adjustments + * performed by adjtime(3) and NTP. This clock does not count time that the system is suspended. + * TS_MONOTONIC_COARSE: A faster but less precise version of TS_MONOTONIC. + * TS_MONOTONIC_RAW: Similar to TS_MONOTONIC, but provides access to a raw hardware-based time that is not subject + * to NTP adjustments or the incremental adjustments performed by adjtime(3). This clock does not count time that the + * system is suspended. + * TS_BOOTTIME: A nonsettable system-wide clock that is identical to TS_MONOTONIC, except that it also includes + * any time that the system is suspended. + */ + +using ClockId = uint32_t; +struct SnapShot { + ClockId clockId; + uint64_t ts; +}; +class ClockFilter : private FilterBase { +public: + using ConvertClockMap = std::map; + + ClockFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter); + ~ClockFilter() override; + + void SetPrimaryClock(ClockId primary) + { + primaryClock_ = primary; + } + uint64_t ToPrimaryTraceTime(ClockId srcClockId, uint64_t srcTs) const; + uint64_t Convert(ClockId srcClockId, uint64_t srcTs, ClockId desClockId) const; + void AddClockSnapshot(const std::vector& snapShot); + +private: + std::string GenClockKey(ClockId srcClockId, ClockId desClockId) const; + void AddConvertClockMap(ClockId srcClockId, ClockId dstClockId, uint64_t srcTs, uint64_t dstTs); + +private: + std::unordered_map clockMaps_ = {}; + + ClockId primaryClock_; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // CLOCK_FILTER_H diff --git a/host/trace_streamer/src/filter/cpu_filter.cpp b/host/trace_streamer/src/filter/cpu_filter.cpp new file mode 100644 index 0000000..8ce2283 --- /dev/null +++ b/host/trace_streamer/src/filter/cpu_filter.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cpu_filter.h" + +namespace SysTuning { +namespace TraceStreamer { +CpuFilter::CpuFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter) : FilterBase(dataCache, filter) {} +CpuFilter::~CpuFilter() = default; +void CpuFilter::InsertSwitchEvent(uint64_t ts, + uint64_t cpu, + uint64_t prevPid, + uint64_t prevPior, + uint64_t prevState, + uint64_t nextPid, + uint64_t nextPior) +{ + auto switchEvent = std::make_unique(ts, cpu, prevPid, prevPior, prevState, nextPid, nextPior); + auto cpuEvent = std::make_unique(); + cpuEvent->type_ = TS_EVENT_THREAD_SWITCH; + cpuEvent->switchEvent_ = std::move(switchEvent); + tsCpuEventQueue_.insert(std::make_pair(ts, std::move(cpuEvent))); + MaybeDealEvent(); +} +bool CpuFilter::InsertProcessExitEvent(uint64_t ts, uint64_t cpu, uint64_t pid) +{ + auto processExitEvent = std::make_unique(ts, cpu, pid); + auto cpuEvent = std::make_unique(); + cpuEvent->type_ = TS_EVENT_PROCESS_EXIT; + cpuEvent->processExitEvent_ = std::move(processExitEvent); + tsCpuEventQueue_.insert(std::make_pair(ts, + std::move(cpuEvent))); + MaybeDealEvent(); + return true; +} + +bool CpuFilter::InsertProcessFreeEvent(uint64_t ts, uint64_t pid) +{ + auto processExitEvent = std::make_unique(ts, 0, pid); + auto cpuEvent = std::make_unique(); + cpuEvent->type_ = TS_EVENT_PROCESS_FREE; + cpuEvent->processExitEvent_ = std::move(processExitEvent); + tsCpuEventQueue_.insert(std::make_pair(ts, + std::move(cpuEvent))); + MaybeDealEvent(); + return true; +} +void CpuFilter::InsertWakeupEvent(uint64_t ts, uint64_t internalTid) +{ + auto wakeupEvent = std::make_unique(TSWakeupEvent(ts, internalTid)); + auto cpuEvent = std::make_unique(); + cpuEvent->type_ = TS_EVENT_THREAD_WAKING; + cpuEvent->wakeupEvent_ = std::move(wakeupEvent); + tsCpuEventQueue_.insert(std::make_pair(ts, + std::move(cpuEvent))); + MaybeDealEvent(); +} +uint64_t CpuFilter::RemberInternalTidInStateTable(uint64_t uid, uint64_t row, uint64_t state) +{ + if (internalTidToRowThreadState_.find(uid) != internalTidToRowThreadState_.end()) { + internalTidToRowThreadState_.at(uid) = TPthread{row, state}; + } else { + internalTidToRowThreadState_.insert(std::make_pair(uid, TPthread{row, state})); + } + return 0; +} + +void CpuFilter::MaybeDealEvent() +{ + if (tsCpuEventQueue_.size() > MAX_CACHE_SIZE) { + DealEvent(tsCpuEventQueue_.begin()->second.get()); + tsCpuEventQueue_.erase(tsCpuEventQueue_.begin()); + } +} + +void CpuFilter::DealEvent(const TSCpuEvent* event) +{ + switch (static_cast(event->type_)) { + case TS_EVENT_THREAD_SWITCH: + ExecInsertSwitchEvent(event->switchEvent_->ts_, event->switchEvent_->cpu_, event->switchEvent_->prevPid_, + event->switchEvent_->prevPior_, event->switchEvent_->prevState_, + event->switchEvent_->nextPid_, event->switchEvent_->nextPior_); + break; + case TS_EVENT_THREAD_WAKING: + ExecInsertWakeupEvent(event->wakeupEvent_->ts_, event->wakeupEvent_->pid_); + break; + case TS_EVENT_PROCESS_EXIT: + case TS_EVENT_PROCESS_FREE: + ExecInsertProcessExitEvent(event->processExitEvent_->ts_, event->processExitEvent_->cpu_, + event->processExitEvent_->pid_); + break; + default: + break; + } +} +void CpuFilter::FinishCpuEvent() +{ + for (auto it = tsCpuEventQueue_.begin(); it != tsCpuEventQueue_.end(); it++) { + DealEvent(it->second.get()); + } + tsCpuEventQueue_.clear(); +} +void CpuFilter::ExecInsertSwitchEvent(uint64_t ts, + uint64_t cpu, + uint64_t prevPid, + uint64_t prevPior, + uint64_t prevState, + uint64_t nextPid, + uint64_t nextPior) +{ + auto index = traceDataCache_->GetSchedSliceData()->AppendSchedSlice(ts, 0, cpu, nextPid, 0, nextPior); + + auto prevTidOnCpu = cpuToRowSched_.find(cpu); + if (prevTidOnCpu != cpuToRowSched_.end()) { + traceDataCache_->GetSchedSliceData()->Update(prevTidOnCpu->second, ts, prevState, prevPior); + cpuToRowSched_.at(cpu) = index; + } else { + cpuToRowSched_.insert(std::make_pair(cpu, index)); + } + + if (nextPid) { + CheckWakeupEvent(nextPid); + auto lastRow = RowOfInternalTidInStateTable(nextPid); + if (lastRow != INVALID_UINT64) { + traceDataCache_->GetThreadStateData()->UpdateDuration(lastRow, ts); + } + index = + traceDataCache_->GetThreadStateData()->AppendThreadState(ts, INVALID_UINT64, cpu, nextPid, TASK_RUNNING); + RemberInternalTidInStateTable(nextPid, index, TASK_RUNNING); + if (cpuToRowThreadState_.find(cpu) == cpuToRowThreadState_.end()) { + cpuToRowThreadState_.insert(std::make_pair(cpu, index)); + } else { + cpuToRowThreadState_.at(cpu) = index; + } + } + + if (prevPid) { + CheckWakeupEvent(prevPid); + auto lastRow = RowOfInternalTidInStateTable(prevPid); + if (lastRow != INVALID_UINT64) { + traceDataCache_->GetThreadStateData()->UpdateDuration(lastRow, ts); + } + auto temp = traceDataCache_->GetThreadStateData()->AppendThreadState(ts, INVALID_UINT64, INVALID_UINT64, + prevPid, prevState); + RemberInternalTidInStateTable(prevPid, temp, prevState); + } +} +void CpuFilter::ExecInsertWakeupEvent(uint64_t ts, uint64_t internalTid) +{ + /* repeated wakeup msg may come, we only record last wakeupmsg, and + the wakeup will only insert to DataCache when a sched_switch comes + */ + if (lastWakeUpMsg.find(internalTid) != lastWakeUpMsg.end()) { + lastWakeUpMsg.at(internalTid) = ts; + } else { + lastWakeUpMsg.insert(std::make_pair(internalTid, ts)); + } +} +bool CpuFilter::ExecInsertProcessExitEvent(uint64_t ts, uint64_t cpu, uint64_t pid) +{ + UNUSED(cpu); + auto thread = traceDataCache_->GetThreadData(static_cast(pid)); + if (thread) { + thread->endT_ = ts; + return true; + } + return false; +} +uint64_t CpuFilter::RowOfInternalTidInStateTable(uint64_t uid) const +{ + auto row = internalTidToRowThreadState_.find(uid); + if (row != internalTidToRowThreadState_.end()) { + return (*row).second.row_; + } + return INVALID_UINT64; +} + +uint64_t CpuFilter::StateOfInternalTidInStateTable(uint64_t uid) const +{ + auto row = internalTidToRowThreadState_.find(uid); + if (row != internalTidToRowThreadState_.end()) { + return (*row).second.state_; + } + return TASK_INVALID; +} + +void CpuFilter::CheckWakeupEvent(uint64_t internalTid) +{ + if (lastWakeUpMsg.find(internalTid) == lastWakeUpMsg.end()) { + return; + } + auto ts = lastWakeUpMsg.at(internalTid); + lastWakeUpMsg.erase(internalTid); + uint64_t lastrow = RowOfInternalTidInStateTable(internalTid); + auto lastState = StateOfInternalTidInStateTable(internalTid); + if (lastState == TASK_RUNNING) { + return; + } + if (lastrow != INVALID_UINT64) { + traceDataCache_->GetThreadStateData()->UpdateDuration(lastrow, ts); + } + auto index = traceDataCache_->GetThreadStateData()->AppendThreadState(ts, INVALID_UINT64, INVALID_UINT64, + internalTid, TASK_RUNNABLE); + RemberInternalTidInStateTable(internalTid, index, TASK_RUNNABLE); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/filter/cpu_filter.h b/host/trace_streamer/src/filter/cpu_filter.h new file mode 100644 index 0000000..a798a11 --- /dev/null +++ b/host/trace_streamer/src/filter/cpu_filter.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPU_FILTER_H +#define CPU_FILTER_H + +#include +#include +#include +#include +#include + +#include "filter_base.h" +#include "trace_data_cache.h" +#include "trace_streamer_filters.h" +#include "ts_common.h" + +namespace SysTuning { +namespace TraceStreamer { +class TraceStreamerFilters; +class CpuFilter : private FilterBase { +public: + CpuFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter); + CpuFilter(const CpuFilter&) = delete; + CpuFilter& operator=(const CpuFilter&) = delete; + ~CpuFilter() override; + +public: + void InsertSwitchEvent(uint64_t ts, + uint64_t cpu, + uint64_t prevPid, + uint64_t prevPior, + uint64_t prevState, + uint64_t nextPid, + uint64_t nextPior); + void InsertWakeupEvent(uint64_t ts, uint64_t internalTid); + bool InsertProcessExitEvent(uint64_t ts, uint64_t cpu, uint64_t pid); + bool InsertProcessFreeEvent(uint64_t ts, uint64_t pid); + void FinishCpuEvent(); + +private: + void MaybeDealEvent(); + + class TSSwitchEvent { + public: + TSSwitchEvent(uint64_t ts, + uint64_t cpu, + uint64_t prevPid, + uint64_t prevPior, + uint64_t prevState, + uint64_t nextPid, + uint64_t nextPior) + : ts_(ts), + cpu_(cpu), + prevPid_(prevPid), + prevPior_(prevPior), + prevState_(prevState), + nextPid_(nextPid), + nextPior_(nextPior) + + { + } + ~TSSwitchEvent() {} + uint64_t ts_; + uint64_t cpu_; + uint64_t prevPid_; + uint64_t prevPior_; + uint64_t prevState_; + uint64_t nextPid_; + uint64_t nextPior_; + }; + class TSWakeupEvent { + public: + TSWakeupEvent(uint64_t ts, uint64_t pid) : ts_(ts), pid_(pid) {} + ~TSWakeupEvent() {} + uint64_t ts_; + uint64_t pid_; + }; + class TSProcessExitEvent { + public: + TSProcessExitEvent(uint64_t ts, uint64_t cpu, uint64_t pid) : ts_(ts), cpu_(cpu), pid_(pid) {} + ~TSProcessExitEvent() {} + uint64_t ts_; + uint64_t cpu_; + uint64_t pid_; + }; + enum TSCpuEventType { + TS_EVENT_THREAD_SWITCH, + TS_EVENT_THREAD_WAKING, + TS_EVENT_PROCESS_EXIT, + TS_EVENT_PROCESS_FREE + }; + class TSCpuEvent { + public: + TSCpuEvent() {} + ~TSCpuEvent() {} + TSCpuEventType type_; + // us union below will be a good choice + // but union with unique_ptr can bring about runtime error on windows and mac,only work well on linux + std::unique_ptr switchEvent_ = {}; + std::unique_ptr wakeupEvent_ = {}; + std::unique_ptr processExitEvent_ = {}; + }; + void DealEvent(const TSCpuEvent* event); + void ExecInsertSwitchEvent(uint64_t ts, + uint64_t cpu, + uint64_t prevPid, + uint64_t prevPior, + uint64_t prevState, + uint64_t nextPid, + uint64_t nextPior); + void ExecInsertWakeupEvent(uint64_t ts, uint64_t internalTid); + bool ExecInsertProcessExitEvent(uint64_t ts, uint64_t cpu, uint64_t pid); + void CheckWakeupEvent(uint64_t internalTid); + uint64_t RemberInternalTidInStateTable(uint64_t uid, uint64_t row, uint64_t state = TASK_INVALID); + uint64_t RowOfInternalTidInStateTable(uint64_t uid) const; + uint64_t StateOfInternalTidInStateTable(uint64_t uid) const; + std::multimap> tsCpuEventQueue_; + std::map cpuToRowThreadState_ = {}; + std::map cpuToRowSched_ = {}; + std::map lastWakeUpMsg = {}; + + struct TPthread { + uint64_t row_; + uint64_t state_; + }; + std::map internalTidToRowThreadState_ = {}; + // timestamp of ftrace events from different cpu can be outof order + // keep a cache of ftrace events in memory and keep msg in order + // the value below is the count of msg, maybe you can change it + const size_t MAX_CACHE_SIZE = 10000; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // CPU_FILTER_H diff --git a/host/trace_streamer/src/filter/filter.pri b/host/trace_streamer/src/filter/filter.pri new file mode 100644 index 0000000..1de2180 --- /dev/null +++ b/host/trace_streamer/src/filter/filter.pri @@ -0,0 +1,43 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDEPATH +=$$PWD \ + $$PWD/../cfg +HEADERS += \ + $$PWD/clock_filter.h \ + $$PWD/cpu_filter.h \ + $$PWD/filter_base.h \ + $$PWD/filter_filter.h \ + $$PWD/measure_filter.h \ + $$PWD/process_filter.h \ + $$PWD/slice_filter.h \ + $$PWD/symbols_filter.h \ + $$PWD/stat_filter.h \ + $$PWD/binder_filter.h \ + $$PWD/args_filter.h \ + $$PWD/irq_filter.h \ + $$PWD/system_event_measure_filter.h +SOURCES += \ + $$PWD/clock_filter.cpp \ + $$PWD/cpu_filter.cpp \ + $$PWD/filter_base.cpp \ + $$PWD/filter_filter.cpp \ + $$PWD/measure_filter.cpp \ + $$PWD/process_filter.cpp \ + $$PWD/slice_filter.cpp \ + $$PWD/symbols_filter.cpp \ + $$PWD/stat_filter.cpp \ + $$PWD/binder_filter.cpp \ + $$PWD/args_filter.cpp \ + $$PWD/irq_filter.cpp \ + $$PWD/system_event_measure_filter.cpp diff --git a/host/trace_streamer/src/filter/filter_base.cpp b/host/trace_streamer/src/filter/filter_base.cpp new file mode 100644 index 0000000..d3bbcd5 --- /dev/null +++ b/host/trace_streamer/src/filter/filter_base.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "filter_base.h" +#include "trace_streamer_filters.h" + +namespace SysTuning { +namespace TraceStreamer { +FilterBase::FilterBase(TraceDataCache* dataCache, const TraceStreamerFilters* filter) + : streamFilters_(filter), traceDataCache_(dataCache) +{ +} +FilterBase::~FilterBase() = default; +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/filter/filter_base.h b/host/trace_streamer/src/filter/filter_base.h new file mode 100644 index 0000000..9704b1a --- /dev/null +++ b/host/trace_streamer/src/filter/filter_base.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FILTER_BASE_H +#define FILTER_BASE_H +#include +#include "trace_data_cache.h" +#include "trace_streamer_filters.h" + +#define UNUSED(expr) \ + do { \ + static_cast(expr); \ + } while (0) + +namespace SysTuning { +namespace TraceStreamer { +class FilterBase { +public: + FilterBase(TraceDataCache* dataCache, const TraceStreamerFilters* filter); + FilterBase(const FilterBase&) = delete; + FilterBase& operator=(const FilterBase&) = delete; + virtual ~FilterBase(); + +public: + const TraceStreamerFilters* streamFilters_ = {}; + TraceDataCache* traceDataCache_ = {}; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // FILTER_BASE_H diff --git a/host/trace_streamer/src/filter/filter_filter.cpp b/host/trace_streamer/src/filter/filter_filter.cpp new file mode 100644 index 0000000..9d2f4e6 --- /dev/null +++ b/host/trace_streamer/src/filter/filter_filter.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "filter_filter.h" + +#include "process_filter.h" +#include "trace_data_cache.h" +#include "trace_streamer_filters.h" + +namespace SysTuning { +namespace TraceStreamer { +FilterFilter::FilterFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter) + : FilterBase(dataCache, filter) {} + +FilterFilter::~FilterFilter() = default; + +uint32_t FilterFilter::AddFilter(std::string type, std::string name, uint64_t arg) +{ + auto filter = traceDataCache_->GetFilterData(); + size_t id = filter->AppendNewFilterData(type, name, arg); + return static_cast(id); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/filter/filter_filter.h b/host/trace_streamer/src/filter/filter_filter.h new file mode 100644 index 0000000..cef929b --- /dev/null +++ b/host/trace_streamer/src/filter/filter_filter.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FILTER_FILTER_H +#define FILTER_FILTER_H + +#include "filter_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class TraceStreamerFilters; +class FilterFilter : private FilterBase { +public: + FilterFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter); + FilterFilter(const FilterFilter&) = delete; + FilterFilter& operator=(const FilterFilter&) = delete; + ~FilterFilter() override; + + uint32_t AddFilter(std::string type, std::string name, uint64_t arg); +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // FILTER_FILTER_H diff --git a/host/trace_streamer/src/filter/irq_filter.cpp b/host/trace_streamer/src/filter/irq_filter.cpp new file mode 100644 index 0000000..aef7b3c --- /dev/null +++ b/host/trace_streamer/src/filter/irq_filter.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "irq_filter.h" +#include "measure_filter.h" +#include "process_filter.h" +#include "slice_filter.h" +#include "string_to_numerical.h" +namespace SysTuning { +namespace TraceStreamer { +IrqFilter::IrqFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter) : FilterBase(dataCache, filter) +{ + for (size_t i = 0; i < irqActionNames_.size(); i++) { + irqActionNameIds_.push_back(traceDataCache_->GetDataIndex(irqActionNames_[i])); + } +} +IrqFilter::~IrqFilter() = default; + +void IrqFilter::IrqHandlerEntry(int64_t ts, uint32_t cpu, DataIndex nameId) +{ + streamFilters_->sliceFilter_->IrqHandlerEntry(ts, cpu, irqCatalog_, nameId); +} +void IrqFilter::IrqHandlerExit(int64_t ts, uint32_t cpu, uint32_t ret) +{ + DataIndex irqRet = INVALID_DATAINDEX; + if (ret == 1) { + irqRet = irqHandled_; + } else { + irqRet = irqUnHandled_; + } + ArgsSet args; + args.AppendArg(irqRet_, BASE_DATA_TYPE_STRING, irqRet); + streamFilters_->sliceFilter_->IrqHandlerExit(ts, cpu, args); +} +void IrqFilter::SoftIrqEntry(int64_t ts, uint32_t cpu, uint32_t vec) +{ + if (vec >= irqActionNames_.size()) { + return; + } + streamFilters_->sliceFilter_->SoftIrqEntry(ts, cpu, softIrqCatalog_, irqActionNameIds_[vec]); +} +void IrqFilter::SoftIrqExit(int64_t ts, uint32_t cpu, uint32_t vec) +{ + if (vec >= irqActionNames_.size()) { + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_SOFTIRQ_EXIT, STAT_EVENT_NOTMATCH); + return; + } + ArgsSet args; + args.AppendArg(irqRet_, BASE_DATA_TYPE_STRING, irqActionNameIds_[vec]); + streamFilters_->sliceFilter_->SoftIrqExit(ts, cpu, args); + return; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/filter/irq_filter.h b/host/trace_streamer/src/filter/irq_filter.h new file mode 100644 index 0000000..d13c9b3 --- /dev/null +++ b/host/trace_streamer/src/filter/irq_filter.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IRQ_FILTER_H +#define IRQ_FILTER_H + +#include +#include "args_set.h" +#include "filter_base.h" +#include "trace_data_cache.h" +#include "trace_streamer_filters.h" +namespace SysTuning { +namespace TraceStreamer { +class IrqFilter : private FilterBase { +public: + IrqFilter(TraceDataCache*, const TraceStreamerFilters*); + IrqFilter(const IrqFilter&) = delete; + IrqFilter& operator=(const IrqFilter&) = delete; + ~IrqFilter() override; + +public: + void IrqHandlerEntry(int64_t ts, uint32_t cpu, DataIndex nameId); + void IrqHandlerExit(int64_t ts, uint32_t cpu, uint32_t ret); + void SoftIrqEntry(int64_t ts, uint32_t cpu, uint32_t vec); + void SoftIrqExit(int64_t ts, uint32_t cpu, uint32_t vec); + +private: + DataIndex irqId_ = traceDataCache_->GetDataIndex("irq_id"); + DataIndex irqRet_ = traceDataCache_->GetDataIndex("irq_ret"); + DataIndex irqHandled_ = traceDataCache_->GetDataIndex("handled"); + DataIndex irqUnHandled_ = traceDataCache_->GetDataIndex("unhandled"); + DataIndex irqCatalog_ = traceDataCache_->GetDataIndex("irq"); + DataIndex softIrqCatalog_ = traceDataCache_->GetDataIndex("softirq"); + std::unordered_map lastEventTs_ = {}; + std::unordered_set transReplyWaitingReply_ = {}; + std::unordered_map transWaitingRcv_ = {}; + std::unordered_map transNoNeedReply_ = {}; + std::unordered_map binderFlagDescs_ = {}; + std::vector irqActionNames_ = {"HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", + "BLOCK_IOPOLL", "TASKLET", "SCHED", "HRTIMER", "RCU"}; + std::vector irqActionNameIds_ = {}; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // IRQ_FILTER_H diff --git a/host/trace_streamer/src/filter/measure_filter.cpp b/host/trace_streamer/src/filter/measure_filter.cpp new file mode 100644 index 0000000..ad723fa --- /dev/null +++ b/host/trace_streamer/src/filter/measure_filter.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "measure_filter.h" +#include "filter_filter.h" +#include "log.h" +#include "ts_common.h" + +namespace SysTuning { +namespace TraceStreamer { +MeasureFilter::MeasureFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter, FilterType e) + : FilterBase(dataCache, filter), + tidStreamIdFilterIdMap_(INVALID_UINT64), + filterType_(e) +{ +} + +MeasureFilter::~MeasureFilter() {} + +void MeasureFilter::AppendNewMeasureData(uint64_t internalTid, DataIndex nameIndex, uint64_t timestamp, int64_t value) +{ + auto filterId = GetOrCreateFilterId(internalTid, nameIndex); + traceDataCache_->GetMeasureData()->AppendMeasureData(0, timestamp, value, filterId); +} +uint32_t MeasureFilter::GetOrCreateFilterId(uint64_t internalTid, DataIndex nameIndex) +{ + auto filterId = tidStreamIdFilterIdMap_.Find(internalTid, nameIndex); + if (filterId != INVALID_UINT64) { + return static_cast(filterId); + } + + uint32_t newFilterId = streamFilters_->filterFilter_->AddFilter( + filterTypeValue.at(filterType_), traceDataCache_->GetDataFromDict(nameIndex), internalTid); + AddCertainFilterId(internalTid, nameIndex, newFilterId); + return newFilterId; +} + +void MeasureFilter::AddCertainFilterId(uint64_t internalTid, DataIndex nameIndex, uint64_t filterId) +{ + tidStreamIdFilterIdMap_.Insert(internalTid, nameIndex, filterId); + + if (filterType_ == E_THREADMEASURE_FILTER) { + traceDataCache_->GetThreadMeasureFilterData()->AppendNewFilter(filterId, static_cast(nameIndex), + internalTid); + } else if (filterType_ == E_THREAD_FILTER) { + traceDataCache_->GetThreadFilterData()->AppendNewFilter(filterId, static_cast(nameIndex), + internalTid); + } else if (filterType_ == E_PROCESS_MEASURE_FILTER) { + traceDataCache_->GetProcessMeasureFilterData()->AppendNewFilter( + static_cast(filterId), static_cast(nameIndex), static_cast(internalTid)); + } else if (filterType_ == E_PROCESS_FILTER_FILTER) { + traceDataCache_->GetProcessFilterData()->AppendNewFilter( + static_cast(filterId), static_cast(nameIndex), static_cast(internalTid)); + } else if (filterType_ == E_CPU_MEASURE_FILTER) { + traceDataCache_->GetCpuMeasuresData()->AppendNewFilter(filterId, static_cast(nameIndex), internalTid); + } else if (filterType_ == E_CLOCK_RATE_FILTER) { + traceDataCache_->GetClockEventFilterData()->AppendNewFilter(filterId, clockSetRateDataIndex_, + static_cast(nameIndex), internalTid); + } else if (filterType_ == E_CLOCK_ENABLE_FILTER) { + traceDataCache_->GetClockEventFilterData()->AppendNewFilter(filterId, clockEnableDataIndex_, + static_cast(nameIndex), internalTid); + } else if (filterType_ == E_CLOCK_DISABLE_FILTER) { + traceDataCache_->GetClockEventFilterData()->AppendNewFilter(filterId, clockDisableDataIndex_, + static_cast(nameIndex), internalTid); + } else if (filterType_ == E_CLK_RATE_FILTER) { + traceDataCache_->GetClkEventFilterData()->AppendNewFilter(filterId, clkSetRateDataIndex_, + static_cast(nameIndex), internalTid); + } else if (filterType_ == E_CLK_ENABLE_FILTER) { + traceDataCache_->GetClkEventFilterData()->AppendNewFilter(filterId, clkEnableDataIndex_, + static_cast(nameIndex), internalTid); + } else if (filterType_ == E_CLK_DISABLE_FILTER) { + traceDataCache_->GetClkEventFilterData()->AppendNewFilter(filterId, clkDisableDataIndex_, + static_cast(nameIndex), internalTid); + } +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/filter/measure_filter.h b/host/trace_streamer/src/filter/measure_filter.h new file mode 100644 index 0000000..ef8efeb --- /dev/null +++ b/host/trace_streamer/src/filter/measure_filter.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef THREAD_MEASURE_FILTER_H +#define THREAD_MEASURE_FILTER_H + +#include +#include +#include + +#include "double_map.h" +#include "filter_base.h" +#include "trace_data_cache.h" +#include "trace_streamer_filters.h" + +namespace SysTuning { +namespace TraceStreamer { +enum FilterType { + E_THREADMEASURE_FILTER, + E_THREAD_FILTER, + E_PROCESS_MEASURE_FILTER, + E_PROCESS_FILTER_FILTER, + E_CPU_MEASURE_FILTER, + E_CLOCK_RATE_FILTER, + E_CLOCK_ENABLE_FILTER, + E_CLOCK_DISABLE_FILTER, + E_CLK_RATE_FILTER, + E_CLK_ENABLE_FILTER, + E_CLK_DISABLE_FILTER +}; + +class MeasureFilter : private FilterBase { +public: + MeasureFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter, FilterType); + MeasureFilter(const MeasureFilter&) = delete; + MeasureFilter& operator=(const MeasureFilter&) = delete; + ~MeasureFilter() override; + void AppendNewMeasureData(uint64_t internalTid, DataIndex nameIndex, uint64_t timestamp, int64_t value); + uint32_t GetOrCreateFilterId(uint64_t internalTid, DataIndex nameIndex); +private: + void AddCertainFilterId(uint64_t internalTid, DataIndex nameIndex, uint64_t filterId); + DoubleMap tidStreamIdFilterIdMap_; + FilterType filterType_; + + const std::map filterTypeValue = { + { E_THREADMEASURE_FILTER, "thread_measure_filter" }, + { E_THREAD_FILTER, "thread_measure" }, + { E_PROCESS_MEASURE_FILTER, "process_measure_filter" }, + { E_PROCESS_FILTER_FILTER, "process_filter" }, + { E_CPU_MEASURE_FILTER, "cpu_measure_filter" }, + { E_CLOCK_RATE_FILTER, "clock_rate_filter" }, + { E_CLOCK_ENABLE_FILTER, "clock_enable_filter" }, + { E_CLOCK_DISABLE_FILTER, "clock_disable_filter" }, + { E_CLK_RATE_FILTER, "clk_rate_filter" }, + { E_CLK_ENABLE_FILTER, "clk_enable_filter" }, + { E_CLK_DISABLE_FILTER, "clk_disable_filter" } + }; + DataIndex clockSetRateDataIndex_ = traceDataCache_->GetDataIndex("clock_set_rate"); + DataIndex clockEnableDataIndex_ = traceDataCache_->GetDataIndex("clock_enable"); + DataIndex clockDisableDataIndex_ = traceDataCache_->GetDataIndex("clock_disable"); + DataIndex clkSetRateDataIndex_ = traceDataCache_->GetDataIndex("clk_set_rate"); + DataIndex clkEnableDataIndex_ = traceDataCache_->GetDataIndex("clk_enable"); + DataIndex clkDisableDataIndex_ = traceDataCache_->GetDataIndex("clk_disable"); +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // THREAD_MEASURE_FILTER_H diff --git a/host/trace_streamer/src/filter/process_filter.cpp b/host/trace_streamer/src/filter/process_filter.cpp new file mode 100644 index 0000000..0c941f3 --- /dev/null +++ b/host/trace_streamer/src/filter/process_filter.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "process_filter.h" +#include +#include +#include +#include + +using CustomPair = std::pair; +namespace SysTuning { +namespace TraceStreamer { +namespace { +const uint32_t INVALID_ID = std::numeric_limits::max(); +} +ProcessFilter::ProcessFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter) + : FilterBase(dataCache, filter) +{ + tidMappingSet_.insert(CustomPair(0, 0)); + pidToInternalPidMap_.insert(CustomPair(0, 0)); +} + +ProcessFilter::~ProcessFilter() {} + +uint32_t ProcessFilter::UpdateOrCreateThreadWithName(uint64_t timeStamp, uint32_t tid, std::string_view name) +{ + DataIndex nameIndex = traceDataCache_->GetDataIndex(name); + return UpdateOrCreateThreadWithNameIndex(timeStamp, tid, nameIndex); +} + +uint32_t ProcessFilter::UpdateOrCreateThread(uint64_t timestamp, uint32_t tid) +{ + return UpdateOrCreateThreadWithNameIndex(timestamp, tid, 0); +} +void ProcessFilter::UpdateOrCreateThreadWithPidAndName(uint32_t tid, uint32_t pid, std::string_view name) +{ + uint32_t internalTid = GetOrCreateThreadWithPid(tid, pid); + auto thread = traceDataCache_->GetThreadData(internalTid); + auto nameIndex = traceDataCache_->GetDataIndex(name); + thread->nameIndex_ = nameIndex; + // When the process ID is equal to the thread ID, the process name is also equal to the thread name + if (tid == pid) { + UpdateOrCreateProcessWithName(pid, name); + } +} + +uint32_t ProcessFilter::GetOrCreateThreadWithPid(uint32_t tid, uint32_t pid) +{ + TraceStdtype::Thread* thread = nullptr; + uint32_t internalTid = INVALID_ID; + if (pid == 0) { + internalTid = GetInternalTid(tid); + } else { + internalTid = GetInternalTid(tid, pid); + } + if (internalTid != INVALID_ID) { + thread = traceDataCache_->GetThreadData(internalTid); + } else { + std::tie(internalTid, thread) = NewThread(tid); + } + + if (!thread->internalPid_ && pid != 0) { + std::tie(thread->internalPid_, std::ignore) = CreateProcessMaybe(pid, thread->startT_); + } + + return internalTid; +} + +uint32_t ProcessFilter::UpdateOrCreateProcessWithName(uint32_t pid, std::string_view name) +{ + uint32_t internalPid = 0; + TraceStdtype::Process* process = nullptr; + std::tie(internalPid, process) = CreateProcessMaybe(pid, 0); + if (process && process->cmdLine_ != name) { + process->cmdLine_ = std::string(name); + } + // update main thread name + auto internalTid = GetInternalTid(pid, pid); + if (internalTid != INVALID_ID) { + auto thread = traceDataCache_->GetThreadData(internalTid); + thread->nameIndex_ = traceDataCache_->GetDataIndex(name); + } + return internalPid; +} + +uint32_t ProcessFilter::UpdateOrCreateThreadWithNameIndex(uint64_t timeStamp, uint32_t tid, DataIndex threadNameIndex) +{ + TraceStdtype::Thread* thread = nullptr; + uint32_t internalTid = GetInternalTid(tid); + if (internalTid != INVALID_ID) { + if (threadNameIndex) { + thread = traceDataCache_->GetThreadData(internalTid); + thread->nameIndex_ = threadNameIndex; + } + } else { + std::tie(internalTid, thread) = NewThread(tid); + thread->nameIndex_ = threadNameIndex; + if (timeStamp < thread->startT_) { + thread->startT_ = timeStamp; + } + } + return internalTid; +} +uint32_t ProcessFilter::GetInternalTid(uint32_t tid, uint32_t pid) const +{ + uint32_t internalTid = INVALID_ID; + auto tidsPair = tidMappingSet_.equal_range(tid); + for (auto it = tidsPair.first; it != tidsPair.second; it++) { + uint32_t iterItid = it->second; + auto iterThread = traceDataCache_->GetThreadData(iterItid); + if (!iterThread->internalPid_) { + internalTid = iterItid; + continue; + } + + const auto& iterProcess = traceDataCache_->GetConstProcessData(iterThread->internalPid_); + if (iterProcess.pid_ == pid) { + internalTid = iterItid; + break; + } + } + + return internalTid; +} + +uint32_t ProcessFilter::GetInternalTid(uint32_t tid) const +{ + auto itRange = tidMappingSet_.equal_range(tid); + if (itRange.first != itRange.second) { + auto internalTid = std::prev(itRange.second)->second; + return internalTid; + } + return INVALID_ID; +} + +bool ProcessFilter::isThreadNameEmpty(uint32_t tid) const +{ + auto internalTid = GetInternalTid(tid); + if (internalTid != INVALID_ID) { + auto thread = traceDataCache_->GetThreadData(internalTid); + if (thread->nameIndex_) { + return false; + } + } + return true; +} + +InternalPid ProcessFilter::GetInternalPid(uint32_t pid) const +{ + auto it = pidToInternalPidMap_.find(pid); + if (it != pidToInternalPidMap_.end()) { + return it->second; + } + return INVALID_ID; +} + +InternalTid ProcessFilter::GetOrCreateInternalPid(uint64_t timestamp, uint32_t pid) +{ + auto ipid = GetInternalPid(pid); + if (ipid != INVALID_ID) { + return ipid; + } + + uint32_t internalPid = 0; + TraceStdtype::Process* process = nullptr; + std::tie(internalPid, process) = CreateProcessMaybe(pid, timestamp); + return internalPid; +} +std::tuple ProcessFilter::NewThread(uint32_t tid) +{ + uint32_t internalTid = traceDataCache_->NewInternalThread(tid); + tidMappingSet_.emplace(tid, internalTid); + auto thread = traceDataCache_->GetThreadData(internalTid); + + return std::make_tuple(internalTid, thread); +} + +std::tuple ProcessFilter::NewProcess(uint32_t pid) +{ + uint32_t internalPid = traceDataCache_->GetProcessInternalPid(pid); + pidToInternalPidMap_.emplace(pid, internalPid); + auto process = traceDataCache_->GetProcessData(internalPid); + + return std::make_tuple(internalPid, process); +} + +std::tuple ProcessFilter::CreateProcessMaybe(uint32_t pid, uint64_t startT) +{ + uint32_t internalPid = INVALID_ID; + TraceStdtype::Process* process = nullptr; + auto it = pidToInternalPidMap_.find(pid); + if (it != pidToInternalPidMap_.end()) { + internalPid = it->second; + process = traceDataCache_->GetProcessData(internalPid); + } else { + std::tie(internalPid, process) = NewProcess(pid); + void(GetOrCreateThreadWithPid(pid, pid)); + } + + if (process->startT_ == 0) { + process->startT_ = startT; + } + + return std::make_tuple(internalPid, process); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/filter/process_filter.h b/host/trace_streamer/src/filter/process_filter.h new file mode 100644 index 0000000..a3e04e7 --- /dev/null +++ b/host/trace_streamer/src/filter/process_filter.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PROCESS_FILTER_H +#define PROCESS_FILTER_H + +#include + +#include "filter_base.h" +#include "trace_data_cache.h" +#include "trace_streamer_filters.h" + +namespace SysTuning { +namespace TraceStreamer { +class ProcessFilter : private FilterBase { +public: + ProcessFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter); + ~ProcessFilter() override; + + uint32_t UpdateOrCreateProcessWithName(uint32_t pid, std::string_view name); + uint32_t UpdateOrCreateThreadWithName(uint64_t timestamp, uint32_t tid, std::string_view name); + void UpdateOrCreateThreadWithPidAndName(uint32_t tid, uint32_t pid, std::string_view name); + uint32_t GetOrCreateThreadWithPid(uint32_t tid, uint32_t pid); + uint32_t UpdateOrCreateThread(uint64_t timestamp, uint32_t tid); + InternalPid GetInternalPid(uint32_t pid) const; + InternalTid GetOrCreateInternalPid(uint64_t timestamp, uint32_t pid); + bool isThreadNameEmpty(uint32_t tid) const; + uint32_t UpdateOrCreateThreadWithNameIndex(uint64_t timestamp, uint32_t tid, DataIndex threadNameIndex); +private: + std::tuple CreateProcessMaybe(uint32_t pid, uint64_t start_ns); + std::tuple NewThread(uint32_t tid); + std::tuple NewProcess(uint32_t pid); + + InternalTid GetInternalTid(uint32_t tid) const; + InternalTid GetInternalTid(uint32_t tid, uint32_t pid) const; +private: + std::multimap tidMappingSet_ = {}; + std::map pidToInternalPidMap_ = {}; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // PROCESS_FILTER_H diff --git a/host/trace_streamer/src/filter/slice_filter.cpp b/host/trace_streamer/src/filter/slice_filter.cpp new file mode 100644 index 0000000..5ae3854 --- /dev/null +++ b/host/trace_streamer/src/filter/slice_filter.cpp @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "slice_filter.h" +#include +#include +#include + +#include "args_filter.h" +#include "log.h" +#include "measure_filter.h" +#include "process_filter.h" +#include "stat_filter.h" +#include "ts_common.h" + +namespace SysTuning { +namespace TraceStreamer { +SliceFilter::SliceFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter) + : FilterBase(dataCache, filter), asyncEventMap_(INVALID_UINT64) +{ +} + +SliceFilter::~SliceFilter() = default; + +bool SliceFilter::BeginSlice(uint64_t timestamp, + uint32_t pid, + uint32_t threadGroupId, + DataIndex cat, + DataIndex nameIndex) +{ + InternalTid internalTid = INVALID_UTID; + if (threadGroupId > 0) { + internalTid = streamFilters_->processFilter_->GetOrCreateThreadWithPid(pid, threadGroupId); + pidTothreadGroupId_[pid] = threadGroupId; + } else { + internalTid = streamFilters_->processFilter_->UpdateOrCreateThread(timestamp, pid); + } + // make a SliceData DataItem, {timestamp, dur, internalTid, cat, nameIndex} + struct SliceData sliceData = {timestamp, 0, internalTid, cat, nameIndex}; + return BeginSliceInternal(sliceData); +} + +void SliceFilter::IrqHandlerEntry(uint64_t timestamp, uint32_t cpu, DataIndex catalog, DataIndex nameIndex) +{ + struct SliceData sliceData = {timestamp, 0, cpu, catalog, nameIndex}; + auto slices = traceDataCache_->GetIrqData(); + size_t index = slices->AppendInternalSlice(sliceData.timestamp, sliceData.duration, sliceData.internalTid, + sliceData.cat, sliceData.name, 0, std::nullopt); + if (irqEventMap_.count(cpu)) { + // not match + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_IRQ_HANDLER_ENTRY, STAT_EVENT_DATA_LOST); + irqEventMap_.at(cpu) = {timestamp, index}; + } else { + irqEventMap_[cpu] = {timestamp, index}; + } + slices->AppendDistributeInfo(); + return; +} + +void SliceFilter::IrqHandlerExit(uint64_t timestamp, uint32_t cpu, ArgsSet args) +{ + if (!irqEventMap_.count(cpu)) { + // not match + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_IRQ_HANDLER_EXIT, STAT_EVENT_NOTMATCH); + return; + } + uint32_t argSetId = INVALID_UINT32; + auto slices = traceDataCache_->GetIrqData(); + argSetId = streamFilters_->argsFilter_->NewArgs(args); + slices->SetDurationAndArg(irqEventMap_.at(cpu).row, timestamp, argSetId); + irqEventMap_.erase(cpu); + return; +} + +void SliceFilter::SoftIrqEntry(uint64_t timestamp, uint32_t cpu, DataIndex catalog, DataIndex nameIndex) +{ + struct SliceData sliceData = {timestamp, 0, cpu, catalog, nameIndex}; + auto slices = traceDataCache_->GetIrqData(); + size_t index = slices->AppendInternalSlice(sliceData.timestamp, sliceData.duration, sliceData.internalTid, + sliceData.cat, sliceData.name, 0, std::nullopt); + if (softIrqEventMap_.count(cpu)) { + // not match + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SOFTIRQ_ENTRY, STAT_EVENT_DATA_LOST); + softIrqEventMap_.at(cpu) = {timestamp, index}; + } else { + softIrqEventMap_[cpu] = {timestamp, index}; + } + slices->AppendDistributeInfo(); + return; +} + +void SliceFilter::SoftIrqExit(uint64_t timestamp, uint32_t cpu, ArgsSet args) +{ + if (!softIrqEventMap_.count(cpu)) { + // not match + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SOFTIRQ_EXIT, STAT_EVENT_DATA_LOST); + return; + } + uint32_t argSetId = INVALID_UINT32; + auto slices = traceDataCache_->GetIrqData(); + argSetId = streamFilters_->argsFilter_->NewArgs(args); + slices->SetDurationAndArg(softIrqEventMap_.at(cpu).row, timestamp, argSetId); + softIrqEventMap_.erase(cpu); + return; +} + +size_t SliceFilter::BeginAsyncBinder(uint64_t timestamp, uint32_t pid, DataIndex cat, DataIndex nameIndex, ArgsSet args) +{ + InternalTid internalTid = streamFilters_->processFilter_->UpdateOrCreateThread(timestamp, pid); + struct SliceData sliceData = {timestamp, 0, internalTid, cat, nameIndex}; + auto slices = traceDataCache_->GetInternalSlicesData(); + + auto sliceStack = &sliceStackMap_[sliceData.internalTid]; + if (sliceStack->size() >= std::numeric_limits::max()) { + TS_LOGW("stack depth out of range."); + } + const uint8_t depth = static_cast(sliceStack->size()); + size_t index = slices->AppendInternalSlice(sliceData.timestamp, sliceData.duration, sliceData.internalTid, + sliceData.cat, sliceData.name, depth, std::nullopt); + + uint32_t argSetId = INVALID_INT32; + if (args.valuesMap_.size()) { + argSetId = streamFilters_->argsFilter_->NewArgs(args); + slices->AppendArgSet(argSetId); + binderQueue_[pid] = argSetId; + } else { + argSetId = streamFilters_->argsFilter_->NewArgs(args); + slices->AppendArgSet(argSetId); + binderQueue_[pid] = argSetId; + } + argsToSliceQueue_[argSetId] = static_cast(index); + return index; +} +size_t SliceFilter::BeginBinder(uint64_t timestamp, uint32_t pid, DataIndex cat, DataIndex nameIndex, + ArgsSet args) +{ + InternalTid internalTid = streamFilters_->processFilter_->UpdateOrCreateThread(timestamp, pid); + struct SliceData sliceData = {timestamp, 0, internalTid, cat, nameIndex}; + auto slices = traceDataCache_->GetInternalSlicesData(); + + auto sliceStack = &sliceStackMap_[sliceData.internalTid]; + if (sliceStack->size() >= std::numeric_limits::max()) { + TS_LOGW("stack depth out of range."); + } + const uint8_t depth = static_cast(sliceStack->size()); + size_t index = slices->AppendInternalSlice(sliceData.timestamp, sliceData.duration, sliceData.internalTid, + sliceData.cat, sliceData.name, depth, std::nullopt); + + sliceStack->push_back(index); + + uint32_t argSetId = INVALID_INT32; + if (args.valuesMap_.size()) { + argSetId = streamFilters_->argsFilter_->NewArgs(args); + slices->AppendArgSet(argSetId); + binderQueue_[pid] = argSetId; + } else { + argSetId = streamFilters_->argsFilter_->NewArgs(args); + slices->AppendArgSet(argSetId); + binderQueue_[pid] = argSetId; + } + argsToSliceQueue_[argSetId] = static_cast(index); + return index; +} + +uint32_t SliceFilter::AddArgs(uint32_t tid, DataIndex key1, DataIndex key2, ArgsSet &args) +{ + if (!binderQueue_.count(tid)) { + return INVALID_UINT32; + } + streamFilters_->argsFilter_->AppendArgs(args, binderQueue_[tid]); + return argsToSliceQueue_[binderQueue_[tid]]; +} +bool SliceFilter::EndBinder(uint64_t timestamp, uint32_t pid, DataIndex category, DataIndex name, ArgsSet args) +{ + if (!binderQueue_.count(pid)) { + return false; + } + auto lastRow = argsToSliceQueue_[binderQueue_[pid]]; + auto slices = traceDataCache_->GetInternalSlicesData(); + slices->SetDuration(lastRow, timestamp); + streamFilters_->argsFilter_->AppendArgs(args, binderQueue_[pid]); + argsToSliceQueue_.erase(binderQueue_[pid]); + + binderQueue_.erase(pid); + InternalTid internalTid = streamFilters_->processFilter_->UpdateOrCreateThread(timestamp, pid); + + const auto& stack = sliceStackMap_[internalTid]; + if (stack.empty()) { + TS_LOGE("a slice end do not match a slice start event"); + callEventDisMatchCount++; + return false; + } + sliceStackMap_[internalTid].pop_back(); + return true; +} + +void SliceFilter::StartAsyncSlice(uint64_t timestamp, + uint32_t pid, + uint32_t threadGroupId, + int64_t cookie, + DataIndex nameIndex) +{ + InternalPid internalPid = streamFilters_->processFilter_->GetOrCreateInternalPid(timestamp, threadGroupId); + auto lastFilterId = asyncEventMap_.Find(internalPid, cookie, nameIndex); + auto slices = traceDataCache_->GetInternalSlicesData(); + if (lastFilterId != INVALID_UINT64) { + asyncEventDisMatchCount++; + FinishAsyncSlice(timestamp, pid, threadGroupId, cookie, nameIndex); + } + asyncEventSize_++; + // a pid, cookie and function name determain a callstack + asyncEventMap_.Insert(internalPid, cookie, nameIndex, asyncEventSize_); + // the IDE need a depth to paint call slice in different position of the canvas, the depth of async call + // do not mean the parent-to-child relationship, it is different from no-async call + uint8_t depth = 0; + if (asyncNoEndingEventMap_.find(internalPid) == asyncNoEndingEventMap_.end()) { + depth = 0; + asyncNoEndingEventMap_.insert(std::make_pair(internalPid, 1)); + } else { + depth = asyncNoEndingEventMap_.at(internalPid); + asyncNoEndingEventMap_.at(internalPid)++; + } + size_t index = slices->AppendInternalAsyncSlice(timestamp, 0, internalPid, INVALID_UINT64, nameIndex, depth, cookie, + std::nullopt); + asyncEventFilterMap_.insert(std::make_pair(asyncEventSize_, AsyncEvent{timestamp, index})); +} + +void SliceFilter::FinishAsyncSlice(uint64_t timestamp, + uint32_t pid, + uint32_t threadGroupId, + int64_t cookie, + DataIndex nameIndex) +{ + UNUSED(pid); + InternalPid internalPid = streamFilters_->processFilter_->GetOrCreateInternalPid(timestamp, threadGroupId); + auto lastFilterId = asyncEventMap_.Find(internalPid, cookie, nameIndex); + auto slices = traceDataCache_->GetInternalSlicesData(); + if (lastFilterId == INVALID_UINT64) { // if failed + asyncEventDisMatchCount++; + return; + } + if (asyncEventFilterMap_.find(lastFilterId) == asyncEventFilterMap_.end()) { + TS_LOGE("logic error"); + asyncEventDisMatchCount++; + return; + } + // update timestamp + asyncEventFilterMap_.at(lastFilterId).timestamp = timestamp; + slices->SetDuration(asyncEventFilterMap_.at(lastFilterId).row, timestamp); + asyncEventFilterMap_.erase(lastFilterId); + asyncEventMap_.Erase(internalPid, cookie, nameIndex); + + if (asyncNoEndingEventMap_.find(internalPid) == asyncNoEndingEventMap_.end()) { + asyncNoEndingEventMap_.insert(std::make_pair(internalPid, 1)); + } else { + asyncNoEndingEventMap_.at(internalPid)--; + if (!asyncNoEndingEventMap_.at(internalPid)) { + asyncNoEndingEventMap_.erase(internalPid); + } + } +} + +bool SliceFilter::BeginSliceInternal(const SliceData& sliceData) +{ + // the call stack belongs to thread, so we keep a call-tree for the thread + auto sliceStack = &sliceStackMap_[sliceData.internalTid]; // this can be a empty call, but it does not matter + auto slices = traceDataCache_->GetInternalSlicesData(); + if (sliceStack->size() >= std::numeric_limits::max()) { + TS_LOGW("stack depth out of range."); + } + const uint8_t depth = static_cast(sliceStack->size()); + std::optional parentId = std::nullopt; + if (depth != 0) { + size_t lastDepth = sliceStack->back(); + parentId = std::make_optional(slices->IdsData()[lastDepth]); // get the depth here + } + + size_t index = slices->AppendInternalSlice(sliceData.timestamp, sliceData.duration, sliceData.internalTid, + sliceData.cat, sliceData.name, depth, parentId); + sliceStack->push_back(index); + return true; +} + +bool SliceFilter::EndSlice(uint64_t timestamp, uint32_t pid, uint32_t threadGroupId) +{ + InternalTid internalTid = INVALID_UTID; + if (threadGroupId) { + auto actThreadGroupIdIter = pidTothreadGroupId_.find(pid); + if (actThreadGroupIdIter == pidTothreadGroupId_.end()) { + callEventDisMatchCount++; + return false; + } + uint32_t actThreadGroupId = actThreadGroupIdIter->second; + if (threadGroupId != actThreadGroupId) { + TS_LOGD("pid %u mismatched thread group id %u", pid, actThreadGroupId); + } + internalTid = streamFilters_->processFilter_->GetOrCreateThreadWithPid(pid, actThreadGroupId); + } else { + internalTid = streamFilters_->processFilter_->GetOrCreateThreadWithPid(pid, 0); + } + + const auto& stack = sliceStackMap_[internalTid]; + if (stack.empty()) { + TS_LOGW("a slice end do not match a slice start event"); + callEventDisMatchCount++; + return false; + } + + auto slices = traceDataCache_->GetInternalSlicesData(); + size_t index = stack.back(); + slices->SetDuration(index, timestamp); + sliceStackMap_[internalTid].pop_back(); + // update dur of parent slice maybe + auto parentId = slices->ParentIdData()[index]; + if (parentId.has_value()) { + slices->SetDuration(parentId.value(), timestamp); + } + return true; +} + +uint64_t SliceFilter::GenHashByStack(const StackOfSlices& sliceStack) const +{ + std::string hashStr; + const auto& sliceSet = traceDataCache_->GetConstInternalSlicesData(); + for (size_t i = 0; i < sliceStack.size(); i++) { + size_t index = sliceStack[i]; + hashStr += "cat"; + hashStr += std::to_string(sliceSet.CatsData()[index]); + hashStr += "name"; + hashStr += std::to_string(sliceSet.NamesData()[index]); + } + + const uint64_t stackHashMask = uint64_t(-1) >> 1; + return (std::hash{}(hashStr)) & stackHashMask; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/filter/slice_filter.h b/host/trace_streamer/src/filter/slice_filter.h new file mode 100644 index 0000000..c2cb7ad --- /dev/null +++ b/host/trace_streamer/src/filter/slice_filter.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SLICE_FILTER_H +#define SLICE_FILTER_H + +#include +#include +#include "args_set.h" +#include "filter_base.h" +#include "trace_data_cache.h" +#include "trace_streamer_filters.h" +#include "triple_map.h" +#include "ts_common.h" + +namespace SysTuning { +namespace TraceStreamer { +struct SliceData { + uint64_t timestamp; + uint64_t duration; + InternalTid internalTid; + DataIndex cat; + DataIndex name; +}; +struct AsyncEvent { + uint64_t timestamp; + size_t row; +}; +class SliceFilter : private FilterBase { +public: + SliceFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter); + ~SliceFilter() override; + + bool BeginSlice(uint64_t timestamp, uint32_t pid, uint32_t threadGroupId, DataIndex cat, DataIndex nameIndex); + size_t BeginBinder(uint64_t timestamp, uint32_t pid, DataIndex cat, DataIndex nameIndex, ArgsSet args = ArgsSet()); + size_t BeginAsyncBinder(uint64_t timestamp, uint32_t pid, DataIndex cat, DataIndex nameIndex, + ArgsSet args = ArgsSet()); + bool EndBinder(uint64_t timestamp, uint32_t pid, DataIndex category = {}, DataIndex name = {}, ArgsSet args = {}); + bool EndSlice(uint64_t timestamp, uint32_t pid, uint32_t threadGroupId); + void StartAsyncSlice(uint64_t timestamp, uint32_t pid, uint32_t threadGroupId, int64_t cookie, DataIndex nameIndex); + void + FinishAsyncSlice(uint64_t timestamp, uint32_t pid, uint32_t threadGroupId, int64_t cookie, DataIndex nameIndex); + uint32_t AddArgs(FilterId, DataIndex key1, DataIndex key2, ArgsSet& args); + void IrqHandlerEntry(uint64_t timestamp, uint32_t cpu, DataIndex catalog, DataIndex nameIndex); + void IrqHandlerExit(uint64_t timestamp, uint32_t cpu, ArgsSet args); + void SoftIrqEntry(uint64_t timestamp, uint32_t cpu, DataIndex catalog, DataIndex nameIndex); + void SoftIrqExit(uint64_t timestamp, uint32_t cpu, ArgsSet args); +private: + using StackOfSlices = std::vector; + uint64_t GenHashByStack(const StackOfSlices& sliceStack) const; + bool BeginSliceInternal(const SliceData& sliceData); +private: + // The parameter list is tid, cookid, functionName, asyncCallId. + TripleMap asyncEventMap_; + // this is only used to calc the layer of the async event in same time range + std::map asyncNoEndingEventMap_ = {}; + // irq map, key1 is cpu, key2 + struct IrqRecords { + uint64_t ts; + size_t row; + }; + std::unordered_map irqEventMap_ = {}; + // irq map, key1 is cpu, key2 + std::unordered_map softIrqEventMap_ = {}; + std::map asyncEventFilterMap_ = {}; + std::unordered_map sliceStackMap_ = {}; + std::unordered_map binderStackMap_ = {}; + std::unordered_map pidTothreadGroupId_ = {}; + uint64_t asyncEventSize_ = 0; + uint64_t asyncEventDisMatchCount = 0; + uint64_t callEventDisMatchCount = 0; + std::unordered_map binderQueue_ = {}; + std::unordered_map argsToSliceQueue_ = {}; + struct SliceInfo { + uint32_t row; + ArgsSet args_tracker; + }; + std::unordered_map> argsSet_ = {}; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // SLICE_FILTER_H diff --git a/host/trace_streamer/src/filter/stat_filter.cpp b/host/trace_streamer/src/filter/stat_filter.cpp new file mode 100644 index 0000000..8afbb22 --- /dev/null +++ b/host/trace_streamer/src/filter/stat_filter.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stat_filter.h" +#include "filter_filter.h" +#include "log.h" +#include "ts_common.h" + +namespace SysTuning { +namespace TraceStreamer { +StatFilter::StatFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter) : FilterBase(dataCache, filter) {} + +StatFilter::~StatFilter() {} + +void StatFilter::IncreaseStat(SupportedTraceEventType eventType, StatType type) +{ + traceDataCache_->GetStatAndInfo()->IncreaseStat(eventType, type); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/filter/stat_filter.h b/host/trace_streamer/src/filter/stat_filter.h new file mode 100644 index 0000000..2f05116 --- /dev/null +++ b/host/trace_streamer/src/filter/stat_filter.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef STAT_FILTER_H +#define STAT_FILTER_H + +#include +#include +#include + +#include "double_map.h" +#include "filter_base.h" +#include "trace_data_cache.h" +#include "trace_streamer_config.h" +#include "trace_streamer_filters.h" + +namespace SysTuning { +namespace TraceStreamer { +using namespace SysTuning::TraceCfg; +class StatFilter : private FilterBase { +public: + StatFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter); + StatFilter(const StatFilter&) = delete; + StatFilter& operator=(const StatFilter&) = delete; + ~StatFilter() override; + void IncreaseStat(SupportedTraceEventType eventType, StatType type); +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // THREAD_MEASURE_FILTER_H diff --git a/host/trace_streamer/src/filter/symbols_filter.cpp b/host/trace_streamer/src/filter/symbols_filter.cpp new file mode 100644 index 0000000..2772725 --- /dev/null +++ b/host/trace_streamer/src/filter/symbols_filter.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "symbols_filter.h" +#include +#include +#include + +#include "log.h" +#include "measure_filter.h" +#include "process_filter.h" +#include "ts_common.h" + +namespace SysTuning { +namespace TraceStreamer { +SymbolsFilter::SymbolsFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter) + : FilterBase(dataCache, filter) +{ +} + +SymbolsFilter::~SymbolsFilter() = default; +void SymbolsFilter::RegisterFunc(uint64_t addr, DataIndex funcNameDictIndex) +{ + if (symbolsMap_.find(addr) == symbolsMap_.end()) { + symbolsMap_.insert(std::make_pair(addr, funcNameDictIndex)); + traceDataCache_->GetSymbolsData()->InsertSymbol(funcNameDictIndex, addr); + } else { + symbolsMap_.at(addr) = funcNameDictIndex; + } +} + +const DataIndex& SymbolsFilter::GetFunc(uint64_t addr) const +{ + if (symbolsMap_.find(addr) == symbolsMap_.end()) { + auto lastAddr = symbolsMap_.lower_bound(addr); + if (lastAddr == symbolsMap_.end()) { + return INVALID_UINT64; + } + return lastAddr->second; + } else { + return symbolsMap_.at(addr); + } +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/filter/symbols_filter.h b/host/trace_streamer/src/filter/symbols_filter.h new file mode 100644 index 0000000..4814c86 --- /dev/null +++ b/host/trace_streamer/src/filter/symbols_filter.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SYMBOLS_FILTER_H +#define SYMBOLS_FILTER_H + +#include +#include "filter_base.h" +#include "trace_data_cache.h" +#include "trace_streamer_filters.h" + +namespace SysTuning { +namespace TraceStreamer { +class SymbolsFilter : private FilterBase { +public: + SymbolsFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter); + ~SymbolsFilter() override; + + void RegisterFunc(uint64_t addr, DataIndex funcNameDictIndex); + + const DataIndex& GetFunc(uint64_t addr) const; +private: + std::map symbolsMap_ = {}; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // SLICE_FILTER_H diff --git a/host/trace_streamer/src/filter/system_event_measure_filter.cpp b/host/trace_streamer/src/filter/system_event_measure_filter.cpp new file mode 100644 index 0000000..7f79dea --- /dev/null +++ b/host/trace_streamer/src/filter/system_event_measure_filter.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "system_event_measure_filter.h" +#include "filter_filter.h" +#include "log.h" +#include "ts_common.h" + +namespace SysTuning { +namespace TraceStreamer { +SystemEventMeasureFilter::SystemEventMeasureFilter(TraceDataCache* dataCache, + const TraceStreamerFilters* filter, + SysEventType e) + : FilterBase(dataCache, filter), filterType_(e) +{ +} + +SystemEventMeasureFilter::~SystemEventMeasureFilter() {} + +void SystemEventMeasureFilter::AppendNewMeasureData(DataIndex nameIndex, uint64_t timestamp, int64_t value) +{ + auto filterId = GetOrCreateFilterId(nameIndex); + traceDataCache_->GetMeasureData()->AppendMeasureData(0, timestamp, value, filterId); +} +uint32_t SystemEventMeasureFilter::GetOrCreateFilterId(DataIndex nameIndex) +{ + auto filterId = tidStreamIdFilterIdMap_.find(nameIndex); + if (filterId != tidStreamIdFilterIdMap_.end()) { + return static_cast((tidStreamIdFilterIdMap_.at(nameIndex))); + } + + uint32_t newFilterId = streamFilters_->filterFilter_->AddFilter( + filterTypeValue.at(filterType_), traceDataCache_->GetDataFromDict(nameIndex), INVALID_UINT64); + AddCertainFilterId(nameIndex, newFilterId); + return newFilterId; +} + +void SystemEventMeasureFilter::AddCertainFilterId(DataIndex nameIndex, uint64_t filterId) +{ + tidStreamIdFilterIdMap_.insert(std::make_pair(nameIndex, filterId)); + + if (filterType_ == E_SYS_MEMORY_FILTER) { + traceDataCache_->GetSysMeasureFilterData()->AppendNewFilter(filterId, sysMemoryFilterId_, + static_cast(nameIndex)); + } else if (filterType_ == E_SYS_VIRTUAL_MEMORY_FILTER) { + traceDataCache_->GetSysMeasureFilterData()->AppendNewFilter(filterId, sysVMemoryFilterId_, + static_cast(nameIndex)); + } +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/filter/system_event_measure_filter.h b/host/trace_streamer/src/filter/system_event_measure_filter.h new file mode 100644 index 0000000..8baf4a6 --- /dev/null +++ b/host/trace_streamer/src/filter/system_event_measure_filter.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SYS_EVENT_MEASURE_FILTER_H +#define SYS_EVENT_MEASURE_FILTER_H + +#include +#include +#include + +#include "double_map.h" +#include "filter_base.h" +#include "trace_data_cache.h" +#include "trace_streamer_filters.h" + +namespace SysTuning { +namespace TraceStreamer { +enum SysEventType { E_SYS_MEMORY_FILTER, E_SYS_VIRTUAL_MEMORY_FILTER }; + +class SystemEventMeasureFilter : private FilterBase { +public: + SystemEventMeasureFilter(TraceDataCache* dataCache, const TraceStreamerFilters* filter, SysEventType); + SystemEventMeasureFilter(const SystemEventMeasureFilter&) = delete; + SystemEventMeasureFilter& operator=(const SystemEventMeasureFilter&) = delete; + ~SystemEventMeasureFilter() override; + void AppendNewMeasureData(DataIndex nameIndex, uint64_t timestamp, int64_t value); + +private: + uint32_t GetOrCreateFilterId(DataIndex nameIndex); + void AddCertainFilterId(DataIndex nameIndex, uint64_t filterId); + std::map tidStreamIdFilterIdMap_ = {}; + SysEventType filterType_; + + const std::map filterTypeValue = { + {E_SYS_MEMORY_FILTER, "sys_memory_filter"}, + {E_SYS_VIRTUAL_MEMORY_FILTER, "sys_virtual_memory_filter"}}; + const DataIndex sysMemoryFilterId_ = traceDataCache_->GetDataIndex("sys_memory_filter"); + const DataIndex sysVMemoryFilterId_ = traceDataCache_->GetDataIndex("sys_virtual_memory_filter"); +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // SYS_EVENT_MEASURE_FILTER_H diff --git a/host/trace_streamer/src/include/BUILD.gn b/host/trace_streamer/src/include/BUILD.gn new file mode 100644 index 0000000..e0f6aa7 --- /dev/null +++ b/host/trace_streamer/src/include/BUILD.gn @@ -0,0 +1,26 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import("//build/ohos.gni") +ohos_source_set("ibase") { + sources = [ + "codec_cov.h", + "file.h", + "log.h", + "parting_string.h", + "string_to_numerical.h", + ] + include_dirs = [] + public_deps = [] + deps = [] + sources += [ "/usr/x86_64-w64-mingw32/include/windows.h" ] +} diff --git a/host/trace_streamer/src/include/codec_cov.h b/host/trace_streamer/src/include/codec_cov.h new file mode 100644 index 0000000..0bc0cce --- /dev/null +++ b/host/trace_streamer/src/include/codec_cov.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDE_TUNING_BASE_CODEC_COV_H_ +#define INCLUDE_TUNING_BASE_CODEC_COV_H_ + +#include + +namespace SysTuning { +namespace base { +int PreNum(unsigned char byte); + +bool IsUTF8(const uint8_t* data, int len); + +bool IsGBK(const uint8_t* data, int len); + +typedef enum { GBK, UTF8, UNKOWN } CODING; + +CODING GetCoding(const uint8_t* data, int len); + +#ifdef _WIN32 +std::string GbkToUtf8(const char* srcStr); +#endif +} // namespace base +} // namespace SysTuning + +#endif diff --git a/host/trace_streamer/src/include/file.h b/host/trace_streamer/src/include/file.h new file mode 100644 index 0000000..97be566 --- /dev/null +++ b/host/trace_streamer/src/include/file.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDE_TUNING_BASE_FILE_UTILS_H_ +#define INCLUDE_TUNING_BASE_FILE_UTILS_H_ + +#include + +namespace SysTuning { +namespace base { +constexpr uint32_t kFileModeInvalid = 0xFFFFFFFF; +enum TraceParserStatus { + TRACE_PARSER_NORMAL = 0, + TRACE_PARSER_FILE_TYPE_ERROR = 1, + TRACE_PARSE_ERROR = 2, + TRACE_PARSER_ABNORMAL = 3 +}; + +void SetAnalysisResult(TraceParserStatus stat); + +TraceParserStatus GetAnalysisResult(); + +ssize_t Read(int fd, uint8_t* dst, size_t dstSize); + +int OpenFile(const std::string& path, int flags, uint32_t mode = kFileModeInvalid); + +std::string GetExecutionDirectoryPath(); +} // namespace base +} // namespace SysTuning +#endif // INCLUDE_TUNING_BASE_FILE_UTILS_H_ diff --git a/host/trace_streamer/src/include/log.h b/host/trace_streamer/src/include/log.h new file mode 100644 index 0000000..fef94e6 --- /dev/null +++ b/host/trace_streamer/src/include/log.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDE_TS_BASE_LOGGING_H_ +#define INCLUDE_TS_BASE_LOGGING_H_ + +#include +#include + +// namespace SysTuning { +// namespace base { +#define TS_CRASH \ + do { \ + __builtin_trap(); \ + __builtin_unreachable(); \ + } while (0) +enum LogLevel {LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL}; +const enum LogLevel g_currentLogLevel = LOG_DEBUG; +extern bool g_cleanMode; +#define LOGWITHLEVEL(level, motify, fmt, ...) \ + do { \ + if (level >= g_currentLogLevel) { \ + if (!g_cleanMode) { \ + fprintf(stdout, "[-%c][%s][%d]: " fmt "\n", motify, __FUNCTION__, \ + __LINE__, ##__VA_ARGS__); \ + } \ + if (level == LOG_FATAL) { \ + TS_CRASH; \ + } \ + } \ + } while (0) +#define TS_LOGE(fmt, ...) LOGWITHLEVEL(LOG_ERROR, 'E', fmt, ##__VA_ARGS__) +#define TS_LOGF(fmt, ...) LOGWITHLEVEL(LOG_FATAL, 'F', fmt, ##__VA_ARGS__) +#define TS_LOGI(fmt, ...) LOGWITHLEVEL(LOG_INFO, 'I', fmt, ##__VA_ARGS__) +#ifdef NDEBUG +#define TS_LOGD(format, ...) +#define TS_LOGW(format, ...) +#define TS_ASSERT(x) +#else +#define TS_LOGD(fmt, ...) LOGWITHLEVEL(LOG_DEBUG, 'D', fmt, ##__VA_ARGS__) +#define TS_LOGW(fmt, ...) LOGWITHLEVEL(LOG_WARN, 'W', fmt, ##__VA_ARGS__) + +#define TS_ASSERT(x) \ + do { \ + if (!(x)) { \ + TS_CRASH; \ + } \ + } while (0) + +#endif +// } // namespace base +// } // namespace SysTuning + +#endif // INCLUDE_TS_BASE_LOGGING_H_ diff --git a/host/trace_streamer/src/include/parting_string.h b/host/trace_streamer/src/include/parting_string.h new file mode 100644 index 0000000..91a56bf --- /dev/null +++ b/host/trace_streamer/src/include/parting_string.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDE_BASE_STRING_SPLITTER_H_ +#define INCLUDE_BASE_STRING_SPLITTER_H_ + +#include + +namespace SysTuning { +namespace base { +class PartingString { +public: + PartingString(std::string, char delimiter); + + bool Next(); + + char* GetCur() + { + return cur_; + } + +private: + PartingString(const PartingString&) = delete; + PartingString& operator=(const PartingString&) = delete; + + std::string str_; + char* cur_ = nullptr; + std::string::iterator begin_; + std::string::iterator end_; + const char delimiter_; +}; +} // namespace base +} // namespace SysTuning + +#endif diff --git a/host/trace_streamer/src/include/string_to_numerical.h b/host/trace_streamer/src/include/string_to_numerical.h new file mode 100644 index 0000000..3340f50 --- /dev/null +++ b/host/trace_streamer/src/include/string_to_numerical.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDE_BASE_STRING_TO_NUMERICAL_H_ +#define INCLUDE_BASE_STRING_TO_NUMERICAL_H_ + +#include +#include +#include + +namespace SysTuning { +namespace base { +enum IntegerRadixType { + INTEGER_RADIX_TYPE_DEC = 10, + INTEGER_RADIX_TYPE_HEX = 16 +}; +inline std::optional StrToUInt32(const std::string& str, int base = INTEGER_RADIX_TYPE_DEC) +{ + if (!str.empty()) { + uint32_t value = static_cast(std::stoul(str, nullptr, base)); + return std::make_optional(value); + } + + return std::nullopt; +} + +inline std::string number(int value, int base = INTEGER_RADIX_TYPE_DEC) +{ + std::stringstream ss; + if (base == INTEGER_RADIX_TYPE_DEC) { + ss << std::oct << value; + } else if (base == INTEGER_RADIX_TYPE_HEX) { + ss << std::hex << value; + } + return ss.str(); +} + +inline std::optional StrToInt32(const std::string& str, int base = INTEGER_RADIX_TYPE_DEC) +{ + if (!str.empty()) { + int32_t value = static_cast(std::stol(str, nullptr, base)); + return std::make_optional(value); + } + + return std::nullopt; +} + +inline std::optional StrToUInt64(const std::string& str, int base = INTEGER_RADIX_TYPE_DEC) +{ + if (!str.empty()) { + uint64_t value = static_cast(std::stoull(str, nullptr, base)); + return std::make_optional(value); + } + + return std::nullopt; +} + +inline std::optional StrToInt64(const std::string& str, int base = INTEGER_RADIX_TYPE_DEC) +{ + if (!str.empty()) { + int64_t value = static_cast(std::stoll(str, nullptr, base)); + return std::make_optional(value); + } + return std::nullopt; +} + +inline std::optional StrToDouble(const std::string& str) +{ + if (!str.empty()) { + double value = std::stod(str); + return std::make_optional(value); + } + + return std::nullopt; +} +} // namespace base +} // namespace SysTuning + +#endif // INCLUDE_TUNING_EXT_BASE_STRING_UTILS_H_ diff --git a/host/trace_streamer/src/main.cpp b/host/trace_streamer/src/main.cpp new file mode 100644 index 0000000..8b16b5a --- /dev/null +++ b/host/trace_streamer/src/main.cpp @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "codec_cov.h" +#include "file.h" +#include "filter/slice_filter.h" +#include "http_server.h" +#include "log.h" +#include "parser/bytrace_parser/bytrace_event_parser.h" +#include "parser/bytrace_parser/bytrace_parser.h" +#include "parting_string.h" +#include "rpc_server.h" + +#include "thread_state.h" +#include "trace_streamer/trace_streamer_selector.h" +#include "trace_streamer_filters.h" +using namespace SysTuning::TraceStreamer; +using namespace SysTuning; +namespace SysTuning { +namespace TraceStreamer { +using namespace SysTuning::TraceStreamer; +using namespace SysTuning::base; +constexpr size_t G_CHUNK_SIZE = 1024 * 1024; +constexpr int G_MIN_PARAM_NUM = 2; +constexpr size_t G_FILE_PERMISSION = 664; +size_t g_loadSize = 0; +const char* TRACE_STREAM_VERSION = "2.3.118"; // version +const char* TRACE_STREAM_PUBLISHVERSION = "2022/3/29"; // publish datetime +void ExportStatusToLog(const std::string& dbPath, TraceParserStatus status) +{ + std::string path = dbPath + ".ohos.ts"; + std::ofstream out(path, std::ios_base::trunc); + out << (std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch())) + .count() + << ":" << status << std::endl; + using std::chrono::system_clock; + + system_clock::time_point today = system_clock::now(); + + std::time_t tt = system_clock::to_time_t(today); + out << "last running time is: " << ctime(&tt); + out << "last running status is: " << status; + out.close(); +} +void ShowHelpInfo(const char* argv) +{ + TS_LOGI( + "trace analyze tool, it can transfer a bytrace/htrace file into a " + "SQLite database and save result to a local file trace_streamer.log.\n" + "Usage: %s FILE -e sqlite_out.pb\n" + " or %s FILE -c\n" + "Options:\n" + " -e transfer a bytrace file into a SQLiteBased DB. with -nm to except meta table\n" + " -c command line mode.\n" + " -h start HTTP server.\n" + " -p Specify the port of HTTP server, default is 9001.\n" + " -i show information.\n" + " -v show version.", + argv, argv); +} +void PrintInformation() +{ + TraceStreamerConfig cfg; + cfg.PrintInfo(); +} +void PrintVersion() +{ + fprintf(stderr, "version %s\n", TRACE_STREAM_VERSION); +} + +bool ReadAndParser(SysTuning::TraceStreamer::TraceStreamerSelector& ta, int fd) +{ + auto startTime = + (std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch())) + .count(); + g_loadSize = 0; + while (true) { + std::unique_ptr buf = std::make_unique(G_CHUNK_SIZE); + auto rsize = Read(fd, buf.get(), G_CHUNK_SIZE); + if (rsize == 0) { + break; + } + + if (rsize < 0) { + TS_LOGE("Reading trace file failed (errno: %d, %s)", errno, strerror(errno)); + return false; + } + g_loadSize += rsize; + if (!ta.ParseTraceDataSegment(std::move(buf), static_cast(rsize))) { + return false; + }; + printf("\rLoadingFile:\t%.2f MB\r", static_cast(g_loadSize) / 1E6); + } + ta.WaitForParserEnd(); + auto endTime = + (std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch())) + .count(); + fprintf(stdout, "\nParserDuration:\t%u ms\n", static_cast(endTime - startTime)); + fprintf(stdout, "ParserSpeed:\t%.2f MB/s\n", (g_loadSize / (endTime - startTime) / 1E3)); + return true; +} +int OpenAndParserFile(TraceStreamerSelector& ts, const std::string& traceFilePath) +{ + int fd(OpenFile(traceFilePath, O_RDONLY, G_FILE_PERMISSION)); + if (fd < 0) { + TS_LOGE("%s does not exist", traceFilePath.c_str()); + SetAnalysisResult(TRACE_PARSER_ABNORMAL); + return 1; + } + if (!ReadAndParser(ts, fd)) { + close(fd); + SetAnalysisResult(TRACE_PARSER_ABNORMAL); + return 1; + } + MetaData* metaData = ts.GetMetaData(); + + std::string fileNameTmp = traceFilePath; +#ifdef _WIN32 + if (!base::GetCoding(reinterpret_cast(fileNameTmp.c_str()), fileNameTmp.length())) { + fileNameTmp = base::GbkToUtf8(fileNameTmp.c_str()); + } +#endif + metaData->SetSourceFileName(fileNameTmp); + metaData->SetTraceType((ts.DataType() == TRACE_FILETYPE_H_TRACE) ? "proto-based-trace" : "txt-based-trace"); + + close(fd); + return 0; +} +int ExportDatabase(TraceStreamerSelector& ts, const std::string& sqliteFilePath) +{ + auto startTime = + (std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch())) + .count(); + if (!sqliteFilePath.empty()) { + MetaData* metaData = ts.GetMetaData(); + std::string fileNameTmp = sqliteFilePath; +#ifdef _WIN32 + if (!base::GetCoding(reinterpret_cast(fileNameTmp.c_str()), fileNameTmp.length())) { + fileNameTmp = base::GbkToUtf8(fileNameTmp.c_str()); + } +#endif + metaData->SetOutputFileName(fileNameTmp); + metaData->SetParserToolVersion(TRACE_STREAM_VERSION); + metaData->SetParserToolPublishDateTime(TRACE_STREAM_PUBLISHVERSION); + metaData->SetTraceDataSize(g_loadSize); + fprintf(stdout, "ExportDatabase begin...\n"); + if (ts.ExportDatabase(sqliteFilePath)) { + fprintf(stdout, "ExportDatabase failed\n"); + ExportStatusToLog(sqliteFilePath, TRACE_PARSER_ABNORMAL); + return 1; + } + fprintf(stdout, "ExportDatabase end\n"); + } + auto endTime = + (std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch())) + .count(); + endTime += 1; // for any exception of endTime == startTime + fprintf(stdout, "ExportDuration:\t%u ms\n", static_cast(endTime - startTime)); + fprintf(stdout, "ExportSpeed:\t%.2f MB/s\n", (g_loadSize / (endTime - startTime)) / 1E3); + return 0; +} + +struct HttpOption { + bool enable = false; + int port = 9001; +}; + +int CheckArgs(int argc, + char** argv, + bool& interactiveState, + bool& exportMetaTable, + std::string& traceFilePath, + std::string& sqliteFilePath, + HttpOption& httpOption) +{ + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-e")) { + if (++i == argc) { + ShowHelpInfo(argv[0]); + return 1; + } + sqliteFilePath = std::string(argv[i]); + continue; + } else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--command")) { + interactiveState = true; + continue; + } else if (!strcmp(argv[i], "-i") || !strcmp(argv[i], "--info")) { + PrintInformation(); + } else if (!strcmp(argv[i], "-nm") || !strcmp(argv[i], "--nometa")) { + exportMetaTable = false; + continue; + } else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--v") || !strcmp(argv[i], "-version") || + !strcmp(argv[i], "--version")) { + PrintVersion(); + return 1; + } else if (!strcmp(argv[i], "-h")) { + httpOption.enable = true; + continue; + } else if (!strcmp(argv[i], "-p")) { + if (++i == argc) { + ShowHelpInfo(argv[0]); + return 1; + } + httpOption.port = std::stoi(argv[i]); + continue; + } + traceFilePath = std::string(argv[i]); + } + if ((traceFilePath.empty() || (!interactiveState && sqliteFilePath.empty())) + && !httpOption.enable) { + ShowHelpInfo(argv[0]); + return 1; + } + return 0; +} +} // namespace TraceStreamer +} // namespace SysTuning + +int main(int argc, char** argv) +{ + if (argc < G_MIN_PARAM_NUM) { + ShowHelpInfo(argv[0]); + return 1; + } + std::string traceFilePath; + std::string sqliteFilePath; + bool interactiveState = false; + bool exportMetaTable = true; + HttpOption httpOption; + int ret = CheckArgs(argc, argv, interactiveState, exportMetaTable, traceFilePath, sqliteFilePath, httpOption); + if (ret) { + if (!sqliteFilePath.empty()) { + ExportStatusToLog(sqliteFilePath, GetAnalysisResult()); + } + return 0; + } + + if (httpOption.enable) { + RpcServer rpcServer; + HttpServer httpServer; + httpServer.RegisterRpcFunction(&rpcServer); + httpServer.Run(httpOption.port); + return 0; + } + TraceStreamerSelector ts; + ts.EnableMetaTable(exportMetaTable); + if (OpenAndParserFile(ts, traceFilePath)) { + if (!sqliteFilePath.empty()) { + ExportStatusToLog(sqliteFilePath, GetAnalysisResult()); + } + return 1; + } + if (interactiveState) { + ts.SearchData(); + return 0; + } + if (ExportDatabase(ts, sqliteFilePath)) { + ExportStatusToLog(sqliteFilePath, GetAnalysisResult()); + return 1; + } + if (!sqliteFilePath.empty()) { + ExportStatusToLog(sqliteFilePath, GetAnalysisResult()); + } + return 0; +} diff --git a/host/trace_streamer/src/multi_platform/BUILD.gn b/host/trace_streamer/src/multi_platform/BUILD.gn new file mode 100644 index 0000000..35da1af --- /dev/null +++ b/host/trace_streamer/src/multi_platform/BUILD.gn @@ -0,0 +1,305 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import("//build/ohos.gni") +import("../ts.gni") +if (use_wasm) { +} else { +} +if (use_wasm) { + source_set("proto_services_cpp") { + sources = [ + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/compaction.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/compaction.pb.h", + ] + include_dirs = [ + "//third_party/protobuf/src", + "//src/include", + ] + } +} else { + shared_library("proto_services_cpp") { + sources = [ + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/compaction.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/compaction.pb.h", + ] + include_dirs = [ + "//third_party/protobuf/src", + "//src/include", + ] + } +} +if (use_wasm) { + source_set("ftrace_data_cpp") { + sources = [ + "${OHOS_PROTO_GEN}/services/common_types.pb.cc", + "${OHOS_PROTO_GEN}/services/common_types.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/binder.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/binder.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/block.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/block.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/cgroup.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/cgroup.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/clk.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/clk.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/cpuhp.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/cpuhp.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/dma_fence.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/dma_fence.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/ext4.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/ext4.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/filelock.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/filelock.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/filemap.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/filemap.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/ftrace.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/ftrace.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/ftrace_event.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/ftrace_event.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/gpio.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/gpio.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/i2c.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/i2c.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/ipi.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/ipi.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/irq.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/irq.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/kmem.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/kmem.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/net.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/net.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/oom.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/oom.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/pagemap.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/pagemap.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/power.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/power.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/printk.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/printk.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/raw_syscalls.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/raw_syscalls.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/rcu.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/rcu.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/sched.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/sched.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/signal.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/signal.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/sunrpc.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/sunrpc.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/task.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/task.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/timer.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/timer.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/trace_plugin_result.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/trace_plugin_result.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/v4l2.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/v4l2.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/vmscan.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/vmscan.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/workqueue.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/workqueue.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/writeback.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/writeback.pb.h", + ] + include_dirs = [ + "//third_party/protobuf/src", + "//src/include", + ] + } +} else { + shared_library("ftrace_data_cpp") { + sources = [ + "${OHOS_PROTO_GEN}/services/common_types.pb.cc", + "${OHOS_PROTO_GEN}/services/common_types.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/binder.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/binder.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/block.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/block.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/cgroup.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/cgroup.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/clk.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/clk.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/cpuhp.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/cpuhp.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/dma_fence.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/dma_fence.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/ext4.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/ext4.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/filelock.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/filelock.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/filemap.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/filemap.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/ftrace.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/ftrace.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/ftrace_event.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/ftrace_event.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/gpio.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/gpio.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/i2c.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/i2c.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/ipi.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/ipi.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/irq.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/irq.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/kmem.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/kmem.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/net.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/net.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/oom.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/oom.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/pagemap.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/pagemap.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/power.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/power.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/printk.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/printk.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/raw_syscalls.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/raw_syscalls.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/rcu.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/rcu.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/sched.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/sched.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/signal.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/signal.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/sunrpc.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/sunrpc.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/task.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/task.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/timer.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/timer.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/trace_plugin_result.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/trace_plugin_result.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/v4l2.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/v4l2.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/vmscan.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/vmscan.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/workqueue.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/workqueue.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/writeback.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data/writeback.pb.h", + ] + include_dirs = [ + "//third_party/protobuf/src", + "//src/include", + ] + } +} +if (use_wasm) { + source_set("memory_data_cpp") { + sources = [ + "${OHOS_PROTO_GEN}/types/plugins/memory_data/memory_plugin_common.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/memory_data/memory_plugin_common.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/memory_data/memory_plugin_config.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/memory_data/memory_plugin_config.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/memory_data/memory_plugin_result.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/memory_data/memory_plugin_result.pb.h", + ] + include_dirs = [ + "//third_party/protobuf/src", + "//src/include", + ] + } +} else { + shared_library("memory_data_cpp") { + sources = [ + "${OHOS_PROTO_GEN}/types/plugins/memory_data/memory_plugin_common.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/memory_data/memory_plugin_common.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/memory_data/memory_plugin_config.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/memory_data/memory_plugin_config.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/memory_data/memory_plugin_result.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/memory_data/memory_plugin_result.pb.h", + ] + include_dirs = [ + "//third_party/protobuf/src", + "//src/include", + ] + } +} +if (use_wasm) { + source_set("hilog_data_cpp") { + sources = [ + "${OHOS_PROTO_GEN}/types/plugins/hilog_data/hilog_plugin_result.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/hilog_data/hilog_plugin_result.pb.h", + ] + include_dirs = [ + "//third_party/protobuf/src", + "//src/include", + ] + } +} else { + shared_library("hilog_data_cpp") { + sources = [ + "${OHOS_PROTO_GEN}/types/plugins/hilog_data/hilog_plugin_result.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/hilog_data/hilog_plugin_result.pb.h", + ] + include_dirs = [ + "//third_party/protobuf/src", + "//src/include", + ] + } +} + +if (use_wasm) { + source_set("native_hook_cpp") { + sources = [ + "${OHOS_PROTO_GEN}/types/plugins/native_hook/native_hook_config.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/native_hook/native_hook_config.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/native_hook/native_hook_result.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/native_hook/native_hook_result.pb.h", + ] + include_dirs = [ + "//third_party/protobuf/src", + "//src/include", + ] + } +} else { + shared_library("native_hook_cpp") { + sources = [ + "${OHOS_PROTO_GEN}/types/plugins/native_hook/native_hook_config.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/native_hook/native_hook_config.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/native_hook/native_hook_result.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/native_hook/native_hook_result.pb.h", + ] + include_dirs = [ + "//third_party/protobuf/src", + "//src/include", + ] + } +} + +if (use_wasm) { + source_set("hidump_data_cpp") { + sources = [ + "${OHOS_PROTO_GEN}/types/plugins/hidump_data/hidump_plugin_config.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/hidump_data/hidump_plugin_config.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/hidump_data/hidump_plugin_result.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/hidump_data/hidump_plugin_result.pb.h", + ] + include_dirs = [ + "//third_party/protobuf/src", + "//src/include", + ] + } +} else { + shared_library("hidump_data_cpp") { + sources = [ + "${OHOS_PROTO_GEN}/types/plugins/hidump_data/hidump_plugin_config.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/hidump_data/hidump_plugin_config.pb.h", + "${OHOS_PROTO_GEN}/types/plugins/hidump_data/hidump_plugin_result.pb.cc", + "${OHOS_PROTO_GEN}/types/plugins/hidump_data/hidump_plugin_result.pb.h", + ] + include_dirs = [ + "//third_party/protobuf/src", + "//src/include", + ] + } +} diff --git a/host/trace_streamer/src/multi_platform/global.pri b/host/trace_streamer/src/multi_platform/global.pri new file mode 100644 index 0000000..a1dab5b --- /dev/null +++ b/host/trace_streamer/src/multi_platform/global.pri @@ -0,0 +1,75 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#DEFINES +=_AIX +#DEFINES +=_GLIBCXX_BITS_STD_ABS_H +#DEFINES +=__CORRECT_ISO_CPP_STDLIB_H_PROTO +#DEFINES += __CORRECT_ISO_CPP11_MATH_H_PROTO_FP +#DEFINES +=__CORRECT_ISO_CPP_MATH_H_PROTO +#DEFINES +=_GLIBCXX_USE_C99_LONG_LONG_DYNAMIC + +INCLUDEPATH +=$${ROOTSRCDIR}/include +INCLUDEPATH +=$${ROOTSRCDIR}/ +CONFIG(debug, debug|release){ + BUILDVERSION = _debug + message("debug") +}else{ + message("release") + BUILDVERSION = + DEFINES += NDEBUG +} +contains(QT_ARCH, i386) { + message("qmake" $$TARGET "32-bit") + BIT = x32 +} else { + message("qmake" $$TARGET "64-bit") + BIT = +} +macx{ + PLATFORM = macx + TOOL = + BIT = +} +unix:!macx { + PLATFORM = linux + TOOL = + BIT = +} +win32 { + PLATFORM = windows + TOOL = +} +DESTFOLDER =$${PLATFORM}$${BIT}$${TOOL}$${BUILDVERSION} +islib{ +message("this is for lib") +DESTDIR = $${ROOTSRCDIR}/lib/$${DESTFOLDER} +} else { +message("this is for app") +DESTDIR = $${ROOTSRCDIR}/out/$${DESTFOLDER} +} +unix{ +QMAKE_CXXFLAGS += -BigObj +INCLUDEPATH += $$DESTDIR/gen/build_config +INCLUDEPATH += $$DESTDIR/gen +INCLUDEPATH +=/usr/include/c++/7 +INCLUDEPATH +=/usr/include/x86_64-linux-gnu/ +} else { +INCLUDEPATH += $${GENDIR}/gen/build_config +INCLUDEPATH += $${GENDIR}/gen +DEFINES += WIN32 +QMAKE_CXXFLAGS += -BigObj +staticlib{ +QMAKE_CXXFLAGS += -Ofast -flto +} +} +OBJECTS_DIR = $${ROOTSRCDIR}/tmp_$${TARGET}_$${DESTFOLDER} diff --git a/host/trace_streamer/src/multi_platform/protogen.pri b/host/trace_streamer/src/multi_platform/protogen.pri new file mode 100644 index 0000000..72da7fd --- /dev/null +++ b/host/trace_streamer/src/multi_platform/protogen.pri @@ -0,0 +1,107 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +PROTOGEN = $$PWD/../../third_party/protogen +INCLUDEPATH += $${PROTOGEN}/types/plugins/ftrace_data \ + $${PROTOGEN}/types/plugins/memory_data \ + $${PROTOGEN}/types/plugins/hilog_data \ + $${PROTOGEN}/types/plugins/native_hook \ + $${PROTOGEN}/types/plugins/hidump_data \ + $${PROTOGEN} +SOURCES +=$${PROTOGEN}/services/common_types.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/trace_plugin_result.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/ftrace_event.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/irq.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/vmscan.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/workqueue.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/task.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/power.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/sched.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/filemap.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/i2c.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/kmem.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/block.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/ipi.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/ftrace.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/ext4.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/oom.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/compaction.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/clk.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/cgroup.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/binder.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/signal.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/sunrpc.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/net.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/cpuhp.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/writeback.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/v4l2.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/pagemap.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/dma_fence.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/printk.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/filelock.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/gpio.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/timer.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/raw_syscalls.pb.cc \ + $${PROTOGEN}/types/plugins/ftrace_data/rcu.pb.cc \ + \ + $${PROTOGEN}/types/plugins/memory_data/memory_plugin_common.pb.cc \ + $${PROTOGEN}/types/plugins/memory_data/memory_plugin_config.pb.cc \ + $${PROTOGEN}/types/plugins/memory_data/memory_plugin_result.pb.cc \ + $${PROTOGEN}/types/plugins/hilog_data/hilog_plugin_result.pb.cc \ + $${PROTOGEN}/types/plugins/native_hook/native_hook_result.pb.cc \ + $${PROTOGEN}/types/plugins/native_hook/native_hook_config.pb.cc \ + $${PROTOGEN}/types/plugins/hidump_data/hidump_plugin_result.pb.cc \ + $${PROTOGEN}/types/plugins/hidump_data/hidump_plugin_config.pb.cc + +HEADERS += $${PROTOGEN}/services/common_types.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/trace_plugin_result.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/ftrace_event.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/irq.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/vmscan.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/workqueue.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/task.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/power.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/sched.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/filemap.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/i2c.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/kmem.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/block.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/ipi.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/ftrace.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/ext4.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/oom.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/compaction.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/clk.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/cgroup.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/signal.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/binder.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/net.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/v4l2.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/writeback.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/cpuhp.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/pagemap.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/dma_fence.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/printk.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/filelock.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/gpio.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/timer.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/raw_syscalls.pb.h \ + $${PROTOGEN}/types/plugins/ftrace_data/rcu.pb.h \ + \ + $${PROTOGEN}/types/plugins/memory_data/memory_plugin_common.pb.h \ + $${PROTOGEN}/types/plugins/memory_data/memory_plugin_config.pb.h \ + $${PROTOGEN}/types/plugins/memory_data/memory_plugin_result.pb.h \ + $${PROTOGEN}/types/plugins/hilog_data/hilog_plugin_result.pb.h \ + $${PROTOGEN}/types/plugins/native_hook/native_hook_result.pb.h \ + $${PROTOGEN}/types/plugins/native_hook/native_hook_config.pb.h \ + $${PROTOGEN}/types/plugins/hidump_data/hidump_plugin_result.pb.h \ + $${PROTOGEN}/types/plugins/hidump_data/hidump_plugin_config.pb.h diff --git a/host/trace_streamer/src/parser/bytrace_parser/bytrace_event_parser.cpp b/host/trace_streamer/src/parser/bytrace_parser/bytrace_event_parser.cpp new file mode 100644 index 0000000..8ab3d13 --- /dev/null +++ b/host/trace_streamer/src/parser/bytrace_parser/bytrace_event_parser.cpp @@ -0,0 +1,509 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bytrace_event_parser.h" +#include "binder_filter.h" +#include "cpu_filter.h" +#include "filter_filter.h" +#include "irq_filter.h" +#include "measure_filter.h" +#include "parting_string.h" +#include "process_filter.h" +#include "slice_filter.h" +#include "stat_filter.h" +#include "string_to_numerical.h" +#include "thread_state.h" +#include "ts_common.h" +namespace SysTuning { +namespace TraceStreamer { +namespace { +std::string GetFunctionName(const std::string_view& text, const std::string_view& delimiter) +{ + std::string str(""); + if (delimiter.empty()) { + return str; + } + + std::size_t foundIndex = text.find(delimiter); + if (foundIndex != std::string::npos) { + std::size_t funIndex = foundIndex + delimiter.size(); + str = std::string(text.substr(funIndex, text.size() - funIndex)); + } + return str; +} +} // namespace + +BytraceEventParser::BytraceEventParser(TraceDataCache* dataCache, const TraceStreamerFilters* filter) + : EventParserBase(dataCache, filter), + ioWaitId_(const_cast(dataCache)->GetDataIndex("io_wait")), + workQueueId_(const_cast(dataCache)->GetDataIndex("workqueue")), + schedWakeupId_(const_cast(dataCache)->GetDataIndex("sched_wakeup")), + schedBlockedReasonId_(const_cast(dataCache)->GetDataIndex("sched_blocked_reason")), + printEventParser_(traceDataCache_, streamFilters_) +{ + eventToFunctionMap_ = { + {config_.eventNameMap_.at(TRACE_EVENT_SCHED_SWITCH), + bind(&BytraceEventParser::SchedSwitchEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_TASK_RENAME), + bind(&BytraceEventParser::TaskRenameEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_TASK_NEWTASK), + bind(&BytraceEventParser::TaskNewtaskEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_TRACING_MARK_WRITE), + bind(&BytraceEventParser::TracingMarkWriteEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_SCHED_WAKEUP), + bind(&BytraceEventParser::SchedWakeupEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_SCHED_WAKING), + bind(&BytraceEventParser::SchedWakingEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_CPU_IDLE), + bind(&BytraceEventParser::CpuIdleEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_CPU_FREQUENCY), + bind(&BytraceEventParser::CpuFrequencyEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_WORKQUEUE_EXECUTE_START), + bind(&BytraceEventParser::WorkqueueExecuteStartEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_WORKQUEUE_EXECUTE_END), + bind(&BytraceEventParser::WorkqueueExecuteEndEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_CLOCK_SET_RATE), + bind(&BytraceEventParser::SetRateEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_CLOCK_ENABLE), + bind(&BytraceEventParser::ClockEnableEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_CLOCK_DISABLE), + bind(&BytraceEventParser::ClockDisableEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_REGULATOR_SET_VOLTAGE), + bind(&BytraceEventParser::RegulatorSetVoltageEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_REGULATOR_SET_VOLTAGE_COMPLETE), + bind(&BytraceEventParser::RegulatorSetVoltageCompleteEvent, this, std::placeholders::_1, + std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_REGULATOR_DISABLE), + bind(&BytraceEventParser::RegulatorDisableEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_REGULATOR_DISABLE_COMPLETE), + bind(&BytraceEventParser::RegulatorDisableCompleteEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_IPI_ENTRY), + bind(&BytraceEventParser::IpiEntryEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_IPI_EXIT), + bind(&BytraceEventParser::IpiExitEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_IRQ_HANDLER_ENTRY), + bind(&BytraceEventParser::IrqHandlerEntryEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_IRQ_HANDLER_EXIT), + bind(&BytraceEventParser::IrqHandlerExitEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_SOFTIRQ_RAISE), + bind(&BytraceEventParser::SoftIrqRaiseEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_SOFTIRQ_ENTRY), + bind(&BytraceEventParser::SoftIrqEntryEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_SOFTIRQ_EXIT), + bind(&BytraceEventParser::SoftIrqExitEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_BINDER_TRANSACTION), + bind(&BytraceEventParser::BinderTransaction, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_BINDER_TRANSACTION_RECEIVED), + bind(&BytraceEventParser::BinderTransactionReceived, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_BINDER_TRANSACTION_ALLOC_BUF), + bind(&BytraceEventParser::BinderTransactionAllocBufEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_SCHED_WAKEUP_NEW), + bind(&BytraceEventParser::SchedWakeupEvent, this, std::placeholders::_1, std::placeholders::_2)}, + {config_.eventNameMap_.at(TRACE_EVENT_PROCESS_EXIT), + bind(&BytraceEventParser::ProcessExitEvent, this, std::placeholders::_1, std::placeholders::_2)}}; +} + +bool BytraceEventParser::SchedSwitchEvent(const ArgsMap& args, const BytraceLine& line) const +{ + if (args.empty() || args.size() < MIN_SCHED_ARGS_COUNT) { + TS_LOGD("Failed to parse sched_switch event, no args or args size < 6"); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SCHED_SWITCH, STAT_EVENT_DATA_INVALID); + return false; + } + auto prevCommStr = std::string_view(args.at("prev_comm")); + auto nextCommStr = std::string_view(args.at("next_comm")); + auto prevPrioValue = base::StrToInt32(args.at("prev_prio")); + auto nextPrioValue = base::StrToInt32(args.at("next_prio")); + auto prevPidValue = base::StrToUInt32(args.at("prev_pid")); + auto nextPidValue = base::StrToUInt32(args.at("next_pid")); + if (!(!prevCommStr.empty() && prevPidValue.has_value() && prevPrioValue.has_value() && nextPidValue.has_value() && + nextPrioValue.has_value())) { + TS_LOGD("Failed to parse sched_switch event"); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SCHED_SWITCH, STAT_EVENT_DATA_INVALID); + return false; + } + auto prevStateStr = args.at("prev_state"); + auto threadState = ThreadState(prevStateStr.c_str()); + uint64_t prevState = threadState.State(); + if (!threadState.IsValid()) { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SCHED_SWITCH, STAT_EVENT_DATA_INVALID); + } + auto nextInternalTid = 0; + auto uprevtid = 0; + if (streamFilters_->processFilter_->isThreadNameEmpty(nextPidValue.value())) { + nextInternalTid = + streamFilters_->processFilter_->UpdateOrCreateThreadWithName(line.ts, nextPidValue.value(), nextCommStr); + } else { + nextInternalTid = streamFilters_->processFilter_->UpdateOrCreateThread(line.ts, nextPidValue.value()); + } + if (streamFilters_->processFilter_->isThreadNameEmpty(prevPidValue.value())) { + uprevtid = + streamFilters_->processFilter_->UpdateOrCreateThreadWithName(line.ts, prevPidValue.value(), prevCommStr); + } else { + uprevtid = streamFilters_->processFilter_->UpdateOrCreateThread(line.ts, prevPidValue.value()); + } + streamFilters_->cpuFilter_->InsertSwitchEvent(line.ts, line.cpu, uprevtid, + static_cast(prevPrioValue.value()), prevState, + nextInternalTid, static_cast(nextPrioValue.value())); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SCHED_SWITCH, STAT_EVENT_RECEIVED); + return true; +} + +bool BytraceEventParser::TaskRenameEvent(const ArgsMap& args, const BytraceLine& line) const +{ + auto prevCommStr = std::string_view(args.at("newcomm")); + auto pidValue = base::StrToUInt32(args.at("pid")); + streamFilters_->processFilter_->UpdateOrCreateThreadWithName(line.ts, pidValue.value(), prevCommStr); + return true; +} + +bool BytraceEventParser::TaskNewtaskEvent(const ArgsMap& args, const BytraceLine& line) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_TASK_NEWTASK, STAT_EVENT_RECEIVED); + // the clone flag from txt trace from kernel original is HEX, but when it is converted from proto + // based trace, it will be OCT number, it is not stable, so we decide to ignore it + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_TASK_NEWTASK, STAT_EVENT_NOTSUPPORTED); + return true; +} + +bool BytraceEventParser::TracingMarkWriteEvent(const ArgsMap& args, const BytraceLine& line) const +{ + UNUSED(args); + printEventParser_.ParsePrintEvent(line.ts, line.pid, line.argsStr.c_str()); + return true; +} + +bool BytraceEventParser::SchedWakeupEvent(const ArgsMap& args, const BytraceLine& line) const +{ + if (args.size() < MIN_SCHED_WAKEUP_ARGS_COUNT) { + TS_LOGD("Failed to parse SchedWakeupEvent event, no args or args size < 2"); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SCHED_WAKEUP, STAT_EVENT_DATA_INVALID); + return false; + } + std::optional wakePidValue = base::StrToUInt32(args.at("pid")); + if (!wakePidValue.has_value()) { + TS_LOGD("Failed to convert wake_pid"); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SCHED_WAKEUP, STAT_EVENT_DATA_INVALID); + return false; + } + DataIndex name = traceDataCache_->GetDataIndex(std::string_view("sched_wakeup")); + auto instants = traceDataCache_->GetInstantsData(); + InternalTid internalTid = streamFilters_->processFilter_->UpdateOrCreateThread(line.ts, wakePidValue.value()); + instants->AppendInstantEventData(line.ts, name, internalTid); + std::optional targetCpu = base::StrToUInt32(args.at("target_cpu")); + if (targetCpu.has_value()) { + traceDataCache_->GetRawData()->AppendRawData(0, line.ts, RAW_SCHED_WAKEUP, targetCpu.value(), internalTid); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SCHED_WAKEUP, STAT_EVENT_RECEIVED); + } + return true; +} + +bool BytraceEventParser::SchedWakingEvent(const ArgsMap& args, const BytraceLine& line) const +{ + std::optional wakePidValue = base::StrToUInt32(args.at("pid")); + auto wakePidStr = std::string_view(args.at("comm")); + if (!wakePidValue.has_value()) { + TS_LOGD("Failed to convert wake_pid"); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SCHED_WAKING, STAT_EVENT_DATA_INVALID); + return false; + } + DataIndex name = traceDataCache_->GetDataIndex(std::string_view("sched_waking")); + auto instants = traceDataCache_->GetInstantsData(); + DataIndex wakePidStrIndex = traceDataCache_->GetDataIndex(wakePidStr); + InternalTid internalTid = streamFilters_->processFilter_->UpdateOrCreateThreadWithNameIndex(line.ts, + wakePidValue.value(), + wakePidStrIndex); + streamFilters_->cpuFilter_->InsertWakeupEvent(line.ts, internalTid); + instants->AppendInstantEventData(line.ts, name, internalTid); + std::optional targetCpu = base::StrToUInt32(args.at("target_cpu")); + if (targetCpu.has_value()) { + traceDataCache_->GetRawData()->AppendRawData(0, line.ts, RAW_SCHED_WAKING, targetCpu.value(), internalTid); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SCHED_WAKING, STAT_EVENT_RECEIVED); + } + + return true; +} + +bool BytraceEventParser::CpuIdleEvent(const ArgsMap& args, const BytraceLine& line) const +{ + std::optional eventCpuValue = base::StrToUInt32(args.at("cpu_id")); + std::optional newStateValue = base::StrToInt64(args.at("state")); + if (!eventCpuValue.has_value()) { + TS_LOGD("Failed to convert event cpu"); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CPU_IDLE, STAT_EVENT_DATA_INVALID); + return false; + } + if (!newStateValue.has_value()) { + TS_LOGD("Failed to convert state"); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CPU_IDLE, STAT_EVENT_DATA_INVALID); + return false; + } + auto cpuIdleNameIndex = traceDataCache_->GetDataIndex(line.eventName.c_str()); + streamFilters_->cpuMeasureFilter_->AppendNewMeasureData(eventCpuValue.value(), cpuIdleNameIndex, line.ts, + newStateValue.value()); + // Add cpu_idle event to raw_data_table + traceDataCache_->GetRawData()->AppendRawData(0, line.ts, RAW_CPU_IDLE, eventCpuValue.value(), 0); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CPU_IDLE, STAT_EVENT_RECEIVED); + return true; +} + +bool BytraceEventParser::CpuFrequencyEvent(const ArgsMap& args, const BytraceLine& line) const +{ + std::optional eventCpuValue = base::StrToUInt32(args.at("cpu_id")); + std::optional newStateValue = base::StrToInt64(args.at("state")); + + if (!newStateValue.has_value()) { + TS_LOGD("Failed to convert state"); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CPU_FREQUENCY, STAT_EVENT_DATA_INVALID); + return false; + } + if (!eventCpuValue.has_value()) { + TS_LOGD("Failed to convert event cpu"); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CPU_FREQUENCY, STAT_EVENT_DATA_INVALID); + return false; + } + + auto cpuidleNameIndex = traceDataCache_->GetDataIndex(line.eventName.c_str()); + streamFilters_->cpuMeasureFilter_->AppendNewMeasureData(eventCpuValue.value(), cpuidleNameIndex, line.ts, + newStateValue.value()); + return true; +} + +bool BytraceEventParser::WorkqueueExecuteStartEvent(const ArgsMap& args, const BytraceLine& line) const +{ + UNUSED(args); + auto splitStr = GetFunctionName(line.argsStr, "function "); + auto splitStrIndex = traceDataCache_->GetDataIndex(splitStr); + bool result = streamFilters_->sliceFilter_->BeginSlice(line.ts, line.pid, 0, workQueueId_, splitStrIndex); + traceDataCache_->GetInternalSlicesData()->AppendDistributeInfo(); + if (result) { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_WORKQUEUE_EXECUTE_START, STAT_EVENT_RECEIVED); + return true; + } else { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_WORKQUEUE_EXECUTE_START, STAT_EVENT_DATA_LOST); + return false; + } +} + +bool BytraceEventParser::WorkqueueExecuteEndEvent(const ArgsMap& args, const BytraceLine& line) const +{ + UNUSED(args); + if (streamFilters_->sliceFilter_->EndSlice(line.ts, line.pid, 0)) { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_WORKQUEUE_EXECUTE_END, STAT_EVENT_RECEIVED); + return true; + } else { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_WORKQUEUE_EXECUTE_END, STAT_EVENT_NOTMATCH); + return false; + } +} + +bool BytraceEventParser::ProcessExitEvent(const ArgsMap& args, const BytraceLine& line) const +{ + auto comm = std::string_view(args.at("comm")); + auto pid = base::StrToUInt32(args.at("pid")); + if (!pid.has_value()) { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_PROCESS_EXIT, STAT_EVENT_DATA_INVALID); + return false; + } + auto itid = streamFilters_->processFilter_->UpdateOrCreateThreadWithName(line.ts, pid.value(), comm); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_PROCESS_EXIT, STAT_EVENT_RECEIVED); + if (streamFilters_->cpuFilter_->InsertProcessExitEvent(line.ts, line.cpu, itid)) { + return true; + } else { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_PROCESS_EXIT, STAT_EVENT_NOTMATCH); + return false; + } +} + +bool BytraceEventParser::SetRateEvent(const ArgsMap& args, const BytraceLine& line) const +{ + auto name = std::string_view(args.at("name")); + auto state = base::StrToInt64(args.at("state")); + auto cpu = base::StrToUInt64(args.at("cpu_id")); + DataIndex nameIndex = traceDataCache_->GetDataIndex(name); + streamFilters_->clockRateFilter_->AppendNewMeasureData(cpu.value(), nameIndex, line.ts, state.value()); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CLOCK_SET_RATE, STAT_EVENT_RECEIVED); + return true; +} + +bool BytraceEventParser::ClockEnableEvent(const ArgsMap& args, const BytraceLine& line) const +{ + auto name = std::string_view(args.at("name")); + auto state = base::StrToInt64(args.at("state")); + auto cpuId = base::StrToUInt64(args.at("cpu_id")); + DataIndex nameIndex = traceDataCache_->GetDataIndex(name); + streamFilters_->clockEnableFilter_->AppendNewMeasureData(cpuId.value(), nameIndex, line.ts, state.value()); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CLOCK_ENABLE, STAT_EVENT_RECEIVED); + return true; +} +bool BytraceEventParser::ClockDisableEvent(const ArgsMap& args, const BytraceLine& line) const +{ + auto name = std::string_view(args.at("name")); + auto state = base::StrToInt64(args.at("state")); + auto cpuId = base::StrToUInt64(args.at("cpu_id")); + DataIndex nameIndex = traceDataCache_->GetDataIndex(name); + streamFilters_->clockDisableFilter_->AppendNewMeasureData(cpuId.value(), nameIndex, line.ts, state.value()); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CLOCK_DISABLE, STAT_EVENT_RECEIVED); + return true; +} + +bool BytraceEventParser::RegulatorSetVoltageEvent(const ArgsMap& args, const BytraceLine& line) const +{ + UNUSED(args); + UNUSED(line); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_REGULATOR_SET_VOLTAGE, STAT_EVENT_RECEIVED); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_REGULATOR_SET_VOLTAGE, STAT_EVENT_NOTSUPPORTED); + return true; +} +bool BytraceEventParser::RegulatorSetVoltageCompleteEvent(const ArgsMap& args, const BytraceLine& line) const +{ + UNUSED(args); + UNUSED(line); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_REGULATOR_SET_VOLTAGE_COMPLETE, STAT_EVENT_RECEIVED); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_REGULATOR_SET_VOLTAGE_COMPLETE, + STAT_EVENT_NOTSUPPORTED); + return true; +} +bool BytraceEventParser::RegulatorDisableEvent(const ArgsMap& args, const BytraceLine& line) const +{ + UNUSED(args); + UNUSED(line); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_REGULATOR_DISABLE, STAT_EVENT_RECEIVED); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_REGULATOR_DISABLE, STAT_EVENT_NOTSUPPORTED); + return true; +} +bool BytraceEventParser::RegulatorDisableCompleteEvent(const ArgsMap& args, const BytraceLine& line) const +{ + UNUSED(args); + UNUSED(line); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_REGULATOR_DISABLE_COMPLETE, STAT_EVENT_RECEIVED); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_REGULATOR_DISABLE_COMPLETE, STAT_EVENT_NOTSUPPORTED); + return true; +} + +bool BytraceEventParser::IpiEntryEvent(const ArgsMap& args, const BytraceLine& line) const +{ + UNUSED(args); + UNUSED(line); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_IPI_ENTRY, STAT_EVENT_RECEIVED); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_IPI_ENTRY, STAT_EVENT_NOTSUPPORTED); + return true; +} +bool BytraceEventParser::IpiExitEvent(const ArgsMap& args, const BytraceLine& line) const +{ + UNUSED(args); + UNUSED(line); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_IPI_EXIT, STAT_EVENT_RECEIVED); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_IPI_EXIT, STAT_EVENT_NOTSUPPORTED); + return true; +} +bool BytraceEventParser::IrqHandlerEntryEvent(const ArgsMap& args, const BytraceLine& line) const +{ + UNUSED(args); + UNUSED(line); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_IRQ_HANDLER_ENTRY, STAT_EVENT_RECEIVED); + auto name = std::string_view(args.at("name")); + streamFilters_->irqFilter_->IrqHandlerEntry(line.ts, line.cpu, traceDataCache_->GetDataIndex(name)); + return true; +} +bool BytraceEventParser::IrqHandlerExitEvent(const ArgsMap& args, const BytraceLine& line) const +{ + UNUSED(args); + UNUSED(line); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_IRQ_HANDLER_EXIT, STAT_EVENT_RECEIVED); + uint32_t ret = (args.at("ret") == "handled") ? 1 : 0; + streamFilters_->irqFilter_->IrqHandlerExit(line.ts, line.cpu, ret); + return true; +} +bool BytraceEventParser::SoftIrqRaiseEvent(const ArgsMap& args, const BytraceLine& line) const +{ + UNUSED(args); + UNUSED(line); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_SOFTIRQ_RAISE, STAT_EVENT_RECEIVED); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_SOFTIRQ_RAISE, STAT_EVENT_NOTSUPPORTED); + return true; +} +bool BytraceEventParser::SoftIrqEntryEvent(const ArgsMap& args, const BytraceLine& line) const +{ + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_SOFTIRQ_ENTRY, STAT_EVENT_RECEIVED); + auto vec = base::StrToUInt32(args.at("vec")); + streamFilters_->irqFilter_->SoftIrqEntry(line.ts, line.cpu, vec.value()); + return true; +} +bool BytraceEventParser::SoftIrqExitEvent(const ArgsMap& args, const BytraceLine& line) const +{ + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_SOFTIRQ_EXIT, STAT_EVENT_RECEIVED); + auto vec = base::StrToUInt32(args.at("vec")); + streamFilters_->irqFilter_->SoftIrqExit(line.ts, line.cpu, vec.value()); + return true; +} + +bool BytraceEventParser::BinderTransaction(const ArgsMap& args, const BytraceLine& line) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_BINDER_TRANSACTION, STAT_EVENT_RECEIVED); + auto transactionId = base::StrToInt64(args.at("transaction")); + auto destNode = base::StrToUInt32(args.at("dest_node")); + auto destProc = base::StrToUInt32(args.at("dest_proc")); + auto destThread = base::StrToUInt32(args.at("dest_thread")); + auto isReply = base::StrToUInt32(args.at("reply")); + auto flags = base::StrToUInt32(args.at("flags"), base::INTEGER_RADIX_TYPE_HEX); + auto codeStr = base::StrToUInt32(args.at("code"), base::INTEGER_RADIX_TYPE_HEX); + TS_LOGD("ts:%lu, pid:%u, destNode:%u, destTgid:%u, destTid:%u, transactionId:%lu, isReply:%u flags:%u, code:%u", + line.ts, line.pid, destNode.value(), destProc.value(), destThread.value(), transactionId.value(), + isReply.value(), flags.value(), codeStr.value()); + streamFilters_->binderFilter_->SendTraction(line.ts, line.pid, transactionId.value(), destNode.value(), + destProc.value(), destThread.value(), isReply.value(), flags.value(), + codeStr.value()); + return true; +} +bool BytraceEventParser::BinderTransactionReceived(const ArgsMap& args, const BytraceLine& line) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_BINDER_TRANSACTION_RECEIVED, STAT_EVENT_RECEIVED); + auto transactionId = base::StrToInt64(args.at("transaction")); + streamFilters_->binderFilter_->ReceiveTraction(line.ts, line.pid, transactionId.value()); + TS_LOGD("ts:%lu, pid:%u, transactionId:%lu", line.ts, line.pid, transactionId.value()); + return true; +} +bool BytraceEventParser::BinderTransactionAllocBufEvent(const ArgsMap& args, const BytraceLine& line) const +{ + UNUSED(args); + UNUSED(line); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_BINDER_TRANSACTION_ALLOC_BUF, STAT_EVENT_RECEIVED); + auto dataSize = base::StrToUInt64(args.at("data_size")); + auto offsetsSize = base::StrToUInt64(args.at("offsets_size")); + streamFilters_->binderFilter_->TransactionAllocBuf(line.ts, line.pid, dataSize.value(), offsetsSize.value()); + TS_LOGD("dataSize:%lu, offsetSize:%lu", dataSize.value(), offsetsSize.value()); + return true; +} +bool BytraceEventParser::ParseDataItem(const BytraceLine& line, const ArgsMap& args, uint32_t tgid) const +{ + traceDataCache_->UpdateTraceTime(line.ts); + if (tgid) { + streamFilters_->processFilter_->UpdateOrCreateThreadWithPidAndName(line.pid, tgid, line.task); + } else { + // When tgid is zero, only use tid create thread + streamFilters_->processFilter_->GetOrCreateThreadWithPid(line.pid, tgid); + } + + auto it = eventToFunctionMap_.find(line.eventName); + if (it != eventToFunctionMap_.end()) { + return it->second(args, line); + } + TS_LOGW("UnRecognizable event name:%s", line.eventName.c_str()); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_OTHER, STAT_EVENT_NOTSUPPORTED); + return false; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/parser/bytrace_parser/bytrace_event_parser.h b/host/trace_streamer/src/parser/bytrace_parser/bytrace_event_parser.h new file mode 100644 index 0000000..6f3aee8 --- /dev/null +++ b/host/trace_streamer/src/parser/bytrace_parser/bytrace_event_parser.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_BYTRACE_EVENT_PARSER_H +#define SRC_BYTRACE_EVENT_PARSER_H + +#include + +#include "common_types.h" +#include "event_parser_base.h" +#include "print_event_parser.h" +#include "trace_data_cache.h" +#include "trace_streamer_config.h" +#include "trace_streamer_filters.h" + +namespace SysTuning { +namespace TraceStreamer { +using ArgsMap = std::unordered_map; +class BytraceEventParser : private EventParserBase { +public: + BytraceEventParser(TraceDataCache* dataCache, const TraceStreamerFilters* filter); + bool ParseDataItem(const BytraceLine& line, const ArgsMap& args, uint32_t tgid) const; + +private: + using FuncCall = std::function; + bool SchedSwitchEvent(const ArgsMap& args, const BytraceLine& line) const; + bool TaskRenameEvent(const ArgsMap& args, const BytraceLine& line) const; + bool TaskNewtaskEvent(const ArgsMap& args, const BytraceLine& line) const; + bool TracingMarkWriteEvent(const ArgsMap& args, const BytraceLine& line) const; + bool SchedWakeupEvent(const ArgsMap& args, const BytraceLine& line) const; + bool SchedWakingEvent(const ArgsMap& args, const BytraceLine& line) const; + bool CpuIdleEvent(const ArgsMap& args, const BytraceLine& line) const; + bool CpuFrequencyEvent(const ArgsMap& args, const BytraceLine& line) const; + bool WorkqueueExecuteStartEvent(const ArgsMap& args, const BytraceLine& line) const; + bool WorkqueueExecuteEndEvent(const ArgsMap& args, const BytraceLine& line) const; + bool ProcessExitEvent(const ArgsMap& args, const BytraceLine& line) const; + bool SetRateEvent(const ArgsMap& args, const BytraceLine& line) const; + bool ClockEnableEvent(const ArgsMap& args, const BytraceLine& line) const; + bool ClockDisableEvent(const ArgsMap& args, const BytraceLine& line) const; + bool RegulatorSetVoltageEvent(const ArgsMap& args, const BytraceLine& line) const; + bool RegulatorSetVoltageCompleteEvent(const ArgsMap& args, const BytraceLine& line) const; + bool RegulatorDisableEvent(const ArgsMap& args, const BytraceLine& line) const; + bool RegulatorDisableCompleteEvent(const ArgsMap& args, const BytraceLine& line) const; + bool IpiEntryEvent(const ArgsMap& args, const BytraceLine& line) const; + bool IpiExitEvent(const ArgsMap& args, const BytraceLine& line) const; + bool IrqHandlerEntryEvent(const ArgsMap& args, const BytraceLine& line) const; + bool IrqHandlerExitEvent(const ArgsMap& args, const BytraceLine& line) const; + bool SoftIrqRaiseEvent(const ArgsMap& args, const BytraceLine& line) const; + bool SoftIrqEntryEvent(const ArgsMap& args, const BytraceLine& line) const; + bool SoftIrqExitEvent(const ArgsMap& args, const BytraceLine& line) const; + bool BinderTransaction(const ArgsMap& args, const BytraceLine& line) const; + bool BinderTransactionReceived(const ArgsMap& args, const BytraceLine& line) const; + bool BinderTransactionAllocBufEvent(const ArgsMap& args, const BytraceLine& line) const; +private: + const DataIndex ioWaitId_; + const DataIndex workQueueId_; + const DataIndex schedWakeupId_; + const DataIndex schedBlockedReasonId_; + std::map eventToFunctionMap_ = {}; + const unsigned int MIN_SCHED_ARGS_COUNT = 6; + const unsigned int MIN_SCHED_WAKEUP_ARGS_COUNT = 2; + PrintEventParser printEventParser_; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // SRC_BYTRACE_EVENT_PARSER_H diff --git a/host/trace_streamer/src/parser/bytrace_parser/bytrace_parser.cpp b/host/trace_streamer/src/parser/bytrace_parser/bytrace_parser.cpp new file mode 100644 index 0000000..cdf20e3 --- /dev/null +++ b/host/trace_streamer/src/parser/bytrace_parser/bytrace_parser.cpp @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bytrace_parser.h" +#include +#include "binder_filter.h" +#include "cpu_filter.h" +#include "parting_string.h" +#include "stat_filter.h" +namespace SysTuning { +namespace TraceStreamer { +BytraceParser::BytraceParser(TraceDataCache* dataCache, const TraceStreamerFilters* filters) + : ParserBase(filters), + eventParser_(std::make_unique(dataCache, filters)), + dataSegArray(new DataSegment[MAX_SEG_ARRAY_SIZE]) +{ +#ifdef SUPPORTTHREAD + noThread_ = false; +#endif +} + +BytraceParser::~BytraceParser() = default; + +void BytraceParser::WaitForParserEnd() +{ + if (parseThreadStarted_ || filterThreadStarted_) { + toExit_ = true; + while (!exited_) { + usleep(sleepDur_ * sleepDur_); + } + } + streamFilters_->cpuFilter_->FinishCpuEvent(); + streamFilters_->binderFilter_->FinishBinderEvent(); +} +void BytraceParser::ParseTraceDataSegment(std::unique_ptr bufferStr, size_t size) +{ + if (isParsingOver_) { + return; + } + packagesBuffer_.insert(packagesBuffer_.end(), &bufferStr[0], &bufferStr[size]); + auto packagesBegin = packagesBuffer_.begin(); + + while (1) { + auto packagesLine = std::find(packagesBegin, packagesBuffer_.end(), '\n'); + if (packagesLine == packagesBuffer_.end()) { + break; + } + + std::string bufferLine(packagesBegin, packagesLine); + + if (IsTraceComment(bufferLine)) { + traceCommentLines_++; + goto NEXT_LINE; + } + if (bufferLine.empty()) { + parsedTraceInvalidLines_++; + goto NEXT_LINE; + } + + if (bufferLine.find(script_.c_str()) != std::string::npos) { + isParsingOver_ = true; + break; + } + ParseTraceDataItem(bufferLine); + + NEXT_LINE: + packagesBegin = packagesLine + 1; + continue; + } + + if (isParsingOver_) { + packagesBuffer_.clear(); + } else { + packagesBuffer_.erase(packagesBuffer_.begin(), packagesBegin); + } + return; +} + +void BytraceParser::ParseTraceDataItem(const std::string& buffer) +{ + int head = rawDataHead_; + while (!toExit_) { + if (dataSegArray[head].status.load() != TS_PARSE_STATUS_INIT) { + TS_LOGD("rawDataHead_:\t%d, parseHead_:\t%d, filterHead_:\t%d\n", rawDataHead_, parseHead_, filterHead_); + usleep(sleepDur_); + continue; + } + dataSegArray[head].seg = std::move(buffer); + dataSegArray[head].status = TS_PARSE_STATUS_SEPRATED; + if (!noThread_) { + rawDataHead_ = (rawDataHead_ + 1) % MAX_SEG_ARRAY_SIZE; + } + break; + } + if (!parseThreadStarted_ && !noThread_) { + parseThreadStarted_ = true; + int tmp = maxThread_; + while (tmp--) { + parserThreadCount_++; + std::thread MatchLineThread(&BytraceParser::ParseThread, this); + MatchLineThread.detach(); + TS_LOGI("parser Thread:%d/%d start working ...\n", maxThread_ - tmp, maxThread_); + } + } + if (noThread_) { + ParserData(dataSegArray[head]); + } + return; +} +int BytraceParser::GetNextSegment() +{ + int head; + dataSegMux_.lock(); + head = parseHead_; + DataSegment& seg = dataSegArray[head]; + if (seg.status.load() != TS_PARSE_STATUS_SEPRATED) { + if (toExit_) { + parserThreadCount_--; + TS_LOGI("exiting parser, parserThread Count:%d\n", parserThreadCount_); + dataSegMux_.unlock(); + if (!parserThreadCount_ && !filterThreadStarted_) { + exited_ = true; + } + return ERROR_CODE_EXIT; + } + if (seg.status == TS_PARSE_STATUS_PARSING) { + dataSegMux_.unlock(); + usleep(sleepDur_); + return ERROR_CODE_NODATA; + } + dataSegMux_.unlock(); + TS_LOGD("ParseThread watting:\t%d, parseHead_:\t%d, filterHead_:\t%d\n", rawDataHead_, parseHead_, filterHead_); + usleep(sleepDur_); + return ERROR_CODE_NODATA; + } + parseHead_ = (parseHead_ + 1) % MAX_SEG_ARRAY_SIZE; + seg.status = TS_PARSE_STATUS_PARSING; + dataSegMux_.unlock(); + return head; +} + +void BytraceParser::GetDataSegAttr(DataSegment& seg, const std::smatch& matcheLine) const +{ + size_t index = 0; + std::string pidStr = matcheLine[++index].str(); + std::optional optionalPid = base::StrToUInt32(pidStr); + if (!optionalPid.has_value()) { + TS_LOGD("Illegal pid: %s", pidStr.c_str()); + seg.status = TS_PARSE_STATUS_INVALID; + return; + } + + std::string tGidStr = matcheLine[++index].str(); + std::string cpuStr = matcheLine[++index].str(); + std::optional optionalCpu = base::StrToUInt32(cpuStr); + if (!optionalCpu.has_value()) { + TS_LOGD("Illegal cpu %s", cpuStr.c_str()); + seg.status = TS_PARSE_STATUS_INVALID; + return; + } + std::string timeStr = matcheLine[++index].str(); + std::optional optionalTime = base::StrToDouble(timeStr); + if (!optionalTime.has_value()) { + TS_LOGD("Illegal ts %s", timeStr.c_str()); + seg.status = TS_PARSE_STATUS_INVALID; + return; + } + std::string eventName = matcheLine[++index].str(); + seg.bufLine.task = StrTrim(matcheLine.prefix()); + if (seg.bufLine.task == "<...>") { + seg.bufLine.task = ""; + } + seg.bufLine.argsStr = StrTrim(matcheLine.suffix()); + seg.bufLine.pid = optionalPid.value(); + seg.bufLine.cpu = optionalCpu.value(); + seg.bufLine.ts = static_cast(optionalTime.value() * 1e9); + seg.bufLine.tGidStr = tGidStr; + seg.bufLine.eventName = eventName; + GetDataSegArgs(seg); + seg.status = TS_PARSE_STATUS_PARSED; +} + +void BytraceParser::GetDataSegArgs(DataSegment& seg) const +{ + seg.args.clear(); + if (seg.bufLine.tGidStr != "-----") { + seg.tgid = base::StrToUInt32(seg.bufLine.tGidStr).value_or(0); + } else { + seg.tgid = 0; + } + + for (base::PartingString ss(seg.bufLine.argsStr, ' '); ss.Next();) { + std::string key; + std::string value; + if (!(std::string(ss.GetCur()).find("=") != std::string::npos)) { + key = "name"; + value = ss.GetCur(); + seg.args.emplace(std::move(key), std::move(value)); + continue; + } + for (base::PartingString inner(ss.GetCur(), '='); inner.Next();) { + if (key.empty()) { + key = inner.GetCur(); + } else { + value = inner.GetCur(); + } + } + seg.args.emplace(std::move(key), std::move(value)); + } +} +void BytraceParser::ParseThread() +{ + while (1) { + int head = GetNextSegment(); + if (head < 0) { + if (head == ERROR_CODE_NODATA) { + continue; + } + if (!filterThreadStarted_) { + exited_ = true; + } + return; + } + DataSegment& seg = dataSegArray[head]; + ParserData(seg); + } +} + +void BytraceParser::ParserData(DataSegment& seg) +{ + std::smatch matcheLine; + if (!std::regex_search(seg.seg, matcheLine, bytraceMatcher_)) { + TS_LOGD("Not support this event (line: %s)", seg.seg.c_str()); + seg.status = TS_PARSE_STATUS_INVALID; + parsedTraceInvalidLines_++; + FilterData(seg); + return; + } else { + parsedTraceValidLines_++; + } + GetDataSegAttr(seg, matcheLine); + if (!filterThreadStarted_ && !noThread_) { + filterThreadStarted_ = true; + std::thread ParserThread(&BytraceParser::FilterThread, this); + ParserThread.detach(); + } + if (noThread_) { + FilterData(seg); + } +} +void BytraceParser::FilterThread() +{ + while (1) { + DataSegment& seg = dataSegArray[filterHead_]; + if (!FilterData(seg)) { + return; + } + } +} +bool BytraceParser::FilterData(DataSegment& seg) +{ + if (seg.status.load() == TS_PARSE_STATUS_INVALID) { + seg.status = TS_PARSE_STATUS_INIT; + if (!noThread_) { + filterHead_ = (filterHead_ + 1) % MAX_SEG_ARRAY_SIZE; + } + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_OTHER, STAT_EVENT_DATA_INVALID); + return true; + } + if (seg.status.load() != TS_PARSE_STATUS_PARSED) { + if (toExit_ && !parserThreadCount_) { + TS_LOGI("exiting FilterThread Thread\n"); + exited_ = true; + filterThreadStarted_ = false; + return false; + } + if (!noThread_) { // wasm do not allow thread + usleep(sleepDur_); + } + return true; + } + BytraceLine line = seg.bufLine; + uint32_t tgid = seg.tgid; + eventParser_->ParseDataItem(line, seg.args, tgid); + if (!noThread_) { + filterHead_ = (filterHead_ + 1) % MAX_SEG_ARRAY_SIZE; + } + seg.status = TS_PARSE_STATUS_INIT; + return true; +} +// Remove space at the beginning and end of the string +std::string BytraceParser::StrTrim(const std::string& input) const +{ + std::string str = input; + auto posBegin = std::find_if(str.begin(), str.end(), IsNotSpace); + str.erase(str.begin(), posBegin); + + auto posEnd = std::find_if(str.rbegin(), str.rend(), IsNotSpace); + str.erase(posEnd.base(), str.end()); + + return str; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/parser/bytrace_parser/bytrace_parser.h b/host/trace_streamer/src/parser/bytrace_parser/bytrace_parser.h new file mode 100644 index 0000000..95c552e --- /dev/null +++ b/host/trace_streamer/src/parser/bytrace_parser/bytrace_parser.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BYTRACE_PARSER_H +#define BYTRACE_PARSER_H + +#include +#include +#include +#include + +#include "bytrace_event_parser.h" +#include "log.h" +#include "parser_base.h" +#include "string_to_numerical.h" +#include "trace_data/trace_data_cache.h" +#include "trace_streamer_filters.h" + +namespace SysTuning { +namespace TraceStreamer { +class BytraceParser : public ParserBase { +public: + BytraceParser(TraceDataCache* dataCache, const TraceStreamerFilters* filters); + ~BytraceParser(); + + void ParseTraceDataSegment(std::unique_ptr bufferStr, size_t size) override; + size_t ParsedTraceValidLines() const + { + return parsedTraceValidLines_; + } + size_t ParsedTraceInvalidLines() const + { + return parsedTraceInvalidLines_; + } + size_t TraceCommentLines() const + { + return traceCommentLines_; + } + void WaitForParserEnd(); + +private: + enum ErrorCode { ERROR_CODE_EXIT = -2, ERROR_CODE_NODATA = -1 }; + int GetNextSegment(); + void GetDataSegAttr(DataSegment& seg, const std::smatch& matcheLine) const; + void GetDataSegArgs(DataSegment& seg) const; + void FilterThread(); + inline static bool IsNotSpace(char c) + { + return !std::isspace(c); + } + inline static bool IsTraceComment(const std::string& buffer) + { + return ((buffer[0] == '#') || buffer.find("TASK-PID") != std::string::npos); + } + + void ParseTraceDataItem(const std::string& buffer) override; + std::string StrTrim(const std::string& input) const; + void ParseThread(); + void ParserData(DataSegment& seg); + bool FilterData(DataSegment& seg); + +private: + using ArgsMap = std::unordered_map; + bool isParsingOver_ = false; + std::unique_ptr eventParser_; + const std::regex bytraceMatcher_ = std::regex(R"(-(\d+)\s+\(?\s*(\d+|-+)?\)?\s?\[(\d+)\]\s*)" + R"([a-zA-Z0-9.]{0,5}\s+(\d+\.\d+):\s+(\S+):)"); + + const std::string script_ = R"()"; + + size_t parsedTraceValidLines_ = 0; + size_t parsedTraceInvalidLines_ = 0; + size_t traceCommentLines_ = 0; + std::mutex dataSegMux_; + int parseHead_ = 0; + std::atomic filterThreadStarted_{false}; + bool parseThreadStarted_ = false; + const int MAX_SEG_ARRAY_SIZE = 5000; + const int maxThread_ = 4; // 4 is the best on ubuntu 113MB/s, max 138MB/s, 6 is best on mac m1 21MB/s, + int parserThreadCount_ = 0; + bool toExit_ = false; + bool exited_ = false; + std::unique_ptr dataSegArray; + int rawDataHead_ = 0; + int filterHead_ = 0; + const int sleepDur_ = 100; + bool noThread_ = true; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // _BYTRACE_PARSER_H_ diff --git a/host/trace_streamer/src/parser/common_types.h b/host/trace_streamer/src/parser/common_types.h new file mode 100644 index 0000000..5d7c0a4 --- /dev/null +++ b/host/trace_streamer/src/parser/common_types.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BYTRACE_COMMON_TYPES_H +#define BYTRACE_COMMON_TYPES_H + +#include +#include +#include +#include "hidump_plugin_result.pb.h" +#include "hilog_plugin_result.pb.h" +#include "memory_plugin_result.pb.h" +#include "native_hook_result.pb.h" +#include "services/common_types.pb.h" +#include "trace_plugin_result.pb.h" +#include "ts_common.h" + +namespace SysTuning { +namespace TraceStreamer { +enum ParseResult { ERROR = 0, SUCCESS }; +enum RawType { RAW_CPU_IDLE = 1, RAW_SCHED_WAKEUP = 2, RAW_SCHED_WAKING = 3 }; + +enum Stat : uint32_t { + RUNNABLE = 0, + INTERRUPTABLESLEEP = 1, + UNINTERRUPTIBLESLEEP = 2, + STOPPED = 4, + TRACED = 8, // the process is being debug + EXITDEAD = 16, + EXITZOMBIE = 32, + TASKDEAD = 64, + WAKEKILL = 128, + WAKING = 256, + PARKED = 512, + NOLOAD = 1024, + TASKNEW = 2048, + VALID = 0X8000, +}; + +struct BytraceLine { + uint64_t ts = 0; + uint32_t pid = 0; + uint32_t cpu = 0; + + std::string task; // thread name + std::string pidStr; // thread str + std::string tGidStr; // process thread_group + std::string eventName; + std::string argsStr; +}; +enum ParseStatus { + TS_PARSE_STATUS_INIT = 0, + TS_PARSE_STATUS_SEPRATED = 1, + TS_PARSE_STATUS_PARSING = 2, + TS_PARSE_STATUS_PARSED = 3, + TS_PARSE_STATUS_INVALID = 4 +}; +struct DataSegment { + std::string seg; + BytraceLine bufLine; + std::unordered_map args; + uint32_t tgid; + std::atomic status{TS_PARSE_STATUS_INIT}; +}; +enum DataSourceType { + DATA_SOURCE_TYPE_TRACE, + DATA_SOURCE_TYPE_MEM, + DATA_SOURCE_TYPE_HILOG, + DATA_SOURCE_TYPE_HEAP, + DATA_SOURCE_TYPE_FPS +}; +// 注意使用完之后恢复初始化状态,保证下次使用不会出现数据混乱。 +struct HtraceDataSegment { + std::string seg; + MemoryData memData; + HilogInfo logData; + BatchNativeHookData batchNativeHookData; + HidumpInfo hidumpInfo; + uint64_t timeStamp; + TracePluginResult traceData; + BuiltinClocks clockId; + DataSourceType dataType; + std::atomic status{TS_PARSE_STATUS_INIT}; +}; + +struct TracePoint { + char phase_ = '\0'; + uint32_t tgid_ = 0; + std::string name_ = ""; + uint64_t value_ = 0; + std::string categoryGroup_ = ""; + // Distributed Data + std::string chainId_ = ""; + std::string spanId_ = ""; + std::string parentSpanId_ = ""; + std::string flag_ = ""; + std::string args_ = ""; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // _BYTRACE_COMMON_TYPES_H_ diff --git a/host/trace_streamer/src/parser/event_parser_base.cpp b/host/trace_streamer/src/parser/event_parser_base.cpp new file mode 100644 index 0000000..a4bdbf4 --- /dev/null +++ b/host/trace_streamer/src/parser/event_parser_base.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "event_parser_base.h" +namespace SysTuning { +namespace TraceStreamer { +EventParserBase::EventParserBase(TraceDataCache* dataCache, const TraceStreamerFilters* filter) + : streamFilters_(filter), traceDataCache_(dataCache) +{ +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/parser/event_parser_base.h b/host/trace_streamer/src/parser/event_parser_base.h new file mode 100644 index 0000000..2df1e46 --- /dev/null +++ b/host/trace_streamer/src/parser/event_parser_base.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_EVENT_PARSER_BASE_H +#define SRC_EVENT_PARSER_BASE_H +#include "trace_data_cache.h" +#include "trace_streamer_config.h" +#include "trace_streamer_filters.h" +namespace SysTuning { +namespace TraceStreamer { +using namespace SysTuning::TraceCfg; +class EventParserBase { +public: + EventParserBase(TraceDataCache* dataCache, const TraceStreamerFilters* filter); + virtual ~EventParserBase() = default; + +public: + const TraceStreamerFilters* streamFilters_; + TraceDataCache* traceDataCache_; +protected: + TraceStreamerConfig config_; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // SRC_PARSER_BASE_H diff --git a/host/trace_streamer/src/parser/htrace_parser/BUILD.gn b/host/trace_streamer/src/parser/htrace_parser/BUILD.gn new file mode 100644 index 0000000..17f81c4 --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/BUILD.gn @@ -0,0 +1,78 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import("//build/ohos.gni") +import("../../ts.gni") +ohos_source_set("htrace_parser_src") { + sources = [ + "../parser_base.cpp", + "htrace_clock_detail_parser.cpp", + "htrace_clock_detail_parser.h", + "htrace_cpu_parser/htrace_cpu_detail_parser.cpp", + "htrace_cpu_parser/htrace_cpu_detail_parser.h", + "htrace_event_parser/htrace_event_parser.cpp", + "htrace_event_parser/htrace_event_parser.h", + "htrace_hidump_parser.cpp", + "htrace_hidump_parser.h", + "htrace_hilog_parser.cpp", + "htrace_hilog_parser.h", + "htrace_mem_parser.cpp", + "htrace_mem_parser.h", + "htrace_native_hook_parser.cpp", + "htrace_native_hook_parser.h", + "htrace_parser.cpp", + "htrace_symbols_detail_parser.cpp", + "htrace_symbols_detail_parser.h", + ] + include_dirs = [ + "htrace_event_parser", + "htrace_cpu_parser", + ".", + "${OHOS_PROTO_GEN}", + "${OHOS_PROTO_GEN}/types/plugins/memory_data", + "${OHOS_PROTO_GEN}/types/plugins/ftrace_data", + "${OHOS_PROTO_GEN}/types/plugins/hilog_data", + "${OHOS_PROTO_GEN}/types/plugins/native_hook", + "${OHOS_PROTO_GEN}/types/plugins/hidump_data", + "../../include", + "../../", + "../", + "../../trace_data", + "../../cfg", + "../../trace_streamer", + "//third_party/protobuf/src", + "//third_party/sqlite/include", + "../../filter", + "../../base", + ] + if (enable_ts_utest && !use_wasm) { + cflags = [ + "-fprofile-arcs", + "-ftest-coverage", + ] + ldflags = [ + "-fprofile-arcs", + "-ftest-coverage", + "--coverage", + ] + } + public_deps = [] + deps = [] +} +group("htrace_parser") { + deps = [ + ":htrace_parser_src", + "//prebuilts/protos:ts_proto_data_cpp", + "//third_party/protobuf:protobuf", + "//third_party/protobuf:protobuf_lite", + ] +} diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_clock_detail_parser.cpp b/host/trace_streamer/src/parser/htrace_parser/htrace_clock_detail_parser.cpp new file mode 100644 index 0000000..bc4158b --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_clock_detail_parser.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "htrace_clock_detail_parser.h" +#include "clock_filter.h" +#include "htrace_event_parser.h" +#include "measure_filter.h" +#include "process_filter.h" +#include "stat_filter.h" +#include "symbols_filter.h" +namespace SysTuning { +namespace TraceStreamer { +HtraceClockDetailParser::HtraceClockDetailParser(TraceDataCache* dataCache, const TraceStreamerFilters* ctx) + : streamFilters_(ctx), traceDataCache_(dataCache) +{ + for (auto i = 0; i < MEM_MAX; i++) { + memNameDictMap_.insert(std::make_pair(static_cast(i), + traceDataCache_->GetDataIndex(config_.memNameMap_.at(static_cast(i))))); + } +} + +HtraceClockDetailParser::~HtraceClockDetailParser() = default; +void HtraceClockDetailParser::Parse(TracePluginResult& tracePacket) const +{ + if (!tracePacket.clocks_detail_size()) { + return; + } + std::vector snapShot; + TS_LOGI("got clock snapshot"); + for (int i = 0; i < tracePacket.clocks_detail_size(); i++) { + auto clockInfo = tracePacket.mutable_clocks_detail(i); + TS_LOGI("clockid:%d, ts:%llu", clockInfo->id(), + static_cast(clockInfo->time().tv_nsec() + clockInfo->time().tv_sec() * SEC_TO_NS)); + snapShot.push_back(SnapShot{static_cast(clockInfo->id()), + clockInfo->time().tv_nsec() + clockInfo->time().tv_sec() * SEC_TO_NS}); + streamFilters_->clockFilter_->AddClockSnapshot(snapShot); + } + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CLOCK_SYNC, STAT_EVENT_RECEIVED); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_clock_detail_parser.h b/host/trace_streamer/src/parser/htrace_parser/htrace_clock_detail_parser.h new file mode 100644 index 0000000..c36c73d --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_clock_detail_parser.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef HTRACE_CLOCKDETAIL_PARSER_H +#define HTRACE_CLOCKDETAIL_PARSER_H +#include +#include +#include +#include +#include +#include "trace_data/trace_data_cache.h" +#include "trace_plugin_result.pb.h" +#include "trace_streamer_config.h" +#include "trace_streamer_filters.h" + +namespace SysTuning { +namespace TraceStreamer { +class HtraceClockDetailParser { +public: + HtraceClockDetailParser(TraceDataCache* dataCache, const TraceStreamerFilters* filters); + ~HtraceClockDetailParser(); + void Parse(TracePluginResult& tracePacket) const; + +private: + const TraceStreamerFilters* streamFilters_; + TraceDataCache* traceDataCache_; + TraceStreamerConfig config_ = {}; + std::map memNameDictMap_ = {}; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // HTRACE_CLOCKDETAIL_PARSER_H diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_cpu_parser/htrace_cpu_detail_parser.cpp b/host/trace_streamer/src/parser/htrace_parser/htrace_cpu_parser/htrace_cpu_detail_parser.cpp new file mode 100644 index 0000000..cb37c7c --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_cpu_parser/htrace_cpu_detail_parser.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "htrace_cpu_detail_parser.h" +#include "htrace_event_parser.h" +#include "stat_filter.h" +namespace SysTuning { +namespace TraceStreamer { +HtraceCpuDetailParser::HtraceCpuDetailParser(TraceDataCache* dataCache, const TraceStreamerFilters* ctx) + : eventParser_(std::make_unique(dataCache, ctx)) +{ +} + +HtraceCpuDetailParser::~HtraceCpuDetailParser() = default; +void HtraceCpuDetailParser::Parse(TracePluginResult& tracePacket, BuiltinClocks clock) +{ + if (!tracePacket.ftrace_cpu_detail_size()) { + return; + } + + for (int i = 0; i < tracePacket.ftrace_cpu_detail_size(); i++) { + FtraceCpuDetailMsg* cpuDetail = tracePacket.mutable_ftrace_cpu_detail(i); + eventParser_->ParseDataItem(cpuDetail, clock); + } +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_cpu_parser/htrace_cpu_detail_parser.h b/host/trace_streamer/src/parser/htrace_parser/htrace_cpu_parser/htrace_cpu_detail_parser.h new file mode 100644 index 0000000..f422770 --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_cpu_parser/htrace_cpu_detail_parser.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef HTRACE_CPU_DETAIL_PARSER_H +#define HTRACE_CPU_DETAIL_PARSER_H +#include +#include +#include +#include +#include +#include "event_parser_base.h" +#include "htrace_event_parser.h" +#include "log.h" +#include "parser_base.h" +#include "trace_data/trace_data_cache.h" +#include "trace_plugin_result.pb.h" +#include "trace_streamer_filters.h" + +namespace SysTuning { +namespace TraceStreamer { +class HtraceCpuDetailParser { +public: + HtraceCpuDetailParser(TraceDataCache* dataCache, const TraceStreamerFilters* ctx); + ~HtraceCpuDetailParser(); + void Parse(TracePluginResult& tracePacket, BuiltinClocks clock); + +private: + std::unique_ptr eventParser_; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // HTRACE_CPU_DETAIL_PARSER_H_ diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_event_parser/htrace_event_parser.cpp b/host/trace_streamer/src/parser/htrace_parser/htrace_event_parser/htrace_event_parser.cpp new file mode 100644 index 0000000..2005537 --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_event_parser/htrace_event_parser.cpp @@ -0,0 +1,671 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "htrace_event_parser.h" +#include +#include "binder_filter.h" +#include "binder.pb.h" +#include "clock_filter.h" +#include "cpu_filter.h" +#include "irq_filter.h" +#include "irq.pb.h" +#include "log.h" +#include "measure_filter.h" +#include "process_filter.h" +#include "slice_filter.h" +#include "stat_filter.h" +#include "symbols_filter.h" +#include "thread_state.h" +namespace SysTuning { +namespace TraceStreamer { +HtraceEventParser::HtraceEventParser(TraceDataCache* dataCache, const TraceStreamerFilters* filter) + : EventParserBase(dataCache, filter), + workQueueId_(dataCache->dataDict_.GetStringIndex("workqueue")), + printEventParser_(traceDataCache_, streamFilters_) +{ + eventToFunctionMap_ = {{config_.eventNameMap_.at(TRACE_EVENT_BINDER_TRANSACTION), + std::bind(&HtraceEventParser::BinderTractionEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_BINDER_TRANSACTION_RECEIVED), + std::bind(&HtraceEventParser::BinderTractionReceivedEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_BINDER_TRANSACTION_ALLOC_BUF), + std::bind(&HtraceEventParser::BinderTractionAllocBufEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_BINDER_TRANSACTION_LOCK), + std::bind(&HtraceEventParser::BinderTractionLockEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_BINDER_TRANSACTION_LOCKED), + std::bind(&HtraceEventParser::BinderTractionLockedEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_BINDER_TRANSACTION_UNLOCK), + std::bind(&HtraceEventParser::BinderTractionUnLockEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_SCHED_SWITCH), + std::bind(&HtraceEventParser::SchedSwitchEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_TASK_RENAME), + std::bind(&HtraceEventParser::TaskRenameEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_TASK_NEWTASK), + std::bind(&HtraceEventParser::TaskNewtaskEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_PRINT), + std::bind(&HtraceEventParser::ParsePrintEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_SCHED_WAKEUP), + std::bind(&HtraceEventParser::SchedWakeupEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_SCHED_WAKEUP_NEW), + std::bind(&HtraceEventParser::SchedWakeupNewEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_PROCESS_EXIT), + std::bind(&HtraceEventParser::ProcessExitEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_PROCESS_FREE), + std::bind(&HtraceEventParser::ProcessFreeEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_SCHED_WAKING), + std::bind(&HtraceEventParser::SchedWakingEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_CPU_IDLE), + std::bind(&HtraceEventParser::CpuIdleEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_CPU_FREQUENCY), + std::bind(&HtraceEventParser::CpuFrequencyEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_SUSPEND_RESUME), + std::bind(&HtraceEventParser::SuspendResumeEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_WORKQUEUE_EXECUTE_START), + std::bind(&HtraceEventParser::WorkqueueExecuteStartEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_WORKQUEUE_EXECUTE_END), + std::bind(&HtraceEventParser::WorkqueueExecuteEndEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_CLOCK_SET_RATE), + std::bind(&HtraceEventParser::ClockSetRateEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_CLOCK_ENABLE), + std::bind(&HtraceEventParser::ClockEnableEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_CLOCK_DISABLE), + std::bind(&HtraceEventParser::ClockDisableEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_CLK_SET_RATE), + std::bind(&HtraceEventParser::ClkSetRateEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_CLK_ENABLE), + std::bind(&HtraceEventParser::ClkEnableEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_CLK_DISABLE), + std::bind(&HtraceEventParser::ClkDisableEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_IRQ_HANDLER_ENTRY), + std::bind(&HtraceEventParser::IrqHandlerEntryEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_IRQ_HANDLER_EXIT), + std::bind(&HtraceEventParser::IrqHandlerExitEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_SOFTIRQ_ENTRY), + std::bind(&HtraceEventParser::SoftIrqEntryEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_SOFTIRQ_RAISE), + std::bind(&HtraceEventParser::SoftIrqRaiseEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_SOFTIRQ_EXIT), + std::bind(&HtraceEventParser::SoftIrqExitEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_SYS_ENTRY), + std::bind(&HtraceEventParser::SysEnterEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_SYS_EXIT), + std::bind(&HtraceEventParser::SysExitEvent, this, std::placeholders::_1)}, + {config_.eventNameMap_.at(TRACE_EVENT_OOM_SCORE_ADJ_UPDATE), + std::bind(&HtraceEventParser::OomScoreAdjUdate, this, std::placeholders::_1)}}; +} + +HtraceEventParser::~HtraceEventParser() +{ + TS_LOGI("thread count:%u", static_cast(tids_.size())); + TS_LOGI("process count:%u", static_cast(pids_.size())); + TS_LOGI("ftrace ts MIN:%llu, MAX:%llu", static_cast(traceStartTime_), + static_cast(traceEndTime_)); +} +void HtraceEventParser::ParseDataItem(const FtraceCpuDetailMsg* cpuDetail, BuiltinClocks clock) +{ + eventCpu_ = cpuDetail->cpu(); + auto events = cpuDetail->event(); + if (!events.size()) { + return; + } + if (cpuDetail->overwrite()) { + if (!lastOverwrite_) { + lastOverwrite_ = cpuDetail->overwrite(); + } + if (lastOverwrite_ != cpuDetail->overwrite()) { + TS_LOGW("lost events:%lu", cpuDetail->overwrite() - lastOverwrite_); + lastOverwrite_ = cpuDetail->overwrite(); + } + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_OTHER, STAT_EVENT_DATA_LOST); + } + // parser cpu event + for (auto i = 0; i < events.size(); i++) { + auto event = cpuDetail->event(i); + eventTimestamp_ = event.timestamp(); + traceStartTime_ = std::min(traceStartTime_, eventTimestamp_); + traceEndTime_ = std::max(traceEndTime_, eventTimestamp_); + eventTimestamp_ = streamFilters_->clockFilter_->ToPrimaryTraceTime(clock, eventTimestamp_); + traceDataCache_->UpdateTraceTime(eventTimestamp_); + if (event.tgid() != INVALID_INT32) { + eventPid_ = event.tgid(); + if (!pids_.count(eventPid_)) { + pids_.insert(eventPid_); + } + streamFilters_->processFilter_->GetOrCreateThreadWithPid(eventPid_, eventPid_); + } + if (event.has_sched_switch_format()) { + InvokeFunc(TRACE_EVENT_SCHED_SWITCH, event.sched_switch_format()); + } else if (event.has_task_rename_format()) { + InvokeFunc(TRACE_EVENT_TASK_RENAME, event.task_rename_format()); + } else if (event.has_task_newtask_format()) { + InvokeFunc(TRACE_EVENT_TASK_NEWTASK, event.task_newtask_format()); + } else if (event.has_sched_wakeup_format()) { + InvokeFunc(TRACE_EVENT_SCHED_WAKEUP, event.sched_wakeup_format()); + } else if (event.has_sched_wakeup_new_format()) { + InvokeFunc(TRACE_EVENT_SCHED_WAKEUP, event.sched_wakeup_new_format()); + } else if (event.has_sched_process_exit_format()) { + InvokeFunc(TRACE_EVENT_PROCESS_EXIT, event.sched_process_exit_format()); + } else if (event.has_sched_process_free_format()) { + InvokeFunc(TRACE_EVENT_PROCESS_FREE, event.sched_process_free_format()); + } else if (event.has_sched_waking_format()) { + InvokeFunc(TRACE_EVENT_SCHED_WAKING, event.sched_waking_format()); + } else if (event.has_cpu_idle_format()) { + InvokeFunc(TRACE_EVENT_CPU_IDLE, event.cpu_idle_format()); + } else if (event.has_cpu_frequency_format()) { + InvokeFunc(TRACE_EVENT_CPU_FREQUENCY, event.cpu_frequency_format()); + } else if (event.has_print_format()) { + InvokeFunc(TRACE_EVENT_PRINT, event.print_format()); + } else if (event.has_suspend_resume_format()) { + InvokeFunc(TRACE_EVENT_SUSPEND_RESUME, event.suspend_resume_format()); + } else if (event.has_workqueue_execute_start_format()) { + InvokeFunc(TRACE_EVENT_WORKQUEUE_EXECUTE_START, event.workqueue_execute_start_format()); + } else if (event.has_workqueue_execute_end_format()) { + InvokeFunc(TRACE_EVENT_WORKQUEUE_EXECUTE_END, event.workqueue_execute_end_format()); + } else if (event.has_clock_disable_format()) { + InvokeFunc(TRACE_EVENT_CLOCK_DISABLE, event.clock_disable_format()); + } else if (event.has_clock_enable_format()) { + InvokeFunc(TRACE_EVENT_CLOCK_ENABLE, event.clock_enable_format()); + } else if (event.has_clock_set_rate_format()) { + InvokeFunc(TRACE_EVENT_CLOCK_SET_RATE, event.clock_set_rate_format()); + } else if (event.has_clk_disable_format()) { + InvokeFunc(TRACE_EVENT_CLK_DISABLE, event.clk_disable_format()); + } else if (event.has_clk_enable_format()) { + InvokeFunc(TRACE_EVENT_CLK_ENABLE, event.clk_enable_format()); + } else if (event.has_clk_set_rate_format()) { + InvokeFunc(TRACE_EVENT_CLK_SET_RATE, event.clk_set_rate_format()); + } else if (event.has_sys_enter_format()) { + InvokeFunc(TRACE_EVENT_SYS_ENTRY, event.sys_enter_format()); + } else if (event.has_sys_exit_format()) { + InvokeFunc(TRACE_EVENT_SYS_EXIT, event.sys_exit_format()); + } else if (event.has_binder_transaction_format()) { + InvokeFunc(TRACE_EVENT_BINDER_TRANSACTION, event.binder_transaction_format()); + } else if (event.has_binder_transaction_received_format()) { + InvokeFunc(TRACE_EVENT_BINDER_TRANSACTION_RECEIVED, event.binder_transaction_received_format()); + } else if (event.has_binder_transaction_alloc_buf_format()) { + InvokeFunc(TRACE_EVENT_BINDER_TRANSACTION_ALLOC_BUF, event.binder_transaction_alloc_buf_format()); + } else if (event.has_binder_lock_format()) { + InvokeFunc(TRACE_EVENT_BINDER_TRANSACTION_LOCK, event.binder_lock_format()); + } else if (event.has_binder_unlock_format()) { + InvokeFunc(TRACE_EVENT_BINDER_TRANSACTION_UNLOCK, event.binder_unlock_format()); + } else if (event.has_binder_locked_format()) { + InvokeFunc(TRACE_EVENT_BINDER_TRANSACTION_LOCKED, event.binder_locked_format()); + } else if (event.has_irq_handler_entry_format()) { + InvokeFunc(TRACE_EVENT_IRQ_HANDLER_ENTRY, event.irq_handler_entry_format()); + } else if (event.has_irq_handler_exit_format()) { + InvokeFunc(TRACE_EVENT_IRQ_HANDLER_EXIT, event.irq_handler_exit_format()); + } else if (event.has_softirq_entry_format()) { + InvokeFunc(TRACE_EVENT_SOFTIRQ_ENTRY, event.softirq_entry_format()); + } else if (event.has_softirq_exit_format()) { + InvokeFunc(TRACE_EVENT_SOFTIRQ_EXIT, event.softirq_exit_format()); + } else if (event.has_oom_score_adj_update_format()) { + InvokeFunc(TRACE_EVENT_OOM_SCORE_ADJ_UPDATE, event.softirq_exit_format()); + } else if (event.has_signal_generate_format()) { + InvokeFunc(TRACE_EVENT_SIGNAL_GENERATE, event.signal_generate_format()); + } else if (event.has_signal_deliver_format()) { + InvokeFunc(TRACE_EVENT_SIGNAL_DELIVER, event.signal_deliver_format()); + } else { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_OTHER, + STAT_EVENT_NOTSUPPORTED); + TS_LOGD("has_rpc_socket_shutdown_format\n"); + } + } +} + +bool HtraceEventParser::BinderTractionAllocBufEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_BINDER_TRANSACTION_ALLOC_BUF, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + uint64_t dataSize = msg.data_size(); + uint64_t offsetsSize = msg.offsets_size(); + streamFilters_->binderFilter_->TransactionAllocBuf(eventTimestamp_, eventPid_, dataSize, offsetsSize); + TS_LOGD("dataSize:%lu, offsetSize:%lu", dataSize, offsetsSize); + return true; +} +bool HtraceEventParser::BinderTractionEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_BINDER_TRANSACTION, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + int32_t destNode = msg.target_node(); + int32_t destTgid = msg.to_proc(); + int32_t destTid = msg.to_thread(); + int32_t transactionId = msg.debug_id(); + bool isReply = msg.reply() == 1; + uint32_t flags = msg.flags(); + TS_LOGD("destNode:%d, destTgid:%d, destTid:%d, transactionId:%d, isReply:%d flags:%d, code:%d", destNode, destTgid, + destTid, transactionId, isReply, flags, msg.code()); + streamFilters_->binderFilter_->SendTraction(eventTimestamp_, eventPid_, transactionId, destNode, destTgid, destTid, + isReply, flags, msg.code()); + return true; +} +bool HtraceEventParser::BinderTractionReceivedEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_BINDER_TRANSACTION_RECEIVED, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + int32_t transactionId = msg.debug_id(); + streamFilters_->binderFilter_->ReceiveTraction(eventTimestamp_, eventPid_, transactionId); + TS_LOGD("transactionId:%d", transactionId); + return true; +} +bool HtraceEventParser::BinderTractionLockEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_BINDER_TRANSACTION_LOCK, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + std::string tag = msg.tag(); + streamFilters_->binderFilter_->TractionLock(eventTimestamp_, eventPid_, tag); + TS_LOGD("tag:%s", tag.c_str()); + return true; +} +bool HtraceEventParser::BinderTractionLockedEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_BINDER_TRANSACTION_LOCKED, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + std::string tag = msg.tag(); + streamFilters_->binderFilter_->TractionLocked(eventTimestamp_, eventPid_, tag); + return true; +} +bool HtraceEventParser::BinderTractionUnLockEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_BINDER_TRANSACTION_UNLOCK, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + std::string tag = msg.tag(); + TS_LOGD("tag:%s", tag.c_str()); + streamFilters_->binderFilter_->TractionUnlock(eventTimestamp_, eventPid_, tag); + return true; +} +bool HtraceEventParser::SchedSwitchEvent(const MessageLite& event) +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SCHED_SWITCH, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + uint32_t prevPrioValue = msg.prev_prio(); + uint32_t nextPrioValue = msg.next_prio(); + uint32_t prevPidValue = msg.prev_pid(); + uint32_t nextPidValue = msg.next_pid(); + if (!tids_.count(prevPidValue)) { + tids_.insert(prevPidValue); + } + if (!tids_.count(nextPidValue)) { + tids_.insert(nextPidValue); + } + std::string prevCommStr = msg.prev_comm(); + std::string nextCommStr = msg.next_comm(); + auto prevState = msg.prev_state(); + + auto nextInternalTid = + streamFilters_->processFilter_->UpdateOrCreateThreadWithName(eventTimestamp_, nextPidValue, nextCommStr); + auto uprevtid = + streamFilters_->processFilter_->UpdateOrCreateThreadWithName(eventTimestamp_, prevPidValue, prevCommStr); + streamFilters_->cpuFilter_->InsertSwitchEvent(eventTimestamp_, eventCpu_, uprevtid, + static_cast(prevPrioValue), prevState, nextInternalTid, + static_cast(nextPrioValue)); + return true; +} +bool HtraceEventParser::ProcessExitEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_PROCESS_EXIT, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + uint32_t pidValue = msg.pid(); + std::string commStr = msg.comm(); + auto iTid = streamFilters_->processFilter_->UpdateOrCreateThreadWithName(eventTimestamp_, pidValue, commStr); + if (streamFilters_->cpuFilter_->InsertProcessExitEvent(eventTimestamp_, eventCpu_, iTid)) { + return true; + } else { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_PROCESS_EXIT, STAT_EVENT_NOTMATCH); + return false; + } +} +bool HtraceEventParser::ProcessFreeEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_PROCESS_FREE, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + uint32_t pidValue = msg.pid(); + std::string commStr = msg.comm(); + auto iTid = streamFilters_->processFilter_->UpdateOrCreateThreadWithName(eventTimestamp_, pidValue, commStr); + if (streamFilters_->cpuFilter_->InsertProcessFreeEvent(eventTimestamp_, iTid)) { + return true; + } else { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_PROCESS_FREE, STAT_EVENT_NOTMATCH); + return false; + } +} +bool HtraceEventParser::TaskRenameEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_TASK_RENAME, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + auto prevCommStr = msg.newcomm(); + auto pidValue = msg.pid(); + streamFilters_->processFilter_->UpdateOrCreateProcessWithName(pidValue, prevCommStr); + return true; +} +bool HtraceEventParser::TaskNewtaskEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_TASK_NEWTASK, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + auto commonStr = msg.comm(); + auto pidValue = msg.pid(); + + uint32_t ftracePid = 0; + if (eventPid_ != INVALID_UINT32) { + ftracePid = eventPid_; + } + + static const uint32_t threadPid = 2; + static const uint32_t cloneThread = 0x00010000; + auto cloneFlags = msg.clone_flags(); + if ((cloneFlags & cloneThread) == 0 && ftracePid != threadPid) { + streamFilters_->processFilter_->UpdateOrCreateProcessWithName(pidValue, commonStr); + } else if (ftracePid == threadPid) { + streamFilters_->processFilter_->GetOrCreateThreadWithPid(pidValue, threadPid); + } + return true; +} +bool HtraceEventParser::ParsePrintEvent(const MessageLite& event) +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_PRINT, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + printEventParser_.ParsePrintEvent(eventTimestamp_, eventPid_, msg.buf().c_str()); + if (!pids_.count(eventPid_)) { + pids_.insert(eventPid_); + } + return true; +} +bool HtraceEventParser::SchedWakeupEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SCHED_WAKEUP, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + DataIndex name = traceDataCache_->GetDataIndex(std::string_view("sched_wakeup")); + auto instants = traceDataCache_->GetInstantsData(); + + InternalTid internalTid = streamFilters_->processFilter_->UpdateOrCreateThread(eventTimestamp_, msg.pid()); + instants->AppendInstantEventData(eventTimestamp_, name, internalTid); + + std::optional targetCpu = msg.target_cpu(); + if (targetCpu.has_value()) { + traceDataCache_->GetRawData()->AppendRawData(0, eventTimestamp_, RAW_SCHED_WAKEUP, targetCpu.value(), + internalTid); + } else { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SCHED_WAKEUP, STAT_EVENT_DATA_INVALID); + } + return true; +} +bool HtraceEventParser::SchedWakeupNewEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SCHED_WAKEUP_NEW, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + DataIndex name = traceDataCache_->GetDataIndex(std::string_view("sched_wakeup_new")); + auto instants = traceDataCache_->GetInstantsData(); + + InternalTid internalTid = streamFilters_->processFilter_->UpdateOrCreateThread(eventTimestamp_, msg.pid()); + instants->AppendInstantEventData(eventTimestamp_, name, internalTid); + + std::optional targetCpu = msg.target_cpu(); + if (targetCpu.has_value()) { + traceDataCache_->GetRawData()->AppendRawData(0, eventTimestamp_, RAW_SCHED_WAKEUP, targetCpu.value(), + internalTid); + } else { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SCHED_WAKEUP_NEW, STAT_EVENT_DATA_INVALID); + } + return true; +} +bool HtraceEventParser::SchedWakingEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SCHED_WAKING, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + std::optional wakePidValue = msg.pid(); + if (!wakePidValue.has_value()) { + TS_LOGD("Failed to convert wake_pid"); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SCHED_WAKING, STAT_EVENT_DATA_INVALID); + return false; + } + DataIndex name = traceDataCache_->GetDataIndex(std::string_view("sched_waking")); + auto instants = traceDataCache_->GetInstantsData(); + InternalTid internalTid = streamFilters_->processFilter_->UpdateOrCreateThread(eventTimestamp_, eventPid_); + instants->AppendInstantEventData(eventTimestamp_, name, internalTid); + streamFilters_->cpuFilter_->InsertWakeupEvent(eventTimestamp_, internalTid); + std::optional targetCpu = msg.target_cpu(); + if (targetCpu.has_value()) { + traceDataCache_->GetRawData()->AppendRawData(0, eventTimestamp_, RAW_SCHED_WAKING, targetCpu.value(), + internalTid); + } + return true; +} +bool HtraceEventParser::CpuIdleEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CPU_IDLE, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + std::optional eventCpuValue = msg.cpu_id(); + std::optional newStateValue = msg.state(); + if (!eventCpuValue.has_value()) { + TS_LOGW("Failed to convert event cpu"); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CPU_IDLE, STAT_EVENT_DATA_INVALID); + return false; + } + if (!newStateValue.has_value()) { + TS_LOGW("Failed to convert state"); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CPU_IDLE, STAT_EVENT_DATA_INVALID); + return false; + } + auto cpuIdleNameIndex = traceDataCache_->GetDataIndex(std::string_view("cpu_idle")); + + streamFilters_->cpuMeasureFilter_->AppendNewMeasureData(eventCpuValue.value(), cpuIdleNameIndex, eventTimestamp_, + newStateValue.value()); + + // Add cpu_idle event to raw_data_table + traceDataCache_->GetRawData()->AppendRawData(0, eventTimestamp_, RAW_CPU_IDLE, eventCpuValue.value(), 0); + return true; +} +bool HtraceEventParser::CpuFrequencyEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CPU_FREQUENCY, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + std::optional newStateValue = msg.state(); + std::optional eventCpuValue = msg.cpu_id(); + + if (!newStateValue.has_value()) { + TS_LOGW("Failed to convert state"); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CPU_FREQUENCY, STAT_EVENT_DATA_INVALID); + return false; + } + if (!eventCpuValue.has_value()) { + TS_LOGW("Failed to convert event cpu"); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CPU_FREQUENCY, STAT_EVENT_DATA_INVALID); + return false; + } + auto cpuidleNameIndex = traceDataCache_->GetDataIndex(std::string_view("cpu_frequency")); + + streamFilters_->cpuMeasureFilter_->AppendNewMeasureData(eventCpuValue.value(), cpuidleNameIndex, eventTimestamp_, + newStateValue.value()); + return true; +} +bool HtraceEventParser::SuspendResumeEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SUSPEND_RESUME, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + int32_t val = msg.val(); + uint32_t start = msg.start(); + std::string action = msg.action(); + UNUSED(val); + UNUSED(start); + UNUSED(action); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SUSPEND_RESUME, STAT_EVENT_NOTSUPPORTED); + return true; +} +bool HtraceEventParser::WorkqueueExecuteStartEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_WORKQUEUE_EXECUTE_START, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + auto result = streamFilters_->sliceFilter_->BeginSlice(eventTimestamp_, eventPid_, eventPid_, workQueueId_, + streamFilters_->symbolsFilter_->GetFunc(msg.function())); + + traceDataCache_->GetInternalSlicesData()->AppendDistributeInfo(); + UNUSED(result); + return true; +} +bool HtraceEventParser::WorkqueueExecuteEndEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_WORKQUEUE_EXECUTE_END, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + if (streamFilters_->sliceFilter_->EndSlice(eventTimestamp_, eventPid_, eventPid_)) { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_WORKQUEUE_EXECUTE_END, STAT_EVENT_NOTMATCH); + } + return true; +} +bool HtraceEventParser::ClockSetRateEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CLOCK_SET_RATE, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + DataIndex nameIndex = traceDataCache_->GetDataIndex(msg.name()); + streamFilters_->clockRateFilter_->AppendNewMeasureData(msg.cpu_id(), nameIndex, eventTimestamp_, msg.state()); + return true; +} +bool HtraceEventParser::ClockEnableEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CLOCK_ENABLE, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + DataIndex nameIndex = traceDataCache_->GetDataIndex(msg.name()); + streamFilters_->clockEnableFilter_->AppendNewMeasureData(msg.cpu_id(), nameIndex, eventTimestamp_, msg.state()); + return true; +} +bool HtraceEventParser::ClockDisableEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CLOCK_DISABLE, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + DataIndex nameIndex = traceDataCache_->GetDataIndex(msg.name()); + streamFilters_->clockDisableFilter_->AppendNewMeasureData(msg.cpu_id(), nameIndex, eventTimestamp_, msg.state()); + return true; +} +bool HtraceEventParser::ClkSetRateEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CLK_SET_RATE, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + DataIndex nameIndex = traceDataCache_->GetDataIndex(msg.name()); + streamFilters_->clkRateFilter_->AppendNewMeasureData(eventCpu_, nameIndex, eventTimestamp_, msg.rate()); + return true; +} +bool HtraceEventParser::ClkEnableEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CLK_ENABLE, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + DataIndex nameIndex = traceDataCache_->GetDataIndex(msg.name()); + streamFilters_->clkEnableFilter_->AppendNewMeasureData(eventCpu_, nameIndex, eventTimestamp_, 1); + return true; +} +bool HtraceEventParser::ClkDisableEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_CLK_DISABLE, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + DataIndex nameIndex = traceDataCache_->GetDataIndex(msg.name()); + streamFilters_->clkDisableFilter_->AppendNewMeasureData(eventCpu_, nameIndex, eventTimestamp_, 0); + return true; +} + +bool HtraceEventParser::IrqHandlerEntryEvent(const MessageLite& event) const +{ + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_IRQ_HANDLER_ENTRY, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + auto name = std::string_view(msg.name()); + streamFilters_->irqFilter_->IrqHandlerEntry(eventTimestamp_, eventCpu_, traceDataCache_->GetDataIndex(name)); + return true; +} +bool HtraceEventParser::IrqHandlerExitEvent(const MessageLite& event) const +{ + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_IRQ_HANDLER_EXIT, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + streamFilters_->irqFilter_->IrqHandlerExit(eventTimestamp_, eventCpu_, static_cast(msg.ret())); + return true; +} +bool HtraceEventParser::SoftIrqEntryEvent(const MessageLite& event) const +{ + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_SOFTIRQ_ENTRY, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + streamFilters_->irqFilter_->SoftIrqEntry(eventTimestamp_, eventCpu_, static_cast(msg.vec())); + return true; +} +bool HtraceEventParser::SoftIrqRaiseEvent(const MessageLite& event) const +{ + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_SOFTIRQ_RAISE, STAT_EVENT_RECEIVED); + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_SOFTIRQ_RAISE, STAT_EVENT_NOTSUPPORTED); + return true; +} +bool HtraceEventParser::SoftIrqExitEvent(const MessageLite& event) const +{ + traceDataCache_->GetStatAndInfo()->IncreaseStat(TRACE_EVENT_SOFTIRQ_EXIT, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + streamFilters_->irqFilter_->SoftIrqExit(eventTimestamp_, eventCpu_, static_cast(msg.vec())); + return true; +} +bool HtraceEventParser::SysEnterEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SYS_ENTRY, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + DataIndex nameIndex = traceDataCache_->GetDataIndex("sys_enter"); + auto ipid = streamFilters_->processFilter_->UpdateOrCreateThread(eventTimestamp_, eventPid_); + traceDataCache_->GetSysCallData()->AppendSysCallData(msg.id(), nameIndex, ipid, eventTimestamp_, 0); + return true; +} +bool HtraceEventParser::SysExitEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_SYS_EXIT, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + DataIndex nameIndex = traceDataCache_->GetDataIndex("sys_exit"); + auto ipid = streamFilters_->processFilter_->UpdateOrCreateThread(eventTimestamp_, eventPid_); + traceDataCache_->GetSysCallData()->AppendSysCallData(msg.id(), nameIndex, ipid, eventTimestamp_, msg.ret()); + return true; +} + +bool HtraceEventParser::OomScoreAdjUdate(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_OOM_SCORE_ADJ_UPDATE, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + DataIndex nameIndex = traceDataCache_->GetDataIndex("oom_score_adj"); + streamFilters_->processMeasureFilter_->AppendNewMeasureData(msg.pid(), nameIndex, eventTimestamp_, + msg.oom_score_adj()); + return true; +} + +bool HtraceEventParser::SignalGenerateEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_BLOCK_BIO_BACKMERGE, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + InternalTid internalTid = streamFilters_->processFilter_->UpdateOrCreateThreadWithName(eventTimestamp_, msg.pid(), + msg.comm()); + streamFilters_->threadFilter_->AppendNewMeasureData(internalTid, signalGenerateId_, eventTimestamp_, msg.sig()); + return true; +} +bool HtraceEventParser::SignalDeleverEvent(const MessageLite& event) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_BLOCK_BIO_BACKMERGE, STAT_EVENT_RECEIVED); + const auto msg = static_cast(event); + InternalTid internalTid = streamFilters_->processFilter_->UpdateOrCreateThread(eventTimestamp_, eventPid_); + streamFilters_->threadFilter_->AppendNewMeasureData(internalTid, signalDeliverId_, eventTimestamp_, msg.sig()); + return true; +} +bool HtraceEventParser::InvokeFunc(const SupportedTraceEventType& eventType, + const MessageLite& msgBase) +{ + auto eventName = config_.eventNameMap_.find(eventType); + if (eventName == config_.eventNameMap_.end()) { + // log warn + streamFilters_->statFilter_->IncreaseStat(eventType, STAT_EVENT_NOTSUPPORTED); + return false; + } + auto it = eventToFunctionMap_.find(eventName->second); + if (it == eventToFunctionMap_.end()) { + // log warn + streamFilters_->statFilter_->IncreaseStat(eventType, STAT_EVENT_NOTSUPPORTED); + return false; + } + it->second(msgBase); + return true; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_event_parser/htrace_event_parser.h b/host/trace_streamer/src/parser/htrace_parser/htrace_event_parser/htrace_event_parser.h new file mode 100644 index 0000000..05ca103 --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_event_parser/htrace_event_parser.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef HTRACE_EVENT_PARSER_H +#define HTRACE_EVENT_PARSER_H +#include +#include +#include +#include +#include +#include +#include + +#include "event_parser_base.h" +#include "google/protobuf/message_lite.h" +#include "log.h" +#include "print_event_parser.h" +#include "trace_data/trace_data_cache.h" +#include "trace_plugin_result.pb.h" +#include "trace_streamer_filters.h" +#include "ts_common.h" + +namespace SysTuning { +namespace TraceStreamer { +using namespace google::protobuf; +class HtraceEventParser : private EventParserBase { +public: + HtraceEventParser(TraceDataCache* dataCache, const TraceStreamerFilters* filter); + ~HtraceEventParser(); + void ParseDataItem(const FtraceCpuDetailMsg* cpuDetail, BuiltinClocks clock); +private: + bool BinderTractionEvent(const MessageLite& event) const; + bool BinderTractionReceivedEvent(const MessageLite& event) const; + bool BinderTractionAllocBufEvent(const MessageLite& event) const; + bool BinderTractionLockEvent(const MessageLite& event) const; + bool BinderTractionLockedEvent(const MessageLite& event) const; + bool BinderTractionUnLockEvent(const MessageLite& event) const; + bool SchedSwitchEvent(const MessageLite& event); + bool ProcessExitEvent(const MessageLite& event) const; + bool ProcessFreeEvent(const MessageLite& event) const; + bool TaskRenameEvent(const MessageLite& event) const; + bool TaskNewtaskEvent(const MessageLite& event) const; + bool ParsePrintEvent(const MessageLite& event); + bool SchedWakeupEvent(const MessageLite& event) const; + bool SchedWakeupNewEvent(const MessageLite& event) const; + bool SchedWakingEvent(const MessageLite& event) const; + bool CpuIdleEvent(const MessageLite& event) const; + bool CpuFrequencyEvent(const MessageLite& event) const; + bool SuspendResumeEvent(const MessageLite& event) const; + bool WorkqueueExecuteStartEvent(const MessageLite& event) const; + bool WorkqueueExecuteEndEvent(const MessageLite& event) const; + bool ClockSetRateEvent(const MessageLite& event) const; + bool ClockEnableEvent(const MessageLite& event) const; + bool ClockDisableEvent(const MessageLite& event) const; + bool ClkSetRateEvent(const MessageLite& event) const; + bool ClkEnableEvent(const MessageLite& event) const; + bool ClkDisableEvent(const MessageLite& event) const; + bool IrqHandlerEntryEvent(const MessageLite& event) const; + bool IrqHandlerExitEvent(const MessageLite& event) const; + bool SoftIrqEntryEvent(const MessageLite& event) const; + bool SoftIrqRaiseEvent(const MessageLite& event) const; + bool SoftIrqExitEvent(const MessageLite& event) const; + bool SysEnterEvent(const MessageLite& event) const; + bool SysExitEvent(const MessageLite& event) const; + bool OomScoreAdjUdate(const MessageLite& event) const; + bool SignalGenerateEvent(const MessageLite& event) const; + bool SignalDeleverEvent(const MessageLite& event) const; + bool InvokeFunc(const SupportedTraceEventType& eventType, const MessageLite& msgBase); + using FuncCall = std::function; + uint32_t eventCpu_ = INVALID_UINT32; + uint64_t eventTimestamp_ = INVALID_UINT64; + uint32_t eventPid_ = INVALID_UINT32; + std::map eventToFunctionMap_ = {}; + std::unordered_set tids_ = {}; + std::unordered_set pids_ = {}; + DataIndex workQueueId_ = 0; + PrintEventParser printEventParser_; + uint64_t lastOverwrite_ = 0; + DataIndex signalGenerateId_ = traceDataCache_->GetDataIndex("signal_generate"); + DataIndex signalDeliverId_ = traceDataCache_->GetDataIndex("signal_deliver"); + uint64_t traceStartTime_ = std::numeric_limits::max(); + uint64_t traceEndTime_ = 0; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // HTRACE_EVENT_PARSER_H_ diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_hidump_parser.cpp b/host/trace_streamer/src/parser/htrace_parser/htrace_hidump_parser.cpp new file mode 100644 index 0000000..7742db9 --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_hidump_parser.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "clock_filter.h" +#include "hidump_plugin_result.pb.h" +#include "htrace_event_parser.h" +#include "process_filter.h" +#include "stat_filter.h" +#include "htrace_hidump_parser.h" +namespace SysTuning { +namespace TraceStreamer { +HtraceHidumpParser::HtraceHidumpParser(TraceDataCache* dataCache, const TraceStreamerFilters* ctx) + : streamFilters_(ctx), traceDataCache_(dataCache) +{ + if (!traceDataCache_) { + TS_LOGE("traceDataCache_ should not be null"); + return; + } + if (!streamFilters_) { + TS_LOGE("streamFilters_ should not be null"); + return; + } +} + +HtraceHidumpParser::~HtraceHidumpParser() +{ + TS_LOGI("FPS data ts MIN:%llu, MAX:%llu", + static_cast(traceStartTime_), static_cast(traceEndTime_)); +} +void HtraceHidumpParser::Parse(HidumpInfo& tracePacket) +{ + if (!tracePacket.fps_event_size()) { + return; + } + if (!traceDataCache_) { + TS_LOGE("traceDataCache_ should not be null"); + return; + } + if (!streamFilters_) { + TS_LOGE("streamFilters_ should not be null"); + return; + } + for (int i = 0; i < tracePacket.fps_event_size(); i++) { + streamFilters_->statFilter_->IncreaseStat(TRACE_HIDUMP_FPS, STAT_EVENT_RECEIVED); + auto hidumpData = tracePacket.mutable_fps_event(i); + auto timeStamp = hidumpData->time().tv_nsec() + hidumpData->time().tv_sec() * SEC_TO_NS; + auto newTimeStamp = streamFilters_->clockFilter_->ToPrimaryTraceTime(hidumpData->id(), timeStamp); + if (newTimeStamp != timeStamp) { // record the time only when the time is valid + traceStartTime_ = std::min(traceStartTime_, newTimeStamp); + traceEndTime_ = std::max(traceEndTime_, newTimeStamp); + } else { + streamFilters_->statFilter_->IncreaseStat(TRACE_HIDUMP_FPS, STAT_EVENT_DATA_INVALID); + } + auto fps = hidumpData->fps(); + traceDataCache_->GetHidumpData()->AppendNewHidumpInfo(newTimeStamp, fps); + } +} +void HtraceHidumpParser::Finish() +{ + traceDataCache_->MixTraceTime(traceStartTime_, traceEndTime_); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_hidump_parser.h b/host/trace_streamer/src/parser/htrace_parser/htrace_hidump_parser.h new file mode 100644 index 0000000..dc4cf21 --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_hidump_parser.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef HTRACE_HIDUMP_PARSER_H +#define HTRACE_HIDUMP_PARSER_H +#include +#include +#include +#include "trace_data/trace_data_cache.h" +#include "trace_streamer_config.h" +#include "trace_streamer_filters.h" + + +namespace SysTuning { +namespace TraceStreamer { +class HtraceHidumpParser { +public: + HtraceHidumpParser(TraceDataCache* dataCache, const TraceStreamerFilters* ctx); + ~HtraceHidumpParser(); + void Parse(HidumpInfo& tracePacket); + void Finish(); + +private: + const TraceStreamerFilters* streamFilters_; + TraceDataCache* traceDataCache_; + TraceStreamerConfig config_ = {}; + uint64_t traceStartTime_ = std::numeric_limits::max(); + uint64_t traceEndTime_ = 0; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // HTRACE_HIDUMP_PARSER_H diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_hilog_parser.cpp b/host/trace_streamer/src/parser/htrace_parser/htrace_hilog_parser.cpp new file mode 100644 index 0000000..ad3e12f --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_hilog_parser.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "htrace_hilog_parser.h" +#include "clock_filter.h" +#include "htrace_event_parser.h" +#include "process_filter.h" +#include "stat_filter.h" +namespace SysTuning { +namespace TraceStreamer { +HtraceHiLogParser::HtraceHiLogParser(TraceDataCache* dataCache, const TraceStreamerFilters* ctx) + : streamFilters_(ctx), traceDataCache_(dataCache) +{ + if (!traceDataCache_) { + TS_LOGE("traceDataCache_ should not be null"); + return; + } + if (!streamFilters_) { + TS_LOGE("streamFilters_ should not be null"); + return; + } +} + +HtraceHiLogParser::~HtraceHiLogParser() +{ + TS_LOGI("hilog ts MIN:%llu, MAX:%llu", + static_cast(traceStartTime_), static_cast(traceEndTime_)); +} +void HtraceHiLogParser::Parse(HilogInfo& tracePacket) +{ + if (!tracePacket.info_size()) { + return; + } + if (!traceDataCache_) { + TS_LOGE("traceDataCache_ should not be null"); + return; + } + if (!streamFilters_) { + TS_LOGE("streamFilters_ should not be null"); + return; + } + for (int i = 0; i < tracePacket.info_size(); i++) { + auto hilogLine = tracePacket.mutable_info(i); + uint64_t curLineSeq = hilogLine->id(); + streamFilters_->statFilter_->IncreaseStat(TRACE_HILOG, STAT_EVENT_RECEIVED); + if (curLineSeq < lastLineSeq_ + 1) { + streamFilters_->statFilter_->IncreaseStat(TRACE_HILOG, STAT_EVENT_NOTMATCH); + } else if (curLineSeq > lastLineSeq_ + 1) { + streamFilters_->statFilter_->IncreaseStat(TRACE_HILOG, STAT_EVENT_DATA_LOST); + } + lastLineSeq_ = curLineSeq; + auto logData = traceDataCache_->GetDataIndex(hilogLine->context().c_str()); + auto logDetails = hilogLine->detail(); + + streamFilters_->processFilter_->GetOrCreateThreadWithPid(logDetails.tid(), logDetails.pid()); + auto iter = logLevelString_.find(logDetails.level()); + if (iter == logLevelString_.end()) { + streamFilters_->statFilter_->IncreaseStat(TRACE_HILOG, STAT_EVENT_DATA_INVALID); + TS_LOGD("log level do not exit!!!"); + continue; + } + + auto timeStamp = logDetails.tv_nsec() + logDetails.tv_sec() * SEC_TO_NS; + auto newTimeStamp = streamFilters_->clockFilter_->ToPrimaryTraceTime(TS_CLOCK_REALTIME, timeStamp); + if (newTimeStamp != timeStamp) { // record the time only when the time is valid + traceStartTime_ = std::min(traceStartTime_, newTimeStamp); + traceEndTime_ = std::max(traceEndTime_, newTimeStamp); + } else { + streamFilters_->statFilter_->IncreaseStat(TRACE_HILOG, STAT_EVENT_DATA_INVALID); + } + + DataIndex levelData = traceDataCache_->dataDict_.GetStringIndex(iter->second.c_str()); + DataIndex logTag = traceDataCache_->dataDict_.GetStringIndex(logDetails.tag().c_str()); + traceDataCache_->GetHilogData()->AppendNewLogInfo(curLineSeq, newTimeStamp, logDetails.pid(), logDetails.tid(), + levelData, logTag, logData, timeStamp); + } +} +void HtraceHiLogParser::Finish() +{ + traceDataCache_->MixTraceTime(traceStartTime_, traceEndTime_); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_hilog_parser.h b/host/trace_streamer/src/parser/htrace_parser/htrace_hilog_parser.h new file mode 100644 index 0000000..e889f95 --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_hilog_parser.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef HTRACE_HILOG_PARSER_H +#define HTRACE_HILOG_PARSER_H +#include +#include +#include +#include "trace_data/trace_data_cache.h" +#include "hilog_plugin_result.pb.h" +#include "trace_streamer_config.h" +#include "trace_streamer_filters.h" + + +namespace SysTuning { +namespace TraceStreamer { +class HtraceHiLogParser { +public: + HtraceHiLogParser(TraceDataCache* dataCache, const TraceStreamerFilters* ctx); + ~HtraceHiLogParser(); + void Parse(HilogInfo& tracePacket); + std::map logLevelString_ = {{TS_DEBUG, "D"}, + {TS_ERROR, "E"}, + {TS_INFO, "I"}, + {TS_VERBOSE, "V"}, + {TS_WARN, "W"}}; + void Finish(); + +private: + const TraceStreamerFilters* streamFilters_; + TraceDataCache* traceDataCache_; + TraceStreamerConfig config_ = {}; + uint64_t lastLineSeq_ = 0; + uint64_t traceStartTime_ = std::numeric_limits::max(); + uint64_t traceEndTime_ = 0; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // HTRACE_HILOG_PARSER_H diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_mem_parser.cpp b/host/trace_streamer/src/parser/htrace_parser/htrace_mem_parser.cpp new file mode 100644 index 0000000..55ebfb0 --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_mem_parser.cpp @@ -0,0 +1,863 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "htrace_mem_parser.h" +#include "clock_filter.h" +#include "htrace_event_parser.h" +#include "measure_filter.h" +#include "process_filter.h" +#include "stat_filter.h" +#include "symbols_filter.h" +#include "system_event_measure_filter.h" +namespace SysTuning { +namespace TraceStreamer { +HtraceMemParser::HtraceMemParser(TraceDataCache* dataCache, const TraceStreamerFilters* ctx) + : streamFilters_(ctx), traceDataCache_(dataCache) +{ + if (!traceDataCache_) { + TS_LOGE("traceDataCache_ should not be null"); + return; + } + if (!streamFilters_) { + TS_LOGE("streamFilters_ should not be null"); + return; + } + for (auto i = 0; i < MEM_MAX; i++) { + memNameDictMap_.insert( + std::make_pair(static_cast(i), + traceDataCache_->GetDataIndex(config_.memNameMap_.at(static_cast(i))))); + } + for (auto i = 0; i < SysMeminfoType::PMEM_CMA_FREE + 1; i++) { + sysMemNameDictMap_.insert( + std::make_pair(static_cast(i), + traceDataCache_->GetDataIndex(config_.sysMemNameMap_.at(static_cast(i))))); + } + for (auto i = 0; i < SysVMeminfoType::VMEMINFO_WORKINGSET_RESTORE + 1; i++) { + sysVMemNameDictMap_.insert(std::make_pair( + static_cast(i), + traceDataCache_->GetDataIndex(config_.sysVirtualMemNameMap_.at(static_cast(i))))); + } +} + +HtraceMemParser::~HtraceMemParser() +{ + TS_LOGI("mem ts MIN:%llu, MAX:%llu", static_cast(traceStartTime_), + static_cast(traceEndTime_)); +} +void HtraceMemParser::Parse(const MemoryData& tracePacket, uint64_t timeStamp, BuiltinClocks clock) +{ + if (!traceDataCache_) { + TS_LOGE("traceDataCache_ should not be null"); + return; + } + if (!streamFilters_) { + TS_LOGE("streamFilters_ should not be null"); + return; + } + auto newTimeStamp = streamFilters_->clockFilter_->ToPrimaryTraceTime(clock, timeStamp); + if (newTimeStamp != timeStamp) { // record the time only when the time is valid + traceStartTime_ = std::min(traceStartTime_, newTimeStamp); + traceEndTime_ = std::max(traceEndTime_, newTimeStamp); + } + if (tracePacket.processesinfo_size()) { + ParseProcessInfo(tracePacket, newTimeStamp); + } + if (tracePacket.meminfo_size()) { + ParseMemInfo(tracePacket, newTimeStamp); + } + if (tracePacket.vmeminfo_size()) { + ParseVMemInfo(tracePacket, newTimeStamp); + } +} +void HtraceMemParser::ParseProcessInfo(const MemoryData& tracePacket, uint64_t timeStamp) const +{ + if (tracePacket.processesinfo_size()) { + streamFilters_->statFilter_->IncreaseStat(TRACE_MEMORY, STAT_EVENT_RECEIVED); + } + for (int i = 0; i < tracePacket.processesinfo_size(); i++) { + auto memInfo = tracePacket.processesinfo(i); + auto ipid = streamFilters_->processFilter_->UpdateOrCreateProcessWithName(memInfo.pid(), memInfo.name()); + streamFilters_->processMeasureFilter_->AppendNewMeasureData(ipid, memNameDictMap_.at(MEM_VM_SIZE), timeStamp, + memInfo.vm_size_kb()); + streamFilters_->processMeasureFilter_->AppendNewMeasureData(ipid, memNameDictMap_.at(MEM_VM_RSS), timeStamp, + memInfo.vm_rss_kb()); + streamFilters_->processMeasureFilter_->AppendNewMeasureData(ipid, memNameDictMap_.at(MEM_VM_ANON), timeStamp, + memInfo.rss_anon_kb()); + streamFilters_->processMeasureFilter_->AppendNewMeasureData(ipid, memNameDictMap_.at(MEM_RSS_FILE), timeStamp, + memInfo.rss_file_kb()); + streamFilters_->processMeasureFilter_->AppendNewMeasureData(ipid, memNameDictMap_.at(MEM_RSS_SHMEM), timeStamp, + memInfo.rss_shmem_kb()); + streamFilters_->processMeasureFilter_->AppendNewMeasureData(ipid, memNameDictMap_.at(MEM_VM_SWAP), timeStamp, + memInfo.vm_swap_kb()); + streamFilters_->processMeasureFilter_->AppendNewMeasureData(ipid, memNameDictMap_.at(MEM_VM_LOCKED), timeStamp, + memInfo.vm_locked_kb()); + streamFilters_->processMeasureFilter_->AppendNewMeasureData(ipid, memNameDictMap_.at(MEM_VM_HWM), timeStamp, + memInfo.vm_hwm_kb()); + streamFilters_->processMeasureFilter_->AppendNewMeasureData(ipid, memNameDictMap_.at(MEM_OOM_SCORE_ADJ), + timeStamp, memInfo.oom_score_adj()); + } +} + +void HtraceMemParser::ParseMemInfoEasy(const MemoryData& tracePacket, uint64_t timeStamp) const +{ + if (tracePacket.meminfo_size()) { + streamFilters_->statFilter_->IncreaseStat(TRACE_SYS_MEMORY, STAT_EVENT_RECEIVED); + } + for (int i = 0; i < tracePacket.meminfo_size(); i++) { + auto memInfo = tracePacket.meminfo(i); + if (config_.sysMemNameMap_.find(memInfo.key()) != config_.sysMemNameMap_.end()) { + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData(sysMemNameDictMap_.at(memInfo.key()), + timeStamp, memInfo.value()); + } else { + streamFilters_->statFilter_->IncreaseStat(TRACE_SYS_MEMORY, STAT_EVENT_DATA_INVALID); + } + } +} + +void HtraceMemParser::ParseVMemInfoEasy(const MemoryData& tracePacket, uint64_t timeStamp) const +{ + traceDataCache_->UpdateTraceTime(timeStamp); + if (tracePacket.vmeminfo_size()) { + streamFilters_->statFilter_->IncreaseStat(TRACE_SYS_VIRTUAL_MEMORY, STAT_EVENT_RECEIVED); + } + for (int i = 0; i < tracePacket.vmeminfo_size(); i++) { + auto memInfo = tracePacket.vmeminfo(i); + if (config_.sysVirtualMemNameMap_.find(memInfo.key()) != config_.sysVirtualMemNameMap_.end()) { + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData(sysVMemNameDictMap_.at(memInfo.key()), + timeStamp, memInfo.value()); + } else { + streamFilters_->statFilter_->IncreaseStat(TRACE_SYS_VIRTUAL_MEMORY, STAT_EVENT_DATA_INVALID); + } + } +} + +void HtraceMemParser::ParseMemInfo(const MemoryData& tracePacket, uint64_t timeStamp) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_SYS_MEMORY, STAT_EVENT_RECEIVED); + for (int i = 0; i < tracePacket.meminfo_size(); i++) { + auto vMemInfo = tracePacket.meminfo(i); + switch (static_cast(vMemInfo.key())) { + case SysMeminfoType::PMEM_UNSPECIFIED: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_UNSPECIFIED), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_MEM_TOTAL: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_MEM_TOTAL), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_MEM_FREE: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_MEM_FREE), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_MEM_AVAILABLE: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_MEM_AVAILABLE), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_BUFFERS: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_BUFFERS), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_CACHED: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_CACHED), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_SWAP_CACHED: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_SWAP_CACHED), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_ACTIVE: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_ACTIVE), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_INACTIVE: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_INACTIVE), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_ACTIVE_ANON: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_ACTIVE_ANON), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_INACTIVE_ANON: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_INACTIVE_ANON), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_ACTIVE_FILE: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_ACTIVE_FILE), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_INACTIVE_FILE: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_INACTIVE_FILE), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_UNEVICTABLE: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_UNEVICTABLE), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_MLOCKED: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_MLOCKED), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_SWAP_TOTAL: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_SWAP_TOTAL), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_SWAP_FREE: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_SWAP_FREE), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_DIRTY: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_DIRTY), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_WRITEBACK: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_WRITEBACK), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_ANON_PAGES: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_ANON_PAGES), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_MAPPED: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_MAPPED), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_SHMEM: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_SHMEM), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_SLAB: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_SLAB), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_SLAB_RECLAIMABLE: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_SLAB_RECLAIMABLE), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_SLAB_UNRECLAIMABLE: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_SLAB_UNRECLAIMABLE), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_KERNEL_STACK: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_KERNEL_STACK), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_PAGE_TABLES: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_PAGE_TABLES), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_COMMIT_LIMIT: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_COMMIT_LIMIT), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_COMMITED_AS: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_COMMITED_AS), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_VMALLOC_TOTAL: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_VMALLOC_TOTAL), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_VMALLOC_USED: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_VMALLOC_USED), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_VMALLOC_CHUNK: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_VMALLOC_CHUNK), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_CMA_TOTAL: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_CMA_TOTAL), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType::PMEM_CMA_FREE: + streamFilters_->sysEventMemMeasureFilter_->AppendNewMeasureData( + sysMemNameDictMap_.at(SysMeminfoType::PMEM_CMA_FREE), timeStamp, vMemInfo.value()); + break; + case SysMeminfoType_INT_MIN_SENTINEL_DO_NOT_USE_: + case SysMeminfoType_INT_MAX_SENTINEL_DO_NOT_USE_: + default: + streamFilters_->statFilter_->IncreaseStat(TRACE_SYS_MEMORY, STAT_EVENT_DATA_INVALID); + break; + } + } +} +void HtraceMemParser::ParseVMemInfo(const MemoryData& tracePacket, uint64_t timeStamp) const +{ + streamFilters_->statFilter_->IncreaseStat(TRACE_SYS_VIRTUAL_MEMORY, STAT_EVENT_RECEIVED); + for (int i = 0; i < tracePacket.vmeminfo_size(); i++) { + auto vMemInfo = tracePacket.vmeminfo(i); + switch (static_cast(vMemInfo.key())) { + case SysVMeminfoType::VMEMINFO_UNSPECIFIED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_UNSPECIFIED), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_FREE_PAGES: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_FREE_PAGES), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_ALLOC_BATCH: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_ALLOC_BATCH), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_INACTIVE_ANON: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_INACTIVE_ANON), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_ACTIVE_ANON: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_ACTIVE_ANON), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_INACTIVE_FILE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_INACTIVE_FILE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_ACTIVE_FILE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_ACTIVE_FILE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_UNEVICTABLE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_UNEVICTABLE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_MLOCK: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_MLOCK), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_ANON_PAGES: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_ANON_PAGES), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_MAPPED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_MAPPED), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_FILE_PAGES: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_FILE_PAGES), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_DIRTY: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_DIRTY), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_WRITEBACK: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_WRITEBACK), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_SLAB_RECLAIMABLE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_SLAB_RECLAIMABLE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_SLAB_UNRECLAIMABLE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_SLAB_UNRECLAIMABLE), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_PAGE_TABLE_PAGES: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_PAGE_TABLE_PAGES), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_KERNEL_STACK: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_KERNEL_STACK), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_OVERHEAD: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_OVERHEAD), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_UNSTABLE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_UNSTABLE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_BOUNCE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_BOUNCE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_VMSCAN_WRITE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_VMSCAN_WRITE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_VMSCAN_IMMEDIATE_RECLAIM: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_VMSCAN_IMMEDIATE_RECLAIM), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_WRITEBACK_TEMP: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_WRITEBACK_TEMP), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_ISOLATED_ANON: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_ISOLATED_ANON), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_ISOLATED_FILE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_ISOLATED_FILE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_SHMEM: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_SHMEM), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_DIRTIED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_DIRTIED), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_WRITTEN: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_WRITTEN), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_PAGES_SCANNED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_PAGES_SCANNED), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_WORKINGSET_REFAULT: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_WORKINGSET_REFAULT), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_WORKINGSET_ACTIVATE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_WORKINGSET_ACTIVATE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_WORKINGSET_NODERECLAIM: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_WORKINGSET_NODERECLAIM), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_ANON_TRANSPARENT_HUGEPAGES: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_ANON_TRANSPARENT_HUGEPAGES), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_FREE_CMA: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_FREE_CMA), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_SWAPCACHE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_SWAPCACHE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_DIRTY_THRESHOLD: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_DIRTY_THRESHOLD), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_DIRTY_BACKGROUND_THRESHOLD: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_DIRTY_BACKGROUND_THRESHOLD), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGPGIN: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGPGIN), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGPGOUT: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGPGOUT), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGPGOUTCLEAN: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGPGOUTCLEAN), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PSWPIN: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PSWPIN), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PSWPOUT: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PSWPOUT), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGALLOC_DMA: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGALLOC_DMA), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGALLOC_NORMAL: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGALLOC_NORMAL), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGALLOC_MOVABLE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGALLOC_MOVABLE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGFREE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGFREE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGACTIVATE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGACTIVATE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGDEACTIVATE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGDEACTIVATE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGFAULT: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGFAULT), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGMAJFAULT: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGMAJFAULT), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGREFILL_DMA: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGREFILL_DMA), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGREFILL_NORMAL: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGREFILL_NORMAL), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGREFILL_MOVABLE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGREFILL_MOVABLE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSTEAL_KSWAPD_DMA: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSTEAL_KSWAPD_DMA), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSTEAL_KSWAPD_NORMAL: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSTEAL_KSWAPD_NORMAL), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSTEAL_KSWAPD_MOVABLE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSTEAL_KSWAPD_MOVABLE), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSTEAL_DIRECT_DMA: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSTEAL_DIRECT_DMA), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSTEAL_DIRECT_NORMAL: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSTEAL_DIRECT_NORMAL), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSTEAL_DIRECT_MOVABLE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSTEAL_DIRECT_MOVABLE), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSCAN_KSWAPD_DMA: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSCAN_KSWAPD_DMA), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSCAN_KSWAPD_NORMAL: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSCAN_KSWAPD_NORMAL), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSCAN_KSWAPD_MOVABLE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSCAN_KSWAPD_MOVABLE), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSCAN_DIRECT_DMA: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSCAN_DIRECT_DMA), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSCAN_DIRECT_NORMAL: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSCAN_DIRECT_NORMAL), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSCAN_DIRECT_MOVABLE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSCAN_DIRECT_MOVABLE), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSCAN_DIRECT_THROTTLE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSCAN_DIRECT_THROTTLE), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGINODESTEAL: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGINODESTEAL), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_SLABS_SCANNED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_SLABS_SCANNED), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_KSWAPD_INODESTEAL: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_KSWAPD_INODESTEAL), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_KSWAPD_LOW_WMARK_HIT_QUICKLY: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_KSWAPD_LOW_WMARK_HIT_QUICKLY), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_KSWAPD_HIGH_WMARK_HIT_QUICKLY: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_KSWAPD_HIGH_WMARK_HIT_QUICKLY), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PAGEOUTRUN: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PAGEOUTRUN), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_ALLOCSTALL: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_ALLOCSTALL), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGROTATED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGROTATED), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_DROP_PAGECACHE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_DROP_PAGECACHE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_DROP_SLAB: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_DROP_SLAB), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGMIGRATE_SUCCESS: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGMIGRATE_SUCCESS), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGMIGRATE_FAIL: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGMIGRATE_FAIL), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_COMPACT_MIGRATE_SCANNED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_COMPACT_MIGRATE_SCANNED), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_COMPACT_FREE_SCANNED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_COMPACT_FREE_SCANNED), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_COMPACT_ISOLATED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_COMPACT_ISOLATED), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_COMPACT_STALL: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_COMPACT_STALL), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_COMPACT_FAIL: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_COMPACT_FAIL), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_COMPACT_SUCCESS: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_COMPACT_SUCCESS), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_COMPACT_DAEMON_WAKE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_COMPACT_DAEMON_WAKE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_CULLED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_CULLED), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_SCANNED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_SCANNED), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_RESCUED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_RESCUED), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_MLOCKED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_MLOCKED), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_MUNLOCKED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_MUNLOCKED), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_CLEARED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_CLEARED), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_STRANDED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_UNEVICTABLE_PGS_STRANDED), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_ZSPAGES: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_ZSPAGES), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_ION_HEAP: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_ION_HEAP), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_GPU_HEAP: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_GPU_HEAP), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_ALLOCSTALL_DMA: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_ALLOCSTALL_DMA), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_ALLOCSTALL_MOVABLE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_ALLOCSTALL_MOVABLE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_ALLOCSTALL_NORMAL: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_ALLOCSTALL_NORMAL), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_COMPACT_DAEMON_FREE_SCANNED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_COMPACT_DAEMON_FREE_SCANNED), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_COMPACT_DAEMON_MIGRATE_SCANNED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_COMPACT_DAEMON_MIGRATE_SCANNED), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_FASTRPC: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_FASTRPC), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_INDIRECTLY_RECLAIMABLE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_INDIRECTLY_RECLAIMABLE), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_ION_HEAP_POOL: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_ION_HEAP_POOL), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_KERNEL_MISC_RECLAIMABLE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_KERNEL_MISC_RECLAIMABLE), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_SHADOW_CALL_STACK_BYTES: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_SHADOW_CALL_STACK_BYTES), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_SHMEM_HUGEPAGES: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_SHMEM_HUGEPAGES), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_SHMEM_PMDMAPPED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_SHMEM_PMDMAPPED), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_UNRECLAIMABLE_PAGES: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_UNRECLAIMABLE_PAGES), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_ZONE_ACTIVE_ANON: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_ZONE_ACTIVE_ANON), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_ZONE_ACTIVE_FILE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_ZONE_ACTIVE_FILE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_ZONE_INACTIVE_ANON: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_ZONE_INACTIVE_ANON), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_ZONE_INACTIVE_FILE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_ZONE_INACTIVE_FILE), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_ZONE_UNEVICTABLE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_ZONE_UNEVICTABLE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_NR_ZONE_WRITE_PENDING: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_NR_ZONE_WRITE_PENDING), timeStamp, + vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_OOM_KILL: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_OOM_KILL), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGLAZYFREE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGLAZYFREE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGLAZYFREED: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGLAZYFREED), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGREFILL: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGREFILL), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSCAN_DIRECT: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSCAN_DIRECT), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSCAN_KSWAPD: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSCAN_KSWAPD), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSKIP_DMA: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSKIP_DMA), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSKIP_MOVABLE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSKIP_MOVABLE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSKIP_NORMAL: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSKIP_NORMAL), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSTEAL_DIRECT: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSTEAL_DIRECT), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_PGSTEAL_KSWAPD: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_PGSTEAL_KSWAPD), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_SWAP_RA: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_SWAP_RA), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_SWAP_RA_HIT: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_SWAP_RA_HIT), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType::VMEMINFO_WORKINGSET_RESTORE: + streamFilters_->sysEventVMemMeasureFilter_->AppendNewMeasureData( + sysVMemNameDictMap_.at(SysVMeminfoType::VMEMINFO_WORKINGSET_RESTORE), timeStamp, vMemInfo.value()); + break; + case SysVMeminfoType_INT_MIN_SENTINEL_DO_NOT_USE_: + case SysVMeminfoType_INT_MAX_SENTINEL_DO_NOT_USE_: + default: + streamFilters_->statFilter_->IncreaseStat(TRACE_SYS_VIRTUAL_MEMORY, STAT_EVENT_DATA_INVALID); + } + } +} +void HtraceMemParser::Finish() +{ + traceDataCache_->MixTraceTime(traceStartTime_, traceEndTime_); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_mem_parser.h b/host/trace_streamer/src/parser/htrace_parser/htrace_mem_parser.h new file mode 100644 index 0000000..a5d762b --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_mem_parser.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef HTRACE_MEM_PARSER_H +#define HTRACE_MEM_PARSER_H + +#include +#include +#include +#include +#include +#include "memory_plugin_result.pb.h" +#include "trace_data/trace_data_cache.h" +#include "trace_streamer_config.h" +#include "trace_streamer_filters.h" +namespace SysTuning { +namespace TraceStreamer { +class HtraceMemParser { +public: + HtraceMemParser(TraceDataCache* dataCache, const TraceStreamerFilters* ctx); + ~HtraceMemParser(); + void Parse(const MemoryData& tracePacket, uint64_t, BuiltinClocks clock); + void Finish(); +private: + void ParseProcessInfo(const MemoryData& tracePacket, uint64_t timeStamp) const; + void ParseMemInfo(const MemoryData& tracePacket, uint64_t timeStamp) const; + void ParseMemInfoEasy(const MemoryData& tracePacket, uint64_t timeStamp) const; + void ParseVMemInfo(const MemoryData& tracePacket, uint64_t timeStamp) const; + void ParseVMemInfoEasy(const MemoryData& tracePacket, uint64_t timeStamp) const; + const TraceStreamerFilters* streamFilters_; + TraceDataCache* traceDataCache_; + TraceStreamerConfig config_ = {}; + std::map memNameDictMap_ = {}; + std::map sysMemNameDictMap_ = {}; + std::map sysVMemNameDictMap_ = {}; + uint64_t traceStartTime_ = std::numeric_limits::max(); + uint64_t traceEndTime_ = 0; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // HTRACE_MEM_PARSER_H diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_native_hook_parser.cpp b/host/trace_streamer/src/parser/htrace_parser/htrace_native_hook_parser.cpp new file mode 100644 index 0000000..ef80987 --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_native_hook_parser.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "clock_filter.h" +#include "htrace_event_parser.h" +#include "native_hook_result.pb.h" +#include "process_filter.h" +#include "stat_filter.h" +#include "htrace_native_hook_parser.h" +namespace SysTuning { +namespace TraceStreamer { +uint64_t HtraceNativeHookParser::eventId_ = 0; +HtraceNativeHookParser::HtraceNativeHookParser(TraceDataCache* dataCache, const TraceStreamerFilters* ctx) + : streamFilters_(ctx), traceDataCache_(dataCache), addrToAllocEventRow_(INVALID_UINT64) +{ + if (!traceDataCache_) { + TS_LOGE("traceDataCache_ should not be null"); + return; + } + if (!streamFilters_) { + TS_LOGE("streamFilters_ should not be null"); + return; + } +} + +HtraceNativeHookParser::~HtraceNativeHookParser() +{ + TS_LOGI("native hook data ts MIN:%llu, MAX:%llu", + static_cast(traceStartTime_), static_cast(traceEndTime_)); +} +void HtraceNativeHookParser::Parse(BatchNativeHookData& tracePacket) +{ + if (!tracePacket.events_size()) { + return; + } + if (!traceDataCache_) { + TS_LOGE("traceDataCache_ should not be null"); + return; + } + if (!streamFilters_) { + TS_LOGE("streamFilters_ should not be null"); + return; + } + for (auto i = 0; i < tracePacket.events_size(); i++) { + auto nativeHookData = tracePacket.mutable_events(i); + auto timeStamp = nativeHookData->tv_nsec() + nativeHookData->tv_sec() * SEC_TO_NS; + auto newTimeStamp = streamFilters_->clockFilter_->ToPrimaryTraceTime(TS_CLOCK_REALTIME, timeStamp); + if (newTimeStamp != timeStamp) { // record the time only when the time is valid + traceStartTime_ = std::min(traceStartTime_, newTimeStamp); + traceEndTime_ = std::max(traceEndTime_, newTimeStamp); + } + // kAllocEvent = 3 kFreeEvent = 4 EVENT_NOT_SET = 0 + auto eventCase = nativeHookData->event_case(); + if (eventCase == NativeHookData::kAllocEvent) { + streamFilters_->statFilter_->IncreaseStat(TRACE_NATIVE_HOOK_MALLOC, STAT_EVENT_RECEIVED); + if (newTimeStamp == timeStamp) { + streamFilters_->statFilter_->IncreaseStat(TRACE_NATIVE_HOOK_MALLOC, STAT_EVENT_DATA_INVALID); + } + auto allocEvent = nativeHookData->alloc_event(); + DataIndex allocIndex = traceDataCache_->dataDict_.GetStringIndex("AllocEvent"); + auto itid = streamFilters_->processFilter_->GetOrCreateThreadWithPid(allocEvent.tid(), allocEvent.pid()); + auto ipid = streamFilters_->processFilter_->GetInternalPid(allocEvent.pid()); + auto row = traceDataCache_->GetHeapData()->AppendNewHeapInfo(eventId_, ipid, itid, allocIndex, newTimeStamp, + 0, 0, allocEvent.addr(), allocEvent.size(), + allocEvent.size(), 0); + addrToAllocEventRow_.Insert(ipid, allocEvent.addr(), static_cast(row)); + traceDataCache_->GetHeapData()->UpdateCurrentSizeDur(row, newTimeStamp); + for (auto depth = 0; depth < allocEvent.frame_info_size(); depth++) { + auto allocEventFrame = allocEvent.frame_info(depth); + DataIndex symbolNameIndex = + traceDataCache_->dataDict_.GetStringIndex(allocEventFrame.symbol_name().c_str()); + DataIndex filePathIndex = + traceDataCache_->dataDict_.GetStringIndex(allocEventFrame.file_path().c_str()); + traceDataCache_->GetHeapFrameData()->AppendNewHeapFrameInfo( + eventId_, depth, allocEventFrame.ip(), allocEventFrame.sp(), symbolNameIndex, filePathIndex, + allocEventFrame.offset(), allocEventFrame.symbol_offset()); + } + eventId_++; + } else if (eventCase == NativeHookData::kFreeEvent) { + streamFilters_->statFilter_->IncreaseStat(TRACE_NATIVE_HOOK_FREE, STAT_EVENT_RECEIVED); + if (newTimeStamp == timeStamp) { + streamFilters_->statFilter_->IncreaseStat(TRACE_NATIVE_HOOK_FREE, STAT_EVENT_DATA_INVALID); + } + auto freeEvent = nativeHookData->free_event(); + DataIndex freeIndex = traceDataCache_->dataDict_.GetStringIndex("FreeEvent"); + auto itid = streamFilters_->processFilter_->GetOrCreateThreadWithPid(freeEvent.tid(), freeEvent.pid()); + auto ipid = streamFilters_->processFilter_->GetInternalPid(freeEvent.pid()); + int64_t freeHeapSize = 0; + auto row = addrToAllocEventRow_.Find(ipid, freeEvent.addr()); + if (row != INVALID_UINT64 && newTimeStamp > traceDataCache_->GetHeapData()->TimeStamData()[row]) { + addrToAllocEventRow_.Erase(ipid, freeEvent.addr()); + traceDataCache_->GetHeapData()->UpdateHeapDuration(row, newTimeStamp); + freeHeapSize = traceDataCache_->GetHeapData()->HeapSizes()[row]; + } else if (row == INVALID_UINT64) { + TS_LOGW("func addr:%lu is empty", freeEvent.addr()); + streamFilters_->statFilter_->IncreaseStat(TRACE_NATIVE_HOOK_FREE, STAT_EVENT_DATA_INVALID); + } + row = traceDataCache_->GetHeapData()->AppendNewHeapInfo(eventId_, ipid, itid, freeIndex, newTimeStamp, 0, 0, + freeEvent.addr(), freeHeapSize, -freeHeapSize, 0); + traceDataCache_->GetHeapData()->UpdateCurrentSizeDur(row, newTimeStamp); + for (auto depth = 0; depth < freeEvent.frame_info_size(); depth++) { + auto freeEventFrame = freeEvent.frame_info(depth); + DataIndex symbolNameIndex = + traceDataCache_->dataDict_.GetStringIndex(freeEventFrame.symbol_name().c_str()); + DataIndex filePathIndex = traceDataCache_->dataDict_.GetStringIndex(freeEventFrame.file_path().c_str()); + traceDataCache_->GetHeapFrameData()->AppendNewHeapFrameInfo( + eventId_, depth, freeEventFrame.ip(), freeEventFrame.sp(), symbolNameIndex, filePathIndex, + freeEventFrame.offset(), freeEventFrame.symbol_offset()); + } + eventId_++; + } else { + TS_LOGE("An unknown type of data was received!"); + } + } +} +void HtraceNativeHookParser::Finish() +{ + traceDataCache_->MixTraceTime(traceStartTime_, traceEndTime_); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_native_hook_parser.h b/host/trace_streamer/src/parser/htrace_parser/htrace_native_hook_parser.h new file mode 100644 index 0000000..6e1cfa9 --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_native_hook_parser.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef HTRACE_NATIVE_HOOK_PARSER_H +#define HTRACE_NATIVE_HOOK_PARSER_H +#include +#include +#include +#include "double_map.h" +#include "trace_data/trace_data_cache.h" +#include "trace_streamer_config.h" +#include "trace_streamer_filters.h" + + +namespace SysTuning { +namespace TraceStreamer { +class HtraceNativeHookParser { +public: + HtraceNativeHookParser(TraceDataCache* dataCache, const TraceStreamerFilters* ctx); + ~HtraceNativeHookParser(); + void Parse(BatchNativeHookData& tracePacket); + void Finish(); + +private: + const TraceStreamerFilters* streamFilters_; + TraceDataCache* traceDataCache_; + TraceStreamerConfig config_ = {}; + uint64_t traceStartTime_ = std::numeric_limits::max(); + uint64_t traceEndTime_ = 0; + static uint64_t eventId_; + DoubleMap addrToAllocEventRow_; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // HTRACE_NATIVE_HOOK_PARSER_H diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_parser.cpp b/host/trace_streamer/src/parser/htrace_parser/htrace_parser.cpp new file mode 100644 index 0000000..9893c5d --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_parser.cpp @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "htrace_parser.h" +#include +#include "binder_filter.h" +#include "cpu_filter.h" +#include "ftrace_event.pb.h" +#include "log.h" +#include "memory_plugin_result.pb.h" +#include "services/common_types.pb.h" +#include "stat_filter.h" +#include "trace_plugin_config.pb.h" +#include "trace_plugin_result.pb.h" +namespace SysTuning { +namespace TraceStreamer { +HtraceParser::HtraceParser(TraceDataCache* dataCache, const TraceStreamerFilters* filters) + : ParserBase(filters), + htraceCpuDetailParser_(std::make_unique(dataCache, filters)), + htraceSymbolsDetailParser_(std::make_unique(dataCache, filters)), + htraceMemParser_(std::make_unique(dataCache, filters)), + htraceClockDetailParser_(std::make_unique(dataCache, filters)), + htraceHiLogParser_(std::make_unique(dataCache, filters)), + htraceNativeHookParser_(std::make_unique(dataCache, filters)), + htraceHidumpParser_(std::make_unique(dataCache, filters)), + dataSegArray(new HtraceDataSegment[MAX_SEG_ARRAY_SIZE]) +{ +#ifdef SUPPORTTHREAD + noThread_ = false; +#endif +} + +HtraceParser::~HtraceParser() +{ + TS_LOGI("clockid 2 is for RealTime and 1 is for BootTime"); +} + +void HtraceParser::WaitForParserEnd() +{ + if (parseThreadStarted_ || filterThreadStarted_) { + toExit_ = true; + while (!exited_) { + usleep(sleepDur_ * sleepDur_); + } + } + streamFilters_->cpuFilter_->FinishCpuEvent(); + streamFilters_->binderFilter_->FinishBinderEvent(); + htraceHiLogParser_->Finish(); + htraceMemParser_->Finish(); + htraceNativeHookParser_->Finish(); + htraceHidumpParser_->Finish(); +} + +void HtraceParser::ParseTraceDataItem(const std::string& buffer) +{ + int head = rawDataHead_; + while (!toExit_) { + if (!noThread_ && dataSegArray[head].status.load() != TS_PARSE_STATUS_INIT) { + usleep(sleepDur_); + continue; + } + dataSegArray[head].seg = std::move(buffer); + dataSegArray[head].status = TS_PARSE_STATUS_SEPRATED; + if (!noThread_) { + rawDataHead_ = (rawDataHead_ + 1) % MAX_SEG_ARRAY_SIZE; + } + break; + } + if (!parseThreadStarted_ && !noThread_) { + parseThreadStarted_ = true; + int tmp = maxThread_; + while (tmp--) { + parserThreadCount_++; + std::thread ParseTypeThread(&HtraceParser::ParseThread, this); + ParseTypeThread.detach(); + TS_LOGI("parser Thread:%d/%d start working ...\n", maxThread_ - tmp, maxThread_); + } + } + if (noThread_) { + ParserData(dataSegArray[head]); + } +} +void HtraceParser::FilterData(HtraceDataSegment& seg) +{ + if (seg.dataType == DATA_SOURCE_TYPE_TRACE) { + if (seg.traceData.ftrace_cpu_detail_size()) { + htraceCpuDetailParser_->Parse(seg.traceData, clock_); // has Event + } + if (seg.traceData.symbols_detail_size()) { + htraceSymbolsDetailParser_->Parse(seg.traceData); // has Event + } + if (seg.traceData.clocks_detail_size()) { + htraceClockDetailParser_->Parse(seg.traceData); // has Event + } + } else if (seg.dataType == DATA_SOURCE_TYPE_MEM) { + htraceMemParser_->Parse(seg.memData, seg.timeStamp, seg.clockId); + } else if (seg.dataType == DATA_SOURCE_TYPE_HILOG) { + htraceHiLogParser_->Parse(seg.logData); + } else if (seg.dataType == DATA_SOURCE_TYPE_HEAP) { + htraceNativeHookParser_->Parse(seg.batchNativeHookData); + } else if (seg.dataType == DATA_SOURCE_TYPE_FPS) { + htraceHidumpParser_->Parse(seg.hidumpInfo); + } + if (!noThread_) { + filterHead_ = (filterHead_ + 1) % MAX_SEG_ARRAY_SIZE; + } + seg.status = TS_PARSE_STATUS_INIT; +} +void HtraceParser::FilterThread() +{ + while (1) { + HtraceDataSegment& seg = dataSegArray[filterHead_]; + if (seg.status.load() == TS_PARSE_STATUS_INVALID) { + seg.status = TS_PARSE_STATUS_INIT; + filterHead_ = (filterHead_ + 1) % MAX_SEG_ARRAY_SIZE; + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_OTHER, STAT_EVENT_DATA_INVALID); + TS_LOGI("seprateHead_d:\t%d, parseHead_:\t%d, filterHead_:\t%d\n", rawDataHead_, parseHead_, filterHead_); + continue; + } + if (seg.status.load() != TS_PARSE_STATUS_PARSED) { + if (toExit_ && !parserThreadCount_) { + TS_LOGI("exiting ParseLine Thread"); + exited_ = true; + filterThreadStarted_ = false; + TS_LOGD("seprateHead:\t%d, parseHead_:\t%d, filterHead_:\t%d, status:%d\n", rawDataHead_, parseHead_, + filterHead_, seg.status.load()); + return; + } + TS_LOGD("seprateHead:\t%d, parseHead_:\t%d, filterHead_:\t%d, status:%d\n", rawDataHead_, parseHead_, + filterHead_, seg.status.load()); + usleep(sleepDur_); + continue; + } + FilterData(seg); + } +} + +void HtraceParser::ParserData(HtraceDataSegment& dataSeg) +{ + ProfilerPluginData pluginData; + if (!pluginData.ParseFromArray(dataSeg.seg.data(), static_cast(dataSeg.seg.length()))) { + TS_LOGW("ProfilerPluginData ParseFromArray failed\n"); + dataSeg.status = TS_PARSE_STATUS_INVALID; + return; + } + if (pluginData.name() == "memory-plugin") { + ParseMemory(pluginData, dataSeg); + } else if (pluginData.name() == "hilog-plugin" || pluginData.name() == "/data/local/tmp/libhilogplugin.z.so") { + ParseHilog(pluginData, dataSeg); + } else if (pluginData.name() == "ftrace-plugin" || pluginData.name() == "/data/local/tmp/libftrace_plugin.z.so") { + ParseFtrace(pluginData, dataSeg); + } else if (pluginData.name() == "nativehook" || pluginData.name() == "hookdaemon") { + ParseNativeHook(pluginData, dataSeg); + } else if (pluginData.name() == "hidump-plugin" || pluginData.name() == "/data/local/tmp/libhidumpplugin.z.so") { + ParseFPS(pluginData, dataSeg); + } else { + TS_LOGW("unrecognized pluginData.name():%s", pluginData.name().c_str()); + } + if (noThread_) { // do it only in wasm mode, wasm noThead_ will be true + FilterData(dataSeg); + } +} +void HtraceParser::ParseThread() +{ + while (1) { + if (!filterThreadStarted_ && !noThread_) { + filterThreadStarted_ = true; + std::thread ParserThread(&HtraceParser::FilterThread, this); + ParserThread.detach(); + } + int head = GetNextSegment(); + if (head < 0) { + if (head == ERROR_CODE_EXIT) { + return; + } else if (head == ERROR_CODE_NODATA) { + continue; + } + } + HtraceDataSegment& dataSeg = dataSegArray[head]; + ParserData(dataSeg); + } +} + +void HtraceParser::ParseMemory(const ProfilerPluginData& pluginData, HtraceDataSegment& dataSeg) +{ + dataSeg.dataType = DATA_SOURCE_TYPE_MEM; + auto timeStamp = pluginData.tv_nsec() + pluginData.tv_sec() * SEC_TO_NS; + BuiltinClocks clockId = TS_CLOCK_REALTIME; + auto clockIdTemp = pluginData.clock_id(); + if (clockIdTemp == ProfilerPluginData_ClockId_CLOCKID_REALTIME) { + clockId = TS_CLOCK_REALTIME; + } + dataSeg.memData.Clear(); + if (!dataSeg.memData.ParseFromArray(pluginData.data().data(), static_cast(pluginData.data().size()))) { + TS_LOGW("tracePacketParseFromArray failed\n"); + dataSeg.status = TS_PARSE_STATUS_INVALID; + return; + } + if (dataSeg.memData.processesinfo_size()) { + dataSeg.dataType = DATA_SOURCE_TYPE_MEM; + dataSeg.timeStamp = timeStamp; + dataSeg.clockId = clockId; + dataSeg.status = TS_PARSE_STATUS_PARSED; + } else if (dataSeg.memData.meminfo_size()) { + dataSeg.dataType = DATA_SOURCE_TYPE_MEM; + dataSeg.timeStamp = timeStamp; + dataSeg.clockId = clockId; + dataSeg.status = TS_PARSE_STATUS_PARSED; + } else if (dataSeg.memData.vmeminfo_size()) { + dataSeg.dataType = DATA_SOURCE_TYPE_MEM; + dataSeg.timeStamp = timeStamp; + dataSeg.clockId = clockId; + dataSeg.status = TS_PARSE_STATUS_PARSED; + } else { + dataSeg.status = TS_PARSE_STATUS_INVALID; + } +} +void HtraceParser::ParseHilog(const ProfilerPluginData& pluginData, HtraceDataSegment& dataSeg) +{ + dataSeg.dataType = DATA_SOURCE_TYPE_HILOG; + dataSeg.traceData.Clear(); + if (!dataSeg.logData.ParseFromArray(pluginData.data().data(), static_cast(pluginData.data().size()))) { + TS_LOGW("tracePacketParseFromArray failed\n"); + dataSeg.status = TS_PARSE_STATUS_PARSED; + return; + } + if (dataSeg.logData.info_size()) { + dataSeg.status = TS_PARSE_STATUS_PARSED; + return; + } + dataSeg.status = TS_PARSE_STATUS_INVALID; +} +void HtraceParser::ParseFtrace(const ProfilerPluginData& pluginData, HtraceDataSegment& dataSeg) +{ + dataSeg.dataType = DATA_SOURCE_TYPE_TRACE; + dataSeg.traceData.Clear(); + if (!dataSeg.traceData.ParseFromArray(pluginData.data().data(), static_cast(pluginData.data().size()))) { + TS_LOGW("tracePacketParseFromArray failed\n"); + dataSeg.status = TS_PARSE_STATUS_INVALID; + return; + } + if (dataSeg.traceData.ftrace_cpu_stats_size()) { + auto cpuStats = dataSeg.traceData.ftrace_cpu_stats(0); + auto s = cpuStats.per_cpu_stats(0); + TS_LOGD("s.overrun():%lu", s.overrun()); + TS_LOGD("s.dropped_events():%lu", s.dropped_events()); + auto clock = cpuStats.trace_clock(); + if (clock == "boot") { + clock_ = TS_CLOCK_BOOTTIME; + } + dataSeg.clockId = clock_; + dataSeg.status = TS_PARSE_STATUS_PARSED; + return; + } + if (dataSeg.traceData.clocks_detail_size() || dataSeg.traceData.ftrace_cpu_detail_size() || + dataSeg.traceData.symbols_detail_size()) { + dataSeg.status = TS_PARSE_STATUS_PARSED; + return; + } + dataSeg.status = TS_PARSE_STATUS_INVALID; +} + +void HtraceParser::ParseNativeHook(const ProfilerPluginData& pluginData, HtraceDataSegment& dataSeg) +{ + dataSeg.dataType = DATA_SOURCE_TYPE_HEAP; + dataSeg.traceData.Clear(); + if (!dataSeg.batchNativeHookData.ParseFromArray(pluginData.data().data(), + static_cast(pluginData.data().size()))) { + TS_LOGW("tracePacketParseFromArray failed\n"); + dataSeg.status = TS_PARSE_STATUS_INVALID; + return; + } + if (dataSeg.batchNativeHookData.events_size()) { + dataSeg.status = TS_PARSE_STATUS_PARSED; + return; + } + dataSeg.status = TS_PARSE_STATUS_INVALID; +} + +void HtraceParser::ParseFPS(const ProfilerPluginData& pluginData, HtraceDataSegment& dataSeg) +{ + dataSeg.dataType = DATA_SOURCE_TYPE_FPS; + dataSeg.traceData.Clear(); + if (!dataSeg.hidumpInfo.ParseFromArray(pluginData.data().data(), static_cast(pluginData.data().size()))) { + TS_LOGW("tracePacketParseFromArray failed\n"); + dataSeg.status = TS_PARSE_STATUS_INVALID; + return; + } + if (dataSeg.hidumpInfo.fps_event_size()) { + dataSeg.status = TS_PARSE_STATUS_PARSED; + return; + } + dataSeg.status = TS_PARSE_STATUS_INVALID; +} +int HtraceParser::GetNextSegment() +{ + int head; + dataSegMux_.lock(); + head = parseHead_; + HtraceDataSegment& seg = dataSegArray[head]; + if (seg.status.load() != TS_PARSE_STATUS_SEPRATED) { + if (toExit_) { + parserThreadCount_--; + TS_LOGI("exiting parser, parserThread Count:%d\n", parserThreadCount_); + TS_LOGD("seprateHead_x:\t%d, parseHead_:\t%d, filterHead_:\t%d status:%d\n", rawDataHead_, parseHead_, + filterHead_, seg.status.load()); + dataSegMux_.unlock(); + if (!parserThreadCount_ && !filterThreadStarted_) { + exited_ = true; + } + return ERROR_CODE_EXIT; + } + if (seg.status.load() == TS_PARSE_STATUS_PARSING) { + dataSegMux_.unlock(); + usleep(sleepDur_); + return ERROR_CODE_NODATA; + } + dataSegMux_.unlock(); + usleep(sleepDur_); + return ERROR_CODE_NODATA; + } + parseHead_ = (parseHead_ + 1) % MAX_SEG_ARRAY_SIZE; + seg.status = TS_PARSE_STATUS_PARSING; + dataSegMux_.unlock(); + return head; +} +void HtraceParser::ParseTraceDataSegment(std::unique_ptr bufferStr, size_t size) +{ + packagesBuffer_.insert(packagesBuffer_.end(), &bufferStr[0], &bufferStr[size]); + auto packagesBegin = packagesBuffer_.begin(); + auto currentLength = packagesBuffer_.size(); + if (!hasGotHeader) { + std::string start(reinterpret_cast(bufferStr.get()), std::min(size, 20)); + if (start.compare(0, std::string("OHOSPROF").length(), "OHOSPROF") == 0) { + currentLength -= PACKET_HEADER_LENGTH; + packagesBegin += PACKET_HEADER_LENGTH; + } + hasGotHeader = true; + } + while (1) { + if (!hasGotSegLength_) { + if (currentLength < PACKET_SEG_LENGTH) { + break; + } + std::string bufferLine(packagesBegin, packagesBegin + PACKET_SEG_LENGTH); + const uint32_t* len = reinterpret_cast(bufferLine.data()); + nextLength_ = *len; + hasGotSegLength_ = true; + currentLength -= PACKET_SEG_LENGTH; + packagesBegin += PACKET_SEG_LENGTH; + } + if (currentLength < nextLength_) { + break; + } + std::string bufferLine(packagesBegin, packagesBegin + nextLength_); + ParseTraceDataItem(bufferLine); + hasGotSegLength_ = false; + packagesBegin += nextLength_; + currentLength -= nextLength_; + } + packagesBuffer_.erase(packagesBuffer_.begin(), packagesBegin); + return; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_parser.h b/host/trace_streamer/src/parser/htrace_parser/htrace_parser.h new file mode 100644 index 0000000..3b0e6a6 --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_parser.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HTRACE_PARSER_H +#define HTRACE_PARSER_H +#include +#include +#include +#include +#include +#include +#include "common_types.h" +#include "htrace_clock_detail_parser.h" +#include "htrace_cpu_detail_parser.h" +#include "htrace_hidump_parser.h" +#include "htrace_hilog_parser.h" +#include "htrace_mem_parser.h" +#include "htrace_native_hook_parser.h" +#include "htrace_symbols_detail_parser.h" +#include "log.h" +#include "parser_base.h" +#include "trace_data/trace_data_cache.h" +#include "trace_streamer_filters.h" + +namespace SysTuning { +namespace TraceStreamer { +class HtraceParser : public ParserBase { +public: + HtraceParser(TraceDataCache* dataCache, const TraceStreamerFilters* filters); + ~HtraceParser(); + void ParseTraceDataSegment(std::unique_ptr bufferStr, size_t size) override; + void WaitForParserEnd(); +private: + void ParseTraceDataItem(const std::string& buffer) override; + void FilterData(HtraceDataSegment& dataSeg); + void ParserData(HtraceDataSegment& dataSeg); +private: + void ParseMemory(const ProfilerPluginData& pluginData, HtraceDataSegment &dataSeg); + void ParseHilog(const ProfilerPluginData& pluginData, HtraceDataSegment &dataSeg); + void ParseFtrace(const ProfilerPluginData& pluginData, HtraceDataSegment &dataSeg); + void ParseNativeHook(const ProfilerPluginData& pluginData, HtraceDataSegment &dataSeg); + void ParseFPS(const ProfilerPluginData& pluginData, HtraceDataSegment &dataSeg); + void ParseThread(); + int GetNextSegment(); + void FilterThread(); + enum ErrorCode { + ERROR_CODE_EXIT = -2, + ERROR_CODE_NODATA = -1 + }; + bool hasGotSegLength_ = false; + bool hasGotHeader = false; + uint32_t nextLength_ = 0; + const size_t PACKET_SEG_LENGTH = 4; + const size_t PACKET_HEADER_LENGTH = 1024; + std::unique_ptr htraceCpuDetailParser_; + std::unique_ptr htraceSymbolsDetailParser_; + std::unique_ptr htraceMemParser_; + std::unique_ptr htraceClockDetailParser_; + std::unique_ptr htraceHiLogParser_; + std::unique_ptr htraceNativeHookParser_; + std::unique_ptr htraceHidumpParser_; + std::atomic filterThreadStarted_{false}; + const int MAX_SEG_ARRAY_SIZE = 10000; + std::unique_ptr dataSegArray; + int rawDataHead_ = 0; + bool toExit_ = false; + bool exited_ = false; + int filterHead_ = 0; + int parseHead_ = 0; + const int sleepDur_ = 100; + bool parseThreadStarted_ = false; + const int maxThread_ = 4; // 4 is the best on ubuntu 113MB/s, max 138MB/s, 6 is best on mac m1 21MB/s, + int parserThreadCount_ = 0; + std::mutex dataSegMux_; + bool noThread_ = true; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // HTRACE_PARSER_H_ diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_symbols_detail_parser.cpp b/host/trace_streamer/src/parser/htrace_parser/htrace_symbols_detail_parser.cpp new file mode 100644 index 0000000..63f22e8 --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_symbols_detail_parser.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "htrace_symbols_detail_parser.h" +#include "htrace_event_parser.h" +#include "symbols_filter.h" +namespace SysTuning { +namespace TraceStreamer { +HtraceSymbolsDetailParser::HtraceSymbolsDetailParser(TraceDataCache* dataCache, const TraceStreamerFilters* ctx) + : streamFilters_(ctx), traceDataCache_(dataCache) +{ + UNUSED(traceDataCache_); + if (!streamFilters_) { + TS_LOGE("streamFilters_ should not be null"); + return; + } +} + +HtraceSymbolsDetailParser::~HtraceSymbolsDetailParser() = default; +void HtraceSymbolsDetailParser::Parse(const TracePluginResult& tracePacket) +{ + if (!tracePacket.symbols_detail_size()) { + return; + } + if (!streamFilters_) { + TS_LOGE("streamFilters_ should not be null"); + return; + } + for (int i = 0; i < tracePacket.symbols_detail_size(); i++) { + auto symbol = const_cast(tracePacket).mutable_symbols_detail(i); + TS_LOGD("symbol_name:%s, symbol_addr:%lu", symbol->symbol_name().c_str(), symbol->symbol_addr()); + // symbol + streamFilters_->symbolsFilter_->RegisterFunc(symbol->symbol_addr(), + traceDataCache_->GetDataIndex(symbol->symbol_name())); + } +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/parser/htrace_parser/htrace_symbols_detail_parser.h b/host/trace_streamer/src/parser/htrace_parser/htrace_symbols_detail_parser.h new file mode 100644 index 0000000..5af70f9 --- /dev/null +++ b/host/trace_streamer/src/parser/htrace_parser/htrace_symbols_detail_parser.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef HTRACE_SYMBOLS_DETAIL_PARSER_H +#define HTRACE_SYMBOLS_DETAIL_PARSER_H + +#include +#include +#include +#include +#include +#include "trace_data/trace_data_cache.h" +#include "trace_plugin_result.pb.h" +#include "trace_streamer_filters.h" +namespace SysTuning { +namespace TraceStreamer { +class HtraceSymbolsDetailParser { +public: + HtraceSymbolsDetailParser(TraceDataCache* dataCache, const TraceStreamerFilters* ctx); + ~HtraceSymbolsDetailParser(); + void Parse(const TracePluginResult& tracePacket); + +private: + const TraceStreamerFilters* streamFilters_; + TraceDataCache* traceDataCache_; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // HTRACE_SYMBOLS_DETAIL_PARSER_H diff --git a/host/trace_streamer/src/parser/parser.pri b/host/trace_streamer/src/parser/parser.pri new file mode 100644 index 0000000..47f8a89 --- /dev/null +++ b/host/trace_streamer/src/parser/parser.pri @@ -0,0 +1,51 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +INCLUDEPATH += $$PWD \ + $$PWD/bytrace_parser \ + $$PWD/htrace_parser/htrace_cpu_parser \ + $$PWD/htrace_parser/htrace_event_parser \ + $$PWD/htrace_parser/htrace_symbol_parser +HEADERS += \ + $$PWD/common_types.h \ + $$PWD/event_parser_base.h \ + $$PWD/thread_state.h \ + $$PWD/print_event_parser.h \ + $$PWD/bytrace_parser/bytrace_parser.h \ + $$PWD/bytrace_parser/bytrace_event_parser.h \ + $$PWD/htrace_parser/htrace_cpu_parser/htrace_cpu_detail_parser.h \ + $$PWD/htrace_parser/htrace_event_parser/htrace_event_parser.h \ + $$PWD/htrace_parser/htrace_parser.h \ + $$PWD/htrace_parser/htrace_mem_parser.h \ + $$PWD/htrace_parser/htrace_hilog_parser.h \ + $$PWD/htrace_parser/htrace_native_hook_parser.h \ + $$PWD/htrace_parser/htrace_hidump_parser.h \ + $$PWD/htrace_parser/htrace_clock_detail_parser.h \ + $$PWD/htrace_parser/htrace_symbols_detail_parser.h + + +SOURCES += \ + $$PWD/parser_base.cpp \ + $$PWD/event_parser_base.cpp \ + $$PWD/thread_state.cpp \ + $$PWD/print_event_parser.cpp \ + $$PWD/bytrace_parser/bytrace_parser.cpp \ + $$PWD/bytrace_parser/bytrace_event_parser.cpp \ + $$PWD/htrace_parser/htrace_cpu_parser/htrace_cpu_detail_parser.cpp \ + $$PWD/htrace_parser/htrace_event_parser/htrace_event_parser.cpp \ + $$PWD/htrace_parser/htrace_parser.cpp \ + $$PWD/htrace_parser/htrace_mem_parser.cpp \ + $$PWD/htrace_parser/htrace_hilog_parser.cpp \ + $$PWD/htrace_parser/htrace_native_hook_parser.cpp \ + $$PWD/htrace_parser/htrace_hidump_parser.cpp \ + $$PWD/htrace_parser/htrace_clock_detail_parser.cpp \ + $$PWD/htrace_parser/htrace_symbols_detail_parser.cpp diff --git a/host/trace_streamer/src/parser/parser_base.cpp b/host/trace_streamer/src/parser/parser_base.cpp new file mode 100644 index 0000000..5f06e9c --- /dev/null +++ b/host/trace_streamer/src/parser/parser_base.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "parser_base.h" +namespace SysTuning { +namespace TraceStreamer { +ParserBase::ParserBase(const TraceStreamerFilters* filter) + : streamFilters_(filter), clock_(TS_CLOCK_UNKNOW) +{ +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/parser/parser_base.h b/host/trace_streamer/src/parser/parser_base.h new file mode 100644 index 0000000..ed5e142 --- /dev/null +++ b/host/trace_streamer/src/parser/parser_base.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_PARSER_BASE_H +#define SRC_PARSER_BASE_H +#include +#include +#include "trace_streamer_filters.h" +#include "ts_common.h" +namespace SysTuning { +namespace TraceStreamer { +class ParserBase { +public: + explicit ParserBase(const TraceStreamerFilters* filter); + virtual ~ParserBase() = default; + virtual void ParseTraceDataSegment(std::unique_ptr, size_t size) = 0; +protected: + virtual void ParseTraceDataItem(const std::string& buffer) = 0; + std::deque packagesBuffer_ = {}; + const TraceStreamerFilters *streamFilters_; + BuiltinClocks clock_; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif diff --git a/host/trace_streamer/src/parser/print_event_parser.cpp b/host/trace_streamer/src/parser/print_event_parser.cpp new file mode 100644 index 0000000..8773bbb --- /dev/null +++ b/host/trace_streamer/src/parser/print_event_parser.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "print_event_parser.h" +#include "stat_filter.h" +#include "string_to_numerical.h" +namespace SysTuning { +namespace TraceStreamer { +PrintEventParser::PrintEventParser(TraceDataCache* dataCache, const TraceStreamerFilters* filter) + : EventParserBase(dataCache, filter), + pointLength_(1), + maxPointLength_(2) +{ +} + +void PrintEventParser::ParsePrintEvent(uint64_t ts, uint32_t pid, std::string_view event) const +{ + TracePoint point; + if (GetTracePoint(event, point) == SUCCESS) { + ParseTracePoint(ts, pid, point); + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_TRACING_MARK_WRITE, STAT_EVENT_RECEIVED); + } else { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_TRACING_MARK_WRITE, STAT_EVENT_DATA_INVALID); + } +} + +void PrintEventParser::ParseTracePoint(uint64_t ts, uint32_t pid, TracePoint point) const +{ + if (point.tgid_) { + streamFilters_->processFilter_->GetOrCreateInternalPid(ts, point.tgid_); + } + switch (point.phase_) { + case 'B': { + if (streamFilters_->sliceFilter_->BeginSlice(ts, pid, point.tgid_, 0, + traceDataCache_->GetDataIndex(point.name_))) { + // add distributed data + traceDataCache_->GetInternalSlicesData()->AppendDistributeInfo( + point.chainId_, point.spanId_, point.parentSpanId_, point.flag_, point.args_); + } else { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_TRACING_MARK_WRITE, STAT_EVENT_DATA_LOST); + } + break; + } + case 'E': { + streamFilters_->sliceFilter_->EndSlice(ts, pid, point.tgid_); + break; + } + case 'S': { + auto cookie = static_cast(point.value_); + streamFilters_->sliceFilter_->StartAsyncSlice(ts, pid, point.tgid_, cookie, + traceDataCache_->GetDataIndex(point.name_)); + break; + } + case 'F': { + auto cookie = static_cast(point.value_); + streamFilters_->sliceFilter_->FinishAsyncSlice(ts, pid, point.tgid_, cookie, + traceDataCache_->GetDataIndex(point.name_)); + break; + } + case 'C': { + DataIndex nameIndex = traceDataCache_->GetDataIndex(point.name_); + uint32_t internalPid = streamFilters_->processFilter_->GetInternalPid(point.tgid_); + if (internalPid != INVALID_ID) { + streamFilters_->processMeasureFilter_->AppendNewMeasureData(internalPid, nameIndex, ts, point.value_); + } else { + streamFilters_->statFilter_->IncreaseStat(TRACE_EVENT_TRACING_MARK_WRITE, STAT_EVENT_DATA_INVALID); + } + break; + } + default: + TS_LOGD("point missing!"); + break; + } +} + +ParseResult PrintEventParser::CheckTracePoint(std::string_view pointStr) const +{ + if (pointStr.size() == 0) { + TS_LOGD("get trace point data size is 0!"); + return ERROR; + } + + std::string clockSyncSts = "trace_event_clock_sync"; + if (pointStr.compare(0, clockSyncSts.length(), clockSyncSts.c_str()) == 0) { + TS_LOGD("skip trace point :%s!", clockSyncSts.c_str()); + return ERROR; + } + + if (pointStr.find_first_of('B') != 0 && pointStr.find_first_of('E') != 0 && pointStr.find_first_of('C') != 0 && + pointStr.find_first_of('S') != 0 && pointStr.find_first_of('F') != 0) { + TS_LOGD("trace point not supported : [%c] !", pointStr[0]); + return ERROR; + } + + if (pointStr.find_first_of('E') != 0 && pointStr.size() == 1) { + TS_LOGD("point string size error!"); + return ERROR; + } + + if (pointStr.size() >= maxPointLength_) { + if ((pointStr[1] != '|') && (pointStr[1] != '\n')) { + TS_LOGD("not support data formart!"); + return ERROR; + } + } + + return SUCCESS; +} + +std::string_view PrintEventParser::GetPointNameForBegin(std::string_view pointStr, size_t tGidlength) const +{ + size_t index = maxPointLength_ + tGidlength + pointLength_; + + size_t length = pointStr.size() - index - ((pointStr.back() == '\n') ? 1 : 0); + std::string_view name = std::string_view(pointStr.data() + index, length); + return name; +} + +ParseResult PrintEventParser::HandlerB(std::string_view pointStr, TracePoint& outPoint, size_t tGidlength) const +{ + outPoint.name_ = GetPointNameForBegin(pointStr, tGidlength); + if (outPoint.name_.empty()) { + TS_LOGD("point name is empty!"); + return ERROR; + } + // Use $# to differentiate distributed data + if (outPoint.name_.find("$#") == std::string::npos) { + return SUCCESS; + } + // Resolve distributed calls + const std::regex distributeMatcher = + std::regex(R"((?:^\[([a-z0-9]+),(\d+),(\d+)\]:?([CS]?)\$#)?(.*)\$#(.*)$)"); + std::smatch matcheLine; + bool matched = std::regex_match(outPoint.name_, matcheLine, distributeMatcher); + if (matched) { + size_t index = 0; + outPoint.chainId_ = matcheLine[++index].str(); + outPoint.spanId_ = matcheLine[++index].str(); + outPoint.parentSpanId_ = matcheLine[++index].str(); + outPoint.flag_ = matcheLine[++index].str(); + outPoint.name_ = matcheLine[++index].str(); + outPoint.args_ = matcheLine[++index].str(); + } + return SUCCESS; +} + +ParseResult PrintEventParser::HandlerE(void) const +{ + return SUCCESS; +} + +size_t PrintEventParser::GetNameLength(std::string_view pointStr, size_t nameIndex) const +{ + size_t namelength = 0; + for (size_t i = nameIndex; i < pointStr.size(); i++) { + if (pointStr[i] == '|') { + namelength = i - nameIndex; + break; + } + } + return namelength; +} + +size_t PrintEventParser::GetValueLength(std::string_view pointStr, size_t valueIndex) const +{ + size_t valuePipe = pointStr.find('|', valueIndex); + size_t valueLen = pointStr.size() - valueIndex; + if (valuePipe != std::string_view::npos) { + valueLen = valuePipe - valueIndex; + } + + if (valueLen == 0) { + return 0; + } + + if (pointStr[valueIndex + valueLen - pointLength_] == '\n') { + valueLen--; + } + + return valueLen; +} + +ParseResult PrintEventParser::HandlerCSF(std::string_view pointStr, TracePoint& outPoint, size_t tGidlength) const +{ + // point name + size_t nameIndex = maxPointLength_ + tGidlength + pointLength_; + size_t namelength = GetNameLength(pointStr, nameIndex); + if (namelength == 0) { + TS_LOGD("point name length is error!"); + return ERROR; + } + outPoint.name_ = std::string_view(pointStr.data() + nameIndex, namelength); + + // point value + size_t valueIndex = nameIndex + namelength + pointLength_; + size_t valueLen = GetValueLength(pointStr, valueIndex); + if (valueLen == 0) { + TS_LOGD("point value length is error!"); + return ERROR; + } + + std::string valueStr(pointStr.data() + valueIndex, valueLen); + if (!base::StrToUInt64(valueStr).has_value()) { + TS_LOGD("point value is error!"); + return ERROR; + } + outPoint.value_ = base::StrToUInt64(valueStr).value(); + + size_t valuePipe = pointStr.find('|', valueIndex); + if (valuePipe != std::string_view::npos) { + size_t groupLen = pointStr.size() - valuePipe - pointLength_; + if (groupLen == 0) { + return ERROR; + } + + if (pointStr[pointStr.size() - pointLength_] == '\n') { + groupLen--; + } + + outPoint.categoryGroup_ = std::string_view(pointStr.data() + valuePipe + 1, groupLen); + } + + return SUCCESS; +} + +ParseResult PrintEventParser::GetTracePoint(std::string_view pointStr, TracePoint& outPoint) const +{ + if (CheckTracePoint(pointStr) != SUCCESS) { + return ERROR; + } + + size_t tGidlength = 0; + + outPoint.phase_ = pointStr.front(); + outPoint.tgid_ = GetThreadGroupId(pointStr, tGidlength); + + ParseResult ret = ERROR; + switch (outPoint.phase_) { + case 'B': { + ret = HandlerB(pointStr, outPoint, tGidlength); + break; + } + case 'E': { + ret = HandlerE(); + break; + } + case 'S': + case 'F': + case 'C': { + ret = HandlerCSF(pointStr, outPoint, tGidlength); + break; + } + default: + return ERROR; + } + return ret; +} + +uint32_t PrintEventParser::GetThreadGroupId(std::string_view pointStr, size_t& length) const +{ + for (size_t i = maxPointLength_; i < pointStr.size(); i++) { + if (pointStr[i] == '|' || pointStr[i] == '\n') { + break; + } + + if (pointStr[i] < '0' || pointStr[i] > '9') { + return ERROR; + } + + length++; + } + + std::string str(pointStr.data() + maxPointLength_, length); + return base::StrToUInt32(str).value_or(0); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/parser/print_event_parser.h b/host/trace_streamer/src/parser/print_event_parser.h new file mode 100644 index 0000000..7131121 --- /dev/null +++ b/host/trace_streamer/src/parser/print_event_parser.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef SRC_PRINT_EVENT_PARSER_H +#define SRC_PRINT_EVENT_PARSER_H +#include +#include +#include "common_types.h" +#include "event_parser_base.h" +#include "filter/measure_filter.h" +#include "filter/process_filter.h" +#include "filter/slice_filter.h" +#include "string_to_numerical.h" +namespace SysTuning { +namespace TraceStreamer { +class PrintEventParser : private EventParserBase { +public: + PrintEventParser(TraceDataCache* dataCache, const TraceStreamerFilters* filter); + void ParsePrintEvent(uint64_t ts, uint32_t pid, std::string_view event) const; +private: + void ParseTracePoint(uint64_t ts, uint32_t pid, TracePoint point) const; + ParseResult GetTracePoint(std::string_view str, TracePoint& out) const; + ParseResult CheckTracePoint(std::string_view pointStr) const; + uint32_t GetThreadGroupId(std::string_view pointStr, size_t& length) const; + std::string_view GetPointNameForBegin(std::string_view pointStr, size_t tGidlength) const; + ParseResult HandlerB(std::string_view pointStr, TracePoint& outPoint, size_t tGidlength) const; + ParseResult HandlerE(void) const; + ParseResult HandlerCSF(std::string_view pointStr, TracePoint& outPoint, size_t tGidlength) const; + size_t GetNameLength(std::string_view pointStr, size_t nameIndex) const; + size_t GetValueLength(std::string_view pointStr, size_t valueIndex) const; +private: + const uint32_t pointLength_; + const uint32_t maxPointLength_; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // SRC_PRINT_EVENT_PARSER_H diff --git a/host/trace_streamer/src/parser/thread_state.cpp b/host/trace_streamer/src/parser/thread_state.cpp new file mode 100644 index 0000000..a373a78 --- /dev/null +++ b/host/trace_streamer/src/parser/thread_state.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "thread_state.h" + +namespace SysTuning { +namespace TraceStreamer { +Direction ThreadState::SetStatByChar(char ch) +{ + if (ch == 'R') { + if (state_ == 0) { + return NEED_CONTINUE; + } + } + + if (statMap_.find(ch) == statMap_.end()) { + return NEED_BREAK; + } + + state_ |= statMap_[ch]; + return NEED_GO; +} + +void ThreadState::ProcessSate(const std::string& stateStr) +{ + for (size_t i = 0; i < stateStr.size(); i++) { + if (stateStr[i] == '+') { + invalid_ = true; + SetStat(TASKNEW); + continue; + } + + Direction ret = SetStatByChar(stateStr[i]); + if (ret == NEED_CONTINUE) { + invalid_ = true; + continue; + } else if (ret == NEED_BREAK) { + break; + } + } +} + +ThreadState::ThreadState(const std::string& stateStr) +{ + ProcessSate(stateStr); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/parser/thread_state.h b/host/trace_streamer/src/parser/thread_state.h new file mode 100644 index 0000000..f0b9015 --- /dev/null +++ b/host/trace_streamer/src/parser/thread_state.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BYTRACE_THREAD_STATES_H +#define BYTRACE_THREAD_STATES_H + +#include +#include "common_types.h" +#include "log.h" + +namespace SysTuning { +namespace TraceStreamer { +enum Direction { NEED_GO = 0, NEED_CONTINUE, NEED_BREAK }; + +class ThreadState { +public: + explicit ThreadState(const std::string& stateStr); + ~ThreadState() {} + + uint32_t State() const + { + return state_ & ~VALID; + } + bool IsValid() const + { + return invalid_; + } + +private: + void SetStat(Stat value) + { + state_ |= value; + } + + void ProcessSate(const std::string& stateStr); + Direction SetStatByChar(char ch); +private: + uint32_t state_ = 0; + std::map statMap_ = { + {'R', RUNNABLE}, + {'S', INTERRUPTABLESLEEP}, + {'D', UNINTERRUPTIBLESLEEP}, + {'T', STOPPED}, + {'t', TRACED}, + {'X', EXITDEAD}, + {'Z', EXITZOMBIE}, + {'x', TASKDEAD}, + {'I', TASKDEAD}, + {'K', WAKEKILL}, + {'P', PARKED}, + {'N', NOLOAD}, + {'|', VALID}, + }; + bool invalid_ = false; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // _BYTRACE_THREAD_STATES_H_ diff --git a/host/trace_streamer/src/protos/README_zh.md b/host/trace_streamer/src/protos/README_zh.md new file mode 100644 index 0000000..112aca9 --- /dev/null +++ b/host/trace_streamer/src/protos/README_zh.md @@ -0,0 +1,10 @@ +# protos 代码仓 + + + +`services` 目录下存放的是 服务接口定义, + +`types` 目录下存放的是 具体插件业务相关的类型定义,主要为配置类型和结果类型定义; + +例如,`types/plugins/cpu_data/` 目录存放CPU数据插件的配置类型和结果类型; + diff --git a/host/trace_streamer/src/protos/protogen.sh b/host/trace_streamer/src/protos/protogen.sh new file mode 100644 index 0000000..c35b051 --- /dev/null +++ b/host/trace_streamer/src/protos/protogen.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +SOURCE="${BASH_SOURCE[0]}" +cd $(dirname ${SOURCE}) +echo "begin to generate proto based files" +SOURCE=$(dirname ${SOURCE}) +proto_dir="." +services_dir="$proto_dir/services" +ftrace_data_dir="$proto_dir/types/plugins/ftrace_data" +memory_data_dir="$proto_dir/types/plugins/memory_data" +hilog_data_dir="$proto_dir/types/plugins/hilog_data" +native_hook_dir="$proto_dir/types/plugins/native_hook" +hidump_data_dir="$proto_dir/types/plugins/hidump_data" +arrayWen=("${services_dir}/common_types.proto" + "$ftrace_data_dir/trace_plugin_result.proto" + "$ftrace_data_dir/ftrace_event.proto" + "$ftrace_data_dir/irq.proto" + "$ftrace_data_dir/vmscan.proto" + "$ftrace_data_dir/workqueue.proto" + "$ftrace_data_dir/task.proto" + "$ftrace_data_dir/power.proto" + "$ftrace_data_dir/sched.proto" + "$ftrace_data_dir/filemap.proto" + "$ftrace_data_dir/i2c.proto" + "$ftrace_data_dir/kmem.proto" + "$ftrace_data_dir/block.proto" + "$ftrace_data_dir/ipi.proto" + "$ftrace_data_dir/ftrace.proto" + "$ftrace_data_dir/ext4.proto" + "$ftrace_data_dir/oom.proto" + "$ftrace_data_dir/compaction.proto" + "$ftrace_data_dir/clk.proto" + "$ftrace_data_dir/cgroup.proto" + "$ftrace_data_dir/binder.proto" + "$ftrace_data_dir/signal.proto" + "$ftrace_data_dir/sunrpc.proto" + "$ftrace_data_dir/net.proto" + "$ftrace_data_dir/cpuhp.proto" + "$ftrace_data_dir/writeback.proto" + "$ftrace_data_dir/v4l2.proto" + "$ftrace_data_dir/pagemap.proto" + "$ftrace_data_dir/dma_fence.proto" + "$ftrace_data_dir/printk.proto" + "$ftrace_data_dir/filelock.proto" + "$ftrace_data_dir/gpio.proto" + "$ftrace_data_dir/timer.proto" + "$ftrace_data_dir/raw_syscalls.proto" + "$ftrace_data_dir/rcu.proto" + "$memory_data_dir/memory_plugin_common.proto" + "$memory_data_dir/memory_plugin_config.proto" + "$memory_data_dir/memory_plugin_result.proto" + "$hilog_data_dir/hilog_plugin_result.proto" + "$native_hook_dir/native_hook_result.proto" + "$native_hook_dir/native_hook_config.proto" + "$hidump_data_dir/hidump_plugin_result.proto" + "$hidump_data_dir/hidump_plugin_config.proto") + +export LD_LIBRARY_PATH=../../out/linux +for ((i = 0; i < ${#arrayWen[@]}; i ++)) +do + newpath=$(dirname ${arrayWen[$i]}) + newpath=${newpath:2} + cppout=../../third_party/protogen/$newpath + mkdir -p $cppout + ../../out/linux/protoc --proto_path=$memory_data_dir:$native_hook_dir:$hidump_data_dir:$hilog_data_dir:$ftrace_data_dir:$services_dir --cpp_out=$cppout ${arrayWen[$i]} +done +echo "generate proto based files over" \ No newline at end of file diff --git a/host/trace_streamer/src/protos/protos.gni b/host/trace_streamer/src/protos/protos.gni new file mode 100644 index 0000000..59b67bb --- /dev/null +++ b/host/trace_streamer/src/protos/protos.gni @@ -0,0 +1,24 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("../../../build/config.gni") + +subsys_name = OHOS_PROFILER_SUBSYS_NAME +subsys_x64_out = "clang_x64/$subsys_name/$subsys_name" +libc_dir_proto = rebase_path("$asdk_libs_dir", "//") +root_output_dir_proto = rebase_path("$root_out_dir", "//") +# print("grpc_cpp_plugin = ", grpc_cpp_plugin) +# print("ipc_cpp_plugin = ", ipc_cpp_plugin) +# print("root_build_dir = ", root_build_dir) +# print("root_out_dir = ", root_out_dir) +# print("root_gen_dir = ", root_gen_dir) diff --git a/host/trace_streamer/src/protos/services/BUILD.gn b/host/trace_streamer/src/protos/services/BUILD.gn new file mode 100644 index 0000000..81abebb --- /dev/null +++ b/host/trace_streamer/src/protos/services/BUILD.gn @@ -0,0 +1,225 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../protos.gni") + +proto_types_defines = [ + "./common_types.proto", + "./plugin_service_types.proto", + "./profiler_service_types.proto", +] + +plugin_services_defines = [ "./plugin_service.proto" ] + +profiler_services_defines = [ "./profiler_service.proto" ] + +####################################################### +proto_out_dir = "$root_gen_dir/cpp/" + rebase_path(".", "//") +proto_rel_out_dir = rebase_path(proto_out_dir, root_build_dir) +print("proto_out_dir", proto_out_dir, proto_rel_out_dir) + +grpc_cpp_plugin = "$subsys_x64_out/grpc_cpp_plugin" +ipc_cpp_plugin = "$subsys_x64_out/protoc_gen_ipc" + +####################################################### +proto_types_codegen = [] +foreach(proto, proto_types_defines) { + name = get_path_info(proto, "name") + proto_types_codegen += [ + "$proto_out_dir/$name.pb.h", + "$proto_out_dir/$name.pb.cc", + ] +} + +action("service_types_proto_gen") { + script = "${OHOS_PROFILER_DIR}/build/protoc.sh" + sources = proto_types_defines + outputs = proto_types_codegen + args = [ + "$libc_dir_proto", + "$root_output_dir_proto", + "--cpp_out", + "$proto_rel_out_dir", + "--proto_path", + rebase_path(".", root_build_dir), + ] + args += rebase_path(sources, root_build_dir) + deps = [ + "${OHOS_PROFILER_3RDPARTY_GRPC_DIR}:grpc_cpp_plugin(${host_toolchain})", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protoc(${host_toolchain})", + ] +} + +config("service_types_proto_config") { + include_dirs = [ "$proto_out_dir" ] +} + +ohos_source_set("service_types_proto") { + deps = [ ":service_types_proto_gen" ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + ] + include_dirs = [ "$proto_out_dir" ] + public_configs = [ ":service_types_proto_config" ] + sources = proto_types_codegen +} + +ohos_source_set("service_types_proto_static") { + deps = [ ":service_types_proto_gen" ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite_static", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_static", + ] + include_dirs = [ "$proto_out_dir" ] + public_configs = [ ":service_types_proto_config" ] + sources = proto_types_codegen +} + +####################################################### +profiler_services_codegen = [] +foreach(proto, profiler_services_defines) { + name = get_path_info(proto, "name") + profiler_services_codegen += [ + "$proto_out_dir/$name.pb.h", + "$proto_out_dir/$name.pb.cc", + "$proto_out_dir/$name.grpc.pb.h", + "$proto_out_dir/$name.grpc.pb.cc", + ] +} + +action("profiler_services_proto_gen") { + script = "${OHOS_PROFILER_DIR}/build/protoc.sh" + sources = profiler_services_defines + outputs = profiler_services_codegen + args = [ + "$libc_dir_proto", + "$root_output_dir_proto", + "--plugin=protoc-gen-grpc=$grpc_cpp_plugin", + "--grpc_out", + "$proto_rel_out_dir", + "--cpp_out", + "$proto_rel_out_dir", + "--proto_path", + rebase_path(".", root_build_dir), + ] + args += rebase_path(sources, root_build_dir) + deps = [ + "${OHOS_PROFILER_3RDPARTY_GRPC_DIR}:grpc_cpp_plugin(${host_toolchain})", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protoc(${host_toolchain})", + ] +} + +config("profiler_services_proto_config") { + include_dirs = [ "$proto_out_dir" ] +} + +ohos_source_set("profiler_services_proto") { + deps = [ + ":profiler_services_proto_gen", + ":service_types_proto", + ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_GRPC_DIR}:grpc", + "${OHOS_PROFILER_3RDPARTY_GRPC_DIR}:grpcxx", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + ] + include_dirs = [ "$proto_out_dir" ] + public_configs = [ ":profiler_services_proto_config" ] + sources = profiler_services_codegen +} + +####################################################### +plugin_services_codegen = [] +foreach(proto, plugin_services_defines) { + name = get_path_info(proto, "name") + plugin_services_codegen += [ + "$proto_out_dir/$name.pb.h", + "$proto_out_dir/$name.pb.cc", + "$proto_out_dir/$name.ipc.h", + "$proto_out_dir/$name.ipc.cc", + ] +} + +action("plugin_services_proto_gen") { + script = "${OHOS_PROFILER_DIR}/build/protoc.sh" + sources = plugin_services_defines + outputs = plugin_services_codegen + args = [ + "$libc_dir_proto", + "$root_output_dir_proto", + "--plugin=protoc-gen-ipc=$ipc_cpp_plugin", + "--ipc_out", + "$proto_rel_out_dir", + "--cpp_out", + "$proto_rel_out_dir", + "--proto_path", + rebase_path(".", root_build_dir), + ] + args += rebase_path(sources, root_build_dir) + deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protoc(${host_toolchain})", + "${OHOS_PROFILER_DIR}/device/services/ipc:protoc_gen_ipc(${host_toolchain})", + ] +} + +config("plugin_services_proto_config") { + include_dirs = [ "$proto_out_dir" ] +} + +ohos_source_set("plugin_services_proto") { + deps = [ + ":plugin_services_proto_gen", + ":service_types_proto", + ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + "${OHOS_PROFILER_DIR}/device/services/ipc:ipc", + ] + include_dirs = [ + "$proto_out_dir", + "${OHOS_PROFILER_DIR}/device/services/ipc/include", + ] + public_configs = [ ":plugin_services_proto_config" ] + sources = plugin_services_codegen +} + +ohos_source_set("plugin_services_proto_static") { + deps = [ + ":plugin_services_proto_gen", + ":service_types_proto_static", + ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite_static", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_static", + "${OHOS_PROFILER_DIR}/device/services/ipc:ipc", + ] + include_dirs = [ + "$proto_out_dir", + "${OHOS_PROFILER_DIR}/device/services/ipc/include", + ] + public_configs = [ ":plugin_services_proto_config" ] + sources = plugin_services_codegen +} + +####################################################### +ohos_source_set("proto_services_cpp") { + public_deps = [ + ":plugin_services_proto", + ":profiler_services_proto", + ":service_types_proto", + ] +} diff --git a/host/trace_streamer/src/protos/services/common_types.proto b/host/trace_streamer/src/protos/services/common_types.proto new file mode 100644 index 0000000..fa67090 --- /dev/null +++ b/host/trace_streamer/src/protos/services/common_types.proto @@ -0,0 +1,58 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; + +// Common message define for profiler tools, imported by profiler and plugin proto file. +message ProfilerPluginConfig { + string name = 1; + string plugin_sha256 = 2; + uint32 sample_interval = 3; + bytes config_data = 4; +} + +message ProfilerPluginState { + string name = 1; + enum State { + INITED = 0; + REGISTERED = 1; // registered to plugin service. + LOADED = 2; // have created session. + IN_SESSION = 3; // have started session. + }; + State state = 2; +} + +// for FetchDataResponse +message ProfilerPluginData { + string name = 1; + uint32 status = 2; + bytes data = 3; + enum ClockId { + CLOCKID_REALTIME = 0; + CLOCKID_REALTIME_ALARM = 1; // since Linux 3.0; Linux-specific + CLOCKID_REALTIME_COARSE = 2; // since Linux 2.6.32; Linux-specific + CLOCKID_TAI = 3; // since Linux 3.10; Linux-specific + CLOCKID_MONOTONIC = 4; + CLOCKID_MONOTONIC_COARSE = 5; // since Linux 2.6.32; Linux-specific + CLOCKID_MONOTONIC_RAW = 6; // since Linux 2.6.28; Linux-specific + CLOCKID_BOOTTIME = 7; // since Linux 2.6.39; Linux-specific + CLOCKID_BOOTTIME_ALARM = 8; // since Linux 3.0; Linux-specific + CLOCKID_PROCESS_CPUTIME_ID = 9; // since Linux 2.6.12 + CLOCKID_THREAD_CPUTIME_ID = 10; // since Linux 2.6.12 + }; + ClockId clock_id = 4; + uint64 tv_sec = 5; + uint64 tv_nsec = 6; +} diff --git a/host/trace_streamer/src/protos/services/plugin_service.proto b/host/trace_streamer/src/protos/services/plugin_service.proto new file mode 100644 index 0000000..ab8583d --- /dev/null +++ b/host/trace_streamer/src/protos/services/plugin_service.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +import "plugin_service_types.proto"; + +// IPC interface between profiler service and plugin service. +// Use protobuf plug-ins to convert proto define to +// source and header files during the build process. +service IPluginService { + + rpc RegisterPlugin(RegisterPluginRequest) returns (RegisterPluginResponse); + + rpc UnregisterPlugin(UnregisterPluginRequest) returns (UnregisterPluginResponse); + + // service will use this interface to push commands with streamed return channel. + rpc GetCommand(GetCommandRequest) returns (stream GetCommandResponse); + + rpc NotifyResult(NotifyResultRequest) returns (NotifyResultResponse); +}; diff --git a/host/trace_streamer/src/protos/services/plugin_service_types.proto b/host/trace_streamer/src/protos/services/plugin_service_types.proto new file mode 100644 index 0000000..3f54f23 --- /dev/null +++ b/host/trace_streamer/src/protos/services/plugin_service_types.proto @@ -0,0 +1,103 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +import "common_types.proto"; + +// Message define for IPC interface, imported by plugin service proto file. +// for RegisterPlugin API +message RegisterPluginRequest { + uint32 request_id = 1; + string path = 2; + string sha256 = 3; + + string name = 4; + uint32 buffer_size_hint = 5; +} + +message RegisterPluginResponse { + uint32 status = 1; + uint32 plugin_id = 2; +} + +// for UnregisterPlugin API +message UnregisterPluginRequest { + uint32 request_id = 1; + uint32 plugin_id = 2; +} + +message UnregisterPluginResponse { + uint32 status = 1; +} + +// for GetCommand API +message GetCommandRequest { + uint32 request_id = 1; +} + +message CreateSessionCmd { + repeated uint32 buffer_sizes = 1; + repeated ProfilerPluginConfig plugin_configs = 2; +} + +message DestroySessionCmd { + repeated uint32 plugin_ids = 1; +} + +message StartSessionCmd { + repeated uint32 plugin_ids = 1; + repeated ProfilerPluginConfig plugin_configs = 2; +} + +message StopSessionCmd { + repeated uint32 plugin_ids = 1; +} + +message GetCommandResponse { + uint32 status = 1; + bool has_more = 2; + uint32 command_id = 3; + oneof command { + CreateSessionCmd create_session_cmd = 10; + DestroySessionCmd destroy_session_cmd = 11; + StartSessionCmd start_session_cmd = 12; + StopSessionCmd stop_session_cmd = 13; + } +} + +// for NotifyResult API +message BufferDescriptor { + int32 buffer_id = 1; + uint32 offset = 2; + uint32 length = 3; +} + +message PluginResult { + uint32 plugin_id = 1; + oneof descriptor { + bytes data = 2; + BufferDescriptor buffer = 3; + }; + ProfilerPluginState status = 10; +} + +message NotifyResultRequest { + uint32 request_id = 1; + uint32 command_id = 2; + repeated PluginResult result = 3; +} + +message NotifyResultResponse { + uint32 status = 1; +} diff --git a/host/trace_streamer/src/protos/services/profiler_service.proto b/host/trace_streamer/src/protos/services/profiler_service.proto new file mode 100644 index 0000000..35e1e18 --- /dev/null +++ b/host/trace_streamer/src/protos/services/profiler_service.proto @@ -0,0 +1,44 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; + +import "profiler_service_types.proto"; + +// RPC interface between profiler service and host service +// Use protobuf plug-ins to convert proto define to +// source and header files during the build process. +service IProfilerService { + // get all plugin infos and capabilities. + rpc GetCapabilities(GetCapabilitiesRequest) returns (GetCapabilitiesResponse); + + // create tracing sesion and pass tracing config to plugins. + rpc CreateSession(CreateSessionRequest) returns (CreateSessionResponse); + + // start tracing session, active server side tracing triggers. + rpc StartSession(StartSessionRequest) returns (StartSessionResponse); + + // get server-side cached tracing data since current session started. + rpc FetchData(FetchDataRequest) returns (stream FetchDataResponse); + + // stop tracing session, deactivate server side tracing triggers. + rpc StopSession(StopSessionRequest) returns (StopSessionResponse); + + // destroy tracing session. + rpc DestroySession(DestroySessionRequest) returns (DestroySessionResponse); + + // keep tracing session alive, call this interface will restart session expire count down task. + rpc KeepSession(KeepSessionRequest) returns (KeepSessionResponse); +} diff --git a/host/trace_streamer/src/protos/services/profiler_service_types.proto b/host/trace_streamer/src/protos/services/profiler_service_types.proto new file mode 100644 index 0000000..a557e62 --- /dev/null +++ b/host/trace_streamer/src/protos/services/profiler_service_types.proto @@ -0,0 +1,129 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; + +import "common_types.proto"; + +// Message define for profiler service, imported by profiler service proto file. +// for GetCapabilities +message GetCapabilitiesRequest { + uint32 request_id = 1; +} + +message ProfilerPluginCapability { + string path = 1; + string name = 2; +} + +message GetCapabilitiesResponse { + uint32 status = 1; + repeated ProfilerPluginCapability capabilities = 2; +} + + +// for CreateSessionRequest +message ProfilerSessionConfig { + message BufferConfig { + enum Policy { + RECYCLE = 0; + FLATTEN = 1; + }; + uint32 pages = 1; + Policy policy = 2; + } + repeated BufferConfig buffers = 1; + + enum Mode { + OFFLINE = 0; // save all plugin results to result file. + ONLINE = 1; // push all plugin results to host PC with streamed FetchDataResponse. + }; + Mode session_mode = 2; + string result_file = 3; // for OFFLINE mode, result file path + uint32 result_max_size = 4; // for OFFLINE mode, result file max size in KB + uint32 sample_duration = 5; // for OFFLINE mode, sample duration in ms + uint32 keep_alive_time = 6; // if set to non-zero value, session will auto-destroyed after CreateSession in ms +} + +message CreateSessionRequest { + uint32 request_id = 1; + ProfilerSessionConfig session_config = 2; + repeated ProfilerPluginConfig plugin_configs = 3; +} + +message CreateSessionResponse { + uint32 status = 1; + uint32 session_id = 2; + repeated ProfilerPluginState plugin_status = 3; +} + +// for StartSession +message StartSessionRequest { + uint32 request_id = 1; + uint32 session_id = 2; + repeated ProfilerPluginConfig update_configs = 3; +} + +message StartSessionResponse { + uint32 status = 1; + repeated ProfilerPluginState plugin_status = 3; +} + +// for FetchData +message FetchDataRequest { + uint32 request_id = 1; + uint32 session_id = 2; + bytes addtion_data = 3; +} + +message FetchDataResponse { + uint32 status = 1; + uint32 response_id = 2; + bool has_more = 3; + repeated ProfilerPluginData plugin_data = 4; +} + +// for StopSession +message StopSessionRequest { + uint32 request_id = 1; + uint32 session_id = 2; +} + +message StopSessionResponse { + uint32 status = 1; + repeated ProfilerPluginState plugin_status = 3; +} + +// for DestroySession +message DestroySessionRequest { + uint32 request_id = 1; + uint32 session_id = 2; +} + +message DestroySessionResponse { + uint32 status = 1; + repeated ProfilerPluginState plugin_status = 3; +} + +// for KeepSession +message KeepSessionRequest { + uint32 request_id = 1; + uint32 session_id = 2; + uint32 keep_alive_time = 3; +} + +message KeepSessionResponse { + uint32 status = 1; +} diff --git a/host/trace_streamer/src/protos/types/plugins/agent_data/BUILD.gn b/host/trace_streamer/src/protos/types/plugins/agent_data/BUILD.gn new file mode 100644 index 0000000..1d2b050 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/agent_data/BUILD.gn @@ -0,0 +1,69 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../protos.gni") + +agent_sources = [ + "./agent_plugin_app_data.proto", + "./agent_plugin_config.proto", + "./agent_plugin_energy_data.proto", + "./agent_plugin_java_heap.proto", + "./agent_plugin_network_data.proto", + "./agent_plugin_result.proto", +] + +####################################################### +proto_out_dir = "$root_gen_dir/cpp/" + rebase_path(".", "//") +proto_rel_out_dir = rebase_path(proto_out_dir, root_build_dir) + +agent_data_codegen = [] +foreach(proto, agent_sources) { + name = get_path_info(proto, "name") + agent_data_codegen += [ + "$proto_out_dir/$name.pb.h", + "$proto_out_dir/$name.pb.cc", + ] +} + +config("agent_include_config") { + include_dirs = [ "$proto_out_dir" ] +} + +####################################################### +action("agent_data_cpp_gen") { + script = "${OHOS_PROFILER_DIR}/build/protoc.sh" + sources = agent_sources + outputs = agent_data_codegen + args = [ + "$libc_dir_proto", + "$root_output_dir_proto", + "--cpp_out", + "$proto_rel_out_dir", + "--proto_path", + rebase_path(".", root_build_dir), + ] + args += rebase_path(sources, root_build_dir) + deps = [ "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protoc(${host_toolchain})" ] +} + +ohos_source_set("agent_data_cpp") { + deps = [ ":agent_data_cpp_gen" ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + ] + include_dirs = [ "$proto_out_dir" ] + public_configs = [ ":agent_include_config" ] + sources = agent_data_codegen +} diff --git a/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_app_data.proto b/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_app_data.proto new file mode 100644 index 0000000..5ae99f0 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_app_data.proto @@ -0,0 +1,68 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +message AbilityStateInfo { + enum AbilityState { + UNSPECIFIED = 0; + START = 1; + INACTIVE = 2; + ACTIVE = 3; + BACKGROUND = 4; + FOREGROUND = 5; + STOP = 6; + } + int32 life_cycle_id = 1; + string ability_name = 2; + AbilityState state = 3; +} + +message KeyEvent { + int32 key_type = 1; + bool is_down = 2; +} + +message TouchEvent { + int32 touch_type = 1; +} + +message MouseEvent { + int32 action_type = 1; + int32 action_button = 2; +} + +message RotationEvent { + float value = 1; +} + +message AgentAbilityEvent { + // timestamp obtained by CLOCK_REALTIME + uint64 tv_sec = 1; + uint64 tv_nsec = 2; + + oneof event { + AbilityStateInfo ability_state = 3; + KeyEvent key_event = 4; + TouchEvent touch_event = 5; + MouseEvent mouse_event = 6; + RotationEvent rotation_event = 7; + } +} + +message BatchAgentAbilityEvent { + repeated AgentAbilityEvent events = 1; +} \ No newline at end of file diff --git a/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_config.proto b/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_config.proto new file mode 100644 index 0000000..470d946 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_config.proto @@ -0,0 +1,23 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + + +message AgentConfig { + int32 pid = 1; + int32 stack_depth = 2; +} diff --git a/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_energy_data.proto b/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_energy_data.proto new file mode 100644 index 0000000..575dcff --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_energy_data.proto @@ -0,0 +1,201 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +message RunningLockBegin { + enum Type { + UNSPECIFIED = 0; + BACKGROUND = 1; + PROXIMITY_SCREEN_CONTROL = 2; + } + + int32 lock_id = 1; // 锁ID + Type type = 2; // 唤醒锁的类型 + string name = 3; // 唤醒锁名称 +} + +message RunningLockEnd { + int32 lock_id = 1; // 锁ID +} + +message PendingIntent { + string creator_package = 1; // 创建此PendingIntent的应用程序的程序包名称 + int32 creator_uid = 2; // 应用程序UID +} + +enum TimerType { + UNSPECIFIED = 0; + TIMER_TYPE_REALTIME = 1; + TIMER_TYPE_WAKEUP = 2; + TIMER_TYPE_EXACT = 4; + TIMER_TYPE_IDLE = 8; +} + +message OneShotStart { + PendingIntent intent = 1; + int64 oneshot_id = 2; + TimerType type = 3; + int64 triggertime_ns = 4; +} + +message RepeatStart { + PendingIntent intent = 1; + int64 repeat_id = 2; + TimerType type = 3; + int64 triggertime_ns = 4; + int64 interval_ns = 5; +} + +message TimerStop { + int64 stop_id = 1; +} + +message LocationRequest { + enum Priority { + UNSPECIFIED = 0; + PRIORITY_UNSET = 512; + PRIORITY_ACCURACY = 513; + PRIORITY_LOW_POWER = 514; + PRIORITY_FAST_FIRST_FIX = 515; + } + string provider = 1; // 提供位置的程序名称。通过LocationManager设置,则可以是“gps”、“network”、“passive”或空。通过FusedLocationProviderClient设置,则是“fused”。 + int64 interval_ms = 2; // 位置请求所需的间隔时间(ms),间隔时间越短越耗电 + int64 fastest_interval_ms = 3; // 位置请求的最快间隔时间(ms) + Priority priority = 4; // 位置请求精确度,精确度越高越耗电 +} + +message LocationUpdateRequested { + PendingIntent intent = 1; // 要为每个位置更新发送的挂起内容的元数据 + int32 location_id = 2; // 位置ID + LocationRequest request = 3; // 位置更新请求 +} + +message LocationReport { + int32 location_id = 1; // 位置ID + float accuracy_of_metre = 2; // 该位置的估计水平精度,径向,单位为米。 +} + +message LocationUpdateRemoved { + int32 location_id = 1; // 位置ID +} + +message WorkInfo { + enum BatteryLevel { + BATTERY_LEVEL_LOW = 0; + BATTERY_LEVEL_OKAY = 1; + BATTERY_LEVEL_LOW_OR_OKAY = 2; + } + enum ChargeType { + CHARGING_PLUGGED_ANY = 0; + CHARGING_PLUGGED_AC = 1; + CHARGING_PLUGGED_USB = 2; + CHARGING_PLUGGED_WIRELESS = 3; + } + enum NetworkType { + NETWORK_TYPE_ANY = 0; + NETWORK_TYPE_MOBILE = 1; + NETWORK_TYPE_WIFI = 2; + NETWORK_TYPE_BLUETOOTH = 3; + NETWORK_TYPE_WIFI_P2P = 4; + NETWORK_TYPE_ETHERNET = 5; + } + enum StorageType { + STORAGE_LEVEL_LOW = 0; + STORAGE_LEVEL_OKAY = 1; + STORAGE_LEVEL_LOW_OR_OKAY = 2; + } + + string ability_name = 1; // 运行作业的页面名称 + string bundle_name = 2; // 运行作业的程序名称 + int32 repeat_counter = 3; // 重复作业次数 + int64 repeat_cycle_time = 4; // 定期作业重复的间隔时间 + BatteryLevel battery_level = 5; // 作业设备的电池等级 + int32 battery_status = 6; // 作业设备的电池状态 + ChargeType charge_type = 7; // 作业设备的充电类型 + int32 wait_time = 8; // 非定期作业的延迟时间 + NetworkType network_type = 9; // 作业运行的网络类型 + StorageType storage_type = 10; // 作业设备的存储类型 + bool is_request_battery = 11; // 作业是否需要设备的电池电量不低于临界阈值 + bool is_request_charging = 12; // 作业是否需要充电 + bool is_request_deep_idle = 13; // 作业是否需要设备处于空闲维护窗口中 + bool is_request_delay = 14; // 作业是否需要延迟 + bool is_request_network = 15; // 作业是否需要网络 + bool is_request_persisted = 16; // 是否应跨设备重新引导持久化此作业 + bool is_request_repeat = 17; // 是否在给定时间内重复作业 + bool is_request_storage = 18; // 作业是否需要设备的存储空间不低 + bool is_work_info_valid = 19; // 作业是否有效 +} + +message WorkStart { + int64 work_id = 1; + WorkInfo work_info = 2; + bool start_now = 3; + bool is_start = 4; +} + +message WorkCancel { + int64 work_id = 1; + bool is_cancel = 2; +} + +message WorkStop { + int64 work_id = 1; + bool is_stop = 2; +} + +message BackgroundWork { + int64 work_id = 1; + int32 actual_delay_time = 2; + int32 request_id = 3; + string reason = 4; +} + +message OnStartWork { + int64 work_id = 1; + WorkInfo work_info = 2; +} + +message OnStopWork { + int64 work_id = 1; +} + +message AgentEnergyEvent { + // timestamp obtained by CLOCK_REALTIME + uint64 tv_sec = 1; + uint64 tv_nsec = 2; + string callstack = 3; // 生成此事件的调用堆栈 + oneof data { + RunningLockBegin runlock_begin = 4; // 获取唤醒锁(强制设备保持唤醒) + RunningLockEnd runlock_end = 5; // 释放唤醒锁 + OneShotStart oneshot_start = 6; // 闹钟 + RepeatStart repeat_start = 7; // 需要重复的闹钟 + TimerStop timer_stop = 8; + LocationUpdateRequested location_update_requested = 9; // 位置更新请求 + LocationReport location_report = 10; + LocationUpdateRemoved location_update_removed = 11; // 位置更新请求已删除 + WorkStart work_start = 12; + WorkCancel work_cancel = 13; + WorkStop work_stop = 14; + BackgroundWork back_work = 15; + OnStartWork on_start = 16; // 启动承载work的服务 + OnStopWork on_stop = 17; // 停止承载work的服务 + } +} + +message BatchAgentEnergyEvent { + repeated AgentEnergyEvent events = 1; +} diff --git a/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_java_heap.proto b/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_java_heap.proto new file mode 100644 index 0000000..7b89309 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_java_heap.proto @@ -0,0 +1,62 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +// Java heap memory event from jvmti interface. +message ClassInfo { + int32 class_id = 1; + string class_name = 2; +} + +message AllocationInfo { + int32 object_id = 1; + int32 class_id = 2; + int32 object_size = 3; + int32 array_length = 4; + int32 heap_id = 5; + string thread_name = 6; + + message StackFrameInfo { + int32 frame_id = 1; + string class_name = 2; + string method_name = 3; + string file_name = 4; + int32 line_number = 5; + } + // First element means stack top. + repeated StackFrameInfo frame_info = 7; +} + +message DeallocationInfo { + int32 object_id = 1; +} + +message AgentMemoryEvent { + // timestamp obtained by CLOCK_REALTIME + uint64 tv_sec = 1; + uint64 tv_nsec = 2; + + oneof event { + ClassInfo class_data = 3; + AllocationInfo alloc_data = 4; + DeallocationInfo free_data = 5; + } +} + +message BatchAgentMemoryEvent { + repeated AgentMemoryEvent events = 1; +} diff --git a/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_network_data.proto b/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_network_data.proto new file mode 100644 index 0000000..918af63 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_network_data.proto @@ -0,0 +1,64 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +message HttpRequestHead { + string url = 1; + string method = 2; + string fields = 3; + string trace = 4; +} + +message HttpResponseHead { + string fields = 1; +} + +message HttpBody { + string payload_id = 1; + int32 payload_size = 2; + bytes payload_fields = 3; +} + +message HttpEndStatus { + bool aborted = 1; +} + +// thread +message AgentThread { + int64 id = 1; + string name = 2; +} + +message AgentNetworkEvent { + // timestamp obtained by CLOCK_REALTIME + uint64 tv_sec = 1; + uint64 tv_nsec = 2; + int64 event_id = 3; + + oneof event { + HttpRequestHead request_head = 4; + HttpResponseHead response_head = 5; + HttpBody request_body = 6; + HttpBody response_body = 7; + HttpEndStatus end_status = 8; + AgentThread agent_thread = 9; + } +} + +message BatchAgentNetworkEvent { + repeated AgentNetworkEvent events = 1; +} \ No newline at end of file diff --git a/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_result.proto b/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_result.proto new file mode 100644 index 0000000..ad9a370 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/agent_data/agent_plugin_result.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +import "agent_plugin_java_heap.proto"; +import "agent_plugin_app_data.proto"; +import "agent_plugin_network_data.proto"; +import "agent_plugin_energy_data.proto"; + +message AgentData { + oneof data { + BatchAgentMemoryEvent javaheap_data = 1; + BatchAgentAbilityEvent app_data = 2; + BatchAgentNetworkEvent network_data = 3; + BatchAgentEnergyEvent energy_data = 4; + } +} diff --git a/host/trace_streamer/src/protos/types/plugins/bytrace_plugin/BUILD.gn b/host/trace_streamer/src/protos/types/plugins/bytrace_plugin/BUILD.gn new file mode 100644 index 0000000..c2f3dc3 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/bytrace_plugin/BUILD.gn @@ -0,0 +1,63 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../protos.gni") + +bytrace_plugin_protos_defines = [ "./bytrace_plugin_config.proto" ] + +####################################################### +proto_out_dir = "$root_gen_dir/cpp/" + rebase_path(".", "//") +proto_rel_out_dir = rebase_path(proto_out_dir, root_build_dir) +print("proto_out_dir", proto_out_dir, proto_rel_out_dir) + +####################################################### +bytrace_plugin_protos_codegen = [] +foreach(proto, bytrace_plugin_protos_defines) { + name = get_path_info(proto, "name") + bytrace_plugin_protos_codegen += [ + "$proto_out_dir/$name.pb.h", + "$proto_out_dir/$name.pb.cc", + ] +} + +action("bytrace_plugin_protos_protoc") { + script = "${OHOS_PROFILER_DIR}/build/protoc.sh" + sources = bytrace_plugin_protos_defines + outputs = bytrace_plugin_protos_codegen + args = [ + "$libc_dir_proto", + "$root_output_dir_proto", + "--cpp_out", + "$proto_rel_out_dir", + "--proto_path", + rebase_path(".", root_build_dir), + ] + args += rebase_path(sources, root_build_dir) + deps = [ "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protoc(${host_toolchain})" ] +} + +config("bytrace_plugin_protos_config") { + include_dirs = [ "$proto_out_dir" ] +} + +ohos_source_set("bytrace_plugin_protos_cpp") { + deps = [ ":bytrace_plugin_protos_protoc" ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + ] + include_dirs = [ "$proto_out_dir" ] + public_configs = [ ":bytrace_plugin_protos_config" ] + sources = bytrace_plugin_protos_codegen +} diff --git a/host/trace_streamer/src/protos/types/plugins/bytrace_plugin/bytrace_plugin_config.proto b/host/trace_streamer/src/protos/types/plugins/bytrace_plugin/bytrace_plugin_config.proto new file mode 100644 index 0000000..8c26822 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/bytrace_plugin/bytrace_plugin_config.proto @@ -0,0 +1,24 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; +option optimize_for = LITE_RUNTIME; + +message BytracePluginConfig { + uint32 buffe_size = 1; // Sets the size of the buffer (KB) for storing and reading traces. + repeated string categories = 2; // Lists available bytrace categories. + uint32 time = 3; // Sets the bytrace running duration in seconds (5s by default) + string clock = 4; // Sets the clock, which can be boot (default), global, mono, uptime, or perf. + string outfile_name = 5; // Sets the name of the target file (stdout by default). + bool is_root = 6; +} \ No newline at end of file diff --git a/host/trace_streamer/src/protos/types/plugins/cpu_data/BUILD.gn b/host/trace_streamer/src/protos/types/plugins/cpu_data/BUILD.gn new file mode 100644 index 0000000..b881f9d --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/cpu_data/BUILD.gn @@ -0,0 +1,65 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../protos.gni") + +cpu_data_sources = [ + "./cpu_plugin_config.proto", + "./cpu_plugin_result.proto", +] + +####################################################### +proto_out_dir = "$root_gen_dir/cpp/" + rebase_path(".", "//") +proto_rel_out_dir = rebase_path(proto_out_dir, root_build_dir) + +cpu_data_codegen = [] +foreach(proto, cpu_data_sources) { + name = get_path_info(proto, "name") + cpu_data_codegen += [ + "$proto_out_dir/$name.pb.h", + "$proto_out_dir/$name.pb.cc", + ] +} + +config("cpu_include_config") { + include_dirs = [ "$proto_out_dir" ] +} + +####################################################### +action("cpu_data_cpp_gen") { + script = "${OHOS_PROFILER_DIR}/build/protoc.sh" + sources = cpu_data_sources + outputs = cpu_data_codegen + args = [ + "$libc_dir_proto", + "$root_output_dir_proto", + "--cpp_out", + "$proto_rel_out_dir", + "--proto_path", + rebase_path(".", root_build_dir), + ] + args += rebase_path(sources, root_build_dir) + deps = [ "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protoc(${host_toolchain})" ] +} + +ohos_source_set("cpu_data_cpp") { + deps = [ ":cpu_data_cpp_gen" ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + ] + include_dirs = [ "$proto_out_dir" ] + public_configs = [ ":cpu_include_config" ] + sources = cpu_data_codegen +} diff --git a/host/trace_streamer/src/protos/types/plugins/cpu_data/cpu_plugin_config.proto b/host/trace_streamer/src/protos/types/plugins/cpu_data/cpu_plugin_config.proto new file mode 100644 index 0000000..f3a730c --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/cpu_data/cpu_plugin_config.proto @@ -0,0 +1,23 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + + +// Cpu plug-in configuration, passed to plug-in by plug-in service. +message CpuConfig { + int32 pid = 1; +} diff --git a/host/trace_streamer/src/protos/types/plugins/cpu_data/cpu_plugin_result.proto b/host/trace_streamer/src/protos/types/plugins/cpu_data/cpu_plugin_result.proto new file mode 100644 index 0000000..7b18965 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/cpu_data/cpu_plugin_result.proto @@ -0,0 +1,71 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +message SampleTimeStamp { + uint64 tv_sec = 1; + uint64 tv_nsec = 2; +} + +message CpuCoreFrequency { + int32 min_frequency_khz = 1; + int32 max_frequency_khz = 2; + int32 cur_frequency_khz = 3; +} + +message CpuCoreUsageInfo { + int32 cpu_core = 1; + int64 prev_system_cpu_time_ms = 2; + int64 prev_system_boot_time_ms = 3; + int64 system_cpu_time_ms = 4; + int64 system_boot_time_ms = 5; + CpuCoreFrequency frequency = 6; + bool is_little_core = 7; +} + +message CpuUsageInfo { + int64 prev_process_cpu_time_ms = 1; + int64 prev_system_cpu_time_ms = 2; + int64 prev_system_boot_time_ms = 3; + int64 process_cpu_time_ms = 4; + int64 system_cpu_time_ms = 5; + int64 system_boot_time_ms = 6; + repeated CpuCoreUsageInfo cores = 7; + SampleTimeStamp timestamp = 8; +} + +enum ThreadState { + THREAD_UNSPECIFIED = 0; + THREAD_RUNNING = 1; + THREAD_SLEEPING = 2; + THREAD_STOPPED = 3; + THREAD_WAITING = 4; +} + +message ThreadInfo { + int32 tid = 1; + string thread_name = 2; + ThreadState thread_state = 3; + int64 prev_thread_cpu_time_ms = 4; + int64 thread_cpu_time_ms = 5; + SampleTimeStamp timestamp = 6; +} + +message CpuData { + CpuUsageInfo cpu_usage_info = 1; + repeated ThreadInfo thread_info = 2; +} diff --git a/host/trace_streamer/src/protos/types/plugins/diskio_data/BUILD.gn b/host/trace_streamer/src/protos/types/plugins/diskio_data/BUILD.gn new file mode 100644 index 0000000..401a8d7 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/diskio_data/BUILD.gn @@ -0,0 +1,65 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../protos.gni") + +diskio_data_sources = [ + "./diskio_plugin_config.proto", + "./diskio_plugin_result.proto", +] + +####################################################### +proto_out_dir = "$root_gen_dir/cpp/" + rebase_path(".", "//") +proto_rel_out_dir = rebase_path(proto_out_dir, root_build_dir) + +diskio_data_codegen = [] +foreach(proto, diskio_data_sources) { + name = get_path_info(proto, "name") + diskio_data_codegen += [ + "$proto_out_dir/$name.pb.h", + "$proto_out_dir/$name.pb.cc", + ] +} + +config("diskio_include_config") { + include_dirs = [ "$proto_out_dir" ] +} + +####################################################### +action("diskio_data_cpp_gen") { + script = "${OHOS_PROFILER_DIR}/build/protoc.sh" + sources = diskio_data_sources + outputs = diskio_data_codegen + args = [ + "$libc_dir_proto", + "$root_output_dir_proto", + "--cpp_out", + "$proto_rel_out_dir", + "--proto_path", + rebase_path(".", root_build_dir), + ] + args += rebase_path(sources, root_build_dir) + deps = [ "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protoc(${host_toolchain})" ] +} + +ohos_source_set("diskio_data_cpp") { + deps = [ ":diskio_data_cpp_gen" ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + ] + include_dirs = [ "$proto_out_dir" ] + public_configs = [ ":diskio_include_config" ] + sources = diskio_data_codegen +} diff --git a/host/trace_streamer/src/protos/types/plugins/diskio_data/diskio_plugin_config.proto b/host/trace_streamer/src/protos/types/plugins/diskio_data/diskio_plugin_config.proto new file mode 100644 index 0000000..4e69f0d --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/diskio_data/diskio_plugin_config.proto @@ -0,0 +1,23 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + + +// Diskio plug-in configuration, passed to plug-in by plug-in service. +message DiskioConfig { + int32 unspeci_fied = 1; // Reserved field, not used +} diff --git a/host/trace_streamer/src/protos/types/plugins/diskio_data/diskio_plugin_result.proto b/host/trace_streamer/src/protos/types/plugins/diskio_data/diskio_plugin_result.proto new file mode 100644 index 0000000..742e9a6 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/diskio_data/diskio_plugin_result.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +message CollectTimeStamp { + uint64 tv_sec = 1; + uint64 tv_nsec = 2; +} + +message DiskioData { + int64 prev_rd_sectors_kb = 1; + int64 prev_wr_sectors_kb = 2; + CollectTimeStamp prev_timestamp = 3; + int64 rd_sectors_kb = 4; + int64 wr_sectors_kb = 5; + CollectTimeStamp timestamp = 6; +} \ No newline at end of file diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/BUILD.gn b/host/trace_streamer/src/protos/types/plugins/ftrace_data/BUILD.gn new file mode 100644 index 0000000..2dd0861 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/BUILD.gn @@ -0,0 +1,67 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../protos.gni") +import("autogenerated.gni") + +ftrace_data_sources = auto_generated_ftrace_proto_sources + [ + "trace_plugin_config.proto", + "trace_plugin_result.proto", + ] + +####################################################### +proto_out_dir = "$root_gen_dir/cpp/" + rebase_path(".", "//") +proto_rel_out_dir = rebase_path(proto_out_dir, root_build_dir) + +ftrace_data_codegen = [] +foreach(proto, ftrace_data_sources) { + dir = get_path_info(proto, "dir") + name = get_path_info(proto, "name") + ftrace_data_codegen += [ + "$proto_out_dir/$dir/$name.pb.h", + "$proto_out_dir/$dir/$name.pb.cc", + ] +} + +config("ftrace_data_include_config") { + include_dirs = [ "$proto_out_dir" ] +} + +####################################################### +action("ftrace_data_cpp_gen") { + script = "${OHOS_PROFILER_DIR}/build/protoc.sh" + sources = ftrace_data_sources + outputs = ftrace_data_codegen + args = [ + "$libc_dir_proto", + "$root_output_dir_proto", + "--cpp_out", + "$proto_rel_out_dir", + "--proto_path", + rebase_path(".", root_build_dir), + ] + args += rebase_path(sources, root_build_dir) + deps = [ "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protoc(${host_toolchain})" ] +} + +ohos_source_set("ftrace_data_cpp") { + deps = [ ":ftrace_data_cpp_gen" ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + ] + include_dirs = [ "$proto_out_dir" ] + public_configs = [ ":ftrace_data_include_config" ] + sources = ftrace_data_codegen +} diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/autogenerated.gni b/host/trace_streamer/src/protos/types/plugins/ftrace_data/autogenerated.gni new file mode 100644 index 0000000..fcae596 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/autogenerated.gni @@ -0,0 +1,48 @@ +# THIS FILE IS GENERATE BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +auto_generated_ftrace_proto_sources = [ + "binder.proto", + "block.proto", + "cgroup.proto", + "clk.proto", + "compaction.proto", + "cpuhp.proto", + "dma_fence.proto", + "ext4.proto", + "filelock.proto", + "filemap.proto", + "ftrace.proto", + "ftrace_event.proto", + "gpio.proto", + "i2c.proto", + "ipi.proto", + "irq.proto", + "kmem.proto", + "net.proto", + "oom.proto", + "pagemap.proto", + "power.proto", + "printk.proto", + "raw_syscalls.proto", + "rcu.proto", + "sched.proto", + "signal.proto", + "sunrpc.proto", + "task.proto", + "timer.proto", + "v4l2.proto", + "vmscan.proto", + "workqueue.proto", + "writeback.proto", +] diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/binder.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/binder.proto new file mode 100644 index 0000000..f1d0acd --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/binder.proto @@ -0,0 +1,216 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: binder +// /sys/kernel/debug/tracing/events/binder/binder_alloc_lru_end/format +message BinderAllocLruEndFormat { + int32 proc = 1; + uint64 page_index = 2; +} + +// /sys/kernel/debug/tracing/events/binder/binder_alloc_lru_start/format +message BinderAllocLruStartFormat { + int32 proc = 1; + uint64 page_index = 2; +} + +// /sys/kernel/debug/tracing/events/binder/binder_alloc_page_end/format +message BinderAllocPageEndFormat { + int32 proc = 1; + uint64 page_index = 2; +} + +// /sys/kernel/debug/tracing/events/binder/binder_alloc_page_start/format +message BinderAllocPageStartFormat { + int32 proc = 1; + uint64 page_index = 2; +} + +// /sys/kernel/debug/tracing/events/binder/binder_command/format +message BinderCommandFormat { + uint32 cmd = 1; +} + +// /sys/kernel/debug/tracing/events/binder/binder_free_lru_end/format +message BinderFreeLruEndFormat { + int32 proc = 1; + uint64 page_index = 2; +} + +// /sys/kernel/debug/tracing/events/binder/binder_free_lru_start/format +message BinderFreeLruStartFormat { + int32 proc = 1; + uint64 page_index = 2; +} + +// /sys/kernel/debug/tracing/events/binder/binder_ioctl/format +message BinderIoctlFormat { + uint32 cmd = 1; + uint64 arg = 2; +} + +// /sys/kernel/debug/tracing/events/binder/binder_ioctl_done/format +message BinderIoctlDoneFormat { + int32 ret = 1; +} + +// /sys/kernel/debug/tracing/events/binder/binder_lock/format +message BinderLockFormat { + string tag = 1; +} + +// /sys/kernel/debug/tracing/events/binder/binder_locked/format +message BinderLockedFormat { + string tag = 1; +} + +// /sys/kernel/debug/tracing/events/binder/binder_read_done/format +message BinderReadDoneFormat { + int32 ret = 1; +} + +// /sys/kernel/debug/tracing/events/binder/binder_return/format +message BinderReturnFormat { + uint32 cmd = 1; +} + +// /sys/kernel/debug/tracing/events/binder/binder_transaction/format +message BinderTransactionFormat { + int32 debug_id = 1; + int32 target_node = 2; + int32 to_proc = 3; + int32 to_thread = 4; + int32 reply = 5; + uint32 code = 6; + uint32 flags = 7; +} + +// /sys/kernel/debug/tracing/events/binder/binder_transaction_alloc_buf/format +message BinderTransactionAllocBufFormat { + int32 debug_id = 1; + uint64 data_size = 2; + uint64 offsets_size = 3; + uint64 extra_buffers_size = 4; +} + +// /sys/kernel/debug/tracing/events/binder/binder_transaction_buffer_release/format +message BinderTransactionBufferReleaseFormat { + int32 debug_id = 1; + uint64 data_size = 2; + uint64 offsets_size = 3; + uint64 extra_buffers_size = 4; +} + +// /sys/kernel/debug/tracing/events/binder/binder_transaction_failed_buffer_release/format +message BinderTransactionFailedBufferReleaseFormat { + int32 debug_id = 1; + uint64 data_size = 2; + uint64 offsets_size = 3; + uint64 extra_buffers_size = 4; +} + +// /sys/kernel/debug/tracing/events/binder/binder_transaction_fd/format +message BinderTransactionFdFormat { + int32 debug_id = 1; + int32 src_fd = 2; + int32 dest_fd = 3; +} + +// /sys/kernel/debug/tracing/events/binder/binder_transaction_node_to_ref/format +message BinderTransactionNodeToRefFormat { + int32 debug_id = 1; + int32 node_debug_id = 2; + uint64 node_ptr = 3; + int32 ref_debug_id = 4; + uint32 ref_desc = 5; +} + +// /sys/kernel/debug/tracing/events/binder/binder_transaction_received/format +message BinderTransactionReceivedFormat { + int32 debug_id = 1; +} + +// /sys/kernel/debug/tracing/events/binder/binder_transaction_ref_to_node/format +message BinderTransactionRefToNodeFormat { + int32 debug_id = 1; + int32 ref_debug_id = 2; + uint32 ref_desc = 3; + int32 node_debug_id = 4; + uint64 node_ptr = 5; +} + +// /sys/kernel/debug/tracing/events/binder/binder_transaction_ref_to_ref/format +message BinderTransactionRefToRefFormat { + int32 debug_id = 1; + int32 node_debug_id = 2; + int32 src_ref_debug_id = 3; + uint32 src_ref_desc = 4; + int32 dest_ref_debug_id = 5; + uint32 dest_ref_desc = 6; +} + +// /sys/kernel/debug/tracing/events/binder/binder_unlock/format +message BinderUnlockFormat { + string tag = 1; +} + +// /sys/kernel/debug/tracing/events/binder/binder_unmap_kernel_end/format +message BinderUnmapKernelEndFormat { + int32 proc = 1; + uint64 page_index = 2; +} + +// /sys/kernel/debug/tracing/events/binder/binder_unmap_kernel_start/format +message BinderUnmapKernelStartFormat { + int32 proc = 1; + uint64 page_index = 2; +} + +// /sys/kernel/debug/tracing/events/binder/binder_unmap_user_end/format +message BinderUnmapUserEndFormat { + int32 proc = 1; + uint64 page_index = 2; +} + +// /sys/kernel/debug/tracing/events/binder/binder_unmap_user_start/format +message BinderUnmapUserStartFormat { + int32 proc = 1; + uint64 page_index = 2; +} + +// /sys/kernel/debug/tracing/events/binder/binder_update_page_range/format +message BinderUpdatePageRangeFormat { + int32 proc = 1; + uint32 allocate = 2; + uint64 offset = 3; + uint64 size = 4; +} + +// /sys/kernel/debug/tracing/events/binder/binder_wait_for_work/format +message BinderWaitForWorkFormat { + uint32 proc_work = 1; + uint32 transaction_stack = 2; + uint32 thread_todo = 3; +} + +// /sys/kernel/debug/tracing/events/binder/binder_write_done/format +message BinderWriteDoneFormat { + int32 ret = 1; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/block.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/block.proto new file mode 100644 index 0000000..5975c0e --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/block.proto @@ -0,0 +1,179 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: block +// /sys/kernel/debug/tracing/events/block/block_bio_backmerge/format +message BlockBioBackmergeFormat { + uint64 dev = 1; + uint64 sector = 2; + uint32 nr_sector = 3; + string rwbs = 4; + string comm = 5; +} + +// /sys/kernel/debug/tracing/events/block/block_bio_bounce/format +message BlockBioBounceFormat { + uint64 dev = 1; + uint64 sector = 2; + uint32 nr_sector = 3; + string rwbs = 4; + string comm = 5; +} + +// /sys/kernel/debug/tracing/events/block/block_bio_complete/format +message BlockBioCompleteFormat { + uint64 dev = 1; + uint64 sector = 2; + uint32 nr_sector = 3; + int32 error = 4; + string rwbs = 5; +} + +// /sys/kernel/debug/tracing/events/block/block_bio_frontmerge/format +message BlockBioFrontmergeFormat { + uint64 dev = 1; + uint64 sector = 2; + uint32 nr_sector = 3; + string rwbs = 4; + string comm = 5; +} + +// /sys/kernel/debug/tracing/events/block/block_bio_queue/format +message BlockBioQueueFormat { + uint64 dev = 1; + uint64 sector = 2; + uint32 nr_sector = 3; + string rwbs = 4; + string comm = 5; +} + +// /sys/kernel/debug/tracing/events/block/block_bio_remap/format +message BlockBioRemapFormat { + uint64 dev = 1; + uint64 sector = 2; + uint32 nr_sector = 3; + uint64 old_dev = 4; + uint64 old_sector = 5; + string rwbs = 6; +} + +// /sys/kernel/debug/tracing/events/block/block_dirty_buffer/format +message BlockDirtyBufferFormat { + uint64 dev = 1; + uint64 sector = 2; + uint64 size = 3; +} + +// /sys/kernel/debug/tracing/events/block/block_getrq/format +message BlockGetrqFormat { + uint64 dev = 1; + uint64 sector = 2; + uint32 nr_sector = 3; + string rwbs = 4; + string comm = 5; +} + +// /sys/kernel/debug/tracing/events/block/block_plug/format +message BlockPlugFormat { + string comm = 1; +} + +// /sys/kernel/debug/tracing/events/block/block_rq_complete/format +message BlockRqCompleteFormat { + uint64 dev = 1; + uint64 sector = 2; + uint32 nr_sector = 3; + int32 error = 4; + string rwbs = 5; + string cmd = 6; +} + +// /sys/kernel/debug/tracing/events/block/block_rq_insert/format +message BlockRqInsertFormat { + uint64 dev = 1; + uint64 sector = 2; + uint32 nr_sector = 3; + uint32 bytes = 4; + string rwbs = 5; + string comm = 6; + string cmd = 7; +} + +// /sys/kernel/debug/tracing/events/block/block_rq_issue/format +message BlockRqIssueFormat { + uint64 dev = 1; + uint64 sector = 2; + uint32 nr_sector = 3; + uint32 bytes = 4; + string rwbs = 5; + string comm = 6; + string cmd = 7; +} + +// /sys/kernel/debug/tracing/events/block/block_rq_remap/format +message BlockRqRemapFormat { + uint64 dev = 1; + uint64 sector = 2; + uint32 nr_sector = 3; + uint64 old_dev = 4; + uint64 old_sector = 5; + uint32 nr_bios = 6; + string rwbs = 7; +} + +// /sys/kernel/debug/tracing/events/block/block_rq_requeue/format +message BlockRqRequeueFormat { + uint64 dev = 1; + uint64 sector = 2; + uint32 nr_sector = 3; + string rwbs = 4; + string cmd = 5; +} + +// /sys/kernel/debug/tracing/events/block/block_sleeprq/format +message BlockSleeprqFormat { + uint64 dev = 1; + uint64 sector = 2; + uint32 nr_sector = 3; + string rwbs = 4; + string comm = 5; +} + +// /sys/kernel/debug/tracing/events/block/block_split/format +message BlockSplitFormat { + uint64 dev = 1; + uint64 sector = 2; + uint64 new_sector = 3; + string rwbs = 4; + string comm = 5; +} + +// /sys/kernel/debug/tracing/events/block/block_touch_buffer/format +message BlockTouchBufferFormat { + uint64 dev = 1; + uint64 sector = 2; + uint64 size = 3; +} + +// /sys/kernel/debug/tracing/events/block/block_unplug/format +message BlockUnplugFormat { + int32 nr_rq = 1; + string comm = 2; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/cgroup.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/cgroup.proto new file mode 100644 index 0000000..2254432 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/cgroup.proto @@ -0,0 +1,93 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: cgroup +// /sys/kernel/debug/tracing/events/cgroup/cgroup_attach_task/format +message CgroupAttachTaskFormat { + int32 dst_root = 1; + int32 dst_id = 2; + int32 dst_level = 3; + int32 pid = 4; + string dst_path = 5; + string comm = 6; +} + +// /sys/kernel/debug/tracing/events/cgroup/cgroup_destroy_root/format +message CgroupDestroyRootFormat { + int32 root = 1; + uint32 ss_mask = 2; + string name = 3; +} + +// /sys/kernel/debug/tracing/events/cgroup/cgroup_mkdir/format +message CgroupMkdirFormat { + int32 root = 1; + int32 id = 2; + int32 level = 3; + string path = 4; +} + +// /sys/kernel/debug/tracing/events/cgroup/cgroup_release/format +message CgroupReleaseFormat { + int32 root = 1; + int32 id = 2; + int32 level = 3; + string path = 4; +} + +// /sys/kernel/debug/tracing/events/cgroup/cgroup_remount/format +message CgroupRemountFormat { + int32 root = 1; + uint32 ss_mask = 2; + string name = 3; +} + +// /sys/kernel/debug/tracing/events/cgroup/cgroup_rename/format +message CgroupRenameFormat { + int32 root = 1; + int32 id = 2; + int32 level = 3; + string path = 4; +} + +// /sys/kernel/debug/tracing/events/cgroup/cgroup_rmdir/format +message CgroupRmdirFormat { + int32 root = 1; + int32 id = 2; + int32 level = 3; + string path = 4; +} + +// /sys/kernel/debug/tracing/events/cgroup/cgroup_setup_root/format +message CgroupSetupRootFormat { + int32 root = 1; + uint32 ss_mask = 2; + string name = 3; +} + +// /sys/kernel/debug/tracing/events/cgroup/cgroup_transfer_tasks/format +message CgroupTransferTasksFormat { + int32 dst_root = 1; + int32 dst_id = 2; + int32 dst_level = 3; + int32 pid = 4; + string dst_path = 5; + string comm = 6; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/clk.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/clk.proto new file mode 100644 index 0000000..0f74b25 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/clk.proto @@ -0,0 +1,96 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: clk +// /sys/kernel/debug/tracing/events/clk/clk_disable/format +message ClkDisableFormat { + string name = 1; +} + +// /sys/kernel/debug/tracing/events/clk/clk_disable_complete/format +message ClkDisableCompleteFormat { + string name = 1; +} + +// /sys/kernel/debug/tracing/events/clk/clk_enable/format +message ClkEnableFormat { + string name = 1; +} + +// /sys/kernel/debug/tracing/events/clk/clk_enable_complete/format +message ClkEnableCompleteFormat { + string name = 1; +} + +// /sys/kernel/debug/tracing/events/clk/clk_prepare/format +message ClkPrepareFormat { + string name = 1; +} + +// /sys/kernel/debug/tracing/events/clk/clk_prepare_complete/format +message ClkPrepareCompleteFormat { + string name = 1; +} + +// /sys/kernel/debug/tracing/events/clk/clk_set_parent/format +message ClkSetParentFormat { + string name = 1; + string pname = 2; +} + +// /sys/kernel/debug/tracing/events/clk/clk_set_parent_complete/format +message ClkSetParentCompleteFormat { + string name = 1; + string pname = 2; +} + +// /sys/kernel/debug/tracing/events/clk/clk_set_phase/format +message ClkSetPhaseFormat { + string name = 1; + int32 phase = 2; +} + +// /sys/kernel/debug/tracing/events/clk/clk_set_phase_complete/format +message ClkSetPhaseCompleteFormat { + string name = 1; + int32 phase = 2; +} + +// /sys/kernel/debug/tracing/events/clk/clk_set_rate/format +message ClkSetRateFormat { + string name = 1; + uint64 rate = 2; +} + +// /sys/kernel/debug/tracing/events/clk/clk_set_rate_complete/format +message ClkSetRateCompleteFormat { + string name = 1; + uint64 rate = 2; +} + +// /sys/kernel/debug/tracing/events/clk/clk_unprepare/format +message ClkUnprepareFormat { + string name = 1; +} + +// /sys/kernel/debug/tracing/events/clk/clk_unprepare_complete/format +message ClkUnprepareCompleteFormat { + string name = 1; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/compaction.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/compaction.proto new file mode 100644 index 0000000..2df331f --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/compaction.proto @@ -0,0 +1,114 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: compaction +// /sys/kernel/debug/tracing/events/compaction/mm_compaction_begin/format +message MmCompactionBeginFormat { + uint64 zone_start = 1; + uint64 migrate_pfn = 2; + uint64 free_pfn = 3; + uint64 zone_end = 4; + uint32 sync = 5; +} + +// /sys/kernel/debug/tracing/events/compaction/mm_compaction_defer_compaction/format +message MmCompactionDeferCompactionFormat { + int32 nid = 1; + uint32 idx = 2; + int32 order = 3; + uint32 considered = 4; + uint32 defer_shift = 5; + int32 order_failed = 6; +} + +// /sys/kernel/debug/tracing/events/compaction/mm_compaction_defer_reset/format +message MmCompactionDeferResetFormat { + int32 nid = 1; + uint32 idx = 2; + int32 order = 3; + uint32 considered = 4; + uint32 defer_shift = 5; + int32 order_failed = 6; +} + +// /sys/kernel/debug/tracing/events/compaction/mm_compaction_deferred/format +message MmCompactionDeferredFormat { + int32 nid = 1; + uint32 idx = 2; + int32 order = 3; + uint32 considered = 4; + uint32 defer_shift = 5; + int32 order_failed = 6; +} + +// /sys/kernel/debug/tracing/events/compaction/mm_compaction_end/format +message MmCompactionEndFormat { + uint64 zone_start = 1; + uint64 migrate_pfn = 2; + uint64 free_pfn = 3; + uint64 zone_end = 4; + uint32 sync = 5; + int32 status = 6; +} + +// /sys/kernel/debug/tracing/events/compaction/mm_compaction_finished/format +message MmCompactionFinishedFormat { + int32 nid = 1; + uint32 idx = 2; + int32 order = 3; + int32 ret = 4; +} + +// /sys/kernel/debug/tracing/events/compaction/mm_compaction_isolate_freepages/format +message MmCompactionIsolateFreepagesFormat { + uint64 start_pfn = 1; + uint64 end_pfn = 2; + uint64 nr_scanned = 3; + uint64 nr_taken = 4; +} + +// /sys/kernel/debug/tracing/events/compaction/mm_compaction_isolate_migratepages/format +message MmCompactionIsolateMigratepagesFormat { + uint64 start_pfn = 1; + uint64 end_pfn = 2; + uint64 nr_scanned = 3; + uint64 nr_taken = 4; +} + +// /sys/kernel/debug/tracing/events/compaction/mm_compaction_migratepages/format +message MmCompactionMigratepagesFormat { + uint64 nr_migrated = 1; + uint64 nr_failed = 2; +} + +// /sys/kernel/debug/tracing/events/compaction/mm_compaction_suitable/format +message MmCompactionSuitableFormat { + int32 nid = 1; + uint32 idx = 2; + int32 order = 3; + int32 ret = 4; +} + +// /sys/kernel/debug/tracing/events/compaction/mm_compaction_try_to_compact_pages/format +message MmCompactionTryToCompactPagesFormat { + int32 order = 1; + uint32 gfp_mask = 2; + int32 prio = 3; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/cpuhp.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/cpuhp.proto new file mode 100644 index 0000000..db805bc --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/cpuhp.proto @@ -0,0 +1,44 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: cpuhp +// /sys/kernel/debug/tracing/events/cpuhp/cpuhp_enter/format +message CpuhpEnterFormat { + uint32 cpu = 1; + int32 target = 2; + int32 idx = 3; + uint64 fun = 4; +} + +// /sys/kernel/debug/tracing/events/cpuhp/cpuhp_exit/format +message CpuhpExitFormat { + uint32 cpu = 1; + int32 state = 2; + int32 idx = 3; + int32 ret = 4; +} + +// /sys/kernel/debug/tracing/events/cpuhp/cpuhp_multi_enter/format +message CpuhpMultiEnterFormat { + uint32 cpu = 1; + int32 target = 2; + int32 idx = 3; + uint64 fun = 4; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/dma_fence.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/dma_fence.proto new file mode 100644 index 0000000..74b511b --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/dma_fence.proto @@ -0,0 +1,76 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: dma_fence +// /sys/kernel/debug/tracing/events/dma_fence/dma_fence_destroy/format +message DmaFenceDestroyFormat { + string driver = 1; + string timeline = 2; + uint32 context = 3; + uint32 seqno = 4; +} + +// /sys/kernel/debug/tracing/events/dma_fence/dma_fence_emit/format +message DmaFenceEmitFormat { + string driver = 1; + string timeline = 2; + uint32 context = 3; + uint32 seqno = 4; +} + +// /sys/kernel/debug/tracing/events/dma_fence/dma_fence_enable_signal/format +message DmaFenceEnableSignalFormat { + string driver = 1; + string timeline = 2; + uint32 context = 3; + uint32 seqno = 4; +} + +// /sys/kernel/debug/tracing/events/dma_fence/dma_fence_init/format +message DmaFenceInitFormat { + string driver = 1; + string timeline = 2; + uint32 context = 3; + uint32 seqno = 4; +} + +// /sys/kernel/debug/tracing/events/dma_fence/dma_fence_signaled/format +message DmaFenceSignaledFormat { + string driver = 1; + string timeline = 2; + uint32 context = 3; + uint32 seqno = 4; +} + +// /sys/kernel/debug/tracing/events/dma_fence/dma_fence_wait_end/format +message DmaFenceWaitEndFormat { + string driver = 1; + string timeline = 2; + uint32 context = 3; + uint32 seqno = 4; +} + +// /sys/kernel/debug/tracing/events/dma_fence/dma_fence_wait_start/format +message DmaFenceWaitStartFormat { + string driver = 1; + string timeline = 2; + uint32 context = 3; + uint32 seqno = 4; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/ext4.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/ext4.proto new file mode 100644 index 0000000..d66f4ba --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/ext4.proto @@ -0,0 +1,872 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: ext4 +// /sys/kernel/debug/tracing/events/ext4/ext4_alloc_da_blocks/format +message Ext4AllocDaBlocksFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 data_blocks = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_allocate_blocks/format +message Ext4AllocateBlocksFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 block = 3; + uint32 len = 4; + uint32 logical = 5; + uint32 lleft = 6; + uint32 lright = 7; + uint64 goal = 8; + uint64 pleft = 9; + uint64 pright = 10; + uint32 flags = 11; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_allocate_inode/format +message Ext4AllocateInodeFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 dir = 3; + uint32 mode = 4; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_begin_ordered_truncate/format +message Ext4BeginOrderedTruncateFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 new_size = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_collapse_range/format +message Ext4CollapseRangeFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 offset = 3; + uint64 len = 4; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_da_release_space/format +message Ext4DaReleaseSpaceFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 i_blocks = 3; + int32 freed_blocks = 4; + int32 reserved_data_blocks = 5; + uint32 mode = 6; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_da_reserve_space/format +message Ext4DaReserveSpaceFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 i_blocks = 3; + int32 reserved_data_blocks = 4; + uint32 mode = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_da_update_reserve_space/format +message Ext4DaUpdateReserveSpaceFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 i_blocks = 3; + int32 used_blocks = 4; + int32 reserved_data_blocks = 5; + int32 quota_claim = 6; + uint32 mode = 7; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_da_write_begin/format +message Ext4DaWriteBeginFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 pos = 3; + uint32 len = 4; + uint32 flags = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_da_write_end/format +message Ext4DaWriteEndFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 pos = 3; + uint32 len = 4; + uint32 copied = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_da_write_pages/format +message Ext4DaWritePagesFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 first_page = 3; + uint64 nr_to_write = 4; + int32 sync_mode = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_da_write_pages_extent/format +message Ext4DaWritePagesExtentFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 lblk = 3; + uint32 len = 4; + uint32 flags = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_direct_IO_enter/format +message Ext4DirectIoEnterFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 pos = 3; + uint64 len = 4; + int32 rw = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_direct_IO_exit/format +message Ext4DirectIoExitFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 pos = 3; + uint64 len = 4; + int32 rw = 5; + int32 ret = 6; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_discard_blocks/format +message Ext4DiscardBlocksFormat { + uint64 dev = 1; + uint64 blk = 2; + uint64 count = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_discard_preallocations/format +message Ext4DiscardPreallocationsFormat { + uint64 dev = 1; + uint64 ino = 2; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_drop_inode/format +message Ext4DropInodeFormat { + uint64 dev = 1; + uint64 ino = 2; + int32 drop = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_es_cache_extent/format +message Ext4EsCacheExtentFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 lblk = 3; + uint32 len = 4; + uint64 pblk = 5; + uint32 status = 6; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_es_find_delayed_extent_range_enter/format +message Ext4EsFindDelayedExtentRangeEnterFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 lblk = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_es_find_delayed_extent_range_exit/format +message Ext4EsFindDelayedExtentRangeExitFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 lblk = 3; + uint32 len = 4; + uint64 pblk = 5; + uint32 status = 6; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_es_insert_extent/format +message Ext4EsInsertExtentFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 lblk = 3; + uint32 len = 4; + uint64 pblk = 5; + uint32 status = 6; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_es_lookup_extent_enter/format +message Ext4EsLookupExtentEnterFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 lblk = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_es_lookup_extent_exit/format +message Ext4EsLookupExtentExitFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 lblk = 3; + uint32 len = 4; + uint64 pblk = 5; + uint32 status = 6; + int32 found = 7; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_es_remove_extent/format +message Ext4EsRemoveExtentFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 lblk = 3; + uint64 len = 4; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_es_shrink/format +message Ext4EsShrinkFormat { + uint64 dev = 1; + int32 nr_shrunk = 2; + uint64 scan_time = 3; + int32 nr_skipped = 4; + int32 retried = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_es_shrink_count/format +message Ext4EsShrinkCountFormat { + uint64 dev = 1; + int32 nr_to_scan = 2; + int32 cache_cnt = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_es_shrink_scan_enter/format +message Ext4EsShrinkScanEnterFormat { + uint64 dev = 1; + int32 nr_to_scan = 2; + int32 cache_cnt = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_es_shrink_scan_exit/format +message Ext4EsShrinkScanExitFormat { + uint64 dev = 1; + int32 nr_shrunk = 2; + int32 cache_cnt = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_evict_inode/format +message Ext4EvictInodeFormat { + uint64 dev = 1; + uint64 ino = 2; + int32 nlink = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_ext_convert_to_initialized_enter/format +message Ext4ExtConvertToInitializedEnterFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 m_lblk = 3; + uint32 m_len = 4; + uint32 u_lblk = 5; + uint32 u_len = 6; + uint64 u_pblk = 7; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_ext_convert_to_initialized_fastpath/format +message Ext4ExtConvertToInitializedFastpathFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 m_lblk = 3; + uint32 m_len = 4; + uint32 u_lblk = 5; + uint32 u_len = 6; + uint64 u_pblk = 7; + uint32 i_lblk = 8; + uint32 i_len = 9; + uint64 i_pblk = 10; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_ext_handle_unwritten_extents/format +message Ext4ExtHandleUnwrittenExtentsFormat { + uint64 dev = 1; + uint64 ino = 2; + int32 flags = 3; + uint32 lblk = 4; + uint64 pblk = 5; + uint32 len = 6; + uint32 allocated = 7; + uint64 newblk = 8; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_ext_in_cache/format +message Ext4ExtInCacheFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 lblk = 3; + int32 ret = 4; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_ext_load_extent/format +message Ext4ExtLoadExtentFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 pblk = 3; + uint32 lblk = 4; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_ext_map_blocks_enter/format +message Ext4ExtMapBlocksEnterFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 lblk = 3; + uint32 len = 4; + uint32 flags = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_ext_map_blocks_exit/format +message Ext4ExtMapBlocksExitFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 flags = 3; + uint64 pblk = 4; + uint32 lblk = 5; + uint32 len = 6; + uint32 mflags = 7; + int32 ret = 8; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_ext_put_in_cache/format +message Ext4ExtPutInCacheFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 lblk = 3; + uint32 len = 4; + uint64 start = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_ext_remove_space/format +message Ext4ExtRemoveSpaceFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 start = 3; + uint32 end = 4; + int32 depth = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_ext_remove_space_done/format +message Ext4ExtRemoveSpaceDoneFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 start = 3; + uint32 end = 4; + int32 depth = 5; + uint64 partial = 6; + uint32 eh_entries = 7; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_ext_rm_idx/format +message Ext4ExtRmIdxFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 pblk = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_ext_rm_leaf/format +message Ext4ExtRmLeafFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 partial = 3; + uint32 start = 4; + uint32 ee_lblk = 5; + uint64 ee_pblk = 6; + int32 ee_len = 7; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_ext_show_extent/format +message Ext4ExtShowExtentFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 pblk = 3; + uint32 lblk = 4; + uint32 len = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_fallocate_enter/format +message Ext4FallocateEnterFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 offset = 3; + uint64 len = 4; + int32 mode = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_fallocate_exit/format +message Ext4FallocateExitFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 pos = 3; + uint32 blocks = 4; + int32 ret = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_find_delalloc_range/format +message Ext4FindDelallocRangeFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 from = 3; + uint32 to = 4; + int32 reverse = 5; + int32 found = 6; + uint32 found_blk = 7; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_forget/format +message Ext4ForgetFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 block = 3; + int32 is_metadata = 4; + uint32 mode = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_free_blocks/format +message Ext4FreeBlocksFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 block = 3; + uint64 count = 4; + int32 flags = 5; + uint32 mode = 6; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_free_inode/format +message Ext4FreeInodeFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 uid = 3; + uint32 gid = 4; + uint64 blocks = 5; + uint32 mode = 6; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_get_implied_cluster_alloc_exit/format +message Ext4GetImpliedClusterAllocExitFormat { + uint64 dev = 1; + uint32 flags = 2; + uint32 lblk = 3; + uint64 pblk = 4; + uint32 len = 5; + int32 ret = 6; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_get_reserved_cluster_alloc/format +message Ext4GetReservedClusterAllocFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 lblk = 3; + uint32 len = 4; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_ind_map_blocks_enter/format +message Ext4IndMapBlocksEnterFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 lblk = 3; + uint32 len = 4; + uint32 flags = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_ind_map_blocks_exit/format +message Ext4IndMapBlocksExitFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 flags = 3; + uint64 pblk = 4; + uint32 lblk = 5; + uint32 len = 6; + uint32 mflags = 7; + int32 ret = 8; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_insert_range/format +message Ext4InsertRangeFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 offset = 3; + uint64 len = 4; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_invalidatepage/format +message Ext4InvalidatepageFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 index = 3; + uint32 offset = 4; + uint32 length = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_journal_start/format +message Ext4JournalStartFormat { + uint64 dev = 1; + uint64 ip = 2; + int32 blocks = 3; + int32 rsv_blocks = 4; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_journal_start_reserved/format +message Ext4JournalStartReservedFormat { + uint64 dev = 1; + uint64 ip = 2; + int32 blocks = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_journalled_invalidatepage/format +message Ext4JournalledInvalidatepageFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 index = 3; + uint32 offset = 4; + uint32 length = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_journalled_write_end/format +message Ext4JournalledWriteEndFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 pos = 3; + uint32 len = 4; + uint32 copied = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_load_inode/format +message Ext4LoadInodeFormat { + uint64 dev = 1; + uint64 ino = 2; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_load_inode_bitmap/format +message Ext4LoadInodeBitmapFormat { + uint64 dev = 1; + uint32 group = 2; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_mark_inode_dirty/format +message Ext4MarkInodeDirtyFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 ip = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_mb_bitmap_load/format +message Ext4MbBitmapLoadFormat { + uint64 dev = 1; + uint32 group = 2; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_mb_buddy_bitmap_load/format +message Ext4MbBuddyBitmapLoadFormat { + uint64 dev = 1; + uint32 group = 2; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_mb_discard_preallocations/format +message Ext4MbDiscardPreallocationsFormat { + uint64 dev = 1; + int32 needed = 2; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_mb_new_group_pa/format +message Ext4MbNewGroupPaFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 pa_pstart = 3; + uint64 pa_lstart = 4; + uint32 pa_len = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_mb_new_inode_pa/format +message Ext4MbNewInodePaFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 pa_pstart = 3; + uint64 pa_lstart = 4; + uint32 pa_len = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_mb_release_group_pa/format +message Ext4MbReleaseGroupPaFormat { + uint64 dev = 1; + uint64 pa_pstart = 2; + uint32 pa_len = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_mb_release_inode_pa/format +message Ext4MbReleaseInodePaFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 block = 3; + uint32 count = 4; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_mballoc_alloc/format +message Ext4MballocAllocFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 orig_logical = 3; + int32 orig_start = 4; + uint32 orig_group = 5; + int32 orig_len = 6; + uint32 goal_logical = 7; + int32 goal_start = 8; + uint32 goal_group = 9; + int32 goal_len = 10; + uint32 result_logical = 11; + int32 result_start = 12; + uint32 result_group = 13; + int32 result_len = 14; + uint32 found = 15; + uint32 groups = 16; + uint32 buddy = 17; + uint32 flags = 18; + uint32 tail = 19; + uint32 cr = 20; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_mballoc_discard/format +message Ext4MballocDiscardFormat { + uint64 dev = 1; + uint64 ino = 2; + int32 result_start = 3; + uint32 result_group = 4; + int32 result_len = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_mballoc_free/format +message Ext4MballocFreeFormat { + uint64 dev = 1; + uint64 ino = 2; + int32 result_start = 3; + uint32 result_group = 4; + int32 result_len = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_mballoc_prealloc/format +message Ext4MballocPreallocFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 orig_logical = 3; + int32 orig_start = 4; + uint32 orig_group = 5; + int32 orig_len = 6; + uint32 result_logical = 7; + int32 result_start = 8; + uint32 result_group = 9; + int32 result_len = 10; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_other_inode_update_time/format +message Ext4OtherInodeUpdateTimeFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 orig_ino = 3; + uint32 uid = 4; + uint32 gid = 5; + uint32 mode = 6; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_punch_hole/format +message Ext4PunchHoleFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 offset = 3; + uint64 len = 4; + int32 mode = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_read_block_bitmap_load/format +message Ext4ReadBlockBitmapLoadFormat { + uint64 dev = 1; + uint32 group = 2; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_readpage/format +message Ext4ReadpageFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 index = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_releasepage/format +message Ext4ReleasepageFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 index = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_remove_blocks/format +message Ext4RemoveBlocksFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 from = 3; + uint32 to = 4; + uint64 partial = 5; + uint64 ee_pblk = 6; + uint32 ee_lblk = 7; + uint32 ee_len = 8; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_request_blocks/format +message Ext4RequestBlocksFormat { + uint64 dev = 1; + uint64 ino = 2; + uint32 len = 3; + uint32 logical = 4; + uint32 lleft = 5; + uint32 lright = 6; + uint64 goal = 7; + uint64 pleft = 8; + uint64 pright = 9; + uint32 flags = 10; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_request_inode/format +message Ext4RequestInodeFormat { + uint64 dev = 1; + uint64 dir = 2; + uint32 mode = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_sync_file_enter/format +message Ext4SyncFileEnterFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 parent = 3; + int32 datasync = 4; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_sync_file_exit/format +message Ext4SyncFileExitFormat { + uint64 dev = 1; + uint64 ino = 2; + int32 ret = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_sync_fs/format +message Ext4SyncFsFormat { + uint64 dev = 1; + int32 wait = 2; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_trim_all_free/format +message Ext4TrimAllFreeFormat { + int32 dev_major = 1; + int32 dev_minor = 2; + uint32 group = 3; + int32 start = 4; + int32 len = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_trim_extent/format +message Ext4TrimExtentFormat { + int32 dev_major = 1; + int32 dev_minor = 2; + uint32 group = 3; + int32 start = 4; + int32 len = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_truncate_enter/format +message Ext4TruncateEnterFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 blocks = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_truncate_exit/format +message Ext4TruncateExitFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 blocks = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_unlink_enter/format +message Ext4UnlinkEnterFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 parent = 3; + uint64 size = 4; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_unlink_exit/format +message Ext4UnlinkExitFormat { + uint64 dev = 1; + uint64 ino = 2; + int32 ret = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_write_begin/format +message Ext4WriteBeginFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 pos = 3; + uint32 len = 4; + uint32 flags = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_write_end/format +message Ext4WriteEndFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 pos = 3; + uint32 len = 4; + uint32 copied = 5; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_writepage/format +message Ext4WritepageFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 index = 3; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_writepages/format +message Ext4WritepagesFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 nr_to_write = 3; + uint64 pages_skipped = 4; + uint64 range_start = 5; + uint64 range_end = 6; + uint64 writeback_index = 7; + int32 sync_mode = 8; + uint32 for_kupdate = 9; + uint32 range_cyclic = 10; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_writepages_result/format +message Ext4WritepagesResultFormat { + uint64 dev = 1; + uint64 ino = 2; + int32 ret = 3; + int32 pages_written = 4; + uint64 pages_skipped = 5; + uint64 writeback_index = 6; + int32 sync_mode = 7; +} + +// /sys/kernel/debug/tracing/events/ext4/ext4_zero_range/format +message Ext4ZeroRangeFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 offset = 3; + uint64 len = 4; + int32 mode = 5; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/filelock.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/filelock.proto new file mode 100644 index 0000000..1a52d8c --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/filelock.proto @@ -0,0 +1,97 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: filelock +// /sys/kernel/debug/tracing/events/filelock/break_lease_block/format +message BreakLeaseBlockFormat { + uint64 fl = 1; + uint64 i_ino = 2; + uint64 s_dev = 3; + uint64 fl_next = 4; + uint32 fl_owner = 5; + uint32 fl_flags = 6; + uint32 fl_type = 7; + uint64 fl_break_time = 8; + uint64 fl_downgrade_time = 9; +} + +// /sys/kernel/debug/tracing/events/filelock/break_lease_noblock/format +message BreakLeaseNoblockFormat { + uint64 fl = 1; + uint64 i_ino = 2; + uint64 s_dev = 3; + uint64 fl_next = 4; + uint32 fl_owner = 5; + uint32 fl_flags = 6; + uint32 fl_type = 7; + uint64 fl_break_time = 8; + uint64 fl_downgrade_time = 9; +} + +// /sys/kernel/debug/tracing/events/filelock/break_lease_unblock/format +message BreakLeaseUnblockFormat { + uint64 fl = 1; + uint64 i_ino = 2; + uint64 s_dev = 3; + uint64 fl_next = 4; + uint32 fl_owner = 5; + uint32 fl_flags = 6; + uint32 fl_type = 7; + uint64 fl_break_time = 8; + uint64 fl_downgrade_time = 9; +} + +// /sys/kernel/debug/tracing/events/filelock/generic_add_lease/format +message GenericAddLeaseFormat { + uint64 i_ino = 1; + int32 wcount = 2; + int32 dcount = 3; + int32 icount = 4; + uint64 s_dev = 5; + uint32 fl_owner = 6; + uint32 fl_flags = 7; + uint32 fl_type = 8; +} + +// /sys/kernel/debug/tracing/events/filelock/generic_delete_lease/format +message GenericDeleteLeaseFormat { + uint64 fl = 1; + uint64 i_ino = 2; + uint64 s_dev = 3; + uint64 fl_next = 4; + uint32 fl_owner = 5; + uint32 fl_flags = 6; + uint32 fl_type = 7; + uint64 fl_break_time = 8; + uint64 fl_downgrade_time = 9; +} + +// /sys/kernel/debug/tracing/events/filelock/time_out_leases/format +message TimeOutLeasesFormat { + uint64 fl = 1; + uint64 i_ino = 2; + uint64 s_dev = 3; + uint64 fl_next = 4; + uint32 fl_owner = 5; + uint32 fl_flags = 6; + uint32 fl_type = 7; + uint64 fl_break_time = 8; + uint64 fl_downgrade_time = 9; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/filemap.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/filemap.proto new file mode 100644 index 0000000..5410c6f --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/filemap.proto @@ -0,0 +1,36 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: filemap +// /sys/kernel/debug/tracing/events/filemap/mm_filemap_add_to_page_cache/format +message MmFilemapAddToPageCacheFormat { + uint64 pfn = 1; + uint64 i_ino = 2; + uint64 index = 3; + uint64 s_dev = 4; +} + +// /sys/kernel/debug/tracing/events/filemap/mm_filemap_delete_from_page_cache/format +message MmFilemapDeleteFromPageCacheFormat { + uint64 pfn = 1; + uint64 i_ino = 2; + uint64 index = 3; + uint64 s_dev = 4; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/ftrace.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/ftrace.proto new file mode 100644 index 0000000..6c9429d --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/ftrace.proto @@ -0,0 +1,115 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: ftrace +// /sys/kernel/debug/tracing/events/ftrace/bputs/format +message BputsFormat { + uint64 ip = 1; + string str = 2; +} + +// /sys/kernel/debug/tracing/events/ftrace/branch/format +message BranchFormat { + uint32 line = 1; + string func = 2; + string file = 3; + uint32 correct = 4; + uint32 constant = 5; +} + +// /sys/kernel/debug/tracing/events/ftrace/context_switch/format +message ContextSwitchFormat { + uint32 prev_pid = 1; + uint32 next_pid = 2; + uint32 next_cpu = 3; + uint32 prev_prio = 4; + uint32 prev_state = 5; + uint32 next_prio = 6; + uint32 next_state = 7; +} + +// /sys/kernel/debug/tracing/events/ftrace/funcgraph_entry/format +message FuncgraphEntryFormat { + uint64 func = 1; + int32 depth = 2; +} + +// /sys/kernel/debug/tracing/events/ftrace/funcgraph_exit/format +message FuncgraphExitFormat { + uint64 func = 1; + uint64 calltime = 2; + uint64 rettime = 3; + uint64 overrun = 4; + int32 depth = 5; +} + +// /sys/kernel/debug/tracing/events/ftrace/function/format +message FunctionFormat { + uint64 ip = 1; + uint64 parent_ip = 2; +} + +// /sys/kernel/debug/tracing/events/ftrace/kernel_stack/format +message KernelStackFormat { + int32 size = 1; + uint64 caller = 2; +} + +// /sys/kernel/debug/tracing/events/ftrace/mmiotrace_map/format +message MmiotraceMapFormat { + uint64 phys = 1; + uint64 virt = 2; + uint64 len = 3; + int32 map_id = 4; + uint32 opcode = 5; +} + +// /sys/kernel/debug/tracing/events/ftrace/mmiotrace_rw/format +message MmiotraceRwFormat { + uint64 phys = 1; + uint64 value = 2; + uint64 pc = 3; + int32 map_id = 4; + uint32 opcode = 5; + uint32 width = 6; +} + +// /sys/kernel/debug/tracing/events/ftrace/print/format +message PrintFormat { + uint64 ip = 1; + string buf = 2; +} + +// /sys/kernel/debug/tracing/events/ftrace/user_stack/format +message UserStackFormat { + uint32 tgid = 1; + string caller = 2; +} + +// /sys/kernel/debug/tracing/events/ftrace/wakeup/format +message WakeupFormat { + uint32 prev_pid = 1; + uint32 next_pid = 2; + uint32 next_cpu = 3; + uint32 prev_prio = 4; + uint32 prev_state = 5; + uint32 next_prio = 6; + uint32 next_state = 7; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/ftrace_event.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/ftrace_event.proto new file mode 100644 index 0000000..53b3e58 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/ftrace_event.proto @@ -0,0 +1,452 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +import "binder.proto"; +import "block.proto"; +import "cgroup.proto"; +import "clk.proto"; +import "compaction.proto"; +import "cpuhp.proto"; +import "dma_fence.proto"; +import "ext4.proto"; +import "filelock.proto"; +import "filemap.proto"; +import "ftrace.proto"; +import "gpio.proto"; +import "i2c.proto"; +import "ipi.proto"; +import "irq.proto"; +import "kmem.proto"; +import "net.proto"; +import "oom.proto"; +import "pagemap.proto"; +import "power.proto"; +import "printk.proto"; +import "raw_syscalls.proto"; +import "rcu.proto"; +import "sched.proto"; +import "signal.proto"; +import "sunrpc.proto"; +import "task.proto"; +import "timer.proto"; +import "v4l2.proto"; +import "vmscan.proto"; +import "workqueue.proto"; +import "writeback.proto"; + +message FtraceEvent { + uint64 timestamp = 1; + int32 tgid = 2; + string comm = 3; + + message CommonFileds { + uint32 type = 1; + uint32 flags = 2; + uint32 preempt_count = 3; + int32 pid = 4; + }; + CommonFileds common_fields = 50; + + oneof event { + BinderAllocLruEndFormat binder_alloc_lru_end_format = 100; + BinderAllocLruStartFormat binder_alloc_lru_start_format = 101; + BinderAllocPageEndFormat binder_alloc_page_end_format = 102; + BinderAllocPageStartFormat binder_alloc_page_start_format = 103; + BinderCommandFormat binder_command_format = 104; + BinderFreeLruEndFormat binder_free_lru_end_format = 105; + BinderFreeLruStartFormat binder_free_lru_start_format = 106; + BinderIoctlFormat binder_ioctl_format = 107; + BinderIoctlDoneFormat binder_ioctl_done_format = 108; + BinderLockFormat binder_lock_format = 109; + BinderLockedFormat binder_locked_format = 110; + BinderReadDoneFormat binder_read_done_format = 111; + BinderReturnFormat binder_return_format = 112; + BinderTransactionFormat binder_transaction_format = 113; + BinderTransactionAllocBufFormat binder_transaction_alloc_buf_format = 114; + BinderTransactionBufferReleaseFormat binder_transaction_buffer_release_format = 115; + BinderTransactionFailedBufferReleaseFormat binder_transaction_failed_buffer_release_format = 116; + BinderTransactionFdFormat binder_transaction_fd_format = 117; + BinderTransactionNodeToRefFormat binder_transaction_node_to_ref_format = 118; + BinderTransactionReceivedFormat binder_transaction_received_format = 119; + BinderTransactionRefToNodeFormat binder_transaction_ref_to_node_format = 120; + BinderTransactionRefToRefFormat binder_transaction_ref_to_ref_format = 121; + BinderUnlockFormat binder_unlock_format = 122; + BinderUnmapKernelEndFormat binder_unmap_kernel_end_format = 123; + BinderUnmapKernelStartFormat binder_unmap_kernel_start_format = 124; + BinderUnmapUserEndFormat binder_unmap_user_end_format = 125; + BinderUnmapUserStartFormat binder_unmap_user_start_format = 126; + BinderUpdatePageRangeFormat binder_update_page_range_format = 127; + BinderWaitForWorkFormat binder_wait_for_work_format = 128; + BinderWriteDoneFormat binder_write_done_format = 129; + BlockBioBackmergeFormat block_bio_backmerge_format = 200; + BlockBioBounceFormat block_bio_bounce_format = 201; + BlockBioCompleteFormat block_bio_complete_format = 202; + BlockBioFrontmergeFormat block_bio_frontmerge_format = 203; + BlockBioQueueFormat block_bio_queue_format = 204; + BlockBioRemapFormat block_bio_remap_format = 205; + BlockDirtyBufferFormat block_dirty_buffer_format = 206; + BlockGetrqFormat block_getrq_format = 207; + BlockPlugFormat block_plug_format = 208; + BlockRqCompleteFormat block_rq_complete_format = 209; + BlockRqInsertFormat block_rq_insert_format = 210; + BlockRqIssueFormat block_rq_issue_format = 211; + BlockRqRemapFormat block_rq_remap_format = 212; + BlockRqRequeueFormat block_rq_requeue_format = 213; + BlockSleeprqFormat block_sleeprq_format = 214; + BlockSplitFormat block_split_format = 215; + BlockTouchBufferFormat block_touch_buffer_format = 216; + BlockUnplugFormat block_unplug_format = 217; + CgroupAttachTaskFormat cgroup_attach_task_format = 300; + CgroupDestroyRootFormat cgroup_destroy_root_format = 301; + CgroupMkdirFormat cgroup_mkdir_format = 302; + CgroupReleaseFormat cgroup_release_format = 303; + CgroupRemountFormat cgroup_remount_format = 304; + CgroupRenameFormat cgroup_rename_format = 305; + CgroupRmdirFormat cgroup_rmdir_format = 306; + CgroupSetupRootFormat cgroup_setup_root_format = 307; + CgroupTransferTasksFormat cgroup_transfer_tasks_format = 308; + ClkDisableFormat clk_disable_format = 400; + ClkDisableCompleteFormat clk_disable_complete_format = 401; + ClkEnableFormat clk_enable_format = 402; + ClkEnableCompleteFormat clk_enable_complete_format = 403; + ClkPrepareFormat clk_prepare_format = 404; + ClkPrepareCompleteFormat clk_prepare_complete_format = 405; + ClkSetParentFormat clk_set_parent_format = 406; + ClkSetParentCompleteFormat clk_set_parent_complete_format = 407; + ClkSetPhaseFormat clk_set_phase_format = 408; + ClkSetPhaseCompleteFormat clk_set_phase_complete_format = 409; + ClkSetRateFormat clk_set_rate_format = 410; + ClkSetRateCompleteFormat clk_set_rate_complete_format = 411; + ClkUnprepareFormat clk_unprepare_format = 412; + ClkUnprepareCompleteFormat clk_unprepare_complete_format = 413; + MmCompactionBeginFormat mm_compaction_begin_format = 500; + MmCompactionDeferCompactionFormat mm_compaction_defer_compaction_format = 501; + MmCompactionDeferResetFormat mm_compaction_defer_reset_format = 502; + MmCompactionDeferredFormat mm_compaction_deferred_format = 503; + MmCompactionEndFormat mm_compaction_end_format = 504; + MmCompactionFinishedFormat mm_compaction_finished_format = 505; + MmCompactionIsolateFreepagesFormat mm_compaction_isolate_freepages_format = 506; + MmCompactionIsolateMigratepagesFormat mm_compaction_isolate_migratepages_format = 507; + MmCompactionMigratepagesFormat mm_compaction_migratepages_format = 508; + MmCompactionSuitableFormat mm_compaction_suitable_format = 509; + MmCompactionTryToCompactPagesFormat mm_compaction_try_to_compact_pages_format = 510; + CpuhpEnterFormat cpuhp_enter_format = 600; + CpuhpExitFormat cpuhp_exit_format = 601; + CpuhpMultiEnterFormat cpuhp_multi_enter_format = 602; + DmaFenceDestroyFormat dma_fence_destroy_format = 700; + DmaFenceEmitFormat dma_fence_emit_format = 701; + DmaFenceEnableSignalFormat dma_fence_enable_signal_format = 702; + DmaFenceInitFormat dma_fence_init_format = 703; + DmaFenceSignaledFormat dma_fence_signaled_format = 704; + DmaFenceWaitEndFormat dma_fence_wait_end_format = 705; + DmaFenceWaitStartFormat dma_fence_wait_start_format = 706; + Ext4AllocDaBlocksFormat ext4_alloc_da_blocks_format = 800; + Ext4AllocateBlocksFormat ext4_allocate_blocks_format = 801; + Ext4AllocateInodeFormat ext4_allocate_inode_format = 802; + Ext4BeginOrderedTruncateFormat ext4_begin_ordered_truncate_format = 803; + Ext4CollapseRangeFormat ext4_collapse_range_format = 804; + Ext4DaReleaseSpaceFormat ext4_da_release_space_format = 805; + Ext4DaReserveSpaceFormat ext4_da_reserve_space_format = 806; + Ext4DaUpdateReserveSpaceFormat ext4_da_update_reserve_space_format = 807; + Ext4DaWriteBeginFormat ext4_da_write_begin_format = 808; + Ext4DaWriteEndFormat ext4_da_write_end_format = 809; + Ext4DaWritePagesFormat ext4_da_write_pages_format = 810; + Ext4DaWritePagesExtentFormat ext4_da_write_pages_extent_format = 811; + Ext4DirectIoEnterFormat ext4_direct_io_enter_format = 812; + Ext4DirectIoExitFormat ext4_direct_io_exit_format = 813; + Ext4DiscardBlocksFormat ext4_discard_blocks_format = 814; + Ext4DiscardPreallocationsFormat ext4_discard_preallocations_format = 815; + Ext4DropInodeFormat ext4_drop_inode_format = 816; + Ext4EsCacheExtentFormat ext4_es_cache_extent_format = 817; + Ext4EsFindDelayedExtentRangeEnterFormat ext4_es_find_delayed_extent_range_enter_format = 818; + Ext4EsFindDelayedExtentRangeExitFormat ext4_es_find_delayed_extent_range_exit_format = 819; + Ext4EsInsertExtentFormat ext4_es_insert_extent_format = 820; + Ext4EsLookupExtentEnterFormat ext4_es_lookup_extent_enter_format = 821; + Ext4EsLookupExtentExitFormat ext4_es_lookup_extent_exit_format = 822; + Ext4EsRemoveExtentFormat ext4_es_remove_extent_format = 823; + Ext4EsShrinkFormat ext4_es_shrink_format = 824; + Ext4EsShrinkCountFormat ext4_es_shrink_count_format = 825; + Ext4EsShrinkScanEnterFormat ext4_es_shrink_scan_enter_format = 826; + Ext4EsShrinkScanExitFormat ext4_es_shrink_scan_exit_format = 827; + Ext4EvictInodeFormat ext4_evict_inode_format = 828; + Ext4ExtConvertToInitializedEnterFormat ext4_ext_convert_to_initialized_enter_format = 829; + Ext4ExtConvertToInitializedFastpathFormat ext4_ext_convert_to_initialized_fastpath_format = 830; + Ext4ExtHandleUnwrittenExtentsFormat ext4_ext_handle_unwritten_extents_format = 831; + Ext4ExtInCacheFormat ext4_ext_in_cache_format = 832; + Ext4ExtLoadExtentFormat ext4_ext_load_extent_format = 833; + Ext4ExtMapBlocksEnterFormat ext4_ext_map_blocks_enter_format = 834; + Ext4ExtMapBlocksExitFormat ext4_ext_map_blocks_exit_format = 835; + Ext4ExtPutInCacheFormat ext4_ext_put_in_cache_format = 836; + Ext4ExtRemoveSpaceFormat ext4_ext_remove_space_format = 837; + Ext4ExtRemoveSpaceDoneFormat ext4_ext_remove_space_done_format = 838; + Ext4ExtRmIdxFormat ext4_ext_rm_idx_format = 839; + Ext4ExtRmLeafFormat ext4_ext_rm_leaf_format = 840; + Ext4ExtShowExtentFormat ext4_ext_show_extent_format = 841; + Ext4FallocateEnterFormat ext4_fallocate_enter_format = 842; + Ext4FallocateExitFormat ext4_fallocate_exit_format = 843; + Ext4FindDelallocRangeFormat ext4_find_delalloc_range_format = 844; + Ext4ForgetFormat ext4_forget_format = 845; + Ext4FreeBlocksFormat ext4_free_blocks_format = 846; + Ext4FreeInodeFormat ext4_free_inode_format = 847; + Ext4GetImpliedClusterAllocExitFormat ext4_get_implied_cluster_alloc_exit_format = 848; + Ext4GetReservedClusterAllocFormat ext4_get_reserved_cluster_alloc_format = 849; + Ext4IndMapBlocksEnterFormat ext4_ind_map_blocks_enter_format = 850; + Ext4IndMapBlocksExitFormat ext4_ind_map_blocks_exit_format = 851; + Ext4InsertRangeFormat ext4_insert_range_format = 852; + Ext4InvalidatepageFormat ext4_invalidatepage_format = 853; + Ext4JournalStartFormat ext4_journal_start_format = 854; + Ext4JournalStartReservedFormat ext4_journal_start_reserved_format = 855; + Ext4JournalledInvalidatepageFormat ext4_journalled_invalidatepage_format = 856; + Ext4JournalledWriteEndFormat ext4_journalled_write_end_format = 857; + Ext4LoadInodeFormat ext4_load_inode_format = 858; + Ext4LoadInodeBitmapFormat ext4_load_inode_bitmap_format = 859; + Ext4MarkInodeDirtyFormat ext4_mark_inode_dirty_format = 860; + Ext4MbBitmapLoadFormat ext4_mb_bitmap_load_format = 861; + Ext4MbBuddyBitmapLoadFormat ext4_mb_buddy_bitmap_load_format = 862; + Ext4MbDiscardPreallocationsFormat ext4_mb_discard_preallocations_format = 863; + Ext4MbNewGroupPaFormat ext4_mb_new_group_pa_format = 864; + Ext4MbNewInodePaFormat ext4_mb_new_inode_pa_format = 865; + Ext4MbReleaseGroupPaFormat ext4_mb_release_group_pa_format = 866; + Ext4MbReleaseInodePaFormat ext4_mb_release_inode_pa_format = 867; + Ext4MballocAllocFormat ext4_mballoc_alloc_format = 868; + Ext4MballocDiscardFormat ext4_mballoc_discard_format = 869; + Ext4MballocFreeFormat ext4_mballoc_free_format = 870; + Ext4MballocPreallocFormat ext4_mballoc_prealloc_format = 871; + Ext4OtherInodeUpdateTimeFormat ext4_other_inode_update_time_format = 872; + Ext4PunchHoleFormat ext4_punch_hole_format = 873; + Ext4ReadBlockBitmapLoadFormat ext4_read_block_bitmap_load_format = 874; + Ext4ReadpageFormat ext4_readpage_format = 875; + Ext4ReleasepageFormat ext4_releasepage_format = 876; + Ext4RemoveBlocksFormat ext4_remove_blocks_format = 877; + Ext4RequestBlocksFormat ext4_request_blocks_format = 878; + Ext4RequestInodeFormat ext4_request_inode_format = 879; + Ext4SyncFileEnterFormat ext4_sync_file_enter_format = 880; + Ext4SyncFileExitFormat ext4_sync_file_exit_format = 881; + Ext4SyncFsFormat ext4_sync_fs_format = 882; + Ext4TrimAllFreeFormat ext4_trim_all_free_format = 883; + Ext4TrimExtentFormat ext4_trim_extent_format = 884; + Ext4TruncateEnterFormat ext4_truncate_enter_format = 885; + Ext4TruncateExitFormat ext4_truncate_exit_format = 886; + Ext4UnlinkEnterFormat ext4_unlink_enter_format = 887; + Ext4UnlinkExitFormat ext4_unlink_exit_format = 888; + Ext4WriteBeginFormat ext4_write_begin_format = 889; + Ext4WriteEndFormat ext4_write_end_format = 890; + Ext4WritepageFormat ext4_writepage_format = 891; + Ext4WritepagesFormat ext4_writepages_format = 892; + Ext4WritepagesResultFormat ext4_writepages_result_format = 893; + Ext4ZeroRangeFormat ext4_zero_range_format = 894; + BreakLeaseBlockFormat break_lease_block_format = 900; + BreakLeaseNoblockFormat break_lease_noblock_format = 901; + BreakLeaseUnblockFormat break_lease_unblock_format = 902; + GenericAddLeaseFormat generic_add_lease_format = 903; + GenericDeleteLeaseFormat generic_delete_lease_format = 904; + TimeOutLeasesFormat time_out_leases_format = 905; + MmFilemapAddToPageCacheFormat mm_filemap_add_to_page_cache_format = 1000; + MmFilemapDeleteFromPageCacheFormat mm_filemap_delete_from_page_cache_format = 1001; + BputsFormat bputs_format = 1100; + BranchFormat branch_format = 1101; + ContextSwitchFormat context_switch_format = 1102; + FuncgraphEntryFormat funcgraph_entry_format = 1103; + FuncgraphExitFormat funcgraph_exit_format = 1104; + FunctionFormat function_format = 1105; + KernelStackFormat kernel_stack_format = 1106; + MmiotraceMapFormat mmiotrace_map_format = 1107; + MmiotraceRwFormat mmiotrace_rw_format = 1108; + PrintFormat print_format = 1109; + UserStackFormat user_stack_format = 1110; + WakeupFormat wakeup_format = 1111; + GpioDirectionFormat gpio_direction_format = 1200; + GpioValueFormat gpio_value_format = 1201; + I2cReadFormat i2c_read_format = 1300; + I2cReplyFormat i2c_reply_format = 1301; + I2cResultFormat i2c_result_format = 1302; + I2cWriteFormat i2c_write_format = 1303; + IpiEntryFormat ipi_entry_format = 1400; + IpiExitFormat ipi_exit_format = 1401; + IpiRaiseFormat ipi_raise_format = 1402; + IrqHandlerEntryFormat irq_handler_entry_format = 1500; + IrqHandlerExitFormat irq_handler_exit_format = 1501; + SoftirqEntryFormat softirq_entry_format = 1502; + SoftirqExitFormat softirq_exit_format = 1503; + SoftirqRaiseFormat softirq_raise_format = 1504; + KfreeFormat kfree_format = 1600; + KmallocFormat kmalloc_format = 1601; + KmallocNodeFormat kmalloc_node_format = 1602; + KmemCacheAllocFormat kmem_cache_alloc_format = 1603; + KmemCacheAllocNodeFormat kmem_cache_alloc_node_format = 1604; + KmemCacheFreeFormat kmem_cache_free_format = 1605; + MmPageAllocFormat mm_page_alloc_format = 1606; + MmPageAllocExtfragFormat mm_page_alloc_extfrag_format = 1607; + MmPageAllocZoneLockedFormat mm_page_alloc_zone_locked_format = 1608; + MmPageFreeFormat mm_page_free_format = 1609; + MmPageFreeBatchedFormat mm_page_free_batched_format = 1610; + MmPagePcpuDrainFormat mm_page_pcpu_drain_format = 1611; + NapiGroFragsEntryFormat napi_gro_frags_entry_format = 1700; + NapiGroReceiveEntryFormat napi_gro_receive_entry_format = 1701; + NetDevQueueFormat net_dev_queue_format = 1702; + NetDevStartXmitFormat net_dev_start_xmit_format = 1703; + NetDevXmitFormat net_dev_xmit_format = 1704; + NetifReceiveSkbFormat netif_receive_skb_format = 1705; + NetifReceiveSkbEntryFormat netif_receive_skb_entry_format = 1706; + NetifRxFormat netif_rx_format = 1707; + NetifRxEntryFormat netif_rx_entry_format = 1708; + NetifRxNiEntryFormat netif_rx_ni_entry_format = 1709; + OomScoreAdjUpdateFormat oom_score_adj_update_format = 1800; + MmLruActivateFormat mm_lru_activate_format = 1900; + MmLruInsertionFormat mm_lru_insertion_format = 1901; + ClockDisableFormat clock_disable_format = 2000; + ClockEnableFormat clock_enable_format = 2001; + ClockSetRateFormat clock_set_rate_format = 2002; + CpuFrequencyFormat cpu_frequency_format = 2003; + CpuFrequencyLimitsFormat cpu_frequency_limits_format = 2004; + CpuIdleFormat cpu_idle_format = 2005; + DevPmQosAddRequestFormat dev_pm_qos_add_request_format = 2006; + DevPmQosRemoveRequestFormat dev_pm_qos_remove_request_format = 2007; + DevPmQosUpdateRequestFormat dev_pm_qos_update_request_format = 2008; + DevicePmCallbackEndFormat device_pm_callback_end_format = 2009; + DevicePmCallbackStartFormat device_pm_callback_start_format = 2010; + PmQosAddRequestFormat pm_qos_add_request_format = 2011; + PmQosRemoveRequestFormat pm_qos_remove_request_format = 2012; + PmQosUpdateFlagsFormat pm_qos_update_flags_format = 2013; + PmQosUpdateRequestFormat pm_qos_update_request_format = 2014; + PmQosUpdateRequestTimeoutFormat pm_qos_update_request_timeout_format = 2015; + PmQosUpdateTargetFormat pm_qos_update_target_format = 2016; + PowerDomainTargetFormat power_domain_target_format = 2017; + PstateSampleFormat pstate_sample_format = 2018; + SuspendResumeFormat suspend_resume_format = 2019; + WakeupSourceActivateFormat wakeup_source_activate_format = 2020; + WakeupSourceDeactivateFormat wakeup_source_deactivate_format = 2021; + ConsoleFormat console_format = 2100; + SysEnterFormat sys_enter_format = 2200; + SysExitFormat sys_exit_format = 2201; + RcuUtilizationFormat rcu_utilization_format = 2300; + SchedKthreadStopFormat sched_kthread_stop_format = 2400; + SchedKthreadStopRetFormat sched_kthread_stop_ret_format = 2401; + SchedMigrateTaskFormat sched_migrate_task_format = 2402; + SchedMoveNumaFormat sched_move_numa_format = 2403; + SchedPiSetprioFormat sched_pi_setprio_format = 2404; + SchedProcessExecFormat sched_process_exec_format = 2405; + SchedProcessExitFormat sched_process_exit_format = 2406; + SchedProcessForkFormat sched_process_fork_format = 2407; + SchedProcessFreeFormat sched_process_free_format = 2408; + SchedProcessWaitFormat sched_process_wait_format = 2409; + SchedStatBlockedFormat sched_stat_blocked_format = 2410; + SchedStatIowaitFormat sched_stat_iowait_format = 2411; + SchedStatRuntimeFormat sched_stat_runtime_format = 2412; + SchedStatSleepFormat sched_stat_sleep_format = 2413; + SchedStatWaitFormat sched_stat_wait_format = 2414; + SchedStickNumaFormat sched_stick_numa_format = 2415; + SchedSwapNumaFormat sched_swap_numa_format = 2416; + SchedSwitchFormat sched_switch_format = 2417; + SchedWaitTaskFormat sched_wait_task_format = 2418; + SchedWakeIdleWithoutIpiFormat sched_wake_idle_without_ipi_format = 2419; + SchedWakeupFormat sched_wakeup_format = 2420; + SchedWakeupNewFormat sched_wakeup_new_format = 2421; + SchedWakingFormat sched_waking_format = 2422; + SignalDeliverFormat signal_deliver_format = 2500; + SignalGenerateFormat signal_generate_format = 2501; + RpcBindStatusFormat rpc_bind_status_format = 2600; + RpcCallStatusFormat rpc_call_status_format = 2601; + RpcConnectStatusFormat rpc_connect_status_format = 2602; + RpcSocketCloseFormat rpc_socket_close_format = 2603; + RpcSocketConnectFormat rpc_socket_connect_format = 2604; + RpcSocketErrorFormat rpc_socket_error_format = 2605; + RpcSocketResetConnectionFormat rpc_socket_reset_connection_format = 2606; + RpcSocketShutdownFormat rpc_socket_shutdown_format = 2607; + RpcSocketStateChangeFormat rpc_socket_state_change_format = 2608; + RpcTaskBeginFormat rpc_task_begin_format = 2609; + RpcTaskCompleteFormat rpc_task_complete_format = 2610; + RpcTaskRunActionFormat rpc_task_run_action_format = 2611; + RpcTaskSleepFormat rpc_task_sleep_format = 2612; + RpcTaskWakeupFormat rpc_task_wakeup_format = 2613; + SvcHandleXprtFormat svc_handle_xprt_format = 2614; + SvcProcessFormat svc_process_format = 2615; + SvcRecvFormat svc_recv_format = 2616; + SvcSendFormat svc_send_format = 2617; + SvcWakeUpFormat svc_wake_up_format = 2618; + SvcXprtDequeueFormat svc_xprt_dequeue_format = 2619; + SvcXprtDoEnqueueFormat svc_xprt_do_enqueue_format = 2620; + XprtCompleteRqstFormat xprt_complete_rqst_format = 2621; + XprtLookupRqstFormat xprt_lookup_rqst_format = 2622; + XprtTransmitFormat xprt_transmit_format = 2623; + XsTcpDataReadyFormat xs_tcp_data_ready_format = 2624; + XsTcpDataRecvFormat xs_tcp_data_recv_format = 2625; + TaskNewtaskFormat task_newtask_format = 2700; + TaskRenameFormat task_rename_format = 2701; + HrtimerCancelFormat hrtimer_cancel_format = 2800; + HrtimerExpireEntryFormat hrtimer_expire_entry_format = 2801; + HrtimerExpireExitFormat hrtimer_expire_exit_format = 2802; + HrtimerInitFormat hrtimer_init_format = 2803; + HrtimerStartFormat hrtimer_start_format = 2804; + ItimerExpireFormat itimer_expire_format = 2805; + ItimerStateFormat itimer_state_format = 2806; + TimerCancelFormat timer_cancel_format = 2807; + TimerExpireEntryFormat timer_expire_entry_format = 2808; + TimerExpireExitFormat timer_expire_exit_format = 2809; + TimerInitFormat timer_init_format = 2810; + TimerStartFormat timer_start_format = 2811; + V4l2DqbufFormat v4l2_dqbuf_format = 2900; + V4l2QbufFormat v4l2_qbuf_format = 2901; + Vb2V4l2BufDoneFormat vb2_v4l2_buf_done_format = 2902; + Vb2V4l2BufQueueFormat vb2_v4l2_buf_queue_format = 2903; + Vb2V4l2DqbufFormat vb2_v4l2_dqbuf_format = 2904; + Vb2V4l2QbufFormat vb2_v4l2_qbuf_format = 2905; + MmShrinkSlabEndFormat mm_shrink_slab_end_format = 3000; + MmShrinkSlabStartFormat mm_shrink_slab_start_format = 3001; + MmVmscanDirectReclaimBeginFormat mm_vmscan_direct_reclaim_begin_format = 3002; + MmVmscanDirectReclaimEndFormat mm_vmscan_direct_reclaim_end_format = 3003; + MmVmscanKswapdSleepFormat mm_vmscan_kswapd_sleep_format = 3004; + MmVmscanKswapdWakeFormat mm_vmscan_kswapd_wake_format = 3005; + MmVmscanLruIsolateFormat mm_vmscan_lru_isolate_format = 3006; + MmVmscanLruShrinkInactiveFormat mm_vmscan_lru_shrink_inactive_format = 3007; + MmVmscanWakeupKswapdFormat mm_vmscan_wakeup_kswapd_format = 3008; + MmVmscanWritepageFormat mm_vmscan_writepage_format = 3009; + WorkqueueActivateWorkFormat workqueue_activate_work_format = 3100; + WorkqueueExecuteEndFormat workqueue_execute_end_format = 3101; + WorkqueueExecuteStartFormat workqueue_execute_start_format = 3102; + WorkqueueQueueWorkFormat workqueue_queue_work_format = 3103; + BalanceDirtyPagesFormat balance_dirty_pages_format = 3200; + BdiDirtyRatelimitFormat bdi_dirty_ratelimit_format = 3201; + GlobalDirtyStateFormat global_dirty_state_format = 3202; + WbcWritepageFormat wbc_writepage_format = 3203; + WritebackBdiRegisterFormat writeback_bdi_register_format = 3204; + WritebackCongestionWaitFormat writeback_congestion_wait_format = 3205; + WritebackDirtyInodeFormat writeback_dirty_inode_format = 3206; + WritebackDirtyInodeEnqueueFormat writeback_dirty_inode_enqueue_format = 3207; + WritebackDirtyInodeStartFormat writeback_dirty_inode_start_format = 3208; + WritebackDirtyPageFormat writeback_dirty_page_format = 3209; + WritebackExecFormat writeback_exec_format = 3210; + WritebackLazytimeFormat writeback_lazytime_format = 3211; + WritebackLazytimeIputFormat writeback_lazytime_iput_format = 3212; + WritebackMarkInodeDirtyFormat writeback_mark_inode_dirty_format = 3213; + WritebackPagesWrittenFormat writeback_pages_written_format = 3214; + WritebackQueueFormat writeback_queue_format = 3215; + WritebackQueueIoFormat writeback_queue_io_format = 3216; + WritebackSbInodesRequeueFormat writeback_sb_inodes_requeue_format = 3217; + WritebackSingleInodeFormat writeback_single_inode_format = 3218; + WritebackSingleInodeStartFormat writeback_single_inode_start_format = 3219; + WritebackStartFormat writeback_start_format = 3220; + WritebackWaitFormat writeback_wait_format = 3221; + WritebackWaitIffCongestedFormat writeback_wait_iff_congested_format = 3222; + WritebackWakeBackgroundFormat writeback_wake_background_format = 3223; + WritebackWriteInodeFormat writeback_write_inode_format = 3224; + WritebackWriteInodeStartFormat writeback_write_inode_start_format = 3225; + WritebackWrittenFormat writeback_written_format = 3226; + } +} diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/gpio.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/gpio.proto new file mode 100644 index 0000000..1410af5 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/gpio.proto @@ -0,0 +1,34 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: gpio +// /sys/kernel/debug/tracing/events/gpio/gpio_direction/format +message GpioDirectionFormat { + uint32 gpio = 1; + int32 in = 2; + int32 err = 3; +} + +// /sys/kernel/debug/tracing/events/gpio/gpio_value/format +message GpioValueFormat { + uint32 gpio = 1; + int32 get = 2; + int32 value = 3; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/i2c.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/i2c.proto new file mode 100644 index 0000000..4f1de54 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/i2c.proto @@ -0,0 +1,56 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: i2c +// /sys/kernel/debug/tracing/events/i2c/i2c_read/format +message I2cReadFormat { + int32 adapter_nr = 1; + uint32 msg_nr = 2; + uint32 addr = 3; + uint32 flags = 4; + uint32 len = 5; +} + +// /sys/kernel/debug/tracing/events/i2c/i2c_reply/format +message I2cReplyFormat { + int32 adapter_nr = 1; + uint32 msg_nr = 2; + uint32 addr = 3; + uint32 flags = 4; + uint32 len = 5; + uint32 buf = 6; +} + +// /sys/kernel/debug/tracing/events/i2c/i2c_result/format +message I2cResultFormat { + int32 adapter_nr = 1; + uint32 nr_msgs = 2; + int32 ret = 3; +} + +// /sys/kernel/debug/tracing/events/i2c/i2c_write/format +message I2cWriteFormat { + int32 adapter_nr = 1; + uint32 msg_nr = 2; + uint32 addr = 3; + uint32 flags = 4; + uint32 len = 5; + uint32 buf = 6; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/ipi.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/ipi.proto new file mode 100644 index 0000000..0eda02f --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/ipi.proto @@ -0,0 +1,36 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: ipi +// /sys/kernel/debug/tracing/events/ipi/ipi_entry/format +message IpiEntryFormat { + string reason = 1; +} + +// /sys/kernel/debug/tracing/events/ipi/ipi_exit/format +message IpiExitFormat { + string reason = 1; +} + +// /sys/kernel/debug/tracing/events/ipi/ipi_raise/format +message IpiRaiseFormat { + uint64 target_cpus = 1; + string reason = 2; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/irq.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/irq.proto new file mode 100644 index 0000000..1e3ce1a --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/irq.proto @@ -0,0 +1,47 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: irq +// /sys/kernel/debug/tracing/events/irq/irq_handler_entry/format +message IrqHandlerEntryFormat { + int32 irq = 1; + string name = 2; +} + +// /sys/kernel/debug/tracing/events/irq/irq_handler_exit/format +message IrqHandlerExitFormat { + int32 irq = 1; + int32 ret = 2; +} + +// /sys/kernel/debug/tracing/events/irq/softirq_entry/format +message SoftirqEntryFormat { + uint32 vec = 1; +} + +// /sys/kernel/debug/tracing/events/irq/softirq_exit/format +message SoftirqExitFormat { + uint32 vec = 1; +} + +// /sys/kernel/debug/tracing/events/irq/softirq_raise/format +message SoftirqRaiseFormat { + uint32 vec = 1; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/kmem.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/kmem.proto new file mode 100644 index 0000000..51b7d49 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/kmem.proto @@ -0,0 +1,113 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: kmem +// /sys/kernel/debug/tracing/events/kmem/kfree/format +message KfreeFormat { + uint64 call_site = 1; + uint64 ptr = 2; +} + +// /sys/kernel/debug/tracing/events/kmem/kmalloc/format +message KmallocFormat { + uint64 call_site = 1; + uint64 ptr = 2; + uint64 bytes_req = 3; + uint64 bytes_alloc = 4; + uint32 gfp_flags = 5; +} + +// /sys/kernel/debug/tracing/events/kmem/kmalloc_node/format +message KmallocNodeFormat { + uint64 call_site = 1; + uint64 ptr = 2; + uint64 bytes_req = 3; + uint64 bytes_alloc = 4; + uint32 gfp_flags = 5; + int32 node = 6; +} + +// /sys/kernel/debug/tracing/events/kmem/kmem_cache_alloc/format +message KmemCacheAllocFormat { + uint64 call_site = 1; + uint64 ptr = 2; + uint64 bytes_req = 3; + uint64 bytes_alloc = 4; + uint32 gfp_flags = 5; +} + +// /sys/kernel/debug/tracing/events/kmem/kmem_cache_alloc_node/format +message KmemCacheAllocNodeFormat { + uint64 call_site = 1; + uint64 ptr = 2; + uint64 bytes_req = 3; + uint64 bytes_alloc = 4; + uint32 gfp_flags = 5; + int32 node = 6; +} + +// /sys/kernel/debug/tracing/events/kmem/kmem_cache_free/format +message KmemCacheFreeFormat { + uint64 call_site = 1; + uint64 ptr = 2; +} + +// /sys/kernel/debug/tracing/events/kmem/mm_page_alloc/format +message MmPageAllocFormat { + uint64 pfn = 1; + uint32 order = 2; + uint32 gfp_flags = 3; + int32 migratetype = 4; +} + +// /sys/kernel/debug/tracing/events/kmem/mm_page_alloc_extfrag/format +message MmPageAllocExtfragFormat { + uint64 pfn = 1; + int32 alloc_order = 2; + int32 fallback_order = 3; + int32 alloc_migratetype = 4; + int32 fallback_migratetype = 5; + int32 change_ownership = 6; +} + +// /sys/kernel/debug/tracing/events/kmem/mm_page_alloc_zone_locked/format +message MmPageAllocZoneLockedFormat { + uint64 pfn = 1; + uint32 order = 2; + int32 migratetype = 3; +} + +// /sys/kernel/debug/tracing/events/kmem/mm_page_free/format +message MmPageFreeFormat { + uint64 pfn = 1; + uint32 order = 2; +} + +// /sys/kernel/debug/tracing/events/kmem/mm_page_free_batched/format +message MmPageFreeBatchedFormat { + uint64 pfn = 1; +} + +// /sys/kernel/debug/tracing/events/kmem/mm_page_pcpu_drain/format +message MmPagePcpuDrainFormat { + uint64 pfn = 1; + uint32 order = 2; + int32 migratetype = 3; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/net.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/net.proto new file mode 100644 index 0000000..c4b3c31 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/net.proto @@ -0,0 +1,185 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: net +// /sys/kernel/debug/tracing/events/net/napi_gro_frags_entry/format +message NapiGroFragsEntryFormat { + string name = 1; + uint32 napi_id = 2; + uint32 queue_mapping = 3; + uint64 skbaddr = 4; + uint32 vlan_tagged = 5; + uint32 vlan_proto = 6; + uint32 vlan_tci = 7; + uint32 protocol = 8; + uint32 ip_summed = 9; + uint32 hash = 10; + uint32 l4_hash = 11; + uint32 len = 12; + uint32 data_len = 13; + uint32 truesize = 14; + uint32 mac_header_valid = 15; + int32 mac_header = 16; + uint32 nr_frags = 17; + uint32 gso_size = 18; + uint32 gso_type = 19; +} + +// /sys/kernel/debug/tracing/events/net/napi_gro_receive_entry/format +message NapiGroReceiveEntryFormat { + string name = 1; + uint32 napi_id = 2; + uint32 queue_mapping = 3; + uint64 skbaddr = 4; + uint32 vlan_tagged = 5; + uint32 vlan_proto = 6; + uint32 vlan_tci = 7; + uint32 protocol = 8; + uint32 ip_summed = 9; + uint32 hash = 10; + uint32 l4_hash = 11; + uint32 len = 12; + uint32 data_len = 13; + uint32 truesize = 14; + uint32 mac_header_valid = 15; + int32 mac_header = 16; + uint32 nr_frags = 17; + uint32 gso_size = 18; + uint32 gso_type = 19; +} + +// /sys/kernel/debug/tracing/events/net/net_dev_queue/format +message NetDevQueueFormat { + uint64 skbaddr = 1; + uint32 len = 2; + string name = 3; +} + +// /sys/kernel/debug/tracing/events/net/net_dev_start_xmit/format +message NetDevStartXmitFormat { + string name = 1; + uint32 queue_mapping = 2; + uint64 skbaddr = 3; + uint32 vlan_tagged = 4; + uint32 vlan_proto = 5; + uint32 vlan_tci = 6; + uint32 protocol = 7; + uint32 ip_summed = 8; + uint32 len = 9; + uint32 data_len = 10; + int32 network_offset = 11; + uint32 transport_offset_valid = 12; + int32 transport_offset = 13; + uint32 tx_flags = 14; + uint32 gso_size = 15; + uint32 gso_segs = 16; + uint32 gso_type = 17; +} + +// /sys/kernel/debug/tracing/events/net/net_dev_xmit/format +message NetDevXmitFormat { + uint64 skbaddr = 1; + uint32 len = 2; + int32 rc = 3; + string name = 4; +} + +// /sys/kernel/debug/tracing/events/net/netif_receive_skb/format +message NetifReceiveSkbFormat { + uint64 skbaddr = 1; + uint32 len = 2; + string name = 3; +} + +// /sys/kernel/debug/tracing/events/net/netif_receive_skb_entry/format +message NetifReceiveSkbEntryFormat { + string name = 1; + uint32 napi_id = 2; + uint32 queue_mapping = 3; + uint64 skbaddr = 4; + uint32 vlan_tagged = 5; + uint32 vlan_proto = 6; + uint32 vlan_tci = 7; + uint32 protocol = 8; + uint32 ip_summed = 9; + uint32 hash = 10; + uint32 l4_hash = 11; + uint32 len = 12; + uint32 data_len = 13; + uint32 truesize = 14; + uint32 mac_header_valid = 15; + int32 mac_header = 16; + uint32 nr_frags = 17; + uint32 gso_size = 18; + uint32 gso_type = 19; +} + +// /sys/kernel/debug/tracing/events/net/netif_rx/format +message NetifRxFormat { + uint64 skbaddr = 1; + uint32 len = 2; + string name = 3; +} + +// /sys/kernel/debug/tracing/events/net/netif_rx_entry/format +message NetifRxEntryFormat { + string name = 1; + uint32 napi_id = 2; + uint32 queue_mapping = 3; + uint64 skbaddr = 4; + uint32 vlan_tagged = 5; + uint32 vlan_proto = 6; + uint32 vlan_tci = 7; + uint32 protocol = 8; + uint32 ip_summed = 9; + uint32 hash = 10; + uint32 l4_hash = 11; + uint32 len = 12; + uint32 data_len = 13; + uint32 truesize = 14; + uint32 mac_header_valid = 15; + int32 mac_header = 16; + uint32 nr_frags = 17; + uint32 gso_size = 18; + uint32 gso_type = 19; +} + +// /sys/kernel/debug/tracing/events/net/netif_rx_ni_entry/format +message NetifRxNiEntryFormat { + string name = 1; + uint32 napi_id = 2; + uint32 queue_mapping = 3; + uint64 skbaddr = 4; + uint32 vlan_tagged = 5; + uint32 vlan_proto = 6; + uint32 vlan_tci = 7; + uint32 protocol = 8; + uint32 ip_summed = 9; + uint32 hash = 10; + uint32 l4_hash = 11; + uint32 len = 12; + uint32 data_len = 13; + uint32 truesize = 14; + uint32 mac_header_valid = 15; + int32 mac_header = 16; + uint32 nr_frags = 17; + uint32 gso_size = 18; + uint32 gso_type = 19; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/oom.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/oom.proto new file mode 100644 index 0000000..d7b6b9c --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/oom.proto @@ -0,0 +1,27 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: oom +// /sys/kernel/debug/tracing/events/oom/oom_score_adj_update/format +message OomScoreAdjUpdateFormat { + int32 pid = 1; + string comm = 2; + int32 oom_score_adj = 3; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/pagemap.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/pagemap.proto new file mode 100644 index 0000000..c931e12 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/pagemap.proto @@ -0,0 +1,34 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: pagemap +// /sys/kernel/debug/tracing/events/pagemap/mm_lru_activate/format +message MmLruActivateFormat { + uint64 page = 1; + uint64 pfn = 2; +} + +// /sys/kernel/debug/tracing/events/pagemap/mm_lru_insertion/format +message MmLruInsertionFormat { + uint64 page = 1; + uint64 pfn = 2; + int32 lru = 3; + uint64 flags = 4; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/power.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/power.proto new file mode 100644 index 0000000..3a223d1 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/power.proto @@ -0,0 +1,175 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: power +// /sys/kernel/debug/tracing/events/power/clock_disable/format +message ClockDisableFormat { + string name = 1; + uint64 state = 2; + uint64 cpu_id = 3; +} + +// /sys/kernel/debug/tracing/events/power/clock_enable/format +message ClockEnableFormat { + string name = 1; + uint64 state = 2; + uint64 cpu_id = 3; +} + +// /sys/kernel/debug/tracing/events/power/clock_set_rate/format +message ClockSetRateFormat { + string name = 1; + uint64 state = 2; + uint64 cpu_id = 3; +} + +// /sys/kernel/debug/tracing/events/power/cpu_frequency/format +message CpuFrequencyFormat { + uint32 state = 1; + uint32 cpu_id = 2; +} + +// /sys/kernel/debug/tracing/events/power/cpu_frequency_limits/format +message CpuFrequencyLimitsFormat { + uint32 min_freq = 1; + uint32 max_freq = 2; + uint32 cpu_id = 3; +} + +// /sys/kernel/debug/tracing/events/power/cpu_idle/format +message CpuIdleFormat { + uint32 state = 1; + uint32 cpu_id = 2; +} + +// /sys/kernel/debug/tracing/events/power/dev_pm_qos_add_request/format +message DevPmQosAddRequestFormat { + string name = 1; + uint32 type = 2; + int32 new_value = 3; +} + +// /sys/kernel/debug/tracing/events/power/dev_pm_qos_remove_request/format +message DevPmQosRemoveRequestFormat { + string name = 1; + uint32 type = 2; + int32 new_value = 3; +} + +// /sys/kernel/debug/tracing/events/power/dev_pm_qos_update_request/format +message DevPmQosUpdateRequestFormat { + string name = 1; + uint32 type = 2; + int32 new_value = 3; +} + +// /sys/kernel/debug/tracing/events/power/device_pm_callback_end/format +message DevicePmCallbackEndFormat { + string device = 1; + string driver = 2; + int32 error = 3; +} + +// /sys/kernel/debug/tracing/events/power/device_pm_callback_start/format +message DevicePmCallbackStartFormat { + string device = 1; + string driver = 2; + string parent = 3; + string pm_ops = 4; + int32 event = 5; +} + +// /sys/kernel/debug/tracing/events/power/pm_qos_add_request/format +message PmQosAddRequestFormat { + int32 pm_qos_class = 1; + int32 value = 2; +} + +// /sys/kernel/debug/tracing/events/power/pm_qos_remove_request/format +message PmQosRemoveRequestFormat { + int32 pm_qos_class = 1; + int32 value = 2; +} + +// /sys/kernel/debug/tracing/events/power/pm_qos_update_flags/format +message PmQosUpdateFlagsFormat { + uint32 action = 1; + int32 prev_value = 2; + int32 curr_value = 3; +} + +// /sys/kernel/debug/tracing/events/power/pm_qos_update_request/format +message PmQosUpdateRequestFormat { + int32 pm_qos_class = 1; + int32 value = 2; +} + +// /sys/kernel/debug/tracing/events/power/pm_qos_update_request_timeout/format +message PmQosUpdateRequestTimeoutFormat { + int32 pm_qos_class = 1; + int32 value = 2; + uint64 timeout_us = 3; +} + +// /sys/kernel/debug/tracing/events/power/pm_qos_update_target/format +message PmQosUpdateTargetFormat { + uint32 action = 1; + int32 prev_value = 2; + int32 curr_value = 3; +} + +// /sys/kernel/debug/tracing/events/power/power_domain_target/format +message PowerDomainTargetFormat { + string name = 1; + uint64 state = 2; + uint64 cpu_id = 3; +} + +// /sys/kernel/debug/tracing/events/power/pstate_sample/format +message PstateSampleFormat { + uint32 core_busy = 1; + uint32 scaled_busy = 2; + uint32 from = 3; + uint32 to = 4; + uint64 mperf = 5; + uint64 aperf = 6; + uint64 tsc = 7; + uint32 freq = 8; + uint32 io_boost = 9; +} + +// /sys/kernel/debug/tracing/events/power/suspend_resume/format +message SuspendResumeFormat { + string action = 1; + int32 val = 2; + uint32 start = 3; +} + +// /sys/kernel/debug/tracing/events/power/wakeup_source_activate/format +message WakeupSourceActivateFormat { + string name = 1; + uint64 state = 2; +} + +// /sys/kernel/debug/tracing/events/power/wakeup_source_deactivate/format +message WakeupSourceDeactivateFormat { + string name = 1; + uint64 state = 2; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/printk.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/printk.proto new file mode 100644 index 0000000..39be37e --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/printk.proto @@ -0,0 +1,25 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: printk +// /sys/kernel/debug/tracing/events/printk/console/format +message ConsoleFormat { + string msg = 1; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/raw_syscalls.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/raw_syscalls.proto new file mode 100644 index 0000000..590b964 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/raw_syscalls.proto @@ -0,0 +1,32 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: raw_syscalls +// /sys/kernel/debug/tracing/events/raw_syscalls/sys_enter/format +message SysEnterFormat { + uint64 id = 1; + string args = 2; +} + +// /sys/kernel/debug/tracing/events/raw_syscalls/sys_exit/format +message SysExitFormat { + uint64 id = 1; + uint64 ret = 2; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/rcu.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/rcu.proto new file mode 100644 index 0000000..5448d1e --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/rcu.proto @@ -0,0 +1,25 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: rcu +// /sys/kernel/debug/tracing/events/rcu/rcu_utilization/format +message RcuUtilizationFormat { + string s = 1; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/sched.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/sched.proto new file mode 100644 index 0000000..173a305 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/sched.proto @@ -0,0 +1,206 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: sched +// /sys/kernel/debug/tracing/events/sched/sched_kthread_stop/format +message SchedKthreadStopFormat { + string comm = 1; + int32 pid = 2; +} + +// /sys/kernel/debug/tracing/events/sched/sched_kthread_stop_ret/format +message SchedKthreadStopRetFormat { + int32 ret = 1; +} + +// /sys/kernel/debug/tracing/events/sched/sched_migrate_task/format +message SchedMigrateTaskFormat { + string comm = 1; + int32 pid = 2; + int32 prio = 3; + int32 orig_cpu = 4; + int32 dest_cpu = 5; +} + +// /sys/kernel/debug/tracing/events/sched/sched_move_numa/format +message SchedMoveNumaFormat { + int32 pid = 1; + int32 tgid = 2; + int32 ngid = 3; + int32 src_cpu = 4; + int32 src_nid = 5; + int32 dst_cpu = 6; + int32 dst_nid = 7; +} + +// /sys/kernel/debug/tracing/events/sched/sched_pi_setprio/format +message SchedPiSetprioFormat { + string comm = 1; + int32 pid = 2; + int32 oldprio = 3; + int32 newprio = 4; +} + +// /sys/kernel/debug/tracing/events/sched/sched_process_exec/format +message SchedProcessExecFormat { + string filename = 1; + int32 pid = 2; + int32 old_pid = 3; +} + +// /sys/kernel/debug/tracing/events/sched/sched_process_exit/format +message SchedProcessExitFormat { + string comm = 1; + int32 pid = 2; + int32 prio = 3; +} + +// /sys/kernel/debug/tracing/events/sched/sched_process_fork/format +message SchedProcessForkFormat { + string parent_comm = 1; + int32 parent_pid = 2; + string child_comm = 3; + int32 child_pid = 4; +} + +// /sys/kernel/debug/tracing/events/sched/sched_process_free/format +message SchedProcessFreeFormat { + string comm = 1; + int32 pid = 2; + int32 prio = 3; +} + +// /sys/kernel/debug/tracing/events/sched/sched_process_wait/format +message SchedProcessWaitFormat { + string comm = 1; + int32 pid = 2; + int32 prio = 3; +} + +// /sys/kernel/debug/tracing/events/sched/sched_stat_blocked/format +message SchedStatBlockedFormat { + string comm = 1; + int32 pid = 2; + uint64 delay = 3; +} + +// /sys/kernel/debug/tracing/events/sched/sched_stat_iowait/format +message SchedStatIowaitFormat { + string comm = 1; + int32 pid = 2; + uint64 delay = 3; +} + +// /sys/kernel/debug/tracing/events/sched/sched_stat_runtime/format +message SchedStatRuntimeFormat { + string comm = 1; + int32 pid = 2; + uint64 runtime = 3; + uint64 vruntime = 4; +} + +// /sys/kernel/debug/tracing/events/sched/sched_stat_sleep/format +message SchedStatSleepFormat { + string comm = 1; + int32 pid = 2; + uint64 delay = 3; +} + +// /sys/kernel/debug/tracing/events/sched/sched_stat_wait/format +message SchedStatWaitFormat { + string comm = 1; + int32 pid = 2; + uint64 delay = 3; +} + +// /sys/kernel/debug/tracing/events/sched/sched_stick_numa/format +message SchedStickNumaFormat { + int32 pid = 1; + int32 tgid = 2; + int32 ngid = 3; + int32 src_cpu = 4; + int32 src_nid = 5; + int32 dst_cpu = 6; + int32 dst_nid = 7; +} + +// /sys/kernel/debug/tracing/events/sched/sched_swap_numa/format +message SchedSwapNumaFormat { + int32 src_pid = 1; + int32 src_tgid = 2; + int32 src_ngid = 3; + int32 src_cpu = 4; + int32 src_nid = 5; + int32 dst_pid = 6; + int32 dst_tgid = 7; + int32 dst_ngid = 8; + int32 dst_cpu = 9; + int32 dst_nid = 10; +} + +// /sys/kernel/debug/tracing/events/sched/sched_switch/format +message SchedSwitchFormat { + string prev_comm = 1; + int32 prev_pid = 2; + int32 prev_prio = 3; + uint64 prev_state = 4; + string next_comm = 5; + int32 next_pid = 6; + int32 next_prio = 7; +} + +// /sys/kernel/debug/tracing/events/sched/sched_wait_task/format +message SchedWaitTaskFormat { + string comm = 1; + int32 pid = 2; + int32 prio = 3; +} + +// /sys/kernel/debug/tracing/events/sched/sched_wake_idle_without_ipi/format +message SchedWakeIdleWithoutIpiFormat { + int32 cpu = 1; +} + +// /sys/kernel/debug/tracing/events/sched/sched_wakeup/format +message SchedWakeupFormat { + string comm = 1; + int32 pid = 2; + int32 prio = 3; + int32 success = 4; + int32 target_cpu = 5; +} + +// /sys/kernel/debug/tracing/events/sched/sched_wakeup_new/format +message SchedWakeupNewFormat { + string comm = 1; + int32 pid = 2; + int32 prio = 3; + int32 success = 4; + int32 target_cpu = 5; +} + +// /sys/kernel/debug/tracing/events/sched/sched_waking/format +message SchedWakingFormat { + string comm = 1; + int32 pid = 2; + int32 prio = 3; + int32 success = 4; + int32 target_cpu = 5; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/signal.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/signal.proto new file mode 100644 index 0000000..01246e6 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/signal.proto @@ -0,0 +1,40 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: signal +// /sys/kernel/debug/tracing/events/signal/signal_deliver/format +message SignalDeliverFormat { + int32 sig = 1; + int32 error_code = 2; + int32 code = 3; + uint64 sig_handler = 4; + uint64 sig_flags = 5; +} + +// /sys/kernel/debug/tracing/events/signal/signal_generate/format +message SignalGenerateFormat { + int32 sig = 1; + int32 error_code = 2; + int32 code = 3; + string comm = 4; + int32 pid = 5; + int32 group = 6; + int32 result = 7; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/sunrpc.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/sunrpc.proto new file mode 100644 index 0000000..35c4f67 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/sunrpc.proto @@ -0,0 +1,247 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: sunrpc +// /sys/kernel/debug/tracing/events/sunrpc/rpc_bind_status/format +message RpcBindStatusFormat { + uint32 task_id = 1; + uint32 client_id = 2; + int32 status = 3; +} + +// /sys/kernel/debug/tracing/events/sunrpc/rpc_call_status/format +message RpcCallStatusFormat { + uint32 task_id = 1; + uint32 client_id = 2; + int32 status = 3; +} + +// /sys/kernel/debug/tracing/events/sunrpc/rpc_connect_status/format +message RpcConnectStatusFormat { + uint32 task_id = 1; + uint32 client_id = 2; + int32 status = 3; +} + +// /sys/kernel/debug/tracing/events/sunrpc/rpc_socket_close/format +message RpcSocketCloseFormat { + uint32 socket_state = 1; + uint32 sock_state = 2; + uint64 ino = 3; + string dstaddr = 4; + string dstport = 5; +} + +// /sys/kernel/debug/tracing/events/sunrpc/rpc_socket_connect/format +message RpcSocketConnectFormat { + int32 error = 1; + uint32 socket_state = 2; + uint32 sock_state = 3; + uint64 ino = 4; + string dstaddr = 5; + string dstport = 6; +} + +// /sys/kernel/debug/tracing/events/sunrpc/rpc_socket_error/format +message RpcSocketErrorFormat { + int32 error = 1; + uint32 socket_state = 2; + uint32 sock_state = 3; + uint64 ino = 4; + string dstaddr = 5; + string dstport = 6; +} + +// /sys/kernel/debug/tracing/events/sunrpc/rpc_socket_reset_connection/format +message RpcSocketResetConnectionFormat { + int32 error = 1; + uint32 socket_state = 2; + uint32 sock_state = 3; + uint64 ino = 4; + string dstaddr = 5; + string dstport = 6; +} + +// /sys/kernel/debug/tracing/events/sunrpc/rpc_socket_shutdown/format +message RpcSocketShutdownFormat { + uint32 socket_state = 1; + uint32 sock_state = 2; + uint64 ino = 3; + string dstaddr = 4; + string dstport = 5; +} + +// /sys/kernel/debug/tracing/events/sunrpc/rpc_socket_state_change/format +message RpcSocketStateChangeFormat { + uint32 socket_state = 1; + uint32 sock_state = 2; + uint64 ino = 3; + string dstaddr = 4; + string dstport = 5; +} + +// /sys/kernel/debug/tracing/events/sunrpc/rpc_task_begin/format +message RpcTaskBeginFormat { + uint32 task_id = 1; + uint32 client_id = 2; + uint64 action = 3; + uint64 runstate = 4; + int32 status = 5; + uint32 flags = 6; +} + +// /sys/kernel/debug/tracing/events/sunrpc/rpc_task_complete/format +message RpcTaskCompleteFormat { + uint32 task_id = 1; + uint32 client_id = 2; + uint64 action = 3; + uint64 runstate = 4; + int32 status = 5; + uint32 flags = 6; +} + +// /sys/kernel/debug/tracing/events/sunrpc/rpc_task_run_action/format +message RpcTaskRunActionFormat { + uint32 task_id = 1; + uint32 client_id = 2; + uint64 action = 3; + uint64 runstate = 4; + int32 status = 5; + uint32 flags = 6; +} + +// /sys/kernel/debug/tracing/events/sunrpc/rpc_task_sleep/format +message RpcTaskSleepFormat { + uint32 task_id = 1; + uint32 client_id = 2; + uint64 timeout = 3; + uint64 runstate = 4; + int32 status = 5; + uint32 flags = 6; + string q_name = 7; +} + +// /sys/kernel/debug/tracing/events/sunrpc/rpc_task_wakeup/format +message RpcTaskWakeupFormat { + uint32 task_id = 1; + uint32 client_id = 2; + uint64 timeout = 3; + uint64 runstate = 4; + int32 status = 5; + uint32 flags = 6; + string q_name = 7; +} + +// /sys/kernel/debug/tracing/events/sunrpc/svc_handle_xprt/format +message SvcHandleXprtFormat { + uint64 xprt = 1; + int32 len = 2; + uint64 flags = 3; + string addr = 4; +} + +// /sys/kernel/debug/tracing/events/sunrpc/svc_process/format +message SvcProcessFormat { + uint32 xid = 1; + uint32 vers = 2; + uint32 proc = 3; + string service = 4; + string addr = 5; +} + +// /sys/kernel/debug/tracing/events/sunrpc/svc_recv/format +message SvcRecvFormat { + uint32 xid = 1; + int32 len = 2; + uint64 flags = 3; + string addr = 4; +} + +// /sys/kernel/debug/tracing/events/sunrpc/svc_send/format +message SvcSendFormat { + uint32 xid = 1; + int32 status = 2; + uint64 flags = 3; + string addr = 4; +} + +// /sys/kernel/debug/tracing/events/sunrpc/svc_wake_up/format +message SvcWakeUpFormat { + int32 pid = 1; +} + +// /sys/kernel/debug/tracing/events/sunrpc/svc_xprt_dequeue/format +message SvcXprtDequeueFormat { + uint64 xprt = 1; + uint64 flags = 2; + uint64 wakeup = 3; + string addr = 4; +} + +// /sys/kernel/debug/tracing/events/sunrpc/svc_xprt_do_enqueue/format +message SvcXprtDoEnqueueFormat { + uint64 xprt = 1; + int32 pid = 2; + uint64 flags = 3; + string addr = 4; +} + +// /sys/kernel/debug/tracing/events/sunrpc/xprt_complete_rqst/format +message XprtCompleteRqstFormat { + uint32 xid = 1; + int32 status = 2; + string addr = 3; + string port = 4; +} + +// /sys/kernel/debug/tracing/events/sunrpc/xprt_lookup_rqst/format +message XprtLookupRqstFormat { + uint32 xid = 1; + int32 status = 2; + string addr = 3; + string port = 4; +} + +// /sys/kernel/debug/tracing/events/sunrpc/xprt_transmit/format +message XprtTransmitFormat { + uint32 xid = 1; + int32 status = 2; + string addr = 3; + string port = 4; +} + +// /sys/kernel/debug/tracing/events/sunrpc/xs_tcp_data_ready/format +message XsTcpDataReadyFormat { + int32 err = 1; + uint32 total = 2; + string addr = 3; + string port = 4; +} + +// /sys/kernel/debug/tracing/events/sunrpc/xs_tcp_data_recv/format +message XsTcpDataRecvFormat { + string addr = 1; + string port = 2; + uint32 xid = 3; + uint64 flags = 4; + uint64 copied = 5; + uint32 reclen = 6; + uint64 offset = 7; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/task.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/task.proto new file mode 100644 index 0000000..82177f6 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/task.proto @@ -0,0 +1,36 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: task +// /sys/kernel/debug/tracing/events/task/task_newtask/format +message TaskNewtaskFormat { + int32 pid = 1; + string comm = 2; + uint64 clone_flags = 3; + int32 oom_score_adj = 4; +} + +// /sys/kernel/debug/tracing/events/task/task_rename/format +message TaskRenameFormat { + int32 pid = 1; + string oldcomm = 2; + string newcomm = 3; + int32 oom_score_adj = 4; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/timer.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/timer.proto new file mode 100644 index 0000000..d85b207 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/timer.proto @@ -0,0 +1,101 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: timer +// /sys/kernel/debug/tracing/events/timer/hrtimer_cancel/format +message HrtimerCancelFormat { + uint64 hrtimer = 1; +} + +// /sys/kernel/debug/tracing/events/timer/hrtimer_expire_entry/format +message HrtimerExpireEntryFormat { + uint64 hrtimer = 1; + int64 now = 2; + uint64 function = 3; +} + +// /sys/kernel/debug/tracing/events/timer/hrtimer_expire_exit/format +message HrtimerExpireExitFormat { + uint64 hrtimer = 1; +} + +// /sys/kernel/debug/tracing/events/timer/hrtimer_init/format +message HrtimerInitFormat { + uint64 hrtimer = 1; + int32 clockid = 2; + uint32 mode = 3; +} + +// /sys/kernel/debug/tracing/events/timer/hrtimer_start/format +message HrtimerStartFormat { + uint64 hrtimer = 1; + uint64 function = 2; + int64 expires = 3; + int64 softexpires = 4; + uint32 mode = 5; +} + +// /sys/kernel/debug/tracing/events/timer/itimer_expire/format +message ItimerExpireFormat { + int32 which = 1; + int32 pid = 2; + uint64 now = 3; +} + +// /sys/kernel/debug/tracing/events/timer/itimer_state/format +message ItimerStateFormat { + int32 which = 1; + uint64 expires = 2; + uint64 value_sec = 3; + uint64 value_usec = 4; + uint64 interval_sec = 5; + uint64 interval_usec = 6; +} + +// /sys/kernel/debug/tracing/events/timer/timer_cancel/format +message TimerCancelFormat { + uint64 timer = 1; +} + +// /sys/kernel/debug/tracing/events/timer/timer_expire_entry/format +message TimerExpireEntryFormat { + uint64 timer = 1; + uint64 now = 2; + uint64 function = 3; +} + +// /sys/kernel/debug/tracing/events/timer/timer_expire_exit/format +message TimerExpireExitFormat { + uint64 timer = 1; +} + +// /sys/kernel/debug/tracing/events/timer/timer_init/format +message TimerInitFormat { + uint64 timer = 1; +} + +// /sys/kernel/debug/tracing/events/timer/timer_start/format +message TimerStartFormat { + uint64 timer = 1; + uint64 function = 2; + uint64 expires = 3; + uint64 now = 4; + uint32 flags = 5; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/trace_plugin_config.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/trace_plugin_config.proto new file mode 100644 index 0000000..e562658 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/trace_plugin_config.proto @@ -0,0 +1,28 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; +option optimize_for = LITE_RUNTIME; + +message TracePluginConfig { + repeated string ftrace_events = 1; + repeated string bytrace_categories = 2; + repeated string bytrace_apps = 3; + uint32 buffer_size_kb = 4; // for ftrace procfs + uint32 flush_interval_ms = 5; + uint32 flush_threshold_kb = 6; + bool parse_ksyms = 7; // enable /proc/kallsyms parser + string clock = 8; + uint32 trace_period_ms = 10; + string raw_data_prefix = 13; +} diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/trace_plugin_result.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/trace_plugin_result.proto new file mode 100644 index 0000000..96476fe --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/trace_plugin_result.proto @@ -0,0 +1,80 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +import "ftrace_event.proto"; + +option optimize_for = LITE_RUNTIME; + +message TracePluginResult { + repeated FtraceCpuStatsMsg ftrace_cpu_stats = 1; + repeated FtraceCpuDetailMsg ftrace_cpu_detail = 2; + repeated SymbolsDetailMsg symbols_detail = 5; + repeated ClockDetailMsg clocks_detail = 6; +} + +message ClockDetailMsg { + // man clock_gettime + enum ClockId { + UNKNOW = 0; + BOOTTIME = 1; + REALTIME = 2; + REALTIME_COARSE = 3; + MONOTONIC = 4; + MONOTONIC_COARSE = 5; + MONOTONIC_RAW = 6; + } + ClockId id = 1; + message TimeSpec { + uint32 tv_sec = 1; + uint32 tv_nsec = 2; + }; + TimeSpec time = 2; + TimeSpec resolution = 3; +}; + +message SymbolsDetailMsg { + uint64 symbol_addr = 1; // symbol address + string symbol_name = 2; // symbol name +} + +message FtraceCpuStatsMsg { + enum Status { + TRACE_START = 0; + TRACE_END = 1; + } + + Status status = 1; + repeated PerCpuStatsMsg per_cpu_stats = 2; + string trace_clock = 3; +} + +// cat /sys/kernel/debug/tracing/per_cpu/cpu0/stats +message PerCpuStatsMsg { + uint64 cpu = 1; + uint64 entries = 2; + uint64 overrun = 3; + uint64 commit_overrun = 4; + uint64 bytes = 5; + double oldest_event_ts = 6; + double now_ts = 7; + uint64 dropped_events = 8; + uint64 read_events = 9; +} + +message FtraceCpuDetailMsg { + uint32 cpu = 1; + repeated FtraceEvent event = 2; + uint64 overwrite = 3; +} diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/v4l2.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/v4l2.proto new file mode 100644 index 0000000..7ca31b5 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/v4l2.proto @@ -0,0 +1,140 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: v4l2 +// /sys/kernel/debug/tracing/events/v4l2/v4l2_dqbuf/format +message V4l2DqbufFormat { + int32 minor = 1; + uint32 index = 2; + uint32 type = 3; + uint32 bytesused = 4; + uint32 flags = 5; + uint32 field = 6; + int64 timestamp = 7; + uint32 timecode_type = 8; + uint32 timecode_flags = 9; + uint32 timecode_frames = 10; + uint32 timecode_seconds = 11; + uint32 timecode_minutes = 12; + uint32 timecode_hours = 13; + uint32 timecode_userbits0 = 14; + uint32 timecode_userbits1 = 15; + uint32 timecode_userbits2 = 16; + uint32 timecode_userbits3 = 17; + uint32 sequence = 18; +} + +// /sys/kernel/debug/tracing/events/v4l2/v4l2_qbuf/format +message V4l2QbufFormat { + int32 minor = 1; + uint32 index = 2; + uint32 type = 3; + uint32 bytesused = 4; + uint32 flags = 5; + uint32 field = 6; + int64 timestamp = 7; + uint32 timecode_type = 8; + uint32 timecode_flags = 9; + uint32 timecode_frames = 10; + uint32 timecode_seconds = 11; + uint32 timecode_minutes = 12; + uint32 timecode_hours = 13; + uint32 timecode_userbits0 = 14; + uint32 timecode_userbits1 = 15; + uint32 timecode_userbits2 = 16; + uint32 timecode_userbits3 = 17; + uint32 sequence = 18; +} + +// /sys/kernel/debug/tracing/events/v4l2/vb2_v4l2_buf_done/format +message Vb2V4l2BufDoneFormat { + int32 minor = 1; + uint32 flags = 2; + uint32 field = 3; + uint64 timestamp = 4; + uint32 timecode_type = 5; + uint32 timecode_flags = 6; + uint32 timecode_frames = 7; + uint32 timecode_seconds = 8; + uint32 timecode_minutes = 9; + uint32 timecode_hours = 10; + uint32 timecode_userbits0 = 11; + uint32 timecode_userbits1 = 12; + uint32 timecode_userbits2 = 13; + uint32 timecode_userbits3 = 14; + uint32 sequence = 15; +} + +// /sys/kernel/debug/tracing/events/v4l2/vb2_v4l2_buf_queue/format +message Vb2V4l2BufQueueFormat { + int32 minor = 1; + uint32 flags = 2; + uint32 field = 3; + uint64 timestamp = 4; + uint32 timecode_type = 5; + uint32 timecode_flags = 6; + uint32 timecode_frames = 7; + uint32 timecode_seconds = 8; + uint32 timecode_minutes = 9; + uint32 timecode_hours = 10; + uint32 timecode_userbits0 = 11; + uint32 timecode_userbits1 = 12; + uint32 timecode_userbits2 = 13; + uint32 timecode_userbits3 = 14; + uint32 sequence = 15; +} + +// /sys/kernel/debug/tracing/events/v4l2/vb2_v4l2_dqbuf/format +message Vb2V4l2DqbufFormat { + int32 minor = 1; + uint32 flags = 2; + uint32 field = 3; + uint64 timestamp = 4; + uint32 timecode_type = 5; + uint32 timecode_flags = 6; + uint32 timecode_frames = 7; + uint32 timecode_seconds = 8; + uint32 timecode_minutes = 9; + uint32 timecode_hours = 10; + uint32 timecode_userbits0 = 11; + uint32 timecode_userbits1 = 12; + uint32 timecode_userbits2 = 13; + uint32 timecode_userbits3 = 14; + uint32 sequence = 15; +} + +// /sys/kernel/debug/tracing/events/v4l2/vb2_v4l2_qbuf/format +message Vb2V4l2QbufFormat { + int32 minor = 1; + uint32 flags = 2; + uint32 field = 3; + uint64 timestamp = 4; + uint32 timecode_type = 5; + uint32 timecode_flags = 6; + uint32 timecode_frames = 7; + uint32 timecode_seconds = 8; + uint32 timecode_minutes = 9; + uint32 timecode_hours = 10; + uint32 timecode_userbits0 = 11; + uint32 timecode_userbits1 = 12; + uint32 timecode_userbits2 = 13; + uint32 timecode_userbits3 = 14; + uint32 sequence = 15; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/vmscan.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/vmscan.proto new file mode 100644 index 0000000..c9f1867 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/vmscan.proto @@ -0,0 +1,111 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: vmscan +// /sys/kernel/debug/tracing/events/vmscan/mm_shrink_slab_end/format +message MmShrinkSlabEndFormat { + uint64 shr = 1; + int32 nid = 2; + uint64 shrink = 3; + uint64 unused_scan = 4; + uint64 new_scan = 5; + int32 retval = 6; + uint64 total_scan = 7; +} + +// /sys/kernel/debug/tracing/events/vmscan/mm_shrink_slab_start/format +message MmShrinkSlabStartFormat { + uint64 shr = 1; + uint64 shrink = 2; + int32 nid = 3; + uint64 nr_objects_to_shrink = 4; + uint32 gfp_flags = 5; + uint64 cache_items = 6; + uint64 delta = 7; + uint64 total_scan = 8; + int32 priority = 9; +} + +// /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_begin/format +message MmVmscanDirectReclaimBeginFormat { + int32 order = 1; + int32 may_writepage = 2; + uint32 gfp_flags = 3; + int32 classzone_idx = 4; +} + +// /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_end/format +message MmVmscanDirectReclaimEndFormat { + uint64 nr_reclaimed = 1; +} + +// /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_sleep/format +message MmVmscanKswapdSleepFormat { + int32 nid = 1; +} + +// /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_wake/format +message MmVmscanKswapdWakeFormat { + int32 nid = 1; + int32 zid = 2; + int32 order = 3; +} + +// /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_lru_isolate/format +message MmVmscanLruIsolateFormat { + int32 classzone_idx = 1; + int32 order = 2; + uint64 nr_requested = 3; + uint64 nr_scanned = 4; + uint64 nr_skipped = 5; + uint64 nr_taken = 6; + uint32 isolate_mode = 7; + int32 lru = 8; +} + +// /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_lru_shrink_inactive/format +message MmVmscanLruShrinkInactiveFormat { + int32 nid = 1; + uint64 nr_scanned = 2; + uint64 nr_reclaimed = 3; + uint64 nr_dirty = 4; + uint64 nr_writeback = 5; + uint64 nr_congested = 6; + uint64 nr_immediate = 7; + uint64 nr_activate = 8; + uint64 nr_ref_keep = 9; + uint64 nr_unmap_fail = 10; + int32 priority = 11; + int32 reclaim_flags = 12; +} + +// /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_wakeup_kswapd/format +message MmVmscanWakeupKswapdFormat { + int32 nid = 1; + int32 zid = 2; + int32 order = 3; + uint32 gfp_flags = 4; +} + +// /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_writepage/format +message MmVmscanWritepageFormat { + uint64 pfn = 1; + int32 reclaim_flags = 2; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/workqueue.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/workqueue.proto new file mode 100644 index 0000000..040e5ab --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/workqueue.proto @@ -0,0 +1,45 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: workqueue +// /sys/kernel/debug/tracing/events/workqueue/workqueue_activate_work/format +message WorkqueueActivateWorkFormat { + uint64 work = 1; +} + +// /sys/kernel/debug/tracing/events/workqueue/workqueue_execute_end/format +message WorkqueueExecuteEndFormat { + uint64 work = 1; +} + +// /sys/kernel/debug/tracing/events/workqueue/workqueue_execute_start/format +message WorkqueueExecuteStartFormat { + uint64 work = 1; + uint64 function = 2; +} + +// /sys/kernel/debug/tracing/events/workqueue/workqueue_queue_work/format +message WorkqueueQueueWorkFormat { + uint64 work = 1; + uint64 function = 2; + uint64 workqueue = 3; + uint32 req_cpu = 4; + uint32 cpu = 5; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/ftrace_data/writeback.proto b/host/trace_streamer/src/protos/types/plugins/ftrace_data/writeback.proto new file mode 100644 index 0000000..fdf2bdb --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/ftrace_data/writeback.proto @@ -0,0 +1,288 @@ +// THIS FILE IS GENERATED BY ftrace_proto_generator.py, PLEASE DON'T EDIT IT! +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; + +// category: writeback +// /sys/kernel/debug/tracing/events/writeback/balance_dirty_pages/format +message BalanceDirtyPagesFormat { + string bdi = 1; + uint64 limit = 2; + uint64 setpoint = 3; + uint64 dirty = 4; + uint64 bdi_setpoint = 5; + uint64 bdi_dirty = 6; + uint64 dirty_ratelimit = 7; + uint64 task_ratelimit = 8; + uint32 dirtied = 9; + uint32 dirtied_pause = 10; + uint64 paused = 11; + uint64 pause = 12; + uint64 period = 13; + uint64 think = 14; + uint32 cgroup_ino = 15; +} + +// /sys/kernel/debug/tracing/events/writeback/bdi_dirty_ratelimit/format +message BdiDirtyRatelimitFormat { + string bdi = 1; + uint64 write_bw = 2; + uint64 avg_write_bw = 3; + uint64 dirty_rate = 4; + uint64 dirty_ratelimit = 5; + uint64 task_ratelimit = 6; + uint64 balanced_dirty_ratelimit = 7; + uint32 cgroup_ino = 8; +} + +// /sys/kernel/debug/tracing/events/writeback/global_dirty_state/format +message GlobalDirtyStateFormat { + uint64 nr_dirty = 1; + uint64 nr_writeback = 2; + uint64 nr_unstable = 3; + uint64 background_thresh = 4; + uint64 dirty_thresh = 5; + uint64 dirty_limit = 6; + uint64 nr_dirtied = 7; + uint64 nr_written = 8; +} + +// /sys/kernel/debug/tracing/events/writeback/wbc_writepage/format +message WbcWritepageFormat { + string name = 1; + uint64 nr_to_write = 2; + uint64 pages_skipped = 3; + int32 sync_mode = 4; + int32 for_kupdate = 5; + int32 for_background = 6; + int32 for_reclaim = 7; + int32 range_cyclic = 8; + uint64 range_start = 9; + uint64 range_end = 10; + uint32 cgroup_ino = 11; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_bdi_register/format +message WritebackBdiRegisterFormat { + string name = 1; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_congestion_wait/format +message WritebackCongestionWaitFormat { + uint32 usec_timeout = 1; + uint32 usec_delayed = 2; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_dirty_inode/format +message WritebackDirtyInodeFormat { + string name = 1; + uint64 ino = 2; + uint64 state = 3; + uint64 flags = 4; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_dirty_inode_enqueue/format +message WritebackDirtyInodeEnqueueFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 state = 3; + uint32 mode = 4; + uint64 dirtied_when = 5; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_dirty_inode_start/format +message WritebackDirtyInodeStartFormat { + string name = 1; + uint64 ino = 2; + uint64 state = 3; + uint64 flags = 4; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_dirty_page/format +message WritebackDirtyPageFormat { + string name = 1; + uint64 ino = 2; + uint64 index = 3; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_exec/format +message WritebackExecFormat { + string name = 1; + uint64 nr_pages = 2; + uint64 sb_dev = 3; + int32 sync_mode = 4; + int32 for_kupdate = 5; + int32 range_cyclic = 6; + int32 for_background = 7; + int32 reason = 8; + uint32 cgroup_ino = 9; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_lazytime/format +message WritebackLazytimeFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 state = 3; + uint32 mode = 4; + uint64 dirtied_when = 5; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_lazytime_iput/format +message WritebackLazytimeIputFormat { + uint64 dev = 1; + uint64 ino = 2; + uint64 state = 3; + uint32 mode = 4; + uint64 dirtied_when = 5; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_mark_inode_dirty/format +message WritebackMarkInodeDirtyFormat { + string name = 1; + uint64 ino = 2; + uint64 state = 3; + uint64 flags = 4; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_pages_written/format +message WritebackPagesWrittenFormat { + uint64 pages = 1; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_queue/format +message WritebackQueueFormat { + string name = 1; + uint64 nr_pages = 2; + uint64 sb_dev = 3; + int32 sync_mode = 4; + int32 for_kupdate = 5; + int32 range_cyclic = 6; + int32 for_background = 7; + int32 reason = 8; + uint32 cgroup_ino = 9; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_queue_io/format +message WritebackQueueIoFormat { + string name = 1; + uint64 older = 2; + uint64 age = 3; + int32 moved = 4; + int32 reason = 5; + uint32 cgroup_ino = 6; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_sb_inodes_requeue/format +message WritebackSbInodesRequeueFormat { + string name = 1; + uint64 ino = 2; + uint64 state = 3; + uint64 dirtied_when = 4; + uint32 cgroup_ino = 5; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_single_inode/format +message WritebackSingleInodeFormat { + string name = 1; + uint64 ino = 2; + uint64 state = 3; + uint64 dirtied_when = 4; + uint64 writeback_index = 5; + uint64 nr_to_write = 6; + uint64 wrote = 7; + uint32 cgroup_ino = 8; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_single_inode_start/format +message WritebackSingleInodeStartFormat { + string name = 1; + uint64 ino = 2; + uint64 state = 3; + uint64 dirtied_when = 4; + uint64 writeback_index = 5; + uint64 nr_to_write = 6; + uint64 wrote = 7; + uint32 cgroup_ino = 8; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_start/format +message WritebackStartFormat { + string name = 1; + uint64 nr_pages = 2; + uint64 sb_dev = 3; + int32 sync_mode = 4; + int32 for_kupdate = 5; + int32 range_cyclic = 6; + int32 for_background = 7; + int32 reason = 8; + uint32 cgroup_ino = 9; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_wait/format +message WritebackWaitFormat { + string name = 1; + uint64 nr_pages = 2; + uint64 sb_dev = 3; + int32 sync_mode = 4; + int32 for_kupdate = 5; + int32 range_cyclic = 6; + int32 for_background = 7; + int32 reason = 8; + uint32 cgroup_ino = 9; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_wait_iff_congested/format +message WritebackWaitIffCongestedFormat { + uint32 usec_timeout = 1; + uint32 usec_delayed = 2; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_wake_background/format +message WritebackWakeBackgroundFormat { + string name = 1; + uint32 cgroup_ino = 2; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_write_inode/format +message WritebackWriteInodeFormat { + string name = 1; + uint64 ino = 2; + int32 sync_mode = 3; + uint32 cgroup_ino = 4; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_write_inode_start/format +message WritebackWriteInodeStartFormat { + string name = 1; + uint64 ino = 2; + int32 sync_mode = 3; + uint32 cgroup_ino = 4; +} + +// /sys/kernel/debug/tracing/events/writeback/writeback_written/format +message WritebackWrittenFormat { + string name = 1; + uint64 nr_pages = 2; + uint64 sb_dev = 3; + int32 sync_mode = 4; + int32 for_kupdate = 5; + int32 range_cyclic = 6; + int32 for_background = 7; + int32 reason = 8; + uint32 cgroup_ino = 9; +} + diff --git a/host/trace_streamer/src/protos/types/plugins/hidump_data/BUILD.gn b/host/trace_streamer/src/protos/types/plugins/hidump_data/BUILD.gn new file mode 100644 index 0000000..966f022 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/hidump_data/BUILD.gn @@ -0,0 +1,65 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../protos.gni") + +hidump_data_sources = [ + "./hidump_plugin_config.proto", + "./hidump_plugin_result.proto", +] + +####################################################### +proto_out_dir = "$root_gen_dir/cpp/" + rebase_path(".", "//") +proto_rel_out_dir = rebase_path(proto_out_dir, root_build_dir) + +hidump_data_codegen = [] +foreach(proto, hidump_data_sources) { + name = get_path_info(proto, "name") + hidump_data_codegen += [ + "$proto_out_dir/$name.pb.h", + "$proto_out_dir/$name.pb.cc", + ] +} + +config("hidump_include_config") { + include_dirs = [ "$proto_out_dir" ] +} + +####################################################### +action("hidump_data_cpp_gen") { + script = "${OHOS_PROFILER_DIR}/build/protoc.sh" + sources = hidump_data_sources + outputs = hidump_data_codegen + args = [ + "$libc_dir_proto", + "$root_output_dir_proto", + "--cpp_out", + "$proto_rel_out_dir", + "--proto_path", + rebase_path(".", root_build_dir), + ] + args += rebase_path(sources, root_build_dir) + deps = [ "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protoc(${host_toolchain})" ] +} + +ohos_source_set("hidump_data_cpp") { + deps = [ ":hidump_data_cpp_gen" ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + ] + include_dirs = [ "$proto_out_dir" ] + public_configs = [ ":hidump_include_config" ] + sources = hidump_data_codegen +} diff --git a/host/trace_streamer/src/protos/types/plugins/hidump_data/hidump_plugin_config.proto b/host/trace_streamer/src/protos/types/plugins/hidump_data/hidump_plugin_config.proto new file mode 100644 index 0000000..46e33f6 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/hidump_data/hidump_plugin_config.proto @@ -0,0 +1,20 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +syntax = "proto3"; +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + + +message HidumpConfig { + bool report_fps = 1; +} \ No newline at end of file diff --git a/host/trace_streamer/src/protos/types/plugins/hidump_data/hidump_plugin_result.proto b/host/trace_streamer/src/protos/types/plugins/hidump_data/hidump_plugin_result.proto new file mode 100644 index 0000000..5cf6e55 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/hidump_data/hidump_plugin_result.proto @@ -0,0 +1,39 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +syntax = "proto3"; +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + + +message FpsData { + enum ClockId { + UNKNOW = 0; + BOOTTIME = 1; + REALTIME = 2; + REALTIME_COARSE = 3; + MONOTONIC = 4; + MONOTONIC_COARSE = 5; + MONOTONIC_RAW = 6; + } + ClockId id = 1; + message TimeSpec { + uint32 tv_sec = 1; + uint32 tv_nsec = 2; + }; + TimeSpec time = 2; + uint32 fps = 3; +} + +message HidumpInfo { + repeated FpsData fps_event = 1; +} \ No newline at end of file diff --git a/host/trace_streamer/src/protos/types/plugins/hilog_data/BUILD.gn b/host/trace_streamer/src/protos/types/plugins/hilog_data/BUILD.gn new file mode 100644 index 0000000..e040bbf --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/hilog_data/BUILD.gn @@ -0,0 +1,65 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../protos.gni") + +hilog_data_sources = [ + "./hilog_plugin_config.proto", + "./hilog_plugin_result.proto", +] + +####################################################### +proto_out_dir = "$root_gen_dir/cpp/" + rebase_path(".", "//") +proto_rel_out_dir = rebase_path(proto_out_dir, root_build_dir) + +hilog_data_codegen = [] +foreach(proto, hilog_data_sources) { + name = get_path_info(proto, "name") + hilog_data_codegen += [ + "$proto_out_dir/$name.pb.h", + "$proto_out_dir/$name.pb.cc", + ] +} + +config("hilog_include_config") { + include_dirs = [ "$proto_out_dir" ] +} + +####################################################### +action("hilog_data_cpp_gen") { + script = "${OHOS_PROFILER_DIR}/build/protoc.sh" + sources = hilog_data_sources + outputs = hilog_data_codegen + args = [ + "$libc_dir_proto", + "$root_output_dir_proto", + "--cpp_out", + "$proto_rel_out_dir", + "--proto_path", + rebase_path(".", root_build_dir), + ] + args += rebase_path(sources, root_build_dir) + deps = [ "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protoc(${host_toolchain})" ] +} + +ohos_source_set("hilog_data_cpp") { + deps = [ ":hilog_data_cpp_gen" ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + ] + include_dirs = [ "$proto_out_dir" ] + public_configs = [ ":hilog_include_config" ] + sources = hilog_data_codegen +} diff --git a/host/trace_streamer/src/protos/types/plugins/hilog_data/hilog_plugin_config.proto b/host/trace_streamer/src/protos/types/plugins/hilog_data/hilog_plugin_config.proto new file mode 100644 index 0000000..1b18724 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/hilog_data/hilog_plugin_config.proto @@ -0,0 +1,38 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +syntax = "proto3"; +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +enum Type { + TYPE_UNSPECIFIED = 0; + HI3516 = 1; + P40 = 2; +} + +enum Level { + LEVEL_UNSPECIFIED = 0; + ERROR = 1; + INFO = 2; + DEBUG = 3; + WARN = 4; +} + +message HilogConfig { + Type device_type = 1; + Level log_level = 2; + + int32 pid = 3; + bool need_record = 4; + bool need_clear = 5; +} \ No newline at end of file diff --git a/host/trace_streamer/src/protos/types/plugins/hilog_data/hilog_plugin_result.proto b/host/trace_streamer/src/protos/types/plugins/hilog_data/hilog_plugin_result.proto new file mode 100644 index 0000000..64e678d --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/hilog_data/hilog_plugin_result.proto @@ -0,0 +1,36 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +syntax = "proto3"; +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +message HilogDetails { + // log time + uint64 tv_sec = 1; + uint64 tv_nsec = 2; + uint32 pid = 3; + uint32 tid = 4; + uint32 level = 5; + string tag = 6; +} + +message HilogLine { + HilogDetails detail = 1; + string context = 2; + uint64 id = 3; +} + +message HilogInfo { + repeated HilogLine info = 1; + uint32 clock = 2; // 空值 +} \ No newline at end of file diff --git a/host/trace_streamer/src/protos/types/plugins/hiperf_call_plugin/BUILD.gn b/host/trace_streamer/src/protos/types/plugins/hiperf_call_plugin/BUILD.gn new file mode 100644 index 0000000..fb8d3da --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/hiperf_call_plugin/BUILD.gn @@ -0,0 +1,63 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../protos.gni") + +hiperf_call_plugin_protos_defines = [ "./hiperf_call_plugin_config.proto" ] + +####################################################### +proto_out_dir = "$root_gen_dir/cpp/" + rebase_path(".", "//") +proto_rel_out_dir = rebase_path(proto_out_dir, root_build_dir) +print("proto_out_dir", proto_out_dir, proto_rel_out_dir) + +####################################################### +hiperf_call_plugin_protos_codegen = [] +foreach(proto, hiperf_call_plugin_protos_defines) { + name = get_path_info(proto, "name") + hiperf_call_plugin_protos_codegen += [ + "$proto_out_dir/$name.pb.h", + "$proto_out_dir/$name.pb.cc", + ] +} + +action("hiperf_call_plugin_protos_protoc") { + script = "${OHOS_PROFILER_DIR}/build/protoc.sh" + sources = hiperf_call_plugin_protos_defines + outputs = hiperf_call_plugin_protos_codegen + args = [ + "$libc_dir_proto", + "$root_output_dir_proto", + "--cpp_out", + "$proto_rel_out_dir", + "--proto_path", + rebase_path(".", root_build_dir), + ] + args += rebase_path(sources, root_build_dir) + deps = [ "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protoc(${host_toolchain})" ] +} + +config("hiperf_call_plugin_protos_config") { + include_dirs = [ "$proto_out_dir" ] +} + +ohos_source_set("hiperf_call_plugin_protos_cpp") { + deps = [ ":hiperf_call_plugin_protos_protoc" ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + ] + include_dirs = [ "$proto_out_dir" ] + public_configs = [ ":hiperf_call_plugin_protos_config" ] + sources = hiperf_call_plugin_protos_codegen +} diff --git a/host/trace_streamer/src/protos/types/plugins/hiperf_call_plugin/hiperf_call_plugin_config.proto b/host/trace_streamer/src/protos/types/plugins/hiperf_call_plugin/hiperf_call_plugin_config.proto new file mode 100644 index 0000000..e11b279 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/hiperf_call_plugin/hiperf_call_plugin_config.proto @@ -0,0 +1,26 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; +option optimize_for = LITE_RUNTIME; + +message HiperfCallPluginConfig { + int32 pid = 1; // pid of app. + string app_name = 2; // app name. + string outfile = 4; // the name of the output target file. + + uint32 frequency = 3; // Set the counts of dumpping records per second, default 1000. + bool is_trace = 5; // Set if using --trace-offcpu, default true. + bool is_root = 6; // Set if using root privilege, default true. + bool is_emulator = 7; // Set if the device is emulator, default false. +} \ No newline at end of file diff --git a/host/trace_streamer/src/protos/types/plugins/memory_data/BUILD.gn b/host/trace_streamer/src/protos/types/plugins/memory_data/BUILD.gn new file mode 100644 index 0000000..a1b679a --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/memory_data/BUILD.gn @@ -0,0 +1,66 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../protos.gni") + +memory_data_sources = [ + "./memory_plugin_common.proto", + "./memory_plugin_config.proto", + "./memory_plugin_result.proto", +] + +####################################################### +proto_out_dir = "$root_gen_dir/cpp/" + rebase_path(".", "//") +proto_rel_out_dir = rebase_path(proto_out_dir, root_build_dir) + +memory_data_codegen = [] +foreach(proto, memory_data_sources) { + name = get_path_info(proto, "name") + memory_data_codegen += [ + "$proto_out_dir/$name.pb.h", + "$proto_out_dir/$name.pb.cc", + ] +} + +config("memory_include_config") { + include_dirs = [ "$proto_out_dir" ] +} + +####################################################### +action("memory_data_cpp_gen") { + script = "${OHOS_PROFILER_DIR}/build/protoc.sh" + sources = memory_data_sources + outputs = memory_data_codegen + args = [ + "$libc_dir_proto", + "$root_output_dir_proto", + "--cpp_out", + "$proto_rel_out_dir", + "--proto_path", + rebase_path(".", root_build_dir), + ] + args += rebase_path(sources, root_build_dir) + deps = [ "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protoc(${host_toolchain})" ] +} + +ohos_source_set("memory_data_cpp") { + deps = [ ":memory_data_cpp_gen" ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + ] + include_dirs = [ "$proto_out_dir" ] + public_configs = [ ":memory_include_config" ] + sources = memory_data_codegen +} diff --git a/host/trace_streamer/src/protos/types/plugins/memory_data/memory_plugin_common.proto b/host/trace_streamer/src/protos/types/plugins/memory_data/memory_plugin_common.proto new file mode 100644 index 0000000..1dac1d9 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/memory_data/memory_plugin_common.proto @@ -0,0 +1,187 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +// Common define for memory plug-in, imported by memory data and config proto file. +enum SysMeminfoType { + PMEM_UNSPECIFIED = 0; + PMEM_MEM_TOTAL = 1; + PMEM_MEM_FREE = 2; + PMEM_MEM_AVAILABLE = 3; + PMEM_BUFFERS = 4; + PMEM_CACHED = 5; + PMEM_SWAP_CACHED = 6; + PMEM_ACTIVE = 7; + PMEM_INACTIVE = 8; + PMEM_ACTIVE_ANON = 9; + PMEM_INACTIVE_ANON = 10; + PMEM_ACTIVE_FILE = 11; + PMEM_INACTIVE_FILE = 12; + PMEM_UNEVICTABLE = 13; + PMEM_MLOCKED = 14; + PMEM_SWAP_TOTAL = 15; + PMEM_SWAP_FREE = 16; + PMEM_DIRTY = 17; + PMEM_WRITEBACK = 18; + PMEM_ANON_PAGES = 19; + PMEM_MAPPED = 20; + PMEM_SHMEM = 21; + PMEM_SLAB = 22; + PMEM_SLAB_RECLAIMABLE = 23; + PMEM_SLAB_UNRECLAIMABLE = 24; + PMEM_KERNEL_STACK = 25; + PMEM_PAGE_TABLES = 26; + PMEM_COMMIT_LIMIT = 27; + PMEM_COMMITED_AS = 28; + PMEM_VMALLOC_TOTAL = 29; + PMEM_VMALLOC_USED = 30; + PMEM_VMALLOC_CHUNK = 31; + PMEM_CMA_TOTAL = 32; + PMEM_CMA_FREE = 33; +} + +enum SysVMeminfoType { + VMEMINFO_UNSPECIFIED = 0; + VMEMINFO_NR_FREE_PAGES = 1; + VMEMINFO_NR_ALLOC_BATCH = 2; + VMEMINFO_NR_INACTIVE_ANON = 3; + VMEMINFO_NR_ACTIVE_ANON = 4; + VMEMINFO_NR_INACTIVE_FILE = 5; + VMEMINFO_NR_ACTIVE_FILE = 6; + VMEMINFO_NR_UNEVICTABLE = 7; + VMEMINFO_NR_MLOCK = 8; + VMEMINFO_NR_ANON_PAGES = 9; + VMEMINFO_NR_MAPPED = 10; + VMEMINFO_NR_FILE_PAGES = 11; + VMEMINFO_NR_DIRTY = 12; + VMEMINFO_NR_WRITEBACK = 13; + VMEMINFO_NR_SLAB_RECLAIMABLE = 14; + VMEMINFO_NR_SLAB_UNRECLAIMABLE = 15; + VMEMINFO_NR_PAGE_TABLE_PAGES = 16; + VMEMINFO_NR_KERNEL_STACK = 17; + VMEMINFO_NR_OVERHEAD = 18; + VMEMINFO_NR_UNSTABLE = 19; + VMEMINFO_NR_BOUNCE = 20; + VMEMINFO_NR_VMSCAN_WRITE = 21; + VMEMINFO_NR_VMSCAN_IMMEDIATE_RECLAIM = 22; + VMEMINFO_NR_WRITEBACK_TEMP = 23; + VMEMINFO_NR_ISOLATED_ANON = 24; + VMEMINFO_NR_ISOLATED_FILE = 25; + VMEMINFO_NR_SHMEM = 26; + VMEMINFO_NR_DIRTIED = 27; + VMEMINFO_NR_WRITTEN = 28; + VMEMINFO_NR_PAGES_SCANNED = 29; + VMEMINFO_WORKINGSET_REFAULT = 30; + VMEMINFO_WORKINGSET_ACTIVATE = 31; + VMEMINFO_WORKINGSET_NODERECLAIM = 32; + VMEMINFO_NR_ANON_TRANSPARENT_HUGEPAGES = 33; + VMEMINFO_NR_FREE_CMA = 34; + VMEMINFO_NR_SWAPCACHE = 35; + VMEMINFO_NR_DIRTY_THRESHOLD = 36; + VMEMINFO_NR_DIRTY_BACKGROUND_THRESHOLD = 37; + VMEMINFO_PGPGIN = 38; + VMEMINFO_PGPGOUT = 39; + VMEMINFO_PGPGOUTCLEAN = 40; + VMEMINFO_PSWPIN = 41; + VMEMINFO_PSWPOUT = 42; + VMEMINFO_PGALLOC_DMA = 43; + VMEMINFO_PGALLOC_NORMAL = 44; + VMEMINFO_PGALLOC_MOVABLE = 45; + VMEMINFO_PGFREE = 46; + VMEMINFO_PGACTIVATE = 47; + VMEMINFO_PGDEACTIVATE = 48; + VMEMINFO_PGFAULT = 49; + VMEMINFO_PGMAJFAULT = 50; + VMEMINFO_PGREFILL_DMA = 51; + VMEMINFO_PGREFILL_NORMAL = 52; + VMEMINFO_PGREFILL_MOVABLE = 53; + VMEMINFO_PGSTEAL_KSWAPD_DMA = 54; + VMEMINFO_PGSTEAL_KSWAPD_NORMAL = 55; + VMEMINFO_PGSTEAL_KSWAPD_MOVABLE = 56; + VMEMINFO_PGSTEAL_DIRECT_DMA = 57; + VMEMINFO_PGSTEAL_DIRECT_NORMAL = 58; + VMEMINFO_PGSTEAL_DIRECT_MOVABLE = 59; + VMEMINFO_PGSCAN_KSWAPD_DMA = 60; + VMEMINFO_PGSCAN_KSWAPD_NORMAL = 61; + VMEMINFO_PGSCAN_KSWAPD_MOVABLE = 62; + VMEMINFO_PGSCAN_DIRECT_DMA = 63; + VMEMINFO_PGSCAN_DIRECT_NORMAL = 64; + VMEMINFO_PGSCAN_DIRECT_MOVABLE = 65; + VMEMINFO_PGSCAN_DIRECT_THROTTLE = 66; + VMEMINFO_PGINODESTEAL = 67; + VMEMINFO_SLABS_SCANNED = 68; + VMEMINFO_KSWAPD_INODESTEAL = 69; + VMEMINFO_KSWAPD_LOW_WMARK_HIT_QUICKLY = 70; + VMEMINFO_KSWAPD_HIGH_WMARK_HIT_QUICKLY = 71; + VMEMINFO_PAGEOUTRUN = 72; + VMEMINFO_ALLOCSTALL = 73; + VMEMINFO_PGROTATED = 74; + VMEMINFO_DROP_PAGECACHE = 75; + VMEMINFO_DROP_SLAB = 76; + VMEMINFO_PGMIGRATE_SUCCESS = 77; + VMEMINFO_PGMIGRATE_FAIL = 78; + VMEMINFO_COMPACT_MIGRATE_SCANNED = 79; + VMEMINFO_COMPACT_FREE_SCANNED = 80; + VMEMINFO_COMPACT_ISOLATED = 81; + VMEMINFO_COMPACT_STALL = 82; + VMEMINFO_COMPACT_FAIL = 83; + VMEMINFO_COMPACT_SUCCESS = 84; + VMEMINFO_COMPACT_DAEMON_WAKE = 85; + VMEMINFO_UNEVICTABLE_PGS_CULLED = 86; + VMEMINFO_UNEVICTABLE_PGS_SCANNED = 87; + VMEMINFO_UNEVICTABLE_PGS_RESCUED = 88; + VMEMINFO_UNEVICTABLE_PGS_MLOCKED = 89; + VMEMINFO_UNEVICTABLE_PGS_MUNLOCKED = 90; + VMEMINFO_UNEVICTABLE_PGS_CLEARED = 91; + VMEMINFO_UNEVICTABLE_PGS_STRANDED = 92; + VMEMINFO_NR_ZSPAGES = 93; + VMEMINFO_NR_ION_HEAP = 94; + VMEMINFO_NR_GPU_HEAP = 95; + VMEMINFO_ALLOCSTALL_DMA = 96; + VMEMINFO_ALLOCSTALL_MOVABLE = 97; + VMEMINFO_ALLOCSTALL_NORMAL = 98; + VMEMINFO_COMPACT_DAEMON_FREE_SCANNED = 99; + VMEMINFO_COMPACT_DAEMON_MIGRATE_SCANNED = 100; + VMEMINFO_NR_FASTRPC = 101; + VMEMINFO_NR_INDIRECTLY_RECLAIMABLE = 102; + VMEMINFO_NR_ION_HEAP_POOL = 103; + VMEMINFO_NR_KERNEL_MISC_RECLAIMABLE = 104; + VMEMINFO_NR_SHADOW_CALL_STACK_BYTES = 105; + VMEMINFO_NR_SHMEM_HUGEPAGES = 106; + VMEMINFO_NR_SHMEM_PMDMAPPED = 107; + VMEMINFO_NR_UNRECLAIMABLE_PAGES = 108; + VMEMINFO_NR_ZONE_ACTIVE_ANON = 109; + VMEMINFO_NR_ZONE_ACTIVE_FILE = 110; + VMEMINFO_NR_ZONE_INACTIVE_ANON = 111; + VMEMINFO_NR_ZONE_INACTIVE_FILE = 112; + VMEMINFO_NR_ZONE_UNEVICTABLE = 113; + VMEMINFO_NR_ZONE_WRITE_PENDING = 114; + VMEMINFO_OOM_KILL = 115; + VMEMINFO_PGLAZYFREE = 116; + VMEMINFO_PGLAZYFREED = 117; + VMEMINFO_PGREFILL = 118; + VMEMINFO_PGSCAN_DIRECT = 119; + VMEMINFO_PGSCAN_KSWAPD = 120; + VMEMINFO_PGSKIP_DMA = 121; + VMEMINFO_PGSKIP_MOVABLE = 122; + VMEMINFO_PGSKIP_NORMAL = 123; + VMEMINFO_PGSTEAL_DIRECT = 124; + VMEMINFO_PGSTEAL_KSWAPD = 125; + VMEMINFO_SWAP_RA = 126; + VMEMINFO_SWAP_RA_HIT = 127; + VMEMINFO_WORKINGSET_RESTORE = 128; +} diff --git a/host/trace_streamer/src/protos/types/plugins/memory_data/memory_plugin_config.proto b/host/trace_streamer/src/protos/types/plugins/memory_data/memory_plugin_config.proto new file mode 100644 index 0000000..72f824d --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/memory_data/memory_plugin_config.proto @@ -0,0 +1,42 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +import "memory_plugin_common.proto"; + +// Memory plug-in configuration, passed to plug-in by plug-in service. +message MemoryConfig { + // set true to report process list + bool report_process_tree = 1; + // set true to report memory counter from /proc/meminfo + bool report_sysmem_mem_info = 2; + // set required counter list of system meminfo, eg:MemTotal, MemFree, etc. + repeated SysMeminfoType sys_meminfo_counters = 3; + // set true to report memory counter from /proc/vmstat + bool report_sysmem_vmem_info = 4; + // set required counter list of virtual system meminfo, eg:nr_free_pages, nr_anon_pages, etc. + repeated SysVMeminfoType sys_vmeminfo_counters = 5; + // set true to report process meminfo from /proc/${pid}/stat + bool report_process_mem_info = 6; + // set true to report application memory usage summary, eg:java heap memory, native heap, stack memory, etc. + bool report_app_mem_info = 7; + // set true to report application memory by dumpsys service, otherwise, + // application memory will count up by /proc/${pid}/smaps information + bool report_app_mem_by_dumpsys = 8; + // set required pid list + repeated int32 pid = 9; +} diff --git a/host/trace_streamer/src/protos/types/plugins/memory_data/memory_plugin_result.proto b/host/trace_streamer/src/protos/types/plugins/memory_data/memory_plugin_result.proto new file mode 100644 index 0000000..c0e6227 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/memory_data/memory_plugin_result.proto @@ -0,0 +1,73 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +import "memory_plugin_common.proto"; + +// Data format of memory collect plug-in. +// Various memory data count, including system level and application level. +// Obtained from the proc file system or system service. +message SysMeminfo { + SysMeminfoType key = 1; + uint64 value = 2; +}; +message SysVMeminfo { + SysVMeminfoType key = 1; + uint64 value = 2; +}; + +message SmapsInfo { + string mapinfo = 1; + int32 size = 2; + int32 rss = 3; + int32 pss = 4; + int32 anonymous = 5; +}; + +message AppSummary { + uint64 java_heap = 1; + uint64 native_heap = 2; + uint64 code = 3; + uint64 stack = 4; + uint64 graphics = 5; + uint64 private_other = 6; + uint64 system = 7; +}; + +message ProcessMemoryInfo { + int32 pid = 1; + string name = 2; + // data from /proc/$pid/stat + uint64 vm_size_kb = 3; + uint64 vm_rss_kb = 4; + uint64 rss_anon_kb = 5; + uint64 rss_file_kb = 6; + uint64 rss_shmem_kb = 7; + uint64 vm_swap_kb = 8; + uint64 vm_locked_kb = 9; + uint64 vm_hwm_kb = 10; + int64 oom_score_adj = 11; + // data from /proc/$pid/smaps + repeated SmapsInfo smapinfo = 12; + AppSummary memsummary = 13; +} + +message MemoryData { + repeated ProcessMemoryInfo processesinfo = 1; + repeated SysMeminfo meminfo = 2; + repeated SysVMeminfo vmeminfo = 3; +} diff --git a/host/trace_streamer/src/protos/types/plugins/native_hook/BUILD.gn b/host/trace_streamer/src/protos/types/plugins/native_hook/BUILD.gn new file mode 100644 index 0000000..0ca0fc9 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/native_hook/BUILD.gn @@ -0,0 +1,65 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../protos.gni") + +native_hook_sources = [ + "./native_hook_config.proto", + "./native_hook_result.proto", +] + +####################################################### +proto_out_dir = "$root_gen_dir/cpp/" + rebase_path(".", "//") +proto_rel_out_dir = rebase_path(proto_out_dir, root_build_dir) + +native_hook_codegen = [] +foreach(proto, native_hook_sources) { + name = get_path_info(proto, "name") + native_hook_codegen += [ + "$proto_out_dir/$name.pb.h", + "$proto_out_dir/$name.pb.cc", + ] +} + +config("native_hook_include_config") { + include_dirs = [ "$proto_out_dir" ] +} + +####################################################### +action("native_hook_cpp_gen") { + script = "${OHOS_PROFILER_DIR}/build/protoc.sh" + sources = native_hook_sources + outputs = native_hook_codegen + args = [ + "$libc_dir_proto", + "$root_output_dir_proto", + "--cpp_out", + "$proto_rel_out_dir", + "--proto_path", + rebase_path(".", root_build_dir), + ] + args += rebase_path(sources, root_build_dir) + deps = [ "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protoc(${host_toolchain})" ] +} + +ohos_source_set("native_hook_cpp") { + deps = [ ":native_hook_cpp_gen" ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + ] + include_dirs = [ "$proto_out_dir" ] + public_configs = [ ":native_hook_include_config" ] + sources = native_hook_codegen +} diff --git a/host/trace_streamer/src/protos/types/plugins/native_hook/native_hook_config.proto b/host/trace_streamer/src/protos/types/plugins/native_hook/native_hook_config.proto new file mode 100644 index 0000000..53c39b1 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/native_hook/native_hook_config.proto @@ -0,0 +1,27 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + + +message NativeHookConfig { + int32 pid = 1; + bool save_file = 2; + string file_name = 3; + int32 filter_size = 4; + int32 smb_pages = 5; + int32 max_stack_depth = 6; +} \ No newline at end of file diff --git a/host/trace_streamer/src/protos/types/plugins/native_hook/native_hook_result.proto b/host/trace_streamer/src/protos/types/plugins/native_hook/native_hook_result.proto new file mode 100644 index 0000000..3a93eff --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/native_hook/native_hook_result.proto @@ -0,0 +1,54 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +message Frame { + uint64 ip = 1; + uint64 sp = 2; + string symbol_name = 3; + string file_path = 4; + uint64 offset = 5; + uint64 symbol_offset = 6; +} + +message AllocEvent { + int32 pid = 1; + int32 tid = 2; + uint64 addr = 3; + uint32 size = 4; + repeated Frame frame_info = 5; +} + +message FreeEvent { + int32 pid = 1; + int32 tid = 2; + uint64 addr = 3; + repeated Frame frame_info = 4; +} + +message NativeHookData { + uint64 tv_sec = 1; + uint64 tv_nsec = 2; + oneof event { + AllocEvent alloc_event = 3; + FreeEvent free_event = 4; + } +} + +message BatchNativeHookData { + repeated NativeHookData events = 1; +} \ No newline at end of file diff --git a/host/trace_streamer/src/protos/types/plugins/network_data/BUILD.gn b/host/trace_streamer/src/protos/types/plugins/network_data/BUILD.gn new file mode 100644 index 0000000..206dc98 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/network_data/BUILD.gn @@ -0,0 +1,65 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../protos.gni") + +network_data_sources = [ + "./network_plugin_config.proto", + "./network_plugin_result.proto", +] + +####################################################### +proto_out_dir = "$root_gen_dir/cpp/" + rebase_path(".", "//") +proto_rel_out_dir = rebase_path(proto_out_dir, root_build_dir) + +network_data_codegen = [] +foreach(proto, network_data_sources) { + name = get_path_info(proto, "name") + network_data_codegen += [ + "$proto_out_dir/$name.pb.h", + "$proto_out_dir/$name.pb.cc", + ] +} + +config("network_include_config") { + include_dirs = [ "$proto_out_dir" ] +} + +####################################################### +action("network_data_cpp_gen") { + script = "${OHOS_PROFILER_DIR}/build/protoc.sh" + sources = network_data_sources + outputs = network_data_codegen + args = [ + "$libc_dir_proto", + "$root_output_dir_proto", + "--cpp_out", + "$proto_rel_out_dir", + "--proto_path", + rebase_path(".", root_build_dir), + ] + args += rebase_path(sources, root_build_dir) + deps = [ "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protoc(${host_toolchain})" ] +} + +ohos_source_set("network_data_cpp") { + deps = [ ":network_data_cpp_gen" ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + ] + include_dirs = [ "$proto_out_dir" ] + public_configs = [ ":network_include_config" ] + sources = network_data_codegen +} diff --git a/host/trace_streamer/src/protos/types/plugins/network_data/network_plugin_config.proto b/host/trace_streamer/src/protos/types/plugins/network_data/network_plugin_config.proto new file mode 100644 index 0000000..b993e8c --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/network_data/network_plugin_config.proto @@ -0,0 +1,21 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +message NetworkConfig { + repeated int32 pid = 1; +} \ No newline at end of file diff --git a/host/trace_streamer/src/protos/types/plugins/network_data/network_plugin_result.proto b/host/trace_streamer/src/protos/types/plugins/network_data/network_plugin_result.proto new file mode 100644 index 0000000..8df215f --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/network_data/network_plugin_result.proto @@ -0,0 +1,37 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +message NetworkDetails { + uint64 tx_bytes = 1; + uint64 rx_bytes = 2; + string type = 3; // e.g. "wlan0", "rmnet0", etc. +} + +message NetworkData { + int32 pid = 1; + // timestamp obtained by CLOCK_REALTIME + uint64 tv_sec = 2; + uint64 tv_nsec = 3; + uint64 tx_bytes = 4; + uint64 rx_bytes = 5; + repeated NetworkDetails details = 6; +} + +message NetworkDatas { + repeated NetworkData networkinfo = 1; +} \ No newline at end of file diff --git a/host/trace_streamer/src/protos/types/plugins/process_data/BUILD.gn b/host/trace_streamer/src/protos/types/plugins/process_data/BUILD.gn new file mode 100644 index 0000000..893064d --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/process_data/BUILD.gn @@ -0,0 +1,65 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../protos.gni") + +process_data_sources = [ + "./process_plugin_config.proto", + "./process_plugin_result.proto", +] + +####################################################### +proto_out_dir = "$root_gen_dir/cpp/" + rebase_path(".", "//") +proto_rel_out_dir = rebase_path(proto_out_dir, root_build_dir) + +process_data_codegen = [] +foreach(proto, process_data_sources) { + name = get_path_info(proto, "name") + process_data_codegen += [ + "$proto_out_dir/$name.pb.h", + "$proto_out_dir/$name.pb.cc", + ] +} + +config("process_include_config") { + include_dirs = [ "$proto_out_dir" ] +} + +####################################################### +action("process_data_cpp_gen") { + script = "${OHOS_PROFILER_DIR}/build/protoc.sh" + sources = process_data_sources + outputs = process_data_codegen + args = [ + "$libc_dir_proto", + "$root_output_dir_proto", + "--cpp_out", + "$proto_rel_out_dir", + "--proto_path", + rebase_path(".", root_build_dir), + ] + args += rebase_path(sources, root_build_dir) + deps = [ "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protoc(${host_toolchain})" ] +} + +ohos_source_set("process_data_cpp") { + deps = [ ":process_data_cpp_gen" ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + ] + include_dirs = [ "$proto_out_dir" ] + public_configs = [ ":process_include_config" ] + sources = process_data_codegen +} diff --git a/host/trace_streamer/src/protos/types/plugins/process_data/process_plugin_config.proto b/host/trace_streamer/src/protos/types/plugins/process_data/process_plugin_config.proto new file mode 100644 index 0000000..ecb8389 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/process_data/process_plugin_config.proto @@ -0,0 +1,22 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +message ProcessConfig { + // set true to report process list + bool report_process_tree = 1; +} diff --git a/host/trace_streamer/src/protos/types/plugins/process_data/process_plugin_result.proto b/host/trace_streamer/src/protos/types/plugins/process_data/process_plugin_result.proto new file mode 100644 index 0000000..53f586b --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/process_data/process_plugin_result.proto @@ -0,0 +1,26 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +message ProcessInfo { + int32 pid = 1; + string name = 2; +} + +message ProcessData { + repeated ProcessInfo processesinfo = 1; +} diff --git a/host/trace_streamer/src/protos/types/plugins/sample_data/BUILD.gn b/host/trace_streamer/src/protos/types/plugins/sample_data/BUILD.gn new file mode 100644 index 0000000..954a9b3 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/sample_data/BUILD.gn @@ -0,0 +1,65 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../protos.gni") + +sample_data_sources = [ + "./sample_plugin_config.proto", + "./sample_plugin_result.proto", +] + +####################################################### +proto_out_dir = "$root_gen_dir/cpp/" + rebase_path(".", "//") +proto_rel_out_dir = rebase_path(proto_out_dir, root_build_dir) + +sample_data_codegen = [] +foreach(proto, sample_data_sources) { + name = get_path_info(proto, "name") + sample_data_codegen += [ + "$proto_out_dir/$name.pb.h", + "$proto_out_dir/$name.pb.cc", + ] +} + +config("sample_include_config") { + include_dirs = [ "$proto_out_dir" ] +} + +####################################################### +action("sample_data_cpp_gen") { + script = "${OHOS_PROFILER_DIR}/build/protoc.sh" + sources = sample_data_sources + outputs = sample_data_codegen + args = [ + "$libc_dir_proto", + "$root_output_dir_proto", + "--cpp_out", + "$proto_rel_out_dir", + "--proto_path", + rebase_path(".", root_build_dir), + ] + args += rebase_path(sources, root_build_dir) + deps = [ "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protoc(${host_toolchain})" ] +} + +ohos_source_set("sample_data_cpp") { + deps = [ ":sample_data_cpp_gen" ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + ] + include_dirs = [ "$proto_out_dir" ] + public_configs = [ ":sample_include_config" ] + sources = sample_data_codegen +} diff --git a/host/trace_streamer/src/protos/types/plugins/sample_data/sample_plugin_config.proto b/host/trace_streamer/src/protos/types/plugins/sample_data/sample_plugin_config.proto new file mode 100644 index 0000000..262398e --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/sample_data/sample_plugin_config.proto @@ -0,0 +1,19 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +syntax = "proto3"; +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +message SampleConfig { + int32 pid = 1; +} \ No newline at end of file diff --git a/host/trace_streamer/src/protos/types/plugins/sample_data/sample_plugin_result.proto b/host/trace_streamer/src/protos/types/plugins/sample_data/sample_plugin_result.proto new file mode 100644 index 0000000..0a8d743 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/sample_data/sample_plugin_result.proto @@ -0,0 +1,19 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +syntax = "proto3"; +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +message SampleData { + uint64 time_ms = 1; +} \ No newline at end of file diff --git a/host/trace_streamer/src/protos/types/plugins/stream_data/BUILD.gn b/host/trace_streamer/src/protos/types/plugins/stream_data/BUILD.gn new file mode 100644 index 0000000..8aa3506 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/stream_data/BUILD.gn @@ -0,0 +1,65 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("../../../protos.gni") + +stream_data_sources = [ + "./stream_plugin_config.proto", + "./stream_plugin_result.proto", +] + +####################################################### +proto_out_dir = "$root_gen_dir/cpp/" + rebase_path(".", "//") +proto_rel_out_dir = rebase_path(proto_out_dir, root_build_dir) + +stream_data_codegen = [] +foreach(proto, stream_data_sources) { + name = get_path_info(proto, "name") + stream_data_codegen += [ + "$proto_out_dir/$name.pb.h", + "$proto_out_dir/$name.pb.cc", + ] +} + +config("stream_include_config") { + include_dirs = [ "$proto_out_dir" ] +} + +####################################################### +action("stream_data_cpp_gen") { + script = "${OHOS_PROFILER_DIR}/build/protoc.sh" + sources = stream_data_sources + outputs = stream_data_codegen + args = [ + "$libc_dir_proto", + "$root_output_dir_proto", + "--cpp_out", + "$proto_rel_out_dir", + "--proto_path", + rebase_path(".", root_build_dir), + ] + args += rebase_path(sources, root_build_dir) + deps = [ "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protoc(${host_toolchain})" ] +} + +ohos_source_set("stream_data_cpp") { + deps = [ ":stream_data_cpp_gen" ] + public_deps = [ + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf", + "${OHOS_PROFILER_3RDPARTY_PROTOBUF_DIR}:protobuf_lite", + ] + include_dirs = [ "$proto_out_dir" ] + public_configs = [ ":stream_include_config" ] + sources = stream_data_codegen +} diff --git a/host/trace_streamer/src/protos/types/plugins/stream_data/stream_plugin_config.proto b/host/trace_streamer/src/protos/types/plugins/stream_data/stream_plugin_config.proto new file mode 100644 index 0000000..9d124a7 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/stream_data/stream_plugin_config.proto @@ -0,0 +1,19 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +syntax = "proto3"; +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +message StreamConfig { + int32 pid = 1; +} \ No newline at end of file diff --git a/host/trace_streamer/src/protos/types/plugins/stream_data/stream_plugin_result.proto b/host/trace_streamer/src/protos/types/plugins/stream_data/stream_plugin_result.proto new file mode 100644 index 0000000..ece7d24 --- /dev/null +++ b/host/trace_streamer/src/protos/types/plugins/stream_data/stream_plugin_result.proto @@ -0,0 +1,19 @@ +// Copyright (c) 2021 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +syntax = "proto3"; +option java_package = "ohos.devtools.datasources.transport.grpc.service"; +option optimize_for = LITE_RUNTIME; + +message StreamData { + uint64 time_ms = 1; +} \ No newline at end of file diff --git a/host/trace_streamer/src/rpc/http_server.cpp b/host/trace_streamer/src/rpc/http_server.cpp new file mode 100644 index 0000000..ada95d9 --- /dev/null +++ b/host/trace_streamer/src/rpc/http_server.cpp @@ -0,0 +1,450 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "http_server.h" +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#endif +#include "log.h" +namespace SysTuning { +namespace TraceStreamer { +void HttpServer::RegisterRpcFunction(RpcServer* rpc) +{ + rpcFunctions_.clear(); + + using std::placeholders::_1; + using std::placeholders::_2; + using std::placeholders::_3; + + auto parsedata = std::bind(&RpcServer::ParseData, rpc, _1, _2, _3); + rpcFunctions_["/parsedata"] = parsedata; + + auto parsedataover = std::bind(&RpcServer::ParseDataOver, rpc, _1, _2, _3); + rpcFunctions_["/parsedataover"] = parsedataover; + + auto sqlquery = std::bind(&RpcServer::SqlQuery, rpc, _1, _2, _3); + rpcFunctions_["/sqlquery"] = sqlquery; + + auto sqloperate = std::bind(&RpcServer::SqlOperate, rpc, _1, _2, _3); + rpcFunctions_["/sqloperate"] = sqloperate; + + auto reset = std::bind(&RpcServer::Reset, rpc, _1, _2, _3); + rpcFunctions_["/reset"] = reset; +} + +#ifdef _WIN32 +void HttpServer::Run(int port) +{ + WSADATA ws{}; + if (WSAStartup(MAKEWORD(WS_VERSION_FIRST, WS_VERSION_SEC), &ws) != 0) { + return; + } + if (!CreateSocket(port)) { + return; + } + WSAEVENT events[COUNT_SOCKET]; + for (int i = 0; i < COUNT_SOCKET; i++) { + if ((events[i] = WSACreateEvent()) == WSA_INVALID_EVENT) { + TS_LOGE("WSACreateEvent error %d", WSAGetLastError()); + return; + } + WSAEventSelect(sockets_[i].GetFd(), events[i], FD_ACCEPT | FD_CLOSE); + } + + while (!isExit_) { + ClearDeadClientThread(); + + int index = WSAWaitForMultipleEvents(COUNT_SOCKET, events, false, pollTimeOut_, false); + if (index == WSA_WAIT_FAILED) { + TS_LOGE("WSAWaitForMultipleEvents error %d", WSAGetLastError()); + break; + } else if (index == WSA_WAIT_TIMEOUT) { + continue; + } + + index = index - WSA_WAIT_EVENT_0; + WSANETWORKEVENTS event; + WSAEnumNetworkEvents(sockets_[index].GetFd(), events[index], &event); + if (event.lNetworkEvents & FD_ACCEPT) { + if (event.iErrorCode[FD_ACCEPT_BIT] != 0) { + continue; + } + + std::unique_ptr client = std::make_unique(); + if (sockets_[index].Accept(client->sock_)) { + client->thread_ = std::thread(&HttpServer::ProcessClient, this, std::ref(client->sock_)); + clientThreads_.push_back(std::move(client)); + } else { + TS_LOGE("http socket accept error"); + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + } + } + + for (auto& it : clientThreads_) { + if (it->thread_.joinable()) { + it->sock_.Close(); + it->thread_.join(); + } + } + clientThreads_.clear(); + + WSACleanup(); +} +#else +void HttpServer::Run(int port) +{ + signal(SIGPIPE, SIG_IGN); + + if (!CreateSocket(port)) { + return; + } + TS_LOGI("http server running"); + + struct pollfd fds[COUNT_SOCKET] = {{sockets_[0].GetFd(), POLLIN, 0}, {sockets_[1].GetFd(), POLLIN, 0}}; + while (!isExit_) { + ClearDeadClientThread(); + + if (poll(fds, sizeof(fds)/sizeof(pollfd), pollTimeOut_) <= 0) { + continue; // try again + } + + for (int i = 0; i < COUNT_SOCKET; i++) { + if (fds[i].revents != POLLIN) { + continue; + } + std::unique_ptr client = std::make_unique(); + if (sockets_[i].Accept(client->sock_)) { + client->thread_ = std::thread(&HttpServer::ProcessClient, this, std::ref(client->sock_)); + clientThreads_.push_back(std::move(client)); + } else { + TS_LOGE("http socket accept error"); + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + } + } + + for (auto& it : clientThreads_) { + if (it->thread_.joinable()) { + it->sock_.Close(); + it->thread_.join(); + } + } + clientThreads_.clear(); + TS_LOGI("http server exit"); +} +#endif + +void HttpServer::Exit() +{ + isExit_ = true; + for (int i = 0; i < COUNT_SOCKET; i++) { + sockets_[i].Close(); + } +} + +bool HttpServer::CreateSocket(int port) +{ + if (!sockets_[0].CreateSocket(AF_INET) || !sockets_[1].CreateSocket(AF_INET6)) { + TS_LOGE("Create http socket error"); + return false; + } + for (int i = 0; i < COUNT_SOCKET; i++) { + if (!sockets_[i].Bind(port)) { + TS_LOGE("bind http socket error"); + return false; + } + if (!sockets_[i].Listen(SOMAXCONN)) { + TS_LOGE("listen http socket error"); + return false; + } + } + + return true; +} + +void HttpServer::ClearDeadClientThread() +{ + for (auto it = clientThreads_.begin(); it != clientThreads_.end();) { + if (it->get()->sock_.GetFd() != -1) { + it++; + continue; + } + if (it->get()->thread_.joinable()) { + it->get()->thread_.join(); + } + it = clientThreads_.erase(it); + } +} + +#ifdef _WIN32 +void HttpServer::ProcessClient(HttpSocket& client) +{ + std::vector recvBuf(MAXLEN_REQUEST); + size_t recvLen = recvBuf.size(); + size_t recvPos = 0; + RequestST reqST; + WSAEVENT recvEvent = WSACreateEvent(); + if (recvEvent == WSA_INVALID_EVENT) { + TS_LOGE("WSACreateEvent error %d", WSAGetLastError()); + return; + } + WSAEventSelect(client.GetFd(), recvEvent, FD_READ | FD_CLOSE); + while (!isExit_) { + int index = WSAWaitForMultipleEvents(1, &recvEvent, false, pollTimeOut_, false); + if (index == WSA_WAIT_FAILED) { + TS_LOGE("WSAWaitForMultipleEvents error %d", WSAGetLastError()); + break; + } else if (index == WSA_WAIT_TIMEOUT) { + if (reqST.stat != RequstParseStat::INIT) { + ProcessRequest(client, reqST); + reqST.stat = RequstParseStat::INIT; + recvPos = 0; + recvLen = recvBuf.size(); + } + continue; + } + + WSANETWORKEVENTS event; + WSAEnumNetworkEvents(client.GetFd(), recvEvent, &event); + if (event.lNetworkEvents & FD_READ) { + if (event.iErrorCode[FD_READ_BIT] != 0) { + continue; + } + if (!client.Recv(recvBuf.data() + recvPos, recvLen)) { + break; + } + recvPos += recvLen; + ParseRequest(recvBuf.data(), recvPos, reqST); + recvLen = recvBuf.size() - recvPos; + if (reqST.stat == RequstParseStat::RECVING) { + continue; + } + ProcessRequest(client, reqST); + reqST.stat = RequstParseStat::INIT; + } else if (event.lNetworkEvents & FD_CLOSE) { + TS_LOGI("client close socket(%d)", client.GetFd()); + break; + } + } + TS_LOGI("recive client thread exit. socket(%d)", client.GetFd()); + + client.Close(); +} +#else +void HttpServer::ProcessClient(HttpSocket& client) +{ + std::vector recvBuf(MAXLEN_REQUEST); + size_t recvLen = recvBuf.size(); + size_t recvPos = 0; + RequestST reqST; + + struct pollfd fd = {client.GetFd(), POLLIN, 0}; + while (!isExit_) { + int pollRet = poll(&fd, sizeof(fd)/sizeof(pollfd), pollTimeOut_); + if (pollRet < 0) { + TS_LOGE("poll client socket(%d) error: %d:%s", client.GetFd(), errno, strerror(errno)); + break; + } + if (pollRet == 0) { + if (reqST.stat != RequstParseStat::INIT) { + ProcessRequest(client, reqST); + reqST.stat = RequstParseStat::INIT; + recvPos = 0; + recvLen = recvBuf.size(); + } + continue; + } + if (!client.Recv(recvBuf.data() + recvPos, recvLen)) { + break; + } + recvPos += recvLen; + ParseRequest(recvBuf.data(), recvPos, reqST); + recvLen = recvBuf.size() - recvPos; + if (reqST.stat == RequstParseStat::RECVING) { + continue; + } + ProcessRequest(client, reqST); + reqST.stat = RequstParseStat::INIT; + } + TS_LOGI("recive client thread exit. socket(%d)", client.GetFd()); + + client.Close(); +} +#endif + +void HttpServer::ProcessRequest(HttpSocket& client, RequestST& request) +{ + if (request.stat == RequstParseStat::RECVING) { + TS_LOGE("http request data missing, client %d\n", client.GetFd()); + HttpResponse(client, "408 Request Time-out"); + return; + } else if (request.stat != RequstParseStat::OK) { + TS_LOGE("bad http request, client %d\n", client.GetFd()); + HttpResponse(client, "400 Bad Request"); + return; + } + if (request.method == "OPTIONS") { + HttpResponse(client, "204 No Content\r\n" + "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n" + "Access-Control-Allow-Headers: *\r\n" + "Access-Control-Max-Age: 86400\r\n"); + return; + } else if (request.method != "POST" && request.method != "GET") { + TS_LOGE("method(%s) not allowed, client %d", request.method.c_str(), client.GetFd()); + HttpResponse(client, "405 Method Not Allowed\r\n"); + return; + } + auto it = rpcFunctions_.find(request.uri); + if (it == rpcFunctions_.end()) { + TS_LOGE("http uri(%s) not found, client %d", request.uri.c_str(), client.GetFd()); + HttpResponse(client, "404 Not Found\r\n"); + return; + } + HttpResponse(client, "200 OK\r\n", true); + auto resultCallback = [&](const std::string result) { + std::stringstream chunkLenbuff; + chunkLenbuff << std::hex << result.size() << "\r\n"; + if (!client.Send(chunkLenbuff.str().data(), chunkLenbuff.str().size())) { + TS_LOGE("send client socket(%d) error", client.GetFd()); + return; + } + if (!client.Send(result.data(), result.size())) { + TS_LOGE("send client socket(%d) error", client.GetFd()); + return; + } + if (!client.Send("\r\n", strlen("\r\n"))) { + TS_LOGE("send client socket(%d) error", client.GetFd()); + return; + } + }; + it->second(request.body, request.bodyLen, resultCallback); + if (!client.Send("0\r\n\r\n", strlen("0\r\n\r\n"))) { // chunk tail + TS_LOGE("send client socket(%d) error", client.GetFd()); + } +} + +void HttpServer::ParseRequest(const uint8_t* requst, size_t& len, RequestST& httpReq) +{ + std::string_view reqStr(reinterpret_cast(requst), len); + size_t bodyPos = reqStr.find("\r\n\r\n"); + if (bodyPos == 0) { + len = 0; + httpReq.stat = RequstParseStat::BAD; + return; + } else if (bodyPos == std::string_view::npos) { + httpReq.stat = RequstParseStat::RECVING; + return; + } + std::string_view header = reqStr.substr(0, bodyPos); + bodyPos += strlen("\r\n\r\n"); + httpReq.bodyLen = reqStr.size() - bodyPos; + + std::vector headerlines = StringSplit(header, "\r\n"); + // at least 1 line in headerlines, such as "GET /parsedata HTTP/1.1" + std::vector requestItems = StringSplit(headerlines[0], " "); + const size_t indexHttpMethod = 0; + const size_t indexHttpUri = 1; + const size_t indexHttpVersion = 2; + const size_t countRequestItems = 3; + if (requestItems.size() != countRequestItems || + requestItems[indexHttpVersion] != "HTTP/1.1") { + len = 0; + httpReq.stat = RequstParseStat::BAD; + return; + } + httpReq.method = requestItems[indexHttpMethod]; + httpReq.uri = requestItems[indexHttpUri]; + + for (size_t i = 1; i < headerlines.size(); i++) { + size_t tagPos = headerlines[i].find(": "); + if (tagPos == std::string_view::npos) { + len = 0; + httpReq.stat = RequstParseStat::BAD; + return; + } + std::string_view tag = headerlines[i].substr(0, tagPos); + if (strncasecmp(tag.data(), "content-length", tag.size()) == 0) { + std::string value(headerlines[i].data() + tagPos + strlen(": "), + headerlines[i].size() - tagPos - strlen(": ")); + size_t conterntLen = atoi(value.c_str()); + if (conterntLen > httpReq.bodyLen) { + httpReq.stat = RequstParseStat::RECVING; + return; + } else if (conterntLen < httpReq.bodyLen) { + httpReq.bodyLen = conterntLen; + } + } + } + + if (httpReq.bodyLen > 0) { + httpReq.body = (requst + bodyPos); + } + httpReq.stat = RequstParseStat::OK; + len -= (bodyPos + httpReq.bodyLen); + return; +} + +void HttpServer::HttpResponse(HttpSocket& client, const std::string& status, bool hasBody) +{ + std::string res; + const size_t maxLenResponse = 1024; + res.reserve(maxLenResponse); + res += "HTTP/1.1 "; + res += status; + res += "\r\n"; + + res += "Connection: Keep-Alive\r\n"; + + if (hasBody) { + res += "Content-Type: application/json\r\n"; + res += "Transfer-Encoding: chunked\r\n"; + } + res += "\r\n"; + + if (!client.Send(res.data(), res.size())) { + TS_LOGE("send client socket(%d) error", client.GetFd()); + } +} + +std::vector HttpServer::StringSplit(std::string_view source, std::string_view split) +{ + std::vector result; + if (!split.empty()) { + size_t pos = 0; + while ((pos = source.find(split)) != std::string_view::npos) { + // split + std::string_view token = source.substr(0, pos); + if (!token.empty()) { + result.push_back(token); + } + source = source.substr(pos + split.size(), source.size() - token.size() - split.size()); + } + } + // add last token + if (!source.empty()) { + result.push_back(source); + } + return result; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/rpc/http_server.h b/host/trace_streamer/src/rpc/http_server.h new file mode 100644 index 0000000..e8d4305 --- /dev/null +++ b/host/trace_streamer/src/rpc/http_server.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RPC_HTTPD_H +#define RPC_HTTPD_H + +#include +#include +#include +#include +#include +#include "http_socket.h" +#include "rpc_server.h" +namespace SysTuning { +namespace TraceStreamer { +class HttpServer { +public: + void RegisterRpcFunction(RpcServer* rpc); + void Run(int port = 9001); + void Exit(); + + static constexpr size_t MAXLEN_REQUEST = 2 * 1024 + 1024 * 1024; // header 2K + body 1M + +private: + struct ClientThread { + HttpSocket sock_; + std::thread thread_; + }; + + enum RequstParseStat { + INIT = 0, + OK, + BAD, + RECVING + }; + + struct RequestST { + int stat = RequstParseStat::INIT; + std::string method; + std::string uri; + const uint8_t* body; + size_t bodyLen; + }; + + bool CreateSocket(int port); + void ProcessClient(HttpSocket& client); + void ProcessRequest(HttpSocket& client, RequestST& request); + void HttpResponse(HttpSocket& client, const std::string& status, bool hasBody = false); + void ParseRequest(const uint8_t* requst, size_t& len, RequestST& httpReq); + void ClearDeadClientThread(); + std::vector StringSplit(std::string_view source, std::string_view split); + + static const int COUNT_SOCKET = 2; + HttpSocket sockets_[COUNT_SOCKET]; // ipv4 and ipv6 + std::atomic_bool isExit_ = {false}; + std::vector> clientThreads_; + using RpcFunction = std::function; + std::map rpcFunctions_; + const int pollTimeOut_ = 1000; +#ifdef _WIN32 + const uint32_t WS_VERSION_FIRST = 2; + const uint32_t WS_VERSION_SEC = 2; +#endif +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // RPC_HTTPD_H diff --git a/host/trace_streamer/src/rpc/http_socket.cpp b/host/trace_streamer/src/rpc/http_socket.cpp new file mode 100644 index 0000000..dee3474 --- /dev/null +++ b/host/trace_streamer/src/rpc/http_socket.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "http_socket.h" +#include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#endif +#include "log.h" +namespace SysTuning { +namespace TraceStreamer { +HttpSocket::~HttpSocket() +{ + Close(); +} +bool HttpSocket::CreateSocket(int domain) +{ + SOCKET sockId = socket(domain, SOCK_STREAM, 0); + if (sockId == INVALID_SOCKET) { + TS_LOGE("CreateSocket socket error, domain %d: %d:%s", domain, errno, strerror(errno)); + return false; + } + sockId_ = sockId; + if (domain == AF_INET || domain == AF_INET6) { + int enable = 1; + if (setsockopt(sockId, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&enable), + sizeof(enable)) == SOCKET_ERROR) { + Close(); + return false; + } + if (domain == AF_INET6) { + if (setsockopt(sockId, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&enable), + sizeof(enable)) == SOCKET_ERROR) { + Close(); + return false; + } + } + } + domain_ = domain; + TS_LOGI("CreateSocket socket ok, socket %d domain %d", sockId_, domain); + return true; +} + +bool HttpSocket::Bind(int port) +{ + if (sockId_ == INVALID_SOCKET) { + TS_LOGE("the socket not created"); + return false; + } + + if (domain_ == AF_INET) { + struct sockaddr_in addr; + std::fill(reinterpret_cast(&addr), reinterpret_cast(&addr) + sizeof(addr), 0); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htons(INADDR_ANY); + addr.sin_port = htons(static_cast(port)); + if (bind(sockId_, reinterpret_cast(&addr), sizeof(addr)) == -1) { + TS_LOGE("bind ipv4 socket error, port %d: %d:%s", port, errno, strerror(errno)); + return false; + } + } else if (domain_ == AF_INET6) { + struct sockaddr_in6 addr; + std::fill(reinterpret_cast(&addr), reinterpret_cast(&addr) + sizeof(addr), 0); + addr.sin6_family = AF_INET6; + addr.sin6_addr = in6addr_any; + addr.sin6_port = htons(static_cast(port)); + if (bind(sockId_, reinterpret_cast(&addr), sizeof(addr)) == -1) { + TS_LOGE("bind ipv6 socket error, port %d: %d:%s", port, errno, strerror(errno)); + return false; + } + } else { + return false; + } + TS_LOGI("bind socket ok, port %d", port); + return true; +} + +bool HttpSocket::Listen(int maxConn) +{ + if (listen(sockId_, maxConn) == SOCKET_ERROR) { + TS_LOGE("listen socket error: %d:%s", errno, strerror(errno)); + return false; + } + TS_LOGI("listen socket ok, maxConn %d", maxConn); + return true; +} + +bool HttpSocket::Accept(HttpSocket& client) +{ + int clientId = accept(sockId_, nullptr, nullptr); + if (clientId == INVALID_SOCKET) { + TS_LOGE("accept socket error: %d:%s", errno, strerror(errno)); + return false; + } + + client.domain_ = domain_; + client.sockId_ = clientId; + TS_LOGI("accept client socket id %d domain %d", clientId, domain_); + return true; +} + +bool HttpSocket::Recv(void* data, size_t& len) +{ +#ifdef _WIN32 + ssize_t recvLen = recv(sockId_, static_cast(data), len, 0); +#else + ssize_t recvLen = recv(sockId_, data, len, 0); +#endif + if (recvLen == SOCKET_ERROR) { + if (errno == EAGAIN) { + recvLen = 0; + } else { + TS_LOGE("recv from socket(%d) error: %d:%s", sockId_, errno, strerror(errno)); + return false; + } + } else if (recvLen == 0) { + TS_LOGI("client socket(%d) closed", sockId_); + return false; + } + len = recvLen; + TS_LOGD("Recv from socket(%d) len %zu", sockId_, len); + return true; +} + +bool HttpSocket::Send(const void* data, size_t len) +{ +#ifdef _WIN32 + ssize_t sendLen = send(sockId_, static_cast(data), len, 0); +#else + ssize_t sendLen = send(sockId_, data, len, 0); +#endif + if (sendLen == SOCKET_ERROR) { + TS_LOGE("send to socket(%d) error: %d:%s", sockId_, errno, strerror(errno)); + return false; + } + TS_LOGD("send to socket(%d) len %zu", sockId_, len); + return true; +} + +void HttpSocket::Close() +{ + if (sockId_ == INVALID_SOCKET) { + return; + } + TS_LOGI("close socket(%d)", sockId_); +#ifdef _WIN32 + if (closesocket(sockId_) == SOCKET_ERROR) { +#else + if (close(sockId_) == SOCKET_ERROR) { +#endif + TS_LOGE("close socket(%d) error: %d:%s", sockId_, errno, strerror(errno)); + } + sockId_ = INVALID_SOCKET; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/rpc/http_socket.h b/host/trace_streamer/src/rpc/http_socket.h new file mode 100644 index 0000000..f03ccb5 --- /dev/null +++ b/host/trace_streamer/src/rpc/http_socket.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RPC_HTTPSOCKET_H +#define RPC_HTTPSOCKET_H + +#include +namespace SysTuning { +namespace TraceStreamer { +class HttpSocket { +public: + HttpSocket() {} + HttpSocket(int sockId, int domain) : sockId_(sockId), domain_(domain) {} + ~HttpSocket(); + + bool CreateSocket(int domain); + bool Bind(int port); + bool Listen(int maxConn); + bool Accept(HttpSocket& client); + bool Recv(void* data, size_t& len); + bool Send(const void* data, size_t len); + void Close(); + int GetFd() + { + return sockId_; + } + +private: + int sockId_ = -1; + int domain_ = 0; +#ifndef _WIN32 + using SOCKET = int; + const int SOCKET_ERROR = -1; + const SOCKET INVALID_SOCKET = -1; +#endif +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // RPC_HTTPSOCKET_H diff --git a/host/trace_streamer/src/rpc/rpc.pri b/host/trace_streamer/src/rpc/rpc.pri new file mode 100644 index 0000000..0bee670 --- /dev/null +++ b/host/trace_streamer/src/rpc/rpc.pri @@ -0,0 +1,17 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +INCLUDEPATH +=$$PWD \ + $$PWD/../cfg +SOURCES += $$PWD/rpc_server.cpp \ + $$PWD/http_socket.cpp \ + $$PWD/http_server.cpp diff --git a/host/trace_streamer/src/rpc/rpc_server.cpp b/host/trace_streamer/src/rpc/rpc_server.cpp new file mode 100644 index 0000000..64a9d96 --- /dev/null +++ b/host/trace_streamer/src/rpc/rpc_server.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "rpc_server.h" + +#include +#include +#include + +#include "log.h" + +namespace SysTuning { +namespace TraceStreamer { +bool RpcServer::ParseData(const uint8_t* data, size_t len, ResultCallBack resultCallBack) +{ + TS_LOGI("RPC ParseData, has parsed len %zu + %zu", lenParseData_, len); + do { + constexpr size_t blockSize = 1024 * 1024; + size_t parseSize = std::min(len, blockSize); + std::unique_ptr buf = std::make_unique(parseSize); + std::copy(data, data + parseSize, buf.get()); + + if (!ts_->ParseTraceDataSegment(std::move(buf), parseSize)) { + if (resultCallBack) { + resultCallBack("formaterror\r\n"); + } + return false; + } + data += parseSize; + len -= parseSize; + lenParseData_ += parseSize; + } while (len > 0); + if (resultCallBack) { + resultCallBack("ok\r\n"); + } + return true; +} + +bool RpcServer::ParseDataOver(const uint8_t* data, size_t len, ResultCallBack resultCallBack) +{ + TS_LOGI("RPC ParseDataOver, has parsed len %zu", lenParseData_); + + ts_->WaitForParserEnd(); + ts_->Clear(); + if (resultCallBack) { + resultCallBack("ok\r\n"); + } + lenParseData_ = 0; + return true; +} + +bool RpcServer::SqlOperate(const uint8_t* data, size_t len, ResultCallBack resultCallBack) +{ + std::string sql(reinterpret_cast(data), len); + TS_LOGI("RPC SqlOperate(%s, %zu)", sql.c_str(), len); + + int ret = ts_->OperateDatabase(sql); + if (resultCallBack) { + std::string response = "ok\r\n"; + if (ret != 0) { + response = "dberror\r\n"; + } + resultCallBack(response); + } + return (ret == 0); +} + +bool RpcServer::SqlQuery(const uint8_t* data, size_t len, ResultCallBack resultCallBack) +{ + std::string sql(reinterpret_cast(data), len); + TS_LOGI("RPC SqlQuery %zu:%s", len, sql.c_str()); + + int ret = ts_->SearchDatabase(sql, resultCallBack); + if (resultCallBack && ret != 0) { + resultCallBack("dberror\r\n"); + } + return (ret == 0); +} + +bool RpcServer::Reset(const uint8_t* data, size_t len, ResultCallBack resultCallBack) +{ + TS_LOGI("RPC reset trace_streamer"); + + ts_->WaitForParserEnd(); + ts_ = std::make_unique(); + if (resultCallBack) { + resultCallBack("ok\r\n"); + } + return true; +} + +int RpcServer::WasmSqlQuery(const uint8_t* data, size_t len, uint8_t* out, int outLen) +{ + std::string sql(reinterpret_cast(data), len); + TS_LOGI("WASM RPC SqlQuery out(%p:%d) sql(%zu:%s)", reinterpret_cast(out), outLen, + len, sql.c_str()); + + int ret = ts_->SearchDatabase(sql, out, outLen); + return ret; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/rpc/rpc_server.h b/host/trace_streamer/src/rpc/rpc_server.h new file mode 100644 index 0000000..0c1137d --- /dev/null +++ b/host/trace_streamer/src/rpc/rpc_server.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RPC_RPC_H +#define RPC_RPC_H + +#include +#include +#include "trace_streamer_selector.h" +namespace SysTuning { +namespace TraceStreamer { +class RpcServer { +public: + using ResultCallBack = std::function; + bool ParseData(const uint8_t* data, size_t len, ResultCallBack resultCallBack); + bool ParseDataOver(const uint8_t* data, size_t len, ResultCallBack resultCallBack); + bool SqlOperate(const uint8_t* data, size_t len, ResultCallBack resultCallBack); + bool SqlQuery(const uint8_t* data, size_t len, ResultCallBack resultCallBack); + bool Reset(const uint8_t* data, size_t len, ResultCallBack resultCallBack); + + // only for wasm, no callback + int WasmSqlQuery(const uint8_t* data, size_t len, uint8_t* out, int outLen); + +private: + std::unique_ptr ts_ = std::make_unique(); + size_t lenParseData_ = 0; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // RPC_RPC_H diff --git a/host/trace_streamer/src/rpc/wasm_func.cpp b/host/trace_streamer/src/rpc/wasm_func.cpp new file mode 100644 index 0000000..8a06456 --- /dev/null +++ b/host/trace_streamer/src/rpc/wasm_func.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "wasm_func.h" +#include +#include +#include "rpc_server.h" +namespace SysTuning { +namespace TraceStreamer { +RpcServer g_wasmTraceStreamer; +extern "C" { +// return 0 while ok, -1 while failed +EMSCRIPTEN_KEEPALIVE int TraceStreamerParseData(const uint8_t* data, int dataLen) +{ + if (g_wasmTraceStreamer.ParseData(data, dataLen, nullptr)) { + return 0; + } + return -1; +} +EMSCRIPTEN_KEEPALIVE int TraceStreamerParseDataOver() +{ + if (g_wasmTraceStreamer.ParseDataOver(nullptr, 0, nullptr)) { + return 0; + } + return -1; +} +EMSCRIPTEN_KEEPALIVE int TraceStreamerSqlOperate(const uint8_t* sql, int sqlLen) +{ + if (g_wasmTraceStreamer.SqlOperate(sql, sqlLen, nullptr)) { + return 0; + } + return -1; +} +EMSCRIPTEN_KEEPALIVE int TraceStreamerReset() +{ + g_wasmTraceStreamer.Reset(nullptr, 0, nullptr); + return 0; +} +// return the length of result, -1 while failed +EMSCRIPTEN_KEEPALIVE int TraceStreamerSqlQuery(const uint8_t* sql, int sqlLen, uint8_t* out, int outLen) +{ + return g_wasmTraceStreamer.WasmSqlQuery(sql, sqlLen, out, outLen); +} +} // extern "C" +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/rpc/wasm_func.h b/host/trace_streamer/src/rpc/wasm_func.h new file mode 100644 index 0000000..2182d47 --- /dev/null +++ b/host/trace_streamer/src/rpc/wasm_func.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RPC_WASM_FUNC_H +#define RPC_WASM_FUNC_H + +#endif // RPC_WASM_FUNC_H diff --git a/host/trace_streamer/src/table/args_table.cpp b/host/trace_streamer/src/table/args_table.cpp new file mode 100644 index 0000000..c564148 --- /dev/null +++ b/host/trace_streamer/src/table/args_table.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "args_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, KEY, DATATYPE, VALUE, ARGSETID }; +} +ArgsTable::ArgsTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("key", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("datatype", "UNSIGNED SHORT")); + tableColumn_.push_back(TableBase::ColumnInfo("value", "BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("argset", "UNSIGNED INT")); + tablePriKey_.push_back("id"); +} + +ArgsTable::~ArgsTable() {} + +void ArgsTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +ArgsTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstArgSetData().Size())), + argSet_(dataCache->GetConstArgSetData()) +{ +} + +ArgsTable::Cursor::~Cursor() {} + +int ArgsTable::Cursor::Column(int column) const +{ + switch (column) { + case ID: + sqlite3_result_int64(context_, static_cast(argSet_.IdsData()[CurrentRow()])); + break; + case KEY: + sqlite3_result_int64(context_, static_cast(argSet_.NamesData()[CurrentRow()])); + break; + case DATATYPE: + sqlite3_result_int64(context_, static_cast(argSet_.DataTypes()[CurrentRow()])); + break; + case VALUE: + sqlite3_result_int64(context_, static_cast(argSet_.ValuesData()[CurrentRow()])); + break; + case ARGSETID: + sqlite3_result_int64(context_, static_cast(argSet_.ArgsData()[CurrentRow()])); + break; + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/args_table.h b/host/trace_streamer/src/table/args_table.h new file mode 100644 index 0000000..fdcea56 --- /dev/null +++ b/host/trace_streamer/src/table/args_table.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ARGS_TABLE_H +#define ARGS_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class ArgsTable : public TableBase { +public: + enum Column { ID = 0, TYPE = 1, NAME = 2, ARG_ID = 3 }; + explicit ArgsTable(const TraceDataCache* storage); + ~ArgsTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + + private: + const ArgSet& argSet_; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // ARGS_TABLE_H diff --git a/host/trace_streamer/src/table/callstack_table.cpp b/host/trace_streamer/src/table/callstack_table.cpp new file mode 100644 index 0000000..0c54940 --- /dev/null +++ b/host/trace_streamer/src/table/callstack_table.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "callstack_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { + ID = 0, + TS, + DUR, + CALL_ID, + CAT, + NAME, + DEPTH, + COOKIE_ID, + PARENT_ID, + ARGSET, + CHAIN_ID, + SPAN_ID, + PARENT_SPAN_ID, + FLAG, + ARGS +}; +} +CallStackTable::CallStackTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("ts", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("dur", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("callid", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("cat", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("name", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("depth", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("cookie", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("parent_id", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("argsetid", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("chainId", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("spanId", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("parentSpanId", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("flag", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("args", "STRING")); + tablePriKey_.push_back("callid"); + tablePriKey_.push_back("ts"); + tablePriKey_.push_back("depth"); +} + +CallStackTable::~CallStackTable() {} + +void CallStackTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +CallStackTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstInternalSlicesData().Size())), + slicesObj_(dataCache->GetConstInternalSlicesData()) +{ +} + +CallStackTable::Cursor::~Cursor() {} + +int CallStackTable::Cursor::Column(int column) const +{ + switch (column) { + case ID: + sqlite3_result_int64(context_, CurrentRow()); + break; + case TS: + sqlite3_result_int64(context_, static_cast(slicesObj_.TimeStamData()[CurrentRow()])); + break; + case DUR: + sqlite3_result_int64(context_, static_cast(slicesObj_.DursData()[CurrentRow()])); + break; + case CALL_ID: + sqlite3_result_int64(context_, static_cast(slicesObj_.CallIds()[CurrentRow()])); + break; + case CAT: { + if (slicesObj_.CatsData()[CurrentRow()] != INVALID_UINT64) { + auto catsDataIndex = static_cast(slicesObj_.CatsData()[CurrentRow()]); + sqlite3_result_text(context_, dataCache_->GetDataFromDict(catsDataIndex).c_str(), STR_DEFAULT_LEN, + nullptr); + } + break; + } + case NAME: { + if (slicesObj_.NamesData()[CurrentRow()] != INVALID_UINT64) { + auto nameDataIndex = static_cast(slicesObj_.NamesData()[CurrentRow()]); + sqlite3_result_text(context_, dataCache_->GetDataFromDict(nameDataIndex).c_str(), STR_DEFAULT_LEN, + nullptr); + } + break; + } + case DEPTH: + sqlite3_result_int64(context_, static_cast(slicesObj_.Depths()[CurrentRow()])); + break; + case COOKIE_ID: + if (slicesObj_.Cookies()[CurrentRow()] != INVALID_UINT64) { + sqlite3_result_int64(context_, static_cast(slicesObj_.Cookies()[CurrentRow()])); + } + break; + case PARENT_ID: { + if (slicesObj_.ParentIdData()[CurrentRow()].has_value()) { + sqlite3_result_int64(context_, static_cast(slicesObj_.ParentIdData()[CurrentRow()].value())); + } + break; + } + case ARGSET: + if (slicesObj_.ArgSetIdsData()[CurrentRow()] != INVALID_UINT32) { + sqlite3_result_int64(context_, static_cast(slicesObj_.ArgSetIdsData()[CurrentRow()])); + } + break; + case CHAIN_ID: + sqlite3_result_text(context_, slicesObj_.ChainIds()[CurrentRow()].c_str(), STR_DEFAULT_LEN, nullptr); + break; + case SPAN_ID: + sqlite3_result_text(context_, slicesObj_.SpanIds()[CurrentRow()].c_str(), STR_DEFAULT_LEN, nullptr); + break; + case PARENT_SPAN_ID: + sqlite3_result_text(context_, slicesObj_.ParentSpanIds()[CurrentRow()].c_str(), STR_DEFAULT_LEN, nullptr); + break; + case FLAG: + sqlite3_result_text(context_, slicesObj_.Flags()[CurrentRow()].c_str(), STR_DEFAULT_LEN, nullptr); + break; + case ARGS: + sqlite3_result_text(context_, slicesObj_.ArgsData()[CurrentRow()].c_str(), STR_DEFAULT_LEN, nullptr); + break; + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/callstack_table.h b/host/trace_streamer/src/table/callstack_table.h new file mode 100644 index 0000000..e902e7a --- /dev/null +++ b/host/trace_streamer/src/table/callstack_table.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CALL_STACK_TABLE_H +#define CALL_STACK_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class CallStackTable : public TableBase { +public: + explicit CallStackTable(const TraceDataCache* dataCache); + ~CallStackTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + + private: + const CallStack& slicesObj_; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // CALL_STACK_TABLE_H diff --git a/host/trace_streamer/src/table/clk_event_filter_table.cpp b/host/trace_streamer/src/table/clk_event_filter_table.cpp new file mode 100644 index 0000000..4090694 --- /dev/null +++ b/host/trace_streamer/src/table/clk_event_filter_table.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "clk_event_filter_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, TYPE, NAME, CPU }; +} +ClkEventFilterTable::ClkEventFilterTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "INT")); + tableColumn_.push_back(TableBase::ColumnInfo("type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("name", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("cpu", "INT")); + tablePriKey_.push_back("id"); +} + +ClkEventFilterTable::~ClkEventFilterTable() {} + +void ClkEventFilterTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +ClkEventFilterTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstClkEventFilterData().Size())) +{ +} + +ClkEventFilterTable::Cursor::~Cursor() {} + +int ClkEventFilterTable::Cursor::Column(int col) const +{ + switch (col) { + case ID: + sqlite3_result_int64(context_, + static_cast(dataCache_->GetConstClkEventFilterData().IdsData()[CurrentRow()])); + break; + case TYPE: { + size_t typeId = static_cast(dataCache_->GetConstClkEventFilterData().RatesData()[CurrentRow()]); + sqlite3_result_text(context_, dataCache_->GetDataFromDict(typeId).c_str(), STR_DEFAULT_LEN, nullptr); + break; + } + case NAME: { + size_t strId = + static_cast(dataCache_->GetConstClkEventFilterData().NamesData()[CurrentRow()]); + sqlite3_result_text(context_, dataCache_->GetDataFromDict(strId).c_str(), STR_DEFAULT_LEN, nullptr); + break; + } + case CPU: + sqlite3_result_int64(context_, + static_cast(dataCache_->GetConstClkEventFilterData().CpusData()[CurrentRow()])); + break; + default: + TS_LOGF("Unregistered column : %d", col); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/clk_event_filter_table.h b/host/trace_streamer/src/table/clk_event_filter_table.h new file mode 100644 index 0000000..37a69ff --- /dev/null +++ b/host/trace_streamer/src/table/clk_event_filter_table.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_CLK_EVENT_FILTER_TABLE_H +#define SRC_CLK_EVENT_FILTER_TABLE_H + + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class ClkEventFilterTable : public TableBase { +public: + explicit ClkEventFilterTable(const TraceDataCache*); + ~ClkEventFilterTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int col) const override; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // SRC_CLK_EVENT_FILTER_TABLE_H diff --git a/host/trace_streamer/src/table/clock_event_filter_table.cpp b/host/trace_streamer/src/table/clock_event_filter_table.cpp new file mode 100644 index 0000000..a123ed8 --- /dev/null +++ b/host/trace_streamer/src/table/clock_event_filter_table.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "clock_event_filter_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, TYPE, NAME, CPU }; +} +ClockEventFilterTable::ClockEventFilterTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "INT")); + tableColumn_.push_back(TableBase::ColumnInfo("type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("name", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("cpu", "INT")); + tablePriKey_.push_back("id"); +} + +ClockEventFilterTable::~ClockEventFilterTable() {} + +void ClockEventFilterTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +ClockEventFilterTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstClockEventFilterData().Size())) +{ +} + +ClockEventFilterTable::Cursor::~Cursor() {} + +int ClockEventFilterTable::Cursor::Column(int col) const +{ + switch (col) { + case ID: + sqlite3_result_int64(context_, static_cast( + dataCache_->GetConstClockEventFilterData().IdsData()[CurrentRow()])); + break; + case TYPE: { + size_t typeId = static_cast(dataCache_->GetConstClockEventFilterData().TypesData()[CurrentRow()]); + sqlite3_result_text(context_, dataCache_->GetDataFromDict(typeId).c_str(), STR_DEFAULT_LEN, nullptr); + break; + } + case NAME: { + size_t strId = static_cast(dataCache_->GetConstClockEventFilterData().NamesData()[CurrentRow()]); + sqlite3_result_text(context_, dataCache_->GetDataFromDict(strId).c_str(), STR_DEFAULT_LEN, nullptr); + break; + } + case CPU: + sqlite3_result_int64(context_, static_cast( + dataCache_->GetConstClockEventFilterData().CpusData()[CurrentRow()])); + break; + default: + TS_LOGF("Unregistered column : %d", col); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/clock_event_filter_table.h b/host/trace_streamer/src/table/clock_event_filter_table.h new file mode 100644 index 0000000..d7e3cb0 --- /dev/null +++ b/host/trace_streamer/src/table/clock_event_filter_table.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_CLOCK_EVENT_FILTER_TABLE_H +#define SRC_CLOCK_EVENT_FILTER_TABLE_H + + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class ClockEventFilterTable : public TableBase { +public: + explicit ClockEventFilterTable(const TraceDataCache* dataCache); + ~ClockEventFilterTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int col) const override; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // SRC_CLOCK_EVENT_FILTER_TABLE_H diff --git a/host/trace_streamer/src/table/cpu_measure_filter_table.cpp b/host/trace_streamer/src/table/cpu_measure_filter_table.cpp new file mode 100644 index 0000000..ba89f94 --- /dev/null +++ b/host/trace_streamer/src/table/cpu_measure_filter_table.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cpu_measure_filter_table.h" +#include "log.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, TYPE, NAME, CPU }; +} +CpuMeasureFilterTable::CpuMeasureFilterTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("name", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("cpu", "UNSIGNED INT")); + tablePriKey_.push_back("id"); +} + +CpuMeasureFilterTable::~CpuMeasureFilterTable() {} + +void CpuMeasureFilterTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +CpuMeasureFilterTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstCpuMeasureData().Size())), + cpuMeasureObj_(dataCache->GetConstCpuMeasureData()) +{ +} + +CpuMeasureFilterTable::Cursor::~Cursor() {} + +int CpuMeasureFilterTable::Cursor::Column(int column) const +{ + switch (column) { + case ID: + sqlite3_result_int64(context_, static_cast(cpuMeasureObj_.IdsData()[CurrentRow()])); + break; + case TYPE: + sqlite3_result_text(context_, "cpu_measure_filter", STR_DEFAULT_LEN, nullptr); + break; + case NAME: { + const std::string& str = + dataCache_->GetDataFromDict(static_cast(cpuMeasureObj_.NameData()[CurrentRow()])); + sqlite3_result_text(context_, str.c_str(), STR_DEFAULT_LEN, nullptr); + break; + } + case CPU: + sqlite3_result_int64(context_, static_cast(cpuMeasureObj_.CpuData()[CurrentRow()])); + break; + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/cpu_measure_filter_table.h b/host/trace_streamer/src/table/cpu_measure_filter_table.h new file mode 100644 index 0000000..7463d15 --- /dev/null +++ b/host/trace_streamer/src/table/cpu_measure_filter_table.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPU_MEASURE_FILTER_TABLE_H +#define CPU_MEASURE_FILTER_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class CpuMeasureFilterTable : public TableBase { +public: + explicit CpuMeasureFilterTable(const TraceDataCache* dataCache); + ~CpuMeasureFilterTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + + private: + const CpuMeasureFilter& cpuMeasureObj_; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // CPU_MEASURE_FILTER_TABLE_H diff --git a/host/trace_streamer/src/table/data_dict_table.cpp b/host/trace_streamer/src/table/data_dict_table.cpp new file mode 100644 index 0000000..9624bf4 --- /dev/null +++ b/host/trace_streamer/src/table/data_dict_table.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "data_dict_table.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, STR }; +} +DataDictTable::DataDictTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("data", "STRING")); + tablePriKey_.push_back("id"); +} + +DataDictTable::~DataDictTable() {} + +void DataDictTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +DataDictTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->DataDictSize())) +{ +} + +DataDictTable::Cursor::~Cursor() {} + +int DataDictTable::Cursor::Column(int col) const +{ + DataIndex index = static_cast(CurrentRow()); + switch (col) { + case ID: + sqlite3_result_int64(context_, static_cast(CurrentRow())); + break; + case STR: + sqlite3_result_text(context_, dataCache_->GetDataFromDict(index).c_str(), STR_DEFAULT_LEN, nullptr); + break; + default: + TS_LOGF("Unknown column %d", col); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/data_dict_table.h b/host/trace_streamer/src/table/data_dict_table.h new file mode 100644 index 0000000..9f431e8 --- /dev/null +++ b/host/trace_streamer/src/table/data_dict_table.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DATA_DICT_TABLE_H +#define DATA_DICT_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class DataDictTable : public TableBase { +public: + explicit DataDictTable(const TraceDataCache* dataCache); + ~DataDictTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int col) const override; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // DATA_DICT_TABLE_H diff --git a/host/trace_streamer/src/table/data_type_table.cpp b/host/trace_streamer/src/table/data_type_table.cpp new file mode 100644 index 0000000..479135a --- /dev/null +++ b/host/trace_streamer/src/table/data_type_table.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "data_type_table.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, TYPEID, DESC }; +} +DataTypeTable::DataTypeTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("typeId", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("desc", "STRING")); + tablePriKey_.push_back("id"); +} + +DataTypeTable::~DataTypeTable() {} + +void DataTypeTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +DataTypeTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstDataTypeData().Size())), + dataTypeObj_(dataCache->GetConstDataTypeData()) +{ +} + +DataTypeTable::Cursor::~Cursor() {} + +int DataTypeTable::Cursor::Column(int col) const +{ + switch (col) { + case ID: + sqlite3_result_int64(context_, static_cast(CurrentRow())); + break; + case TYPEID: + sqlite3_result_int64(context_, static_cast(dataTypeObj_.DataTypes()[CurrentRow()])); + break; + case DESC: + sqlite3_result_text(context_, dataCache_->GetDataFromDict(dataTypeObj_.DataDesc()[CurrentRow()]).c_str(), + STR_DEFAULT_LEN, nullptr); + break; + default: + TS_LOGF("Unknown column %d", col); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/data_type_table.h b/host/trace_streamer/src/table/data_type_table.h new file mode 100644 index 0000000..af518f5 --- /dev/null +++ b/host/trace_streamer/src/table/data_type_table.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DATA_TYPE_TABLE_H +#define DATA_TYPE_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class DataTypeTable : public TableBase { +public: + explicit DataTypeTable(const TraceDataCache*); + ~DataTypeTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int col) const override; + private: + const DataType& dataTypeObj_; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // DATA_TYPE_TABLE_H diff --git a/host/trace_streamer/src/table/filter_table.cpp b/host/trace_streamer/src/table/filter_table.cpp new file mode 100644 index 0000000..471cca7 --- /dev/null +++ b/host/trace_streamer/src/table/filter_table.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "filter_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, TYPE, NAME, SOURCE_ARG_SET_ID }; +} +FilterTable::FilterTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("name", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("source_arg_set_id", "UNSIGNED BIG INT")); + tablePriKey_.push_back("id"); +} + +FilterTable::~FilterTable() {} + +void FilterTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +FilterTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstFilterData().Size())), + filterObj_(dataCache->GetConstFilterData()) +{ +} + +FilterTable::Cursor::~Cursor() {} + +int FilterTable::Cursor::Column(int column) const +{ + switch (column) { + case ID: + sqlite3_result_int64(context_, static_cast(filterObj_.IdsData()[CurrentRow()])); + break; + case TYPE: + sqlite3_result_text(context_, filterObj_.TypeData()[CurrentRow()].c_str(), STR_DEFAULT_LEN, nullptr); + break; + case NAME: + sqlite3_result_text(context_, filterObj_.NameData()[CurrentRow()].c_str(), STR_DEFAULT_LEN, nullptr); + break; + case SOURCE_ARG_SET_ID: + sqlite3_result_int64(context_, static_cast(filterObj_.SourceArgSetIdData()[CurrentRow()])); + break; + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/filter_table.h b/host/trace_streamer/src/table/filter_table.h new file mode 100644 index 0000000..5b5a359 --- /dev/null +++ b/host/trace_streamer/src/table/filter_table.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FILTER_TABLE_H +#define FILTER_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class FilterTable : public TableBase { +public: + enum Column { ID = 0, TYPE = 1, NAME = 2, ARG_ID = 3 }; + explicit FilterTable(const TraceDataCache* storage); + ~FilterTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + + private: + const Filter& filterObj_; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // FILTER_TABLE_H diff --git a/host/trace_streamer/src/table/heap_frame_table.cpp b/host/trace_streamer/src/table/heap_frame_table.cpp new file mode 100644 index 0000000..90afefa --- /dev/null +++ b/host/trace_streamer/src/table/heap_frame_table.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "heap_frame_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { EVENT_ID = 0, DEPTH, IP, SP, SYMBOL_NAME, FILE_PATH, OFFSET, SYMBOL_OFFSET }; +} +HeapFrameTable::HeapFrameTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("eventId", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("depth", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("ip", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("sp", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("symbol_name", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("file_path", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("offset", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("symbol_offset", "UNSIGNED BIG INT")); + tablePriKey_.push_back("eventId"); +} + +HeapFrameTable::~HeapFrameTable() {} + +void HeapFrameTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +HeapFrameTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstHeapFrameData().Size())), + heapFrameInfoObj_(dataCache->GetConstHeapFrameData()) +{ +} + +HeapFrameTable::Cursor::~Cursor() {} + +int HeapFrameTable::Cursor::Column(int column) const +{ + switch (column) { + case EVENT_ID: + sqlite3_result_int64(context_, static_cast(heapFrameInfoObj_.EventIds()[CurrentRow()])); + break; + case DEPTH: + sqlite3_result_int64(context_, static_cast(heapFrameInfoObj_.Depths()[CurrentRow()])); + break; + case IP: + sqlite3_result_int64(context_, static_cast(heapFrameInfoObj_.Ips()[CurrentRow()])); + break; + case SP: { + sqlite3_result_int64(context_, static_cast(heapFrameInfoObj_.Sps()[CurrentRow()])); + break; + } + case SYMBOL_NAME: + if (heapFrameInfoObj_.SymbolNames()[CurrentRow()] != INVALID_UINT64) { + auto symbolNameDataIndex = static_cast(heapFrameInfoObj_.SymbolNames()[CurrentRow()]); + sqlite3_result_text(context_, dataCache_->GetDataFromDict(symbolNameDataIndex).c_str(), STR_DEFAULT_LEN, + nullptr); + } + break; + case FILE_PATH: { + if (heapFrameInfoObj_.FilePaths()[CurrentRow()] != INVALID_UINT64) { + auto filePathDataIndex = static_cast(heapFrameInfoObj_.FilePaths()[CurrentRow()]); + sqlite3_result_text(context_, dataCache_->GetDataFromDict(filePathDataIndex).c_str(), STR_DEFAULT_LEN, + nullptr); + } + break; + } + case OFFSET: { + sqlite3_result_int64(context_, static_cast(heapFrameInfoObj_.Offsets()[CurrentRow()])); + break; + } + case SYMBOL_OFFSET: { + sqlite3_result_int64(context_, static_cast(heapFrameInfoObj_.SymbolOffsets()[CurrentRow()])); + break; + } + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/heap_frame_table.h b/host/trace_streamer/src/table/heap_frame_table.h new file mode 100644 index 0000000..572b667 --- /dev/null +++ b/host/trace_streamer/src/table/heap_frame_table.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HEAP_FRAME_TABLE_H +#define HEAP_FRAME_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class HeapFrameTable : public TableBase { +public: + explicit HeapFrameTable(const TraceDataCache* dataCache); + ~HeapFrameTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + + private: + const HeapFrameInfo& heapFrameInfoObj_; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // HEAP_FRAME_TABLE_H diff --git a/host/trace_streamer/src/table/heap_table.cpp b/host/trace_streamer/src/table/heap_table.cpp new file mode 100644 index 0000000..aee2925 --- /dev/null +++ b/host/trace_streamer/src/table/heap_table.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "heap_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { + EVENT_ID = 0, + IPID, + ITID, + EVENT_TYPE, + START_TS, + END_TS, + DURATION, + ADDR, + HEAP_SIZE, + ALL_HEAP_SIZE, + CURRENT_SIZE_DUR +}; +} +HeapTable::HeapTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("eventId", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("ipid", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("itid", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("event_type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("start_ts", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("end_ts", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("dur", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("addr", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("heap_size", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("all_heap_size", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("current_size_dur", "UNSIGNED INT")); + tablePriKey_.push_back("eventId"); +} + +HeapTable::~HeapTable() {} + +void HeapTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +HeapTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstHeapData().Size())), + heapInfoObj_(dataCache->GetConstHeapData()) +{ +} + +HeapTable::Cursor::~Cursor() {} + +int HeapTable::Cursor::Column(int column) const +{ + switch (column) { + case EVENT_ID: + sqlite3_result_int64(context_, static_cast(heapInfoObj_.EventIds()[CurrentRow()])); + break; + case IPID: + sqlite3_result_int64(context_, static_cast(heapInfoObj_.Ipids()[CurrentRow()])); + break; + case ITID: + sqlite3_result_int64(context_, static_cast(heapInfoObj_.Itids()[CurrentRow()])); + break; + case EVENT_TYPE: { + if (heapInfoObj_.EventTypes()[CurrentRow()] != INVALID_UINT64) { + auto eventTypeDataIndex = static_cast(heapInfoObj_.EventTypes()[CurrentRow()]); + sqlite3_result_text(context_, dataCache_->GetDataFromDict(eventTypeDataIndex).c_str(), STR_DEFAULT_LEN, + nullptr); + } + break; + } + case START_TS: + sqlite3_result_int64(context_, static_cast(heapInfoObj_.TimeStamData()[CurrentRow()])); + break; + case END_TS: + if (static_cast(heapInfoObj_.EndTimeStamps()[CurrentRow()]) != 0) { + sqlite3_result_int64(context_, static_cast(heapInfoObj_.EndTimeStamps()[CurrentRow()])); + } + break; + case DURATION: + if (static_cast(heapInfoObj_.Durations()[CurrentRow()]) != 0) { + sqlite3_result_int64(context_, static_cast(heapInfoObj_.Durations()[CurrentRow()])); + } + break; + case ADDR: { + sqlite3_result_int64(context_, static_cast(heapInfoObj_.Addrs()[CurrentRow()])); + break; + } + case HEAP_SIZE: { + sqlite3_result_int64(context_, static_cast(heapInfoObj_.HeapSizes()[CurrentRow()])); + break; + } + case ALL_HEAP_SIZE: { + sqlite3_result_int64(context_, static_cast(heapInfoObj_.AllHeapSizes()[CurrentRow()])); + break; + } + case CURRENT_SIZE_DUR: { + sqlite3_result_int64(context_, static_cast(heapInfoObj_.CurrentSizeDurs()[CurrentRow()])); + break; + } + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/heap_table.h b/host/trace_streamer/src/table/heap_table.h new file mode 100644 index 0000000..704f16d --- /dev/null +++ b/host/trace_streamer/src/table/heap_table.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HEAP_TABLE_H +#define HEAP_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class HeapTable : public TableBase { +public: + explicit HeapTable(const TraceDataCache* dataCache); + ~HeapTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + + private: + const HeapInfo& heapInfoObj_; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // HEAP_TABLE_H diff --git a/host/trace_streamer/src/table/hidump_table.cpp b/host/trace_streamer/src/table/hidump_table.cpp new file mode 100644 index 0000000..0b3cb17 --- /dev/null +++ b/host/trace_streamer/src/table/hidump_table.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hidump_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { TS = 0, FPS }; +} +HidumpTable::HidumpTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("ts", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("fps", "UNSIGNED INT")); + tablePriKey_.push_back("ts"); +} + +HidumpTable::~HidumpTable() {} + +void HidumpTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +HidumpTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstHidumpData().Size())), + hidumpObj_(dataCache->GetConstHidumpData()) +{ +} + +HidumpTable::Cursor::~Cursor() {} + +int HidumpTable::Cursor::Column(int column) const +{ + switch (column) { + case TS: + sqlite3_result_int64(context_, static_cast(hidumpObj_.TimeStamData()[CurrentRow()])); + break; + case FPS: { + sqlite3_result_int64(context_, static_cast(hidumpObj_.Fpss()[CurrentRow()])); + break; + } + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/hidump_table.h b/host/trace_streamer/src/table/hidump_table.h new file mode 100644 index 0000000..8c1bb1e --- /dev/null +++ b/host/trace_streamer/src/table/hidump_table.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HIDUMP_TABLE_H +#define HIDUMP_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class HidumpTable : public TableBase { +public: + explicit HidumpTable(const TraceDataCache* dataCache); + ~HidumpTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + + private: + const Hidump& hidumpObj_; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // HIDUMP_TABLE_H diff --git a/host/trace_streamer/src/table/instants_table.cpp b/host/trace_streamer/src/table/instants_table.cpp new file mode 100644 index 0000000..9d4ec70 --- /dev/null +++ b/host/trace_streamer/src/table/instants_table.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "instants_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { TS = 0, NAME, REF, REF_TYPE }; +} +InstantsTable::InstantsTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("ts", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("name", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("ref", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("ref_type", "STRING")); + tablePriKey_.push_back("ts"); + tablePriKey_.push_back("ref"); +} + +InstantsTable::~InstantsTable() {} + +void InstantsTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +InstantsTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstInstantsData().Size())), + InstantsObj_(dataCache->GetConstInstantsData()) +{ +} + +InstantsTable::Cursor::~Cursor() {} + +int InstantsTable::Cursor::Column(int column) const +{ + size_t stringIdentity = static_cast(InstantsObj_.NameIndexsData()[CurrentRow()]); + switch (column) { + case TS: + sqlite3_result_int64(context_, static_cast(InstantsObj_.TimeStamData()[CurrentRow()])); + break; + case NAME: { + sqlite3_result_text(context_, dataCache_->GetDataFromDict(stringIdentity).c_str(), + STR_DEFAULT_LEN, nullptr); + break; + } + case REF: + sqlite3_result_int64(context_, static_cast(InstantsObj_.InternalTidsData()[CurrentRow()])); + break; + case REF_TYPE: { + sqlite3_result_text(context_, "itid", STR_DEFAULT_LEN, nullptr); + break; + } + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/instants_table.h b/host/trace_streamer/src/table/instants_table.h new file mode 100644 index 0000000..06b7c0a --- /dev/null +++ b/host/trace_streamer/src/table/instants_table.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INSTANTS_TABLE_H +#define INSTANTS_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class InstantsTable : public TableBase { +public: + explicit InstantsTable(const TraceDataCache* dataCache); + ~InstantsTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + + private: + const Instants& InstantsObj_; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // INSTANTS_TABLE_H diff --git a/host/trace_streamer/src/table/irq_table.cpp b/host/trace_streamer/src/table/irq_table.cpp new file mode 100644 index 0000000..a6e6a76 --- /dev/null +++ b/host/trace_streamer/src/table/irq_table.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "irq_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { + ID = 0, + TS, + DUR, + CALL_ID, + CAT, + NAME, + DEPTH, + COOKIE_ID, + PARENT_ID, + ARGSET, + CHAIN_ID, + SPAN_ID, + PARENT_SPAN_ID, + FLAG, + ARGS +}; +} +IrqTable::IrqTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("ts", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("dur", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("callid", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("cat", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("name", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("depth", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("cookie", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("parent_id", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("argsetid", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("chainId", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("spanId", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("parentSpanId", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("flag", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("args", "STRING")); + tablePriKey_.push_back("callid"); + tablePriKey_.push_back("ts"); + tablePriKey_.push_back("depth"); +} + +IrqTable::~IrqTable() {} + +void IrqTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +IrqTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstIrqData().Size())), + slicesObj_(dataCache->GetConstIrqData()) +{ +} + +IrqTable::Cursor::~Cursor() {} + +int IrqTable::Cursor::Column(int column) const +{ + switch (column) { + case ID: + sqlite3_result_int64(context_, CurrentRow()); + break; + case TS: + sqlite3_result_int64(context_, static_cast(slicesObj_.TimeStamData()[CurrentRow()])); + break; + case DUR: + sqlite3_result_int64(context_, static_cast(slicesObj_.DursData()[CurrentRow()])); + break; + case CALL_ID: + sqlite3_result_int64(context_, static_cast(slicesObj_.CallIds()[CurrentRow()])); + break; + case CAT: { + if (slicesObj_.CatsData()[CurrentRow()] != INVALID_UINT64) { + auto catsDataIndex = static_cast(slicesObj_.CatsData()[CurrentRow()]); + sqlite3_result_text(context_, dataCache_->GetDataFromDict(catsDataIndex).c_str(), STR_DEFAULT_LEN, + nullptr); + } + break; + } + case NAME: { + if (slicesObj_.NamesData()[CurrentRow()] != INVALID_UINT64) { + auto nameDataIndex = static_cast(slicesObj_.NamesData()[CurrentRow()]); + sqlite3_result_text(context_, dataCache_->GetDataFromDict(nameDataIndex).c_str(), STR_DEFAULT_LEN, + nullptr); + } + break; + } + case DEPTH: + sqlite3_result_int64(context_, static_cast(slicesObj_.Depths()[CurrentRow()])); + break; + case COOKIE_ID: + if (slicesObj_.Cookies()[CurrentRow()] != INVALID_UINT64) { + sqlite3_result_int64(context_, static_cast(slicesObj_.Cookies()[CurrentRow()])); + } + break; + case PARENT_ID: { + if (slicesObj_.ParentIdData()[CurrentRow()].has_value()) { + sqlite3_result_int64(context_, static_cast(slicesObj_.ParentIdData()[CurrentRow()].value())); + } + break; + } + case ARGSET: + if (slicesObj_.ArgSetIdsData()[CurrentRow()] != INVALID_UINT32) { + sqlite3_result_int64(context_, static_cast(slicesObj_.ArgSetIdsData()[CurrentRow()])); + } + break; + case CHAIN_ID: + sqlite3_result_text(context_, slicesObj_.ChainIds()[CurrentRow()].c_str(), + STR_DEFAULT_LEN, nullptr); + break; + case SPAN_ID: + sqlite3_result_text(context_, slicesObj_.SpanIds()[CurrentRow()].c_str(), + STR_DEFAULT_LEN, nullptr); + break; + case PARENT_SPAN_ID: + sqlite3_result_text(context_, + slicesObj_.ParentSpanIds()[CurrentRow()].c_str(), + STR_DEFAULT_LEN, nullptr); + break; + case FLAG: + sqlite3_result_text(context_, slicesObj_.Flags()[CurrentRow()].c_str(), + STR_DEFAULT_LEN, nullptr); + break; + case ARGS: + sqlite3_result_text(context_, slicesObj_.ArgsData()[CurrentRow()].c_str(), + STR_DEFAULT_LEN, nullptr); + break; + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/irq_table.h b/host/trace_streamer/src/table/irq_table.h new file mode 100644 index 0000000..46b22c7 --- /dev/null +++ b/host/trace_streamer/src/table/irq_table.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IRQ_TABLE_H +#define IRQ_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class IrqTable : public TableBase { +public: + explicit IrqTable(const TraceDataCache* dataCache); + ~IrqTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + + private: + const CallStack& slicesObj_; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // IRQ_TABLE_H diff --git a/host/trace_streamer/src/table/log_table.cpp b/host/trace_streamer/src/table/log_table.cpp new file mode 100644 index 0000000..53b542c --- /dev/null +++ b/host/trace_streamer/src/table/log_table.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "log_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { SEQ = 0, TS, PID, TID, LEVEL, TAG, CONTEXT, ORIGINTS }; +} +LogTable::LogTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("seq", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("ts", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("pid", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("tid", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("level", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("tag", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("context", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("origints", "UNSIGNED BIG INT")); + tablePriKey_.push_back("ts"); +} + +LogTable::~LogTable() {} + +void LogTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +LogTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstHilogData().Size())), + logInfoObj_(dataCache->GetConstHilogData()) +{ +} + +LogTable::Cursor::~Cursor() {} + +int LogTable::Cursor::Column(int column) const +{ + switch (column) { + case SEQ: + sqlite3_result_int64(context_, static_cast(logInfoObj_.HilogLineSeqs()[CurrentRow()])); + break; + case TS: + sqlite3_result_int64(context_, static_cast(logInfoObj_.TimeStamData()[CurrentRow()])); + break; + case PID: { + sqlite3_result_int64(context_, static_cast(logInfoObj_.Pids()[CurrentRow()])); + break; + } + case TID: + sqlite3_result_int64(context_, static_cast(logInfoObj_.Tids()[CurrentRow()])); + break; + case LEVEL: { + if (logInfoObj_.Levels()[CurrentRow()] != INVALID_UINT64) { + auto levelDataIndex = static_cast(logInfoObj_.Levels()[CurrentRow()]); + sqlite3_result_text(context_, dataCache_->GetDataFromDict(levelDataIndex).c_str(), STR_DEFAULT_LEN, + nullptr); + } + break; + } + case TAG: { + if (logInfoObj_.Tags()[CurrentRow()] != INVALID_UINT64) { + auto tagDataIndex = static_cast(logInfoObj_.Tags()[CurrentRow()]); + sqlite3_result_text(context_, dataCache_->GetDataFromDict(tagDataIndex).c_str(), STR_DEFAULT_LEN, + nullptr); + } + break; + } + case CONTEXT: { + if (logInfoObj_.Contexts()[CurrentRow()] != INVALID_UINT64) { + auto contextDataIndex = static_cast(logInfoObj_.Contexts()[CurrentRow()]); + sqlite3_result_text(context_, dataCache_->GetDataFromDict(contextDataIndex).c_str(), STR_DEFAULT_LEN, + nullptr); + } + break; + } + case ORIGINTS: { + sqlite3_result_int64(context_, static_cast(logInfoObj_.OriginTimeStamData()[CurrentRow()])); + break; + } + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/log_table.h b/host/trace_streamer/src/table/log_table.h new file mode 100644 index 0000000..22c5042 --- /dev/null +++ b/host/trace_streamer/src/table/log_table.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LOG_TABLE_H +#define LOG_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class LogTable : public TableBase { +public: + explicit LogTable(const TraceDataCache* dataCache); + ~LogTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + + private: + const LogInfo& logInfoObj_; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // LOG_TABLE_H diff --git a/host/trace_streamer/src/table/measure_filter_table.cpp b/host/trace_streamer/src/table/measure_filter_table.cpp new file mode 100644 index 0000000..18131ee --- /dev/null +++ b/host/trace_streamer/src/table/measure_filter_table.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "measure_filter_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, TYPE, NAME, INTERNAL_TID }; +} +MeasureFilterTable::MeasureFilterTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("name", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("itid", "UNSIGNED INT")); + tablePriKey_.push_back("id"); +} + +MeasureFilterTable::~MeasureFilterTable() {} + +void MeasureFilterTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +MeasureFilterTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstThreadMeasureFilterData().Size())) +{ +} + +MeasureFilterTable::Cursor::~Cursor() {} + +int MeasureFilterTable::Cursor::Column(int column) const +{ + switch (column) { + case ID: + sqlite3_result_int64( + context_, + static_cast(dataCache_->GetConstThreadMeasureFilterData().FilterIdData()[CurrentRow()])); + break; + case TYPE: + sqlite3_result_text(context_, "thread_measure_filter", STR_DEFAULT_LEN, nullptr); + break; + case NAME: { + const std::string& str = dataCache_->GetDataFromDict( + dataCache_->GetConstThreadMeasureFilterData().NameIndexData()[CurrentRow()]); + sqlite3_result_text(context_, str.c_str(), STR_DEFAULT_LEN, nullptr); + break; + } + case INTERNAL_TID: + sqlite3_result_int64(context_, + static_cast(dataCache_->GetConstThreadMeasureFilterData().InternalTidData()[CurrentRow()])); + break; + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/measure_filter_table.h b/host/trace_streamer/src/table/measure_filter_table.h new file mode 100644 index 0000000..54e8cee --- /dev/null +++ b/host/trace_streamer/src/table/measure_filter_table.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef THREAD_MEASURE_FILTER_H +#define THREAD_MEASURE_FILTER_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class MeasureFilterTable : public TableBase { +public: + explicit MeasureFilterTable(const TraceDataCache* dataCache); + ~MeasureFilterTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // THREAD_MEASURE_FILTER_H diff --git a/host/trace_streamer/src/table/measure_table.cpp b/host/trace_streamer/src/table/measure_table.cpp new file mode 100644 index 0000000..2636dc7 --- /dev/null +++ b/host/trace_streamer/src/table/measure_table.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "measure_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { TYPE = 0, TS, VALUE, FILTER_ID }; +} +MeasureTable::MeasureTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("ts", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("value", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("filter_id", "UNSIGNED INT")); + tablePriKey_.push_back("ts"); + tablePriKey_.push_back("filter_id"); +} + +MeasureTable::~MeasureTable() {} + +void MeasureTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +MeasureTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstMeasureData().Size())), + measureObj(dataCache->GetConstMeasureData()) +{ +} + +MeasureTable::Cursor::~Cursor() {} + +int MeasureTable::Cursor::Column(int column) const +{ + switch (column) { + case TYPE: + sqlite3_result_text(context_, "measure", STR_DEFAULT_LEN, nullptr); + break; + case TS: + sqlite3_result_int64(context_, static_cast(measureObj.TimeStamData()[CurrentRow()])); + break; + case VALUE: + sqlite3_result_int64(context_, static_cast(measureObj.ValuesData()[CurrentRow()])); + break; + case FILTER_ID: + sqlite3_result_int64(context_, static_cast(measureObj.FilterIdData()[CurrentRow()])); + break; + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/measure_table.h b/host/trace_streamer/src/table/measure_table.h new file mode 100644 index 0000000..418730d --- /dev/null +++ b/host/trace_streamer/src/table/measure_table.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MEASURE_TABLE_H +#define MEASURE_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class MeasureTable : public TableBase { +public: + explicit MeasureTable(const TraceDataCache* dataCache); + ~MeasureTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + + private: + const Measure& measureObj; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // MEASURE_TABLE_H diff --git a/host/trace_streamer/src/table/meta_table.cpp b/host/trace_streamer/src/table/meta_table.cpp new file mode 100644 index 0000000..4d9304c --- /dev/null +++ b/host/trace_streamer/src/table/meta_table.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "meta_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { NAMEINDEX = 0, VALUE }; +} +MetaTable::MetaTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("name", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("value", "STRING")); + tablePriKey_.push_back("name"); +} + +MetaTable::~MetaTable() {} + +void MetaTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +MetaTable::Cursor::Cursor(const TraceDataCache* dataCache) : TableBase::Cursor(dataCache, 0, METADATA_ITEM_MAX) {} + +MetaTable::Cursor::~Cursor() {} + +int MetaTable::Cursor::Column(int column) const +{ + switch (column) { + case NAMEINDEX: + sqlite3_result_text(context_, dataCache_->GetConstMetaData().Name(CurrentRow()).c_str(), STR_DEFAULT_LEN, + nullptr); + break; + case VALUE: + sqlite3_result_text(context_, dataCache_->GetConstMetaData().Value(CurrentRow()).c_str(), STR_DEFAULT_LEN, + nullptr); + break; + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/meta_table.h b/host/trace_streamer/src/table/meta_table.h new file mode 100644 index 0000000..e75e71b --- /dev/null +++ b/host/trace_streamer/src/table/meta_table.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef META_TABLE_H +#define META_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class MetaTable : public TableBase { +public: + explicit MetaTable(const TraceDataCache* dataCache); + ~MetaTable() override; + void CreateCursor() override; +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // META_TABLE_H diff --git a/host/trace_streamer/src/table/process_filter_table.cpp b/host/trace_streamer/src/table/process_filter_table.cpp new file mode 100644 index 0000000..63bba49 --- /dev/null +++ b/host/trace_streamer/src/table/process_filter_table.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "process_filter_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, TYPE, NAME, INTERNAL_PID }; +} +ProcessFilterTable::ProcessFilterTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("name", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("ipid", "UNSIGNED INT")); + tablePriKey_.push_back("id"); +} + +ProcessFilterTable::~ProcessFilterTable() {} + +void ProcessFilterTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +ProcessFilterTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstProcessFilterData().Size())), + processFilterObj_(dataCache->GetConstProcessFilterData()) +{ +} + +ProcessFilterTable::Cursor::~Cursor() {} + +int ProcessFilterTable::Cursor::Column(int col) const +{ + switch (col) { + case ID: + sqlite3_result_int64(context_, static_cast(processFilterObj_.IdsData()[CurrentRow()])); + break; + case TYPE: + sqlite3_result_text(context_, "process_filter", STR_DEFAULT_LEN, nullptr); + break; + case NAME: { + DataIndex stringIdentity = static_cast(processFilterObj_.NamesData()[CurrentRow()]); + sqlite3_result_text(context_, dataCache_->GetDataFromDict(stringIdentity).c_str(), STR_DEFAULT_LEN, + nullptr); + break; + } + case INTERNAL_PID: + sqlite3_result_int64(context_, static_cast(processFilterObj_.UpidsData()[CurrentRow()])); + break; + default: + TS_LOGF("Unregistered column : %d", col); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/process_filter_table.h b/host/trace_streamer/src/table/process_filter_table.h new file mode 100644 index 0000000..2e8c239 --- /dev/null +++ b/host/trace_streamer/src/table/process_filter_table.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PROCESS_FILTER_TABLE_H +#define PROCESS_FILTER_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class ProcessFilterTable : public TableBase { +public: + explicit ProcessFilterTable(const TraceDataCache* dataCache); + ~ProcessFilterTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int col) const override; + + private: + const ProcessMeasureFilter& processFilterObj_; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // PROCESS_FILTER_TABLE_H diff --git a/host/trace_streamer/src/table/process_measure_filter_table.cpp b/host/trace_streamer/src/table/process_measure_filter_table.cpp new file mode 100644 index 0000000..afd6e80 --- /dev/null +++ b/host/trace_streamer/src/table/process_measure_filter_table.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "process_measure_filter_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, TYPE, NAME, INTERNAL_PID }; +} +ProcessMeasureFilterTable::ProcessMeasureFilterTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "INT")); + tableColumn_.push_back(TableBase::ColumnInfo("type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("name", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("ipid", "INT")); + tablePriKey_.push_back("id"); +} + +ProcessMeasureFilterTable::~ProcessMeasureFilterTable() {} + +void ProcessMeasureFilterTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +ProcessMeasureFilterTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstProcessMeasureFilterData().Size())) +{ +} + +ProcessMeasureFilterTable::Cursor::~Cursor() {} + +int ProcessMeasureFilterTable::Cursor::Column(int col) const +{ + switch (col) { + case ID: + sqlite3_result_int64(context_, static_cast( + dataCache_->GetConstProcessMeasureFilterData().IdsData()[CurrentRow()])); + break; + case TYPE: + sqlite3_result_text(context_, "process_measure_filter", STR_DEFAULT_LEN, nullptr); + break; + case NAME: { + size_t strId = + static_cast(dataCache_->GetConstProcessMeasureFilterData().NamesData()[CurrentRow()]); + sqlite3_result_text(context_, dataCache_->GetDataFromDict(strId).c_str(), STR_DEFAULT_LEN, nullptr); + break; + } + case INTERNAL_PID: + sqlite3_result_int64( + context_, + static_cast(dataCache_->GetConstProcessMeasureFilterData().UpidsData()[CurrentRow()])); + break; + default: + TS_LOGF("Unregistered column : %d", col); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/process_measure_filter_table.h b/host/trace_streamer/src/table/process_measure_filter_table.h new file mode 100644 index 0000000..5b1ff4a --- /dev/null +++ b/host/trace_streamer/src/table/process_measure_filter_table.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_PROCESS_MEASURE_FILTER_TABLE_H +#define SRC_PROCESS_MEASURE_FILTER_TABLE_H + + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class ProcessMeasureFilterTable : public TableBase { +public: + explicit ProcessMeasureFilterTable(const TraceDataCache* dataCache); + ~ProcessMeasureFilterTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int col) const override; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // SRC_PROCESS_MEASURE_FILTER_TABLE_H diff --git a/host/trace_streamer/src/table/process_table.cpp b/host/trace_streamer/src/table/process_table.cpp new file mode 100644 index 0000000..02b05e7 --- /dev/null +++ b/host/trace_streamer/src/table/process_table.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "process_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, TYPE, PID, NAME, START_TS, END_TS, PARENT_ID, UID, APP_ID }; +} +ProcessTable::ProcessTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "INT")); + tableColumn_.push_back(TableBase::ColumnInfo("type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("pid", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("name", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("start_ts", "UNSIGNED INT")); + tablePriKey_.push_back("id"); +} + +ProcessTable::~ProcessTable() {} + +void ProcessTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +ProcessTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->ProcessSize())) +{ +} + +ProcessTable::Cursor::~Cursor() {} + +int ProcessTable::Cursor::Column(int column) const +{ + const auto& process = dataCache_->GetConstProcessData(CurrentRow()); + switch (column) { + case ID: + sqlite3_result_int64(context_, CurrentRow()); + break; + case TYPE: + sqlite3_result_text(context_, "process", STR_DEFAULT_LEN, nullptr); + break; + case PID: + sqlite3_result_int64(context_, process.pid_); + break; + case NAME: + if (process.cmdLine_.size()) { + sqlite3_result_text(context_, process.cmdLine_.c_str(), static_cast(process.cmdLine_.length()), + nullptr); + } + break; + case START_TS: + if (process.startT_) { + sqlite3_result_int64(context_, static_cast(process.startT_)); + } + break; + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/process_table.h b/host/trace_streamer/src/table/process_table.h new file mode 100644 index 0000000..a614ba8 --- /dev/null +++ b/host/trace_streamer/src/table/process_table.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PROCESS_TABLE_H +#define PROCESS_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class ProcessTable : public TableBase { +public: + explicit ProcessTable(const TraceDataCache* dataCache); + ~ProcessTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // PROCESS_TABLE_H diff --git a/host/trace_streamer/src/table/range_table.cpp b/host/trace_streamer/src/table/range_table.cpp new file mode 100644 index 0000000..8ae9f3c --- /dev/null +++ b/host/trace_streamer/src/table/range_table.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "range_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { START_TS = 0, END_TS }; +} +RangeTable::RangeTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("start_ts", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("end_ts", "INT")); + tablePriKey_.push_back("start_ts"); +} + +RangeTable::~RangeTable() {} + +void RangeTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +RangeTable::Cursor::Cursor(const TraceDataCache* dataCache) : TableBase::Cursor(dataCache, 0, 1) {} + +RangeTable::Cursor::~Cursor() {} + +int RangeTable::Cursor::Column(int column) const +{ + switch (column) { + case START_TS: + sqlite3_result_int64(context_, static_cast(dataCache_->TraceStartTime())); + break; + case END_TS: + sqlite3_result_int64(context_, static_cast(dataCache_->TraceEndTime())); + break; + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/range_table.h b/host/trace_streamer/src/table/range_table.h new file mode 100644 index 0000000..ea0d8f6 --- /dev/null +++ b/host/trace_streamer/src/table/range_table.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RANGE_TABLE_H +#define RANGE_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class RangeTable : public TableBase { +public: + explicit RangeTable(const TraceDataCache* dataCache); + ~RangeTable() override; + void CreateCursor() override; +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // RANGE_TABLE_H diff --git a/host/trace_streamer/src/table/raw_table.cpp b/host/trace_streamer/src/table/raw_table.cpp new file mode 100644 index 0000000..6a1086c --- /dev/null +++ b/host/trace_streamer/src/table/raw_table.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "raw_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, TYPE, TS, NAME, CPU, INTERNAL_TID }; +} +RawTable::RawTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("ts", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("name", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("cpu", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("itid", "UNSIGNED INT")); + tablePriKey_.push_back("id"); +} + +RawTable::~RawTable() {} + +void RawTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +RawTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstRawTableData().Size())), + rawObj_(dataCache->GetConstRawTableData()) +{ +} + +RawTable::Cursor::~Cursor() {} + +int RawTable::Cursor::Column(int column) const +{ + switch (column) { + case ID: + sqlite3_result_int64(context_, static_cast(CurrentRow())); + break; + case TYPE: + sqlite3_result_text(context_, "raw", STR_DEFAULT_LEN, nullptr); + break; + case TS: + sqlite3_result_int64(context_, static_cast(rawObj_.TimeStamData()[CurrentRow()])); + break; + case NAME: { + if (rawObj_.NameData()[CurrentRow()] == CPU_IDLE) { + sqlite3_result_text(context_, "cpu_idle", STR_DEFAULT_LEN, nullptr); + } else if (rawObj_.NameData()[CurrentRow()] == SCHED_WAKEUP) { + sqlite3_result_text(context_, "sched_wakeup", STR_DEFAULT_LEN, nullptr); + } else { + sqlite3_result_text(context_, "sched_waking", STR_DEFAULT_LEN, nullptr); + } + break; + } + case CPU: + sqlite3_result_int64(context_, static_cast(rawObj_.CpuData()[CurrentRow()])); + break; + case INTERNAL_TID: + sqlite3_result_int64(context_, static_cast(rawObj_.InternalTidData()[CurrentRow()])); + break; + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/raw_table.h b/host/trace_streamer/src/table/raw_table.h new file mode 100644 index 0000000..2cc92b0 --- /dev/null +++ b/host/trace_streamer/src/table/raw_table.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RAW_TABLE_H +#define RAW_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class RawTable : public TableBase { +public: + enum EventName : uint32_t { CPU_IDLE = 1, SCHED_WAKEUP = 2, SCHED_WAKING = 3 }; + explicit RawTable(const TraceDataCache* dataCache); + ~RawTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + + private: + const Raw& rawObj_; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // RAW_TABLE_H diff --git a/host/trace_streamer/src/table/sched_slice_table.cpp b/host/trace_streamer/src/table/sched_slice_table.cpp new file mode 100644 index 0000000..e87e303 --- /dev/null +++ b/host/trace_streamer/src/table/sched_slice_table.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "sched_slice_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, TYPE, TS, DUR, CPU, INTERNAL_TID, END_STATE, PRIORITY }; +} +SchedSliceTable::SchedSliceTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "INT")); + tableColumn_.push_back(TableBase::ColumnInfo("type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("ts", "INT")); + tableColumn_.push_back(TableBase::ColumnInfo("dur", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("cpu", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("itid", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("end_state", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("priority", "INT")); + tablePriKey_.push_back("id"); +} + +SchedSliceTable::~SchedSliceTable() {} + +void SchedSliceTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +SchedSliceTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstSchedSliceData().Size())), + schedSliceObj_(dataCache->GetConstSchedSliceData()) +{ +} + +SchedSliceTable::Cursor::~Cursor() {} + +int SchedSliceTable::Cursor::Column(int col) const +{ + switch (col) { + case ID: + sqlite3_result_int64(context_, static_cast(CurrentRow())); + break; + case TYPE: + sqlite3_result_text(context_, "sched_slice", STR_DEFAULT_LEN, nullptr); + break; + case TS: + sqlite3_result_int64(context_, static_cast(schedSliceObj_.TimeStamData()[CurrentRow()])); + break; + case DUR: + sqlite3_result_int64(context_, static_cast(schedSliceObj_.DursData()[CurrentRow()])); + break; + case CPU: + sqlite3_result_int64(context_, static_cast(schedSliceObj_.CpusData()[CurrentRow()])); + break; + case INTERNAL_TID: + sqlite3_result_int64(context_, static_cast(schedSliceObj_.InternalTidsData()[CurrentRow()])); + break; + case END_STATE: { + const std::string& str = dataCache_->GetConstSchedStateData(schedSliceObj_.EndStatesData()[CurrentRow()]); + sqlite3_result_text(context_, str.c_str(), STR_DEFAULT_LEN, nullptr); + break; + } + case PRIORITY: + sqlite3_result_int64(context_, static_cast(schedSliceObj_.PriorityData()[CurrentRow()])); + break; + default: + TS_LOGF("Unregistered column : %d", col); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/sched_slice_table.h b/host/trace_streamer/src/table/sched_slice_table.h new file mode 100644 index 0000000..2d4fb34 --- /dev/null +++ b/host/trace_streamer/src/table/sched_slice_table.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SCHED_SLICE_TABLE_H +#define SCHED_SLICE_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class SchedSliceTable : public TableBase { +public: + explicit SchedSliceTable(const TraceDataCache* dataCache); + ~SchedSliceTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int col) const override; + + private: + const SchedSlice& schedSliceObj_; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // SCHED_SLICE_TABLE_H diff --git a/host/trace_streamer/src/table/stat_table.cpp b/host/trace_streamer/src/table/stat_table.cpp new file mode 100644 index 0000000..7294826 --- /dev/null +++ b/host/trace_streamer/src/table/stat_table.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stat_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { EVENT_NAME = 0, STAT_EVENT_TYPE = 1, COUNT = 2, SEVERITY = 3, SOURCE = 4 }; +} +StatTable::StatTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("event_name", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("stat_type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("count", "INT")); + tableColumn_.push_back(TableBase::ColumnInfo("serverity", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("source", "STRING")); + tablePriKey_.push_back("event_name"); + tablePriKey_.push_back("stat_type"); +} + +StatTable::~StatTable() {} + +void StatTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +StatTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, STAT_EVENT_MAX * TRACE_EVENT_MAX) +{ +} + +StatTable::Cursor::~Cursor() {} + +int StatTable::Cursor::Column(int column) const +{ + const StatAndInfo stat = dataCache_->GetConstStatAndInfo(); + SupportedTraceEventType eventType = static_cast(CurrentRow() / STAT_EVENT_MAX); + StatType statType = static_cast(CurrentRow() % STAT_EVENT_MAX); + switch (column) { + case EVENT_NAME: + sqlite3_result_text(context_, dataCache_->GetConstStatAndInfo().GetEvent(eventType).c_str(), + STR_DEFAULT_LEN, nullptr); + break; + case STAT_EVENT_TYPE: + sqlite3_result_text(context_, dataCache_->GetConstStatAndInfo().GetStat(statType).c_str(), + STR_DEFAULT_LEN, nullptr); + break; + case COUNT: + sqlite3_result_int64(context_, static_cast(dataCache_->GetConstStatAndInfo().GetValue(eventType, + statType))); + break; + case SEVERITY: + sqlite3_result_text(context_, dataCache_->GetConstStatAndInfo().GetSeverityDesc(eventType, + statType).c_str(), STR_DEFAULT_LEN, nullptr); + break; + case SOURCE: + sqlite3_result_text(context_, "trace", STR_DEFAULT_LEN, nullptr); + break; + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/stat_table.h b/host/trace_streamer/src/table/stat_table.h new file mode 100644 index 0000000..1e1fd99 --- /dev/null +++ b/host/trace_streamer/src/table/stat_table.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef STAT_TABLE_H +#define STAT_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class StatTable : public TableBase { +public: + explicit StatTable(const TraceDataCache* dataCache); + ~StatTable() override; + void CreateCursor() override; +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // STAT_TABLE_H diff --git a/host/trace_streamer/src/table/symbols_table.cpp b/host/trace_streamer/src/table/symbols_table.cpp new file mode 100644 index 0000000..4ec5421 --- /dev/null +++ b/host/trace_streamer/src/table/symbols_table.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "symbols_table.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, STR, ADDR }; +} +SymbolsTable::SymbolsTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("funcname", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("addr", "UNSIGNED BIG INT")); + tablePriKey_.push_back("id"); +} + +SymbolsTable::~SymbolsTable() {} + +void SymbolsTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +SymbolsTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstSymbolsData().Size())) +{ +} + +SymbolsTable::Cursor::~Cursor() {} + +int SymbolsTable::Cursor::Column(int col) const +{ + DataIndex index = static_cast(CurrentRow()); + switch (col) { + case ID: + sqlite3_result_int64(context_, static_cast(CurrentRow())); + break; + case STR: + sqlite3_result_text(context_, + dataCache_->GetDataFromDict(dataCache_->GetConstSymbolsData().GetConstFuncNames()[index]).c_str(), + STR_DEFAULT_LEN, nullptr); + break; + case ADDR: + sqlite3_result_int64(context_, + static_cast(dataCache_->GetConstSymbolsData().GetConstAddrs()[index])); + break; + default: + TS_LOGF("Unknown column %d", col); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/symbols_table.h b/host/trace_streamer/src/table/symbols_table.h new file mode 100644 index 0000000..1f9fdeb --- /dev/null +++ b/host/trace_streamer/src/table/symbols_table.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SYMBOLS_DICT_TABLE_H +#define SYMBOLS_DICT_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class SymbolsTable : public TableBase { +public: + explicit SymbolsTable(const TraceDataCache* dataCache); + ~SymbolsTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int col) const override; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // SYMBOLS_DICT_TABLE_H diff --git a/host/trace_streamer/src/table/system_call_table.cpp b/host/trace_streamer/src/table/system_call_table.cpp new file mode 100644 index 0000000..a499eb5 --- /dev/null +++ b/host/trace_streamer/src/table/system_call_table.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "system_call_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { SYSCALL_NUM = 0, TYPE, IPID, TS, RET }; +} +SystemCallTable::SystemCallTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("syscall_num", "INT")); + tableColumn_.push_back(TableBase::ColumnInfo("type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("ipid", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("ts", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("ret", "INT")); + tablePriKey_.push_back("syscall_num"); +} + +SystemCallTable::~SystemCallTable() {} + +void SystemCallTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +SystemCallTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstSysCallData().Size())), + sysCallObj_(dataCache->GetConstSysCallData()) +{ +} + +SystemCallTable::Cursor::~Cursor() {} + +int SystemCallTable::Cursor::Column(int column) const +{ + switch (column) { + case SYSCALL_NUM: + sqlite3_result_int64(context_, dataCache_->GetConstSysCallData().SysCallsData()[CurrentRow()]); + break; + case TYPE: + sqlite3_result_text(context_, dataCache_->GetDataFromDict(sysCallObj_.TypesData()[CurrentRow()]).c_str(), + STR_DEFAULT_LEN, nullptr); + break; + case IPID: + sqlite3_result_int64(context_, dataCache_->GetConstSysCallData().IpidsData()[CurrentRow()]); + break; + case TS: + sqlite3_result_int64(context_, dataCache_->GetConstSysCallData().TimeStamData()[CurrentRow()]); + break; + case RET: + sqlite3_result_int64(context_, dataCache_->GetConstSysCallData().RetsData()[CurrentRow()]); + break; + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/system_call_table.h b/host/trace_streamer/src/table/system_call_table.h new file mode 100644 index 0000000..fab317e --- /dev/null +++ b/host/trace_streamer/src/table/system_call_table.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SYSCALL_TABLE_H +#define SYSCALL_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class SystemCallTable : public TableBase { +public: + explicit SystemCallTable(const TraceDataCache*); + ~SystemCallTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + private: + const SysCall& sysCallObj_; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // PROCESS_TABLE_H diff --git a/host/trace_streamer/src/table/system_event_filter_table.cpp b/host/trace_streamer/src/table/system_event_filter_table.cpp new file mode 100644 index 0000000..3ce23b8 --- /dev/null +++ b/host/trace_streamer/src/table/system_event_filter_table.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "system_event_filter_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, TYPE, NAME }; +} +SystemEventFilterTable::SystemEventFilterTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("name", "STRING")); + tablePriKey_.push_back("id"); +} + +SystemEventFilterTable::~SystemEventFilterTable() {} + +void SystemEventFilterTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +SystemEventFilterTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstSysMeasureFilterData().Size())), + sysEventObj_(dataCache->GetConstSysMeasureFilterData()) +{ +} + +SystemEventFilterTable::Cursor::~Cursor() {} + +int SystemEventFilterTable::Cursor::Column(int column) const +{ + switch (column) { + case ID: + sqlite3_result_int64(context_, sysEventObj_.IdsData()[CurrentRow()]); + break; + case TYPE: + sqlite3_result_text(context_, dataCache_->GetDataFromDict(sysEventObj_.TypesData()[CurrentRow()]).c_str(), + STR_DEFAULT_LEN, nullptr); + break; + case NAME: + sqlite3_result_text(context_, dataCache_->GetDataFromDict(sysEventObj_.NamesData()[CurrentRow()]).c_str(), + STR_DEFAULT_LEN, nullptr); + break; + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/system_event_filter_table.h b/host/trace_streamer/src/table/system_event_filter_table.h new file mode 100644 index 0000000..80aeb45 --- /dev/null +++ b/host/trace_streamer/src/table/system_event_filter_table.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SYSTEM_EVENT_FILTER_TABLE_H +#define SYSTEM_EVENT_FILTER_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class SystemEventFilterTable : public TableBase { +public: + explicit SystemEventFilterTable(const TraceDataCache*); + ~SystemEventFilterTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + private: + const SysMeasureFilter& sysEventObj_; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // SYSTEM_EVENT_FILTER_TABLE_H diff --git a/host/trace_streamer/src/table/table.pri b/host/trace_streamer/src/table/table.pri new file mode 100644 index 0000000..a5da2ea --- /dev/null +++ b/host/trace_streamer/src/table/table.pri @@ -0,0 +1,78 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDEPATH += $$PWD +HEADERS += \ + $$PWD/range_table.h \ + $$PWD/cpu_measure_filter_table.h \ + $$PWD/data_dict_table.h \ + $$PWD/filter_table.h \ + $$PWD/instants_table.h \ + $$PWD/callstack_table.h \ + $$PWD/thread_table.h \ + $$PWD/measure_filter_table.h \ + $$PWD/measure_table.h \ + $$PWD/process_filter_table.h \ + $$PWD/process_measure_filter_table.h \ + $$PWD/process_table.h \ + $$PWD/raw_table.h \ + $$PWD/sched_slice_table.h \ + $$PWD/table_base.h \ + $$PWD/thread_filter_table.h \ + $$PWD/thread_state_table.h \ + $$PWD/clock_event_filter_table.h \ + $$PWD/clk_event_filter_table.h \ + $$PWD/stat_table.h \ + $$PWD/meta_table.h \ + $$PWD/symbols_table.h \ + $$PWD/system_call_table.h \ + $$PWD/log_table.h \ + $$PWD/heap_table.h \ + $$PWD/heap_frame_table.h \ + $$PWD/hidump_table.h \ + $$PWD/args_table.h \ + $$PWD/data_type_table.h \ + $$PWD/system_event_filter_table.h + +SOURCES += \ + $$PWD/range_table.cpp \ + $$PWD/cpu_measure_filter_table.cpp \ + $$PWD/data_dict_table.cpp \ + $$PWD/filter_table.cpp \ + $$PWD/instants_table.cpp \ + $$PWD/callstack_table.cpp \ + $$PWD/irq_table.cpp \ + $$PWD/thread_table.cpp \ + $$PWD/measure_filter_table.cpp \ + $$PWD/measure_table.cpp \ + $$PWD/process_filter_table.cpp \ + $$PWD/process_measure_filter_table.cpp \ + $$PWD/process_table.cpp \ + $$PWD/raw_table.cpp \ + $$PWD/sched_slice_table.cpp \ + $$PWD/table_base.cpp \ + $$PWD/thread_filter_table.cpp \ + $$PWD/thread_state_table.cpp \ + $$PWD/clock_event_filter_table.cpp \ + $$PWD/clk_event_filter_table.cpp \ + $$PWD/stat_table.cpp \ + $$PWD/meta_table.cpp \ + $$PWD/symbols_table.cpp \ + $$PWD/system_call_table.cpp \ + $$PWD/log_table.cpp \ + $$PWD/heap_table.cpp \ + $$PWD/heap_frame_table.cpp \ + $$PWD/hidump_table.cpp \ + $$PWD/args_table.cpp \ + $$PWD/data_type_table.cpp \ + $$PWD/system_event_filter_table.cpp diff --git a/host/trace_streamer/src/table/table_base.cpp b/host/trace_streamer/src/table/table_base.cpp new file mode 100644 index 0000000..fef5f10 --- /dev/null +++ b/host/trace_streamer/src/table/table_base.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "table_base.h" +#include +#include +#include "log.h" + +#define UNUSED(expr) \ + do { \ + static_cast(expr); \ + } while (0) + +namespace SysTuning { +namespace TraceStreamer { +namespace { +struct TableContext { + TabTemplate tmplate; + const TraceDataCache* dataCache; + sqlite3_module module; +}; +} // namespace + +TableBase::~TableBase() +{ + dataCache_ = nullptr; + cursor_ = nullptr; +} + +void TableBase::TableRegister(sqlite3& db, + const TraceDataCache* cache, + const std::string& tableName, + TabTemplate tmplate) +{ + std::unique_ptr context(std::make_unique()); + context->dataCache = cache; + context->tmplate = tmplate; + sqlite3_module& module = context->module; + + auto createFn = [](sqlite3* xdb, void* arg, int argc, const char* const* argv, sqlite3_vtab** tab, char** other) { + UNUSED(argc); + UNUSED(argv); + UNUSED(other); + auto xdesc = static_cast(arg); + auto table = xdesc->tmplate(xdesc->dataCache); + std::string createStmt = table->CreateTableSql(); + int res = sqlite3_declare_vtab(xdb, createStmt.c_str()); + if (res != SQLITE_OK) { + return res; + } + *tab = table.release(); + return SQLITE_OK; + }; + + auto destroyFn = [](sqlite3_vtab* t) { + delete static_cast(t); + return SQLITE_OK; + }; + + module.xCreate = createFn; + module.xConnect = createFn; + module.xBestIndex = [](sqlite3_vtab*, sqlite3_index_info*) { return SQLITE_OK; }; + module.xDisconnect = destroyFn; + module.xDestroy = destroyFn; + module.xOpen = [](sqlite3_vtab* t, sqlite3_vtab_cursor** c) { + return (static_cast(t))->Open(c); + }; + module.xClose = [](sqlite3_vtab_cursor* c) { + UNUSED(c); + return SQLITE_OK; + }; + module.xFilter = [](sqlite3_vtab_cursor* c, int arg1, const char* arg2, int, sqlite3_value** sqlite) { + UNUSED(c); + UNUSED(arg1); + UNUSED(arg2); + UNUSED(sqlite); + return SQLITE_OK; + }; + module.xNext = [](sqlite3_vtab_cursor* c) { return static_cast(c)->Next(); }; + module.xEof = [](sqlite3_vtab_cursor* c) { return static_cast(c)->Eof(); }; + module.xColumn = [](sqlite3_vtab_cursor* c, sqlite3_context* a, int b) { + static_cast(c)->context_ = a; + return static_cast(c)->Column(b); + }; + + sqlite3_create_module_v2(&db, tableName.c_str(), &module, context.release(), + [](void* arg) { delete static_cast(arg); }); +} + +std::string TableBase::CreateTableSql() const +{ + std::string stmt = "CREATE TABLE x("; + for (const auto& col : tableColumn_) { + stmt += " " + col.name_ + " " + col.type_; + stmt += ","; + } + stmt += " PRIMARY KEY("; + for (size_t i = 0; i < tablePriKey_.size(); i++) { + if (i != 0) + stmt += ", "; + stmt += tablePriKey_.at(i); + } + stmt += ")) WITHOUT ROWID;"; + return stmt; +} + +int TableBase::Open(sqlite3_vtab_cursor** ppCursor) +{ + CreateCursor(); + *ppCursor = static_cast(cursor_.get()); + return SQLITE_OK; +} + +TableBase::Cursor::Cursor(const TraceDataCache* dataCache, uint32_t row, uint32_t totalRows) + : context_(nullptr), dataCache_(dataCache), currentRow_(row), rowsTotalNum_(totalRows) +{ +} + +TableBase::Cursor::~Cursor() +{ + context_ = nullptr; + dataCache_ = nullptr; +} + +int TableBase::Cursor::Next() +{ + currentRow_++; + return SQLITE_OK; +} + +int TableBase::Cursor::Eof() +{ + return currentRow_ >= rowsTotalNum_; +} + +uint32_t TableBase::Cursor::CurrentRow() const +{ + return currentRow_; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/table_base.h b/host/trace_streamer/src/table/table_base.h new file mode 100644 index 0000000..22e51ca --- /dev/null +++ b/host/trace_streamer/src/table/table_base.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TABLE_H +#define TABLE_H + +#include +#include +#include +#include +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class TableBase; +using TabTemplate = std::unique_ptr (*)(const TraceDataCache* dataCache); +class TableBase : public sqlite3_vtab { +public: + virtual ~TableBase(); + TableBase(const TableBase&) = delete; + TableBase& operator=(const TableBase&) = delete; + + template + static void TableDeclare(sqlite3& db, TraceDataCache* dataCache, const std::string& name) + { + TableRegister(db, dataCache, name, [](const TraceDataCache* cache) { + return std::unique_ptr(std::make_unique(cache)); + }); + dataCache->AppendNewTable(name); + } + + std::string CreateTableSql() const; + + class Cursor : public sqlite3_vtab_cursor { + public: + Cursor(const TraceDataCache*, uint32_t, uint32_t); + virtual ~Cursor(); + virtual int Next(); + virtual int Eof(); + virtual int Column(int) const = 0; + public: + sqlite3_context* context_; + protected: + uint32_t CurrentRow() const; + protected: + const TraceDataCache* dataCache_; + private: + uint32_t currentRow_; + uint32_t rowsTotalNum_; + }; + + struct ColumnInfo { + ColumnInfo(const std::string& name, const std::string& type) : name_(name), type_(type) {} + std::string name_; + std::string type_; + }; + +protected: + explicit TableBase(const TraceDataCache* dataCache) : dataCache_(dataCache), cursor_(nullptr) {} + virtual void CreateCursor() = 0; +protected: + std::vector tableColumn_ = {}; + std::vector tablePriKey_ = {}; + const TraceDataCache* dataCache_; + std::unique_ptr cursor_; +private: + static void TableRegister(sqlite3& db, const TraceDataCache* cache, const std::string& name, TabTemplate tmplate); + int Open(sqlite3_vtab_cursor** ppCursor); +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // TABLE_H diff --git a/host/trace_streamer/src/table/thread_filter_table.cpp b/host/trace_streamer/src/table/thread_filter_table.cpp new file mode 100644 index 0000000..f26908a --- /dev/null +++ b/host/trace_streamer/src/table/thread_filter_table.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "thread_filter_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, TYPE, NAME, INTERNAL_TID }; +} +ThreadFilterTable::ThreadFilterTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("name", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("itid", "UNSIGNED INT")); + tablePriKey_.push_back("id"); +} + +ThreadFilterTable::~ThreadFilterTable() {} + +void ThreadFilterTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +ThreadFilterTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstThreadFilterData().Size())) +{ +} + +ThreadFilterTable::Cursor::~Cursor() {} + +int ThreadFilterTable::Cursor::Column(int column) const +{ + switch (column) { + case ID: + sqlite3_result_int64(context_, + static_cast(dataCache_->GetConstThreadFilterData().FilterIdData()[CurrentRow()])); + break; + case TYPE: + sqlite3_result_text(context_, "thread_filter", STR_DEFAULT_LEN, nullptr); + break; + case NAME: { + std::string str = + dataCache_->GetDataFromDict(dataCache_->GetConstThreadFilterData().NameIndexData()[CurrentRow()]); + sqlite3_result_text(context_, str.c_str(), STR_DEFAULT_LEN, nullptr); + break; + } + case INTERNAL_TID: + sqlite3_result_int64(context_, + static_cast(dataCache_->GetConstThreadFilterData().InternalTidData()[CurrentRow()])); + break; + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/thread_filter_table.h b/host/trace_streamer/src/table/thread_filter_table.h new file mode 100644 index 0000000..c868ddd --- /dev/null +++ b/host/trace_streamer/src/table/thread_filter_table.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef THREAD_FILTER_TABLE_H +#define THREAD_FILTER_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class ThreadFilterTable : public TableBase { +public: + explicit ThreadFilterTable(const TraceDataCache* dataCache); + ~ThreadFilterTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // THREAD_FILTER_TABLE_H diff --git a/host/trace_streamer/src/table/thread_state_table.cpp b/host/trace_streamer/src/table/thread_state_table.cpp new file mode 100644 index 0000000..37dc915 --- /dev/null +++ b/host/trace_streamer/src/table/thread_state_table.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "thread_state_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, TYPE, TS, DUR, CPU, INTERNAL_TID, STATE }; +} +ThreadStateTable::ThreadStateTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "INT")); + tableColumn_.push_back(TableBase::ColumnInfo("type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("ts", "INT")); + tableColumn_.push_back(TableBase::ColumnInfo("dur", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("cpu", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("itid", "INT")); + tableColumn_.push_back(TableBase::ColumnInfo("state", "STRING")); + tablePriKey_.push_back("id"); +} + +ThreadStateTable::~ThreadStateTable() {} + +void ThreadStateTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +ThreadStateTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->GetConstThreadStateData().Size())), + threadStateObj_(dataCache->GetConstThreadStateData()) +{ +} + +ThreadStateTable::Cursor::~Cursor() {} + +int ThreadStateTable::Cursor::Column(int col) const +{ + switch (col) { + case ID: + sqlite3_result_int64(context_, static_cast(CurrentRow())); + break; + case TYPE: + sqlite3_result_text(context_, "thread_state", STR_DEFAULT_LEN, nullptr); + break; + case TS: + sqlite3_result_int64(context_, static_cast(threadStateObj_.TimeStamData()[CurrentRow()])); + break; + case DUR: + if (static_cast(threadStateObj_.DursData()[CurrentRow()]) >= 0) { + sqlite3_result_int64(context_, static_cast(threadStateObj_.DursData()[CurrentRow()])); + } + break; + case CPU: + if (static_cast(threadStateObj_.CpusData()[CurrentRow()]) >= 0) { + sqlite3_result_int64(context_, static_cast(threadStateObj_.CpusData()[CurrentRow()])); + } + break; + case INTERNAL_TID: + sqlite3_result_int64(context_, + static_cast(threadStateObj_.InternalTidsData()[CurrentRow()])); + break; + case STATE: { + const std::string& str = dataCache_->GetConstSchedStateData(threadStateObj_.StatesData()[CurrentRow()]); + sqlite3_result_text(context_, str.c_str(), STR_DEFAULT_LEN, nullptr); + break; + } + default: + TS_LOGF("Unregistered column : %d", col); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/thread_state_table.h b/host/trace_streamer/src/table/thread_state_table.h new file mode 100644 index 0000000..374a4a8 --- /dev/null +++ b/host/trace_streamer/src/table/thread_state_table.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef THREAD_STATE_TABLE_H +#define THREAD_STATE_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class ThreadStateTable : public TableBase { +public: + explicit ThreadStateTable(const TraceDataCache* dataCache); + ~ThreadStateTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int col) const override; + + private: + const ThreadState& threadStateObj_; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif // THREAD_STATE_TABLE_H diff --git a/host/trace_streamer/src/table/thread_table.cpp b/host/trace_streamer/src/table/thread_table.cpp new file mode 100644 index 0000000..af67cb7 --- /dev/null +++ b/host/trace_streamer/src/table/thread_table.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "thread_table.h" + +namespace SysTuning { +namespace TraceStreamer { +namespace { +enum Index { ID = 0, TYPE, TID, NAME, START_TS, END_TS, INTERNAL_PID, IS_MAIN_THREAD }; +} +ThreadTable::ThreadTable(const TraceDataCache* dataCache) : TableBase(dataCache) +{ + tableColumn_.push_back(TableBase::ColumnInfo("id", "INT")); + tableColumn_.push_back(TableBase::ColumnInfo("type", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("tid", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("name", "STRING")); + tableColumn_.push_back(TableBase::ColumnInfo("start_ts", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("end_ts", "UNSIGNED BIG INT")); + tableColumn_.push_back(TableBase::ColumnInfo("ipid", "UNSIGNED INT")); + tableColumn_.push_back(TableBase::ColumnInfo("is_main_thread", "UNSIGNED INT")); + tablePriKey_.push_back("id"); +} + +ThreadTable::~ThreadTable() {} + +void ThreadTable::CreateCursor() +{ + cursor_ = std::make_unique(dataCache_); +} + +ThreadTable::Cursor::Cursor(const TraceDataCache* dataCache) + : TableBase::Cursor(dataCache, 0, static_cast(dataCache->ThreadSize())) +{ +} + +ThreadTable::Cursor::~Cursor() {} + +int ThreadTable::Cursor::Column(int column) const +{ + switch (column) { + case ID: { + sqlite3_result_int64(context_, CurrentRow()); + break; + } + case TYPE: { + sqlite3_result_text(context_, "thread", strlen("thread"), nullptr); + break; + } + case TID: { + const auto& process = dataCache_->GetConstThreadData(CurrentRow()); + sqlite3_result_int64(context_, static_cast(process.tid_)); + break; + } + case NAME: { + const auto& thread = dataCache_->GetConstThreadData(CurrentRow()); + const auto& name = dataCache_->GetDataFromDict(thread.nameIndex_); + sqlite3_result_text(context_, name.c_str(), static_cast(name.length()), nullptr); + break; + } + case START_TS: { + const auto& thread = dataCache_->GetConstThreadData(CurrentRow()); + if (thread.startT_) { + sqlite3_result_int64(context_, static_cast(thread.startT_)); + } + break; + } + case END_TS: { + const auto& thread = dataCache_->GetConstThreadData(CurrentRow()); + if (thread.endT_) { + sqlite3_result_int64(context_, static_cast(thread.endT_)); + } + break; + } + case INTERNAL_PID: { + const auto& thread = dataCache_->GetConstThreadData(CurrentRow()); + if (thread.internalPid_) { + sqlite3_result_int(context_, static_cast(thread.internalPid_)); + } + break; + } + case IS_MAIN_THREAD: { + const auto& thread = dataCache_->GetConstThreadData(CurrentRow()); + // When it is not clear which process the thread belongs to, is_main_thread should be set to null + if (!thread.internalPid_) { + break; + } + const auto& process = dataCache_->GetConstProcessData(thread.internalPid_); + sqlite3_result_int(context_, thread.tid_ == process.pid_); + break; + } + default: + TS_LOGF("Unregistered column : %d", column); + break; + } + return SQLITE_OK; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/table/thread_table.h b/host/trace_streamer/src/table/thread_table.h new file mode 100644 index 0000000..960f908 --- /dev/null +++ b/host/trace_streamer/src/table/thread_table.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_THREAD_TABLE_H +#define SRC_THREAD_TABLE_H + +#include "table_base.h" +#include "trace_data_cache.h" + +namespace SysTuning { +namespace TraceStreamer { +class ThreadTable : public TableBase { +public: + explicit ThreadTable(const TraceDataCache* dataCache); + ~ThreadTable() override; + void CreateCursor() override; + +private: + class Cursor : public TableBase::Cursor { + public: + explicit Cursor(const TraceDataCache* dataCache); + ~Cursor() override; + int Column(int column) const override; + }; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // SRC_THREAD_TABLE_H diff --git a/host/trace_streamer/src/trace_data/trace_data.pri b/host/trace_streamer/src/trace_data/trace_data.pri new file mode 100644 index 0000000..ec15f89 --- /dev/null +++ b/host/trace_streamer/src/trace_data/trace_data.pri @@ -0,0 +1,28 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +INCLUDEPATH +=$$PWD +HEADERS += \ + $$PWD/trace_data_cache.h \ + $$PWD/trace_data_cache_base.h \ + $$PWD/trace_data_cache_reader.h \ + $$PWD/trace_data_cache_writer.h \ + $$PWD/trace_data_db.h \ + $$PWD/trace_stdtype.h + +SOURCES += \ + $$PWD/trace_data_cache.cpp \ + $$PWD/trace_data_cache_base.cpp \ + $$PWD/trace_data_cache_reader.cpp \ + $$PWD/trace_data_cache_writer.cpp \ + $$PWD/trace_data_db.cpp \ + $$PWD/trace_stdtype.cpp diff --git a/host/trace_streamer/src/trace_data/trace_data_cache.cpp b/host/trace_streamer/src/trace_data/trace_data_cache.cpp new file mode 100644 index 0000000..900d411 --- /dev/null +++ b/host/trace_streamer/src/trace_data/trace_data_cache.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "trace_data_cache.h" +#include +#include "args_table.h" +#include "callstack_table.h" +#include "clk_event_filter_table.h" +#include "clock_event_filter_table.h" +#include "cpu_measure_filter_table.h" +#include "data_dict_table.h" +#include "data_type_table.h" +#include "filter_table.h" +#include "heap_frame_table.h" +#include "heap_table.h" +#include "hidump_table.h" +#include "instants_table.h" +#include "irq_table.h" +#include "log_table.h" +#include "measure_filter_table.h" +#include "measure_table.h" +#include "meta_table.h" +#include "process_filter_table.h" +#include "process_measure_filter_table.h" +#include "process_table.h" +#include "range_table.h" +#include "raw_table.h" +#include "sched_slice_table.h" +#include "stat_table.h" +#include "symbols_table.h" +#include "system_call_table.h" +#include "system_event_filter_table.h" +#include "table_base.h" +#include "thread_filter_table.h" +#include "thread_state_table.h" +#include "thread_table.h" + + +namespace SysTuning { +namespace TraceStreamer { +TraceDataCache::TraceDataCache() +{ + InitDB(); +} + +TraceDataCache::~TraceDataCache() {} + +void TraceDataCache::InitDB() +{ + if (dbInited) { + return; + } + TableBase::TableDeclare(*db_, this, "_process"); + TableBase::TableDeclare(*db_, this, "_sched_slice"); + TableBase::TableDeclare(*db_, this, "_callstack"); + TableBase::TableDeclare(*db_, this, "_irq"); + TableBase::TableDeclare(*db_, this, "_data_dict"); + TableBase::TableDeclare(*db_, this, "_thread_state"); + TableBase::TableDeclare(*db_, this, "_instant"); + TableBase::TableDeclare(*db_, this, "_measure"); + TableBase::TableDeclare(*db_, this, "_trace_range"); + TableBase::TableDeclare(*db_, this, "_thread"); + TableBase::TableDeclare(*db_, this, "_raw"); + TableBase::TableDeclare(*db_, this, "_cpu_measure_filter"); + TableBase::TableDeclare(*db_, this, "_measure_filter"); + TableBase::TableDeclare(*db_, this, "_process_measure_filter"); + TableBase::TableDeclare(*db_, this, "_stat"); + TableBase::TableDeclare(*db_, this, "_clock_event_filter"); + TableBase::TableDeclare(*db_, this, "_clk_event_filter"); + TableBase::TableDeclare(*db_, this, "_symbols"); + TableBase::TableDeclare(*db_, this, "_syscall"); + TableBase::TableDeclare(*db_, this, "_args"); + TableBase::TableDeclare(*db_, this, "_data_type"); + TableBase::TableDeclare(*db_, this, "_meta"); + TableBase::TableDeclare(*db_, this, "_log"); + TableBase::TableDeclare(*db_, this, "_heap"); + TableBase::TableDeclare(*db_, this, "_heap_frame"); + TableBase::TableDeclare(*db_, this, "_hidump"); + TableBase::TableDeclare(*db_, this, "_sys_event_filter"); + dbInited = true; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/trace_data/trace_data_cache.h b/host/trace_streamer/src/trace_data/trace_data_cache.h new file mode 100644 index 0000000..e1f995d --- /dev/null +++ b/host/trace_streamer/src/trace_data/trace_data_cache.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TRACE_DATA_CACHE_H +#define TRACE_DATA_CACHE_H + +#include "trace_data_cache_reader.h" +#include "trace_data_cache_writer.h" +#include "trace_data_db.h" + +namespace SysTuning { +namespace TraceStreamer { +using namespace TraceStdtype; +class TraceDataCache : public TraceDataCacheReader, public TraceDataCacheWriter, public TraceDataDB { +public: + TraceDataCache(); + TraceDataCache(const TraceDataCache* dataCache) = delete; + TraceDataCache* operator=(const TraceDataCache* dataCache) = delete; + ~TraceDataCache() override; + +private: + void InitDB() override; + bool dbInited = false; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // TRACE_DATA_CACHE_H diff --git a/host/trace_streamer/src/trace_data/trace_data_cache_base.cpp b/host/trace_streamer/src/trace_data/trace_data_cache_base.cpp new file mode 100644 index 0000000..62314ea --- /dev/null +++ b/host/trace_streamer/src/trace_data/trace_data_cache_base.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "trace_data_cache_base.h" +#include + +namespace SysTuning { +namespace TraceStreamer { +TraceDataCacheBase::TraceDataCacheBase() +{ + internalProcessesData_.emplace_back(0); + internalThreadsData_.emplace_back(0); + + GetDataIndex(""); +} +DataIndex TraceDataCacheBase::GetDataIndex(std::string_view str) +{ + return dataDict_.GetStringIndex(str); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/trace_data/trace_data_cache_base.h b/host/trace_streamer/src/trace_data/trace_data_cache_base.h new file mode 100644 index 0000000..cc1181a --- /dev/null +++ b/host/trace_streamer/src/trace_data/trace_data_cache_base.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TRACE_DATA_CACHE_BASE_H +#define TRACE_DATA_CACHE_BASE_H + + +#include +#include +#include +#include +#include +#include +#include "trace_stdtype.h" +namespace SysTuning { +namespace TraceStreamer { +using namespace TraceStdtype; +class TraceDataCacheBase { +public: + TraceDataCacheBase(); + TraceDataCacheBase(const TraceDataCacheBase&) = delete; + TraceDataCacheBase& operator=(const TraceDataCacheBase&) = delete; + virtual ~TraceDataCacheBase() = default; + +public: + size_t ThreadSize() const + { + return internalThreadsData_.size(); + } + size_t ProcessSize() const + { + return internalProcessesData_.size(); + } + + size_t DataDictSize() const + { + return dataDict_.Size(); + } + void UpdateTraceRange() + { + metaData_.SetTraceDuration((traceEndTime_ - traceStartTime_) / SEC_TO_NS); + } + DataIndex GetDataIndex(std::string_view str); + std::map statusString_ = {{TASK_RUNNABLE, "R"}, + {TASK_INTERRUPTIBLE, "S"}, + {TASK_UNINTERRUPTIBLE, "D"}, + {TASK_RUNNING, "Running"}, + {TASK_INTERRUPTED, "I"}, + {TASK_TRACED, "T"}, + {TASK_EXIT_DEAD, "X"}, + {TASK_ZOMBIE, "Z"}, + {TASK_KILLED, "I"}, + {TASK_WAKEKILL, "R"}, + {TASK_INVALID, "U"}, + {TASK_CLONE, "I"}, + {TASK_DK, "DK"}, + {TASK_TRACED_KILL, "TK"}, + {TASK_FOREGROUND, "R+"}, + {TASK_MAX, "S"}}; + uint64_t traceStartTime_ = std::numeric_limits::max(); + uint64_t traceEndTime_ = 0; + + Raw rawData_; + ThreadState threadStateData_; + Instants instantsData_; + + Filter filterData_; + ProcessMeasureFilter processMeasureFilterData_; + ClockEventData clockEventFilterData_; + ClkEventData clkEventFilterData_; + ProcessMeasureFilter processFilterData_; + ThreadMeasureFilter threadMeasureFilterData_; + ThreadMeasureFilter threadFilterData_; + DataDict dataDict_; + + SchedSlice schedSliceData_; + CallStack callstackData_; + CallStack irqData_; + LogInfo hilogData_; + HeapInfo heapData_; + HeapFrameInfo heapFrameData_; + Hidump hidumpData_; + + std::deque internalProcessesData_ = {}; + std::deque internalThreadsData_ = {}; + + Measure measureData_; + CpuMeasureFilter cpuMeasureData_; + + StatAndInfo stat_; + MetaData metaData_; + SymbolsData symbolsData_; + SysCall sysCallData_; + ArgSet argSet_; + DataType dataType_; + SysMeasureFilter sysEvent_; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // TRACE_DATA_CACHE_BASE_H diff --git a/host/trace_streamer/src/trace_data/trace_data_cache_reader.cpp b/host/trace_streamer/src/trace_data/trace_data_cache_reader.cpp new file mode 100644 index 0000000..095e648 --- /dev/null +++ b/host/trace_streamer/src/trace_data/trace_data_cache_reader.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "trace_data_cache_reader.h" +#include "log.h" +namespace SysTuning { +namespace TraceStreamer { +using namespace TraceStdtype; +TraceDataCacheReader::~TraceDataCacheReader() {} +const std::string& TraceDataCacheReader::GetDataFromDict(DataIndex id) const +{ + return dataDict_.GetDataFromDict(id); +} +const Process& TraceDataCacheReader::GetConstProcessData(InternalPid internalPid) const +{ + TS_ASSERT(internalPid < internalProcessesData_.size()); + return internalProcessesData_[internalPid]; +} +const Thread& TraceDataCacheReader::GetConstThreadData(InternalTid internalTid) const +{ + TS_ASSERT(internalTid < internalThreadsData_.size()); + return internalThreadsData_[internalTid]; +} +const CallStack& TraceDataCacheReader::GetConstInternalSlicesData() const +{ + return callstackData_; +} +const CallStack& TraceDataCacheReader::GetConstIrqData() const +{ + return irqData_; +} +const Filter& TraceDataCacheReader::GetConstFilterData() const +{ + return filterData_; +} +const Raw& TraceDataCacheReader::GetConstRawTableData() const +{ + return rawData_; +} +const Measure& TraceDataCacheReader::GetConstMeasureData() const +{ + return measureData_; +} + +const ThreadMeasureFilter& TraceDataCacheReader::GetConstThreadMeasureFilterData() const +{ + return threadMeasureFilterData_; +} +const ThreadState& TraceDataCacheReader::GetConstThreadStateData() const +{ + return threadStateData_; +} +const SchedSlice& TraceDataCacheReader::GetConstSchedSliceData() const +{ + return schedSliceData_; +} +const CpuMeasureFilter& TraceDataCacheReader::GetConstCpuMeasureData() const +{ + return cpuMeasureData_; +} +const ThreadMeasureFilter& TraceDataCacheReader::GetConstThreadFilterData() const +{ + return threadFilterData_; +} +const Instants& TraceDataCacheReader::GetConstInstantsData() const +{ + return instantsData_; +} +const ProcessMeasureFilter& TraceDataCacheReader::GetConstProcessFilterData() const +{ + return processFilterData_; +} +const ProcessMeasureFilter& TraceDataCacheReader::GetConstProcessMeasureFilterData() const +{ + return processMeasureFilterData_; +} + +const ClockEventData& TraceDataCacheReader::GetConstClockEventFilterData() const +{ + return clockEventFilterData_; +} +const ClkEventData& TraceDataCacheReader::GetConstClkEventFilterData() const +{ + return clkEventFilterData_; +} +const std::string& TraceDataCacheReader::GetConstSchedStateData(uint64_t rowId) const +{ + TS_ASSERT(statusString_.find(rowId) != statusString_.end()); + return statusString_.at(rowId); +} +uint64_t TraceDataCacheReader::TraceStartTime() const +{ + return traceStartTime_; +} +uint64_t TraceDataCacheReader::TraceEndTime() const +{ + return traceEndTime_; +} + +const StatAndInfo& TraceDataCacheReader::GetConstStatAndInfo() const +{ + return stat_; +} +const MetaData& TraceDataCacheReader::GetConstMetaData() const +{ + return metaData_; +} + +const SymbolsData& TraceDataCacheReader::GetConstSymbolsData() const +{ + return symbolsData_; +} + +const LogInfo& TraceDataCacheReader::GetConstHilogData() const +{ + return hilogData_; +} + +const HeapInfo& TraceDataCacheReader::GetConstHeapData() const +{ + return heapData_; +} + +const HeapFrameInfo& TraceDataCacheReader::GetConstHeapFrameData() const +{ + return heapFrameData_; +} + +const Hidump& TraceDataCacheReader::GetConstHidumpData() const +{ + return hidumpData_; +} + +const SysCall& TraceDataCacheReader::GetConstSysCallData() const +{ + return sysCallData_; +} +const ArgSet& TraceDataCacheReader::GetConstArgSetData() const +{ + return argSet_; +} + +const DataType& TraceDataCacheReader::GetConstDataTypeData() const +{ + return dataType_; +} + +const SysMeasureFilter& TraceDataCacheReader::GetConstSysMeasureFilterData() const +{ + return sysEvent_; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/trace_data/trace_data_cache_reader.h b/host/trace_streamer/src/trace_data/trace_data_cache_reader.h new file mode 100644 index 0000000..ffe1f81 --- /dev/null +++ b/host/trace_streamer/src/trace_data/trace_data_cache_reader.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TRACE_DATA_CACHE_READER_H +#define TRACE_DATA_CACHE_READER_H + +#include "log.h" +#include "trace_data_cache_base.h" +#include "trace_stdtype.h" + +namespace SysTuning { +namespace TraceStreamer { +using namespace TraceStdtype; +class TraceDataCacheReader : virtual public TraceDataCacheBase { +public: + TraceDataCacheReader() = default; + TraceDataCacheReader(const TraceDataCacheReader&) = delete; + TraceDataCacheReader& operator=(const TraceDataCacheReader&) = delete; + ~TraceDataCacheReader() override; + +public: + const std::string& GetDataFromDict(DataIndex id) const; + const Process& GetConstProcessData(InternalPid internalPid) const; + const Thread& GetConstThreadData(InternalTid internalTid) const; + const CallStack& GetConstInternalSlicesData() const; + const CallStack& GetConstIrqData() const; + const Filter& GetConstFilterData() const; + const Raw& GetConstRawTableData() const; + const Measure& GetConstMeasureData() const; + const ThreadMeasureFilter& GetConstThreadMeasureFilterData() const; + const ThreadState& GetConstThreadStateData() const; + const SchedSlice& GetConstSchedSliceData() const; + const CpuMeasureFilter& GetConstCpuMeasureData() const; + const ThreadMeasureFilter& GetConstThreadFilterData() const; + const Instants& GetConstInstantsData() const; + const ProcessMeasureFilter& GetConstProcessFilterData() const; + const ProcessMeasureFilter& GetConstProcessMeasureFilterData() const; + const ClockEventData& GetConstClockEventFilterData() const; + const ClkEventData& GetConstClkEventFilterData() const; + const std::string& GetConstSchedStateData(uint64_t rowId) const; + uint64_t TraceStartTime() const; + uint64_t TraceEndTime() const; + const StatAndInfo& GetConstStatAndInfo() const; + const MetaData& GetConstMetaData() const; + const SymbolsData& GetConstSymbolsData() const; + const SysCall& GetConstSysCallData() const; + const LogInfo& GetConstHilogData() const; + const HeapInfo& GetConstHeapData() const; + const HeapFrameInfo& GetConstHeapFrameData() const; + const Hidump& GetConstHidumpData() const; + const ArgSet& GetConstArgSetData() const; + const DataType& GetConstDataTypeData() const; + const SysMeasureFilter& GetConstSysMeasureFilterData() const; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif diff --git a/host/trace_streamer/src/trace_data/trace_data_cache_writer.cpp b/host/trace_streamer/src/trace_data/trace_data_cache_writer.cpp new file mode 100644 index 0000000..4fc2925 --- /dev/null +++ b/host/trace_streamer/src/trace_data/trace_data_cache_writer.cpp @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "trace_data_cache_writer.h" +#include "log.h" +namespace SysTuning { +namespace TraceStreamer { +using namespace TraceStdtype; +TraceDataCacheWriter::~TraceDataCacheWriter() {} +InternalPid TraceDataCacheWriter::GetProcessInternalPid(uint32_t pid) +{ + internalProcessesData_.emplace_back(pid); + return static_cast(internalProcessesData_.size() - 1); +} +Process* TraceDataCacheWriter::GetProcessData(InternalPid internalPid) +{ + TS_ASSERT(internalPid < internalProcessesData_.size()); + return &internalProcessesData_[internalPid]; +} + +InternalTid TraceDataCacheWriter::NewInternalThread(uint32_t tid) +{ + internalThreadsData_.emplace_back(tid); + return static_cast(internalThreadsData_.size() - 1); +} +Thread* TraceDataCacheWriter::GetThreadData(InternalTid internalTid) +{ + if (internalTid >= internalThreadsData_.size()) { + return nullptr; + } + return &internalThreadsData_[internalTid]; +} + +void TraceDataCacheWriter::UpdateTraceTime(uint64_t timestamp) +{ + traceStartTime_ = std::min(traceStartTime_, timestamp); + traceEndTime_ = std::max(traceEndTime_, timestamp); +} + +void TraceDataCacheWriter::MixTraceTime(uint64_t timestampMin, uint64_t timestampMax) +{ + if (timestampMin == std::numeric_limits::max() || timestampMax == 0) { + return; + } + if (traceStartTime_ != std::numeric_limits::max()) { + traceStartTime_ = std::max(traceStartTime_, timestampMin); + } else { + traceStartTime_ = timestampMin; + } + if (traceEndTime_) { + traceEndTime_ = std::min(traceEndTime_, timestampMax); + } else { + traceEndTime_ = timestampMax; + } +} +CallStack* TraceDataCacheWriter::GetInternalSlicesData() +{ + return &callstackData_; +} +CallStack* TraceDataCacheWriter::GetIrqData() +{ + return &irqData_; +} + +Filter* TraceDataCacheWriter::GetFilterData() +{ + return &filterData_; +} + +Raw* TraceDataCacheWriter::GetRawData() +{ + return &rawData_; +} + +Measure* TraceDataCacheWriter::GetMeasureData() +{ + return &measureData_; +} + +ThreadState* TraceDataCacheWriter::GetThreadStateData() +{ + return &threadStateData_; +} + +SchedSlice* TraceDataCacheWriter::GetSchedSliceData() +{ + return &schedSliceData_; +} + +CpuMeasureFilter* TraceDataCacheWriter::GetCpuMeasuresData() +{ + return &cpuMeasureData_; +} + +ThreadMeasureFilter* TraceDataCacheWriter::GetThreadMeasureFilterData() +{ + return &threadMeasureFilterData_; +} + +ThreadMeasureFilter* TraceDataCacheWriter::GetThreadFilterData() +{ + return &threadFilterData_; +} + +Instants* TraceDataCacheWriter::GetInstantsData() +{ + return &instantsData_; +} + +ProcessMeasureFilter* TraceDataCacheWriter::GetProcessFilterData() +{ + return &processFilterData_; +} + +ProcessMeasureFilter* TraceDataCacheWriter::GetProcessMeasureFilterData() +{ + return &processMeasureFilterData_; +} + +ClockEventData* TraceDataCacheWriter::GetClockEventFilterData() +{ + return &clockEventFilterData_; +} + +ClkEventData* TraceDataCacheWriter::GetClkEventFilterData() +{ + return &clkEventFilterData_; +} +StatAndInfo* TraceDataCacheWriter::GetStatAndInfo() +{ + return &stat_; +} + +MetaData* TraceDataCacheWriter::GetMetaData() +{ + return &metaData_; +} + +SymbolsData* TraceDataCacheWriter::GetSymbolsData() +{ + return &symbolsData_; +} +SysCall* TraceDataCacheWriter::GetSysCallData() +{ + return &sysCallData_; +} +LogInfo* TraceDataCacheWriter::GetHilogData() +{ + return &hilogData_; +} + +HeapInfo* TraceDataCacheWriter::GetHeapData() +{ + return &heapData_; +} + +HeapFrameInfo* TraceDataCacheWriter::GetHeapFrameData() +{ + return &heapFrameData_; +} + +Hidump* TraceDataCacheWriter::GetHidumpData() +{ + return &hidumpData_; +} + +ArgSet* TraceDataCacheWriter::GetArgSetData() +{ + return &argSet_; +} + +DataType* TraceDataCacheWriter::GetDataTypeData() +{ + return &dataType_; +} + +SysMeasureFilter* TraceDataCacheWriter::GetSysMeasureFilterData() +{ + return &sysEvent_; +} +void TraceDataCacheWriter::Clear() +{ + rawData_.Clear(); + threadStateData_.Clear(); + instantsData_.Clear(); + + filterData_.Clear(); + processMeasureFilterData_.Clear(); + clockEventFilterData_.Clear(); + clkEventFilterData_.Clear(); + processFilterData_.Clear(); + threadMeasureFilterData_.Clear(); + threadFilterData_.Clear(); + dataDict_.Clear(); + + schedSliceData_.Clear(); + callstackData_.Clear(); + irqData_.Clear(); + hilogData_.Clear(); + heapData_.Clear(); + heapFrameData_.Clear(); + hidumpData_.Clear(); + + internalProcessesData_.clear(); + internalThreadsData_.clear(); + + measureData_.Clear(); + cpuMeasureData_.Clear(); + + metaData_.Clear(); + symbolsData_.Clear(); + sysCallData_.Clear(); + argSet_.Clear(); + dataType_.Clear(); + sysEvent_.Clear(); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/trace_data/trace_data_cache_writer.h b/host/trace_streamer/src/trace_data/trace_data_cache_writer.h new file mode 100644 index 0000000..5d42104 --- /dev/null +++ b/host/trace_streamer/src/trace_data/trace_data_cache_writer.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TRACE_DATA_CACHE_WRITER_H +#define TRACE_DATA_CACHE_WRITER_H + +#include "trace_data_cache_reader.h" + +namespace SysTuning { +namespace TraceStreamer { +using namespace TraceStdtype; +class TraceDataCacheWriter : virtual public TraceDataCacheBase { +public: + TraceDataCacheWriter() = default; + TraceDataCacheWriter(const TraceDataCacheWriter&) = delete; + TraceDataCacheWriter& operator=(const TraceDataCacheWriter&) = delete; + ~TraceDataCacheWriter() override; + void Clear(); + +public: + InternalPid GetProcessInternalPid(uint32_t pid); + Process* GetProcessData(InternalPid internalPid); + InternalTid NewInternalThread(uint32_t tid); + Thread* GetThreadData(InternalTid internalTid); + void UpdateTraceTime(uint64_t timestamp); + void MixTraceTime(uint64_t timestampMin, uint64_t timestampMax); + CallStack* GetInternalSlicesData(); + CallStack* GetIrqData(); + Filter* GetFilterData(); + Raw* GetRawData(); + Measure* GetMeasureData(); + ThreadState* GetThreadStateData(); + SchedSlice* GetSchedSliceData(); + CpuMeasureFilter* GetCpuMeasuresData(); + ThreadMeasureFilter* GetThreadMeasureFilterData(); + ThreadMeasureFilter* GetThreadFilterData(); + Instants* GetInstantsData(); + ProcessMeasureFilter* GetProcessFilterData(); + ProcessMeasureFilter* GetProcessMeasureFilterData(); + ClockEventData* GetClockEventFilterData(); + ClkEventData* GetClkEventFilterData(); + StatAndInfo* GetStatAndInfo(); + MetaData* GetMetaData(); + SymbolsData* GetSymbolsData(); + SysCall* GetSysCallData(); + LogInfo* GetHilogData(); + HeapInfo* GetHeapData(); + HeapFrameInfo* GetHeapFrameData(); + Hidump* GetHidumpData(); + ArgSet* GetArgSetData(); + DataType* GetDataTypeData(); + SysMeasureFilter* GetSysMeasureFilterData(); +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif diff --git a/host/trace_streamer/src/trace_data/trace_data_db.cpp b/host/trace_streamer/src/trace_data/trace_data_db.cpp new file mode 100644 index 0000000..07fd63a --- /dev/null +++ b/host/trace_streamer/src/trace_data/trace_data_db.cpp @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "trace_data_db.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "codec_cov.h" +#include "ext/sqlite_ext_funcs.h" +#include "file.h" +#include "log.h" + +namespace SysTuning { +namespace TraceStreamer { +int PrintQueryResult(void* para, int column, char** columnValue, char** columnName) +{ + int i; + printf("Query results include %d column\n", column); + for (i = 0; i < column; i++) { + printf("name : %s \t value : %s\n", columnName[i], columnValue[i]); + } + printf("------------------\n"); + return 0; +} +TraceDataDB::TraceDataDB() : db_(nullptr) +{ + if (sqlite3_threadsafe() > 0) { + int retCode = sqlite3_config(SQLITE_CONFIG_SERIALIZED); + if (retCode == SQLITE_OK) { + TS_LOGI("Can now use sqlite on multiple threads, using the same connection"); + } else { + TS_LOGE("setting sqlite thread safe mode to serialized failed!!! return code: %d", retCode); + } + } else { + TS_LOGE("Your SQLite database is not compiled to be threadsafe."); + } + if (sqlite3_open(":memory:", &db_)) { + TS_LOGF("open :memory db failed"); + } + CreateExtendFunction(db_); +} + +TraceDataDB::~TraceDataDB() +{ + sqlite3_close(db_); +} + +void TraceDataDB::AppendNewTable(std::string tableName) +{ + internalTables_.push_back(tableName); +} +void TraceDataDB::EnableMetaTable(bool enabled) +{ + exportMetaTable_ = enabled; +} +int TraceDataDB::ExportDatabase(const std::string& outputName) +{ + { + int fd(base::OpenFile(outputName, O_CREAT | O_RDWR, 0600)); + if (!fd) { + fprintf(stdout, "Failed to create file: %s", outputName.c_str()); + return 1; + } + ftruncate(fd, 0); + close(fd); + } + + std::string attachSql("ATTACH DATABASE '" + outputName + "' AS systuning_export"); +#ifdef _WIN32 + if (!base::GetCoding(reinterpret_cast(attachSql.c_str()), attachSql.length())) { + attachSql = base::GbkToUtf8(attachSql.c_str()); + } +#endif + ExecuteSql(attachSql); + + for (auto itor = internalTables_.begin(); itor != internalTables_.end(); itor++) { + if (*itor == "meta") { + if (!exportMetaTable_) { + continue; + } + if ((*itor) != "_data_dict") { + std::string exportSql("CREATE TABLE systuning_export." + (*itor).substr(1, -1) + + " AS SELECT * FROM " + *itor); + ExecuteSql(exportSql); + } + } else { + std::string exportSql("CREATE TABLE systuning_export." + (*itor).substr(1, -1) + + " AS SELECT * FROM " + *itor); + ExecuteSql(exportSql); + } + } + std::string createArgsView = + "create view systuning_export.args_view AS select A.argset, V2.data as keyName, A.id, D.desc, (case when " + "A.datatype==1 then V.data else A.value end) as strValue from args as A left join data_type as D on (D.typeId " + "= A.datatype) left join data_dict as V on V.id = A.value left join data_dict as V2 on V2.id = A.key"; + ExecuteSql(createArgsView); + std::string updateProcessName = + "update process set name = (select name from thread t where t.ipid = process.id and t.name is not null and " + "is_main_thread = 1)"; + ExecuteSql(updateProcessName); + std::string detachSql("DETACH DATABASE systuning_export"); + ExecuteSql(detachSql); + return 0; +} +void TraceDataDB::Prepare() +{ + if (pared_) { + return; + } + pared_ = true; + for (auto itor = internalTables_.begin(); itor != internalTables_.end(); itor++) { + std::string exportSql("CREATE TABLE " + (*itor).substr(1, -1) + " AS SELECT * FROM " + *itor); + ExecuteSql(exportSql); + } + std::string createArgsView = + "create view args_view AS select A.argset, V2.data as keyName, A.id, D.desc, (case when " + "A.datatype==1 then V.data else A.value end) as strValue from args as A left join data_type as D on " + "(D.typeId " + "= A.datatype) left join data_dict as V on V.id = A.value left join data_dict as V2 on V2.id = A.key"; + ExecuteSql(createArgsView); + std::string updateProcessNewName = + "update process set name = (select name from thread t where t.ipid = process.id and t.name is not " + "null and " + "is_main_thread = 1)"; + ExecuteSql(updateProcessNewName); +} +void TraceDataDB::ExecuteSql(const std::string_view& sql) +{ + sqlite3_stmt* stmt = nullptr; + int ret = sqlite3_prepare_v2(db_, sql.data(), static_cast(sql.size()), &stmt, nullptr); + + while (!ret) { + int err = sqlite3_step(stmt); + if (err == SQLITE_ROW) { + continue; + } + if (err == SQLITE_DONE) { + break; + } + ret = err; + } + + sqlite3_finalize(stmt); +} +int TraceDataDB::SearchData() +{ + Prepare(); + int result; + char* errmsg = nullptr; + std::string line; + for (;;) { + std::cout << "> "; + getline(std::cin, line); + if (line.empty()) { + std::cout << "If you want to quit either type -q or press CTRL-Z" << std::endl; + continue; + } + if (!line.compare("-q") || !line.compare("-quit")) { + break; + } else if (!line.compare("-e")) { + TS_LOGI("the db file will be at current folder, the name is default.db"); + return ExportDatabase("default.db"); + } else if (!line.compare("-help") || !line.compare("-h")) { + std::cout << "use info" << std::endl; + continue; + } + result = sqlite3_exec(db_, line.c_str(), PrintQueryResult, NULL, &errmsg); + } + return 0; +} +int TraceDataDB::OperateDatabase(const std::string& sql) +{ + Prepare(); + char* errmsg = nullptr; + int ret = sqlite3_exec(db_, sql.c_str(), NULL, NULL, &errmsg); + if (ret != SQLITE_OK && errmsg) { + TS_LOGE("sqlite3_exec(%s) failed: %d:%s", sql.c_str(), ret, errmsg); + sqlite3_free(errmsg); + } + return ret; +} +int TraceDataDB::SearchDatabase(const std::string& sql, ResultCallBack resultCallBack) +{ + Prepare(); + sqlite3_stmt* stmt = nullptr; + int ret = sqlite3_prepare_v2(db_, sql.c_str(), static_cast(sql.size()), &stmt, nullptr); + if (ret != SQLITE_OK) { + TS_LOGE("sqlite3_prepare_v2(%s) failed: %d:", sql.c_str(), ret); + return ret; + } + if (!resultCallBack) { + return ret; + } + + const size_t maxLenResponse = 4 * 1024; + std::string res; + res.reserve(maxLenResponse); + res = "ok\r\n"; + int colCount = sqlite3_column_count(stmt); + if (colCount == 0) { + resultCallBack(res); + return ret; + } + res += "{\"columns\":["; + for (int i = 0; i < colCount; i++) { + res += "\""; + res += sqlite3_column_name(stmt, i); + res += "\","; + } + res.pop_back(); // remove the last "," + res += "],\"values\":["; + bool hasRow = false; + constexpr int defaultLenRowString = 1024; + std::string row; + row.reserve(defaultLenRowString); + while (sqlite3_step(stmt) == SQLITE_ROW) { + hasRow = true; + GetRowString(stmt, colCount, row); + if (res.size() + row.size() + strlen(",]}\r\n") >= maxLenResponse) { + resultCallBack(res); + res.clear(); + } + res += row + ","; + } + if (hasRow) { + res.pop_back(); // remove the last ',' + } + res += "]}\r\n"; + resultCallBack(res); + + sqlite3_finalize(stmt); + return ret; +} +int TraceDataDB::SearchDatabase(const std::string& sql, uint8_t* out, int outLen) +{ + Prepare(); + sqlite3_stmt* stmt = nullptr; + int ret = sqlite3_prepare_v2(db_, sql.c_str(), static_cast(sql.size()), &stmt, nullptr); + if (ret != SQLITE_OK) { + TS_LOGE("sqlite3_prepare_v2(%s) failed: %d:", sql.c_str(), ret); + return -1; + } + char* res = reinterpret_cast(out); + int retSnprintf = std::snprintf(res, outLen, "%s", "ok\r\n"); + if (retSnprintf < 0) { + return -1; + } + int pos = retSnprintf; + int colCount = sqlite3_column_count(stmt); + if (colCount == 0) { + return pos; + } + retSnprintf = std::snprintf(res + pos, outLen - pos, "%s", "{\"columns\":["); + if (retSnprintf < 0) { + return -1; + } + pos += retSnprintf; + for (int i = 0; i < colCount; i++) { + retSnprintf = std::snprintf(res + pos, outLen - pos, "%s%s%s", "\"", sqlite3_column_name(stmt, i), "\","); + if (retSnprintf < 0) { + return -1; + } + pos += retSnprintf; + } + pos--; // rmove the last ',' + retSnprintf = std::snprintf(res + pos, outLen - pos, "%s", "],\"values\":["); + if (retSnprintf < 0) { + return -1; + } + pos += retSnprintf; + bool hasRow = false; + constexpr int defaultLenRowString = 1024; + std::string row; + row.reserve(defaultLenRowString); + while (sqlite3_step(stmt) == SQLITE_ROW) { + hasRow = true; + GetRowString(stmt, colCount, row); + if (pos + row.size() + strlen(",]}\r\n") >= size_t(outLen)) { + retSnprintf = std::snprintf(res + pos, outLen - pos, "%s", "]}\r\n"); + if (retSnprintf < 0) { + return -1; + } + pos += retSnprintf; + sqlite3_finalize(stmt); + return pos; + } + retSnprintf = std::snprintf(res + pos, outLen - pos, "%s%s", row.c_str(), ","); + if (retSnprintf < 0) { + return -1; + } + pos += retSnprintf; + } + if (hasRow) { + pos--; // remove the last ',' + } + retSnprintf = std::snprintf(res + pos, outLen - pos, "%s", "]}\r\n"); + if (retSnprintf < 0) { + return -1; + } + pos += retSnprintf; + sqlite3_finalize(stmt); + return pos; +} +void TraceDataDB::GetRowString(sqlite3_stmt* stmt, int colCount, std::string& rowStr) +{ + rowStr.clear(); + rowStr = "["; + for (int i = 0; i < colCount; i++) { + const char* p = reinterpret_cast(sqlite3_column_text(stmt, i)); + if (p == nullptr) { + rowStr += "null,"; + continue; + } + int type = sqlite3_column_type(stmt, i); + switch (type) { + case SQLITE_TEXT: + rowStr += "\""; + rowStr += p; + rowStr += "\""; + break; + default: + rowStr += p; + break; + } + rowStr += ","; + } + rowStr.pop_back(); // remove the last ',' + rowStr += "]"; +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/trace_data/trace_data_db.h b/host/trace_streamer/src/trace_data/trace_data_db.h new file mode 100644 index 0000000..d9ff9b6 --- /dev/null +++ b/host/trace_streamer/src/trace_data/trace_data_db.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TRACE_DATA_DB_H +#define TRACE_DATA_DB_H + +#include +#include +#include + +extern "C" { +#include "sqlite3.h" +} + +namespace SysTuning { +namespace TraceStreamer { +class TraceDataDB { +public: + TraceDataDB(); + TraceDataDB(const TraceDataDB&) = delete; + TraceDataDB& operator=(const TraceDataDB&) = delete; + virtual ~TraceDataDB(); + virtual void InitDB() = 0; + void Prepare(); +public: + int ExportDatabase(const std::string& outputName); + int SearchData(); + int OperateDatabase(const std::string& sql); + using ResultCallBack = std::function; + int SearchDatabase(const std::string& sql, ResultCallBack resultCallBack); + int SearchDatabase(const std::string& sql, uint8_t* out, int outLen); + void AppendNewTable(std::string tableName); + void EnableMetaTable(bool enabled); + +public: + sqlite3* db_; + +private: + void ExecuteSql(const std::string_view& sql); + void GetRowString(sqlite3_stmt* stmt, int colCount, std::string& rowStr); + std::list internalTables_ = {}; + bool exportMetaTable_ = false; + bool pared_ = false; +}; +} // namespace TraceStreamer +} // namespace SysTuning +#endif diff --git a/host/trace_streamer/src/trace_data/trace_stdtype.cpp b/host/trace_streamer/src/trace_data/trace_stdtype.cpp new file mode 100644 index 0000000..c791a84 --- /dev/null +++ b/host/trace_streamer/src/trace_data/trace_stdtype.cpp @@ -0,0 +1,695 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "trace_stdtype.h" +#include +namespace SysTuning { +namespace TraceStdtype { +size_t ThreadState::AppendThreadState(uint64_t ts, uint64_t dur, uint64_t cpu, uint64_t internalTid, uint64_t state) +{ + internalTids_.emplace_back(internalTid); + states_.emplace_back(state); + timeStamps_.emplace_back(ts); + durs_.emplace_back(dur); + cpus_.emplace_back(cpu); + return Size() - 1; +} + +void ThreadState::SetDuration(size_t index, uint64_t duration) +{ + durs_[index] = duration; +} + +uint64_t ThreadState::UpdateDuration(size_t index, uint64_t timestamp) +{ + if (durs_[index] == INVALID_UINT64) { + durs_[index] = timestamp - timeStamps_[index]; + } + return internalTids_[index]; +} + +void ThreadState::UpdateState(size_t index, uint64_t state) +{ + states_[index] = state; +} +void ThreadState::UpdateDuration(size_t index, uint64_t timestamp, uint64_t state) +{ + durs_[index] = timestamp - timeStamps_[index]; + states_[index] = state; +} + +uint64_t ThreadState::UpdateDuration(size_t index, uint64_t timestamp, uint64_t cpu, uint64_t state) +{ + cpus_[index] = cpu; + durs_[index] = timestamp - timeStamps_[index]; + states_[index] = state; + return internalTids_[index]; +} + +size_t SchedSlice::AppendSchedSlice(uint64_t ts, + uint64_t dur, + uint64_t cpu, + uint64_t internalTid, + uint64_t endState, + uint64_t priority) +{ + timeStamps_.emplace_back(ts); + durs_.emplace_back(dur); + cpus_.emplace_back(cpu); + internalTids_.emplace_back(internalTid); + endStates_.emplace_back(endState); + priority_.emplace_back(priority); + return Size() - 1; +} + +void SchedSlice::SetDuration(size_t index, uint64_t duration) +{ + durs_[index] = duration; +} + +void SchedSlice::Update(uint64_t index, uint64_t ts, uint64_t state, uint64_t pior) +{ + durs_[index] = ts - timeStamps_[index]; + endStates_[index] = state; + priority_[index] = pior; +} + +size_t CallStack::AppendInternalAsyncSlice(uint64_t startT, + uint64_t durationNs, + InternalTid internalTid, + DataIndex cat, + DataIndex name, + uint8_t depth, + uint64_t cookid, + const std::optional& parentId) +{ + AppendCommonInfo(startT, durationNs, internalTid); + AppendCallStack(cat, name, depth, parentId); + AppendDistributeInfo(); + cookies_.emplace_back(cookid); + ids_.emplace_back(ids_.size()); + return Size() - 1; +} +size_t CallStack::AppendInternalSlice(uint64_t startT, + uint64_t durationNs, + InternalTid internalTid, + DataIndex cat, + DataIndex name, + uint8_t depth, + const std::optional& parentId) +{ + AppendCommonInfo(startT, durationNs, internalTid); + AppendCallStack(cat, name, depth, parentId); + ids_.emplace_back(ids_.size()); + cookies_.emplace_back(INVALID_UINT64); + return Size() - 1; +} + +void CallStack::AppendCommonInfo(uint64_t startT, uint64_t durationNs, InternalTid internalTid) +{ + timeStamps_.emplace_back(startT); + durs_.emplace_back(durationNs); + callIds_.emplace_back(internalTid); +} +void CallStack::AppendCallStack(DataIndex cat, DataIndex name, uint8_t depth, std::optional parentId) +{ + parentIds_.emplace_back(parentId); + cats_.emplace_back(cat); + names_.emplace_back(name); + depths_.emplace_back(depth); +} +void CallStack::AppendDistributeInfo(const std::string& chainId, + const std::string& spanId, + const std::string& parentSpanId, + const std::string& flag, + const std::string& args) +{ + chainIds_.emplace_back(chainId); + spanIds_.emplace_back(spanId); + parentSpanIds_.emplace_back(parentSpanId); + flags_.emplace_back(flag); + args_.emplace_back(args); + argSet_.emplace_back(INVALID_UINT32); +} +void CallStack::AppendDistributeInfo() +{ + chainIds_.emplace_back(""); + spanIds_.emplace_back(""); + parentSpanIds_.emplace_back(""); + flags_.emplace_back(""); + args_.emplace_back(""); + argSet_.emplace_back(INVALID_UINT32); +} +void CallStack::AppendArgSet(uint32_t argSetId) +{ + chainIds_.emplace_back(""); + spanIds_.emplace_back(""); + parentSpanIds_.emplace_back(""); + flags_.emplace_back(""); + args_.emplace_back(""); + argSet_.emplace_back(argSetId); +} +void CallStack::SetDuration(size_t index, uint64_t timestamp) +{ + durs_[index] = timestamp - timeStamps_[index]; +} + +void CallStack::SetDurationAndArg(size_t index, uint64_t timestamp, uint32_t argSetId) +{ + SetTimeStamp(index, timestamp); + argSet_[index] = argSetId; +} +void CallStack::SetTimeStamp(size_t index, uint64_t timestamp) +{ + timeStamps_[index] = timestamp; +} + +const std::deque>& CallStack::ParentIdData() const +{ + return parentIds_; +} +const std::deque& CallStack::CatsData() const +{ + return cats_; +} +const std::deque& CallStack::NamesData() const +{ + return names_; +} +const std::deque& CallStack::Depths() const +{ + return depths_; +} +const std::deque& CallStack::Cookies() const +{ + return cookies_; +} +const std::deque& CallStack::CallIds() const +{ + return callIds_; +} +const std::deque& CallStack::ChainIds() const +{ + return chainIds_; +} +const std::deque& CallStack::SpanIds() const +{ + return spanIds_; +} +const std::deque& CallStack::ParentSpanIds() const +{ + return parentSpanIds_; +} +const std::deque& CallStack::Flags() const +{ + return flags_; +} +const std::deque& CallStack::ArgsData() const +{ + return args_; +} +const std::deque& CallStack::ArgSetIdsData() const +{ + return argSet_; +} + +size_t ArgSet::AppendNewArg(DataIndex nameId, BaseDataType dataType, int64_t value, size_t argSet) +{ + dataTypes_.emplace_back(dataType); + argset_.emplace_back(argSet); + ids_.emplace_back(Size()); + values_.emplace_back(value); + names_.emplace_back(nameId); + return Size() - 1; +} +const std::deque& ArgSet::DataTypes() const +{ + return dataTypes_; +} +const std::deque& ArgSet::ValuesData() const +{ + return values_; +} +const std::deque& ArgSet::ArgsData() const +{ + return argset_; +} +const std::deque& ArgSet::NamesData() const +{ + return names_; +} + +size_t SysMeasureFilter::AppendNewFilter(uint64_t filterId, DataIndex type, DataIndex nameId) +{ + ids_.emplace_back(filterId); + names_.emplace_back(nameId); + types_.emplace_back(type); + return ids_.size() - 1; +} +const std::deque& SysMeasureFilter::NamesData() const +{ + return names_; +} + +const std::deque& SysMeasureFilter::TypesData() const +{ + return types_; +} +size_t DataType::AppendNewDataType(BaseDataType dataType, DataIndex dataDescIndex) +{ + ids_.emplace_back(Size()); + dataTypes_.emplace_back(dataType); + descs_.emplace_back(dataDescIndex); + return Size() - 1; +} + +const std::deque& DataType::DataTypes() const +{ + return dataTypes_; +} +const std::deque& DataType::DataDesc() const +{ + return descs_; +} +size_t Filter::AppendNewFilterData(std::string type, std::string name, uint64_t sourceArgSetId) +{ + nameDeque_.emplace_back(name); + sourceArgSetId_.emplace_back(sourceArgSetId); + ids_.emplace_back(Size()); + typeDeque_.emplace_back(type); + return Size() - 1; +} + +size_t Measure::AppendMeasureData(uint32_t type, uint64_t timestamp, int64_t value, uint32_t filterId) +{ + valuesDeque_.emplace_back(value); + filterIdDeque_.emplace_back(filterId); + typeDeque_.emplace_back(type); + timeStamps_.emplace_back(timestamp); + return Size() - 1; +} + +size_t Raw::AppendRawData(uint32_t id, uint64_t timestamp, uint32_t name, uint32_t cpu, uint32_t internalTid) +{ + ids_.emplace_back(id); + timeStamps_.emplace_back(timestamp); + nameDeque_.emplace_back(name); + cpuDeque_.emplace_back(cpu); + itidDeque_.emplace_back(internalTid); + return Size() - 1; +} + +size_t ThreadMeasureFilter::AppendNewFilter(uint64_t filterId, uint32_t nameIndex, uint64_t internalTid) +{ + filterId_.emplace_back(filterId); + nameIndex_.emplace_back(nameIndex); + internalTids_.emplace_back(internalTid); + return Size() - 1; +} + +size_t Instants::AppendInstantEventData(uint64_t timestamp, DataIndex nameIndex, int64_t internalTid) +{ + internalTids_.emplace_back(internalTid); + timeStamps_.emplace_back(timestamp); + NameIndexs_.emplace_back(nameIndex); + return Size() - 1; +} +size_t LogInfo::AppendNewLogInfo(uint64_t seq, + uint64_t timestamp, + uint32_t pid, + uint32_t tid, + DataIndex level, + DataIndex tag, + DataIndex context, + uint64_t originTs) +{ + hilogLineSeqs_.emplace_back(seq); + timeStamps_.emplace_back(timestamp); + pids_.emplace_back(pid); + tids_.emplace_back(tid); + levels_.emplace_back(level); + tags_.emplace_back(tag); + contexts_.emplace_back(context); + originTs_.emplace_back(originTs); + return Size() - 1; +} +const std::deque& LogInfo::HilogLineSeqs() const +{ + return hilogLineSeqs_; +} +const std::deque& LogInfo::Pids() const +{ + return pids_; +} +const std::deque& LogInfo::Tids() const +{ + return tids_; +} +const std::deque& LogInfo::Levels() const +{ + return levels_; +} +const std::deque& LogInfo::Tags() const +{ + return tags_; +} +const std::deque& LogInfo::Contexts() const +{ + return contexts_; +} +const std::deque& LogInfo::OriginTimeStamData() const +{ + return originTs_; +} + +size_t HeapInfo::AppendNewHeapInfo(uint64_t eventId, + uint32_t ipid, + uint32_t itid, + DataIndex eventType, + uint64_t timestamp, + uint64_t endTimestamp, + uint64_t duration, + uint64_t addr, + int64_t heapSize, + int64_t allHeapSize, + uint64_t currentSizeDur) +{ + eventIds_.emplace_back(eventId); + ipids_.emplace_back(ipid); + itids_.emplace_back(itid); + eventTypes_.emplace_back(eventType); + timeStamps_.emplace_back(timestamp); + endTimestamps_.emplace_back(endTimestamp); + durations_.emplace_back(duration); + addrs_.emplace_back(addr); + heapSizes_.emplace_back(heapSize); + countHeapSizes_ += allHeapSize; + allHeapSizes_.emplace_back(countHeapSizes_); + currentSizeDurs_.emplace_back(currentSizeDur); + return Size() - 1; +} +void HeapInfo::UpdateHeapDuration(size_t row, uint64_t endTimestamp) +{ + endTimestamps_[row] = endTimestamp; + durations_[row] = endTimestamp - timeStamps_[row]; +} +void HeapInfo::UpdateCurrentSizeDur(size_t row, uint64_t nextStartTime) +{ + if (row == 0) { + return; + } + if (nextStartTime > timeStamps_[--row]) { + currentSizeDurs_[row] = nextStartTime - timeStamps_[row]; + } +} +const std::deque& HeapInfo::EventIds() const +{ + return eventIds_; +} +const std::deque& HeapInfo::Ipids() const +{ + return ipids_; +} +const std::deque& HeapInfo::Itids() const +{ + return itids_; +} +const std::deque& HeapInfo::EventTypes() const +{ + return eventTypes_; +} +const std::deque& HeapInfo::EndTimeStamps() const +{ + return endTimestamps_; +} +const std::deque& HeapInfo::Durations() const +{ + return durations_; +} +const std::deque& HeapInfo::Addrs() const +{ + return addrs_; +} +const std::deque& HeapInfo::HeapSizes() const +{ + return heapSizes_; +} +const std::deque& HeapInfo::AllHeapSizes() const +{ + return allHeapSizes_; +} +const std::deque& HeapInfo::CurrentSizeDurs() const +{ + return currentSizeDurs_; +} + +size_t HeapFrameInfo::AppendNewHeapFrameInfo(uint64_t eventId, + uint64_t depth, + DataIndex ip, + DataIndex sp, + DataIndex symbolName, + DataIndex filePath, + DataIndex offset, + uint64_t symbolOffset) +{ + eventIds_.emplace_back(eventId); + depths_.emplace_back(depth); + ips_.emplace_back(ip); + sps_.emplace_back(sp); + symbolNames_.emplace_back(symbolName); + filePaths_.emplace_back(filePath); + offsets_.emplace_back(offset); + symbolOffsets_.emplace_back(symbolOffset); + return Size() - 1; +} +const std::deque& HeapFrameInfo::EventIds() const +{ + return eventIds_; +} +const std::deque& HeapFrameInfo::Depths() const +{ + return depths_; +} +const std::deque& HeapFrameInfo::Ips() const +{ + return ips_; +} +const std::deque& HeapFrameInfo::Sps() const +{ + return sps_; +} +const std::deque& HeapFrameInfo::SymbolNames() const +{ + return symbolNames_; +} +const std::deque& HeapFrameInfo::FilePaths() const +{ + return filePaths_; +} +const std::deque& HeapFrameInfo::Offsets() const +{ + return offsets_; +} +const std::deque& HeapFrameInfo::SymbolOffsets() const +{ + return symbolOffsets_; +} + +size_t Hidump::AppendNewHidumpInfo(uint64_t timestamp, uint32_t fps) +{ + timeStamps_.emplace_back(timestamp); + fpss_.emplace_back(fps); + return Size() - 1; +} +const std::deque& Hidump::Fpss() const +{ + return fpss_; +} +size_t ProcessMeasureFilter::AppendNewFilter(uint64_t id, DataIndex name, uint32_t internalPid) +{ + internalPids_.emplace_back(internalPid); + ids_.emplace_back(id); + names_.emplace_back(name); + return Size() - 1; +} +size_t ClockEventData::AppendNewFilter(uint64_t id, DataIndex type, DataIndex name, uint64_t cpu) +{ + cpus_.emplace_back(cpu); + ids_.emplace_back(id); + types_.emplace_back(type); + names_.emplace_back(name); + return Size() - 1; +} +size_t ClkEventData::AppendNewFilter(uint64_t id, uint64_t rate, DataIndex name, uint64_t cpu) +{ + ids_.emplace_back(id); + rates_.emplace_back(rate); + names_.emplace_back(name); + cpus_.emplace_back(cpu); + return Size() - 1; +} +size_t SysCall::AppendSysCallData(int64_t sysCallNum, DataIndex type, uint64_t ipid, uint64_t timestamp, int64_t ret) +{ + sysCallNums_.emplace_back(sysCallNum); + types_.emplace_back(type); + ipids_.emplace_back(ipid); + timeStamps_.emplace_back(timestamp); + rets_.emplace_back(ret); + return Size() - 1; +} +StatAndInfo::StatAndInfo() +{ + // sched_switch_received | sched_switch_not_match | sched_switch_not_not_supported etc. + for (int i = TRACE_EVENT_START; i < TRACE_EVENT_MAX; i++) { + event_[i] = config_.eventNameMap_.at(static_cast(i)); + } + for (int j = STAT_EVENT_START; j < STAT_EVENT_MAX; j++) { + stat_[j] = config_.eventErrorDescMap_.at(static_cast(j)); + } + + for (int i = TRACE_EVENT_START; i < TRACE_EVENT_MAX; i++) { + for (int j = STAT_EVENT_START; j < STAT_EVENT_MAX; j++) { + statSeverity_[i][j] = config_.eventParserStatSeverityDescMap_.at(static_cast(i)) + .at(static_cast(j)); + } + } + + for (int i = TRACE_EVENT_START; i < TRACE_EVENT_MAX; i++) { + for (int j = STAT_EVENT_START; j < STAT_EVENT_MAX; j++) { + statSeverityDesc_[i][j] = config_.serverityLevelDescMap_.at(statSeverity_[i][j]); + } + } + + for (int i = TRACE_EVENT_START; i < TRACE_EVENT_MAX; i++) { + for (int j = STAT_EVENT_START; j < STAT_EVENT_MAX; j++) { + statCount_[i][j] = 0; + } + } +} +void StatAndInfo::IncreaseStat(SupportedTraceEventType eventType, StatType type) +{ + statCount_[eventType][type]++; +} +const uint32_t& StatAndInfo::GetValue(SupportedTraceEventType eventType, StatType type) const +{ + return statCount_[eventType][type]; +} +const std::string& StatAndInfo::GetEvent(SupportedTraceEventType eventType) const +{ + return event_[eventType]; +} +const std::string& StatAndInfo::GetStat(StatType type) const +{ + return stat_[type]; +} +const std::string& StatAndInfo::GetSeverityDesc(SupportedTraceEventType eventType, StatType type) const +{ + return statSeverityDesc_[eventType][type]; +} +const StatSeverityLevel& StatAndInfo::GetSeverity(SupportedTraceEventType eventType, StatType type) const +{ + return statSeverity_[eventType][type]; +} +uint64_t SymbolsData::Size() const +{ + return addrs_.size(); +} +void SymbolsData::InsertSymbol(const DataIndex& name, const uint64_t& addr) +{ + addrs_.emplace_back(addr); + funcName_.emplace_back(name); +} +const std::deque& SymbolsData::GetConstFuncNames() const +{ + return funcName_; +} +const std::deque& SymbolsData::GetConstAddrs() const +{ + return addrs_; +} +MetaData::MetaData() +{ + columnNames_.resize(METADATA_ITEM_MAX); + values_.resize(METADATA_ITEM_MAX); + columnNames_[METADATA_ITEM_DATASIZE] = METADATA_ITEM_DATASIZE_COLNAME; + columnNames_[METADATA_ITEM_PARSETOOL_NAME] = METADATA_ITEM_PARSETOOL_NAME_COLNAME; + columnNames_[METADATA_ITEM_PARSERTOOL_VERSION] = METADATA_ITEM_PARSERTOOL_VERSION_COLNAME; + columnNames_[METADATA_ITEM_PARSERTOOL_PUBLISH_DATETIME] = METADATA_ITEM_PARSERTOOL_PUBLISH_DATETIME_COLNAME; + columnNames_[METADATA_ITEM_SOURCE_FILENAME] = METADATA_ITEM_SOURCE_FILENAME_COLNAME; + columnNames_[METADATA_ITEM_OUTPUT_FILENAME] = METADATA_ITEM_OUTPUT_FILENAME_COLNAME; + columnNames_[METADATA_ITEM_PARSERTIME] = METADATA_ITEM_PARSERTIME_COLNAME; + columnNames_[METADATA_ITEM_TRACE_DURATION] = METADATA_ITEM_TRACE_DURATION_COLNAME; + columnNames_[METADATA_ITEM_SOURCE_DATETYPE] = METADATA_ITEM_SOURCE_DATETYPE_COLNAME; + values_[METADATA_ITEM_PARSETOOL_NAME] = "trace_streamer"; +} +void MetaData::SetTraceType(const std::string& traceType) +{ + values_[METADATA_ITEM_SOURCE_DATETYPE] = traceType; +} +void MetaData::SetSourceFileName(const std::string& fileName) +{ + MetaData::values_[METADATA_ITEM_SOURCE_FILENAME] = fileName; +} +void MetaData::SetOutputFileName(const std::string& fileName) +{ + MetaData::values_[METADATA_ITEM_OUTPUT_FILENAME] = fileName; +} +void MetaData::SetParserToolVersion(const std::string& version) +{ + values_[METADATA_ITEM_PARSERTOOL_VERSION] = version; +} +void MetaData::SetParserToolPublishDateTime(const std::string& datetime) +{ + values_[METADATA_ITEM_PARSERTOOL_PUBLISH_DATETIME] = datetime; +} +void MetaData::SetTraceDataSize(uint64_t dataSize) +{ + std::stringstream ss; + ss << dataSize; + values_[METADATA_ITEM_DATASIZE] = ss.str(); + // Function 'time' may return error. It is not allowed to do anything that might fail inside the constructor. + time_t rawtime; + struct tm* timeinfo = nullptr; + void(time(&rawtime)); + timeinfo = localtime(&rawtime); + values_[METADATA_ITEM_PARSERTIME] = asctime(timeinfo); +} +void MetaData::SetTraceDuration(uint64_t dur) +{ + values_[METADATA_ITEM_TRACE_DURATION] = std::to_string(dur) + " s"; +} +const std::string& MetaData::Value(uint64_t row) const +{ + return values_[row]; +} +const std::string& MetaData::Name(uint64_t row) const +{ + return columnNames_[row]; +} +DataIndex DataDict::GetStringIndex(std::string_view str) +{ + auto hashValue = hashFun(str); + auto itor = dataDictInnerMap_.find(hashValue); + if (itor != dataDictInnerMap_.end()) { + TS_ASSERT(std::string_view(dataDict_[itor->second]) == str); + return itor->second; + } + dataDict_.emplace_back(std::string(str)); + DataIndex stringIdentity = dataDict_.size() - 1; + dataDictInnerMap_.emplace(hashValue, stringIdentity); + return stringIdentity; +} +} // namespace TraceStdtype +} // namespace SysTuning diff --git a/host/trace_streamer/src/trace_data/trace_stdtype.h b/host/trace_streamer/src/trace_data/trace_stdtype.h new file mode 100644 index 0000000..2ecd236 --- /dev/null +++ b/host/trace_streamer/src/trace_data/trace_stdtype.h @@ -0,0 +1,824 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TRACE_STDTYPE_H +#define TRACE_STDTYPE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cfg/trace_streamer_config.h" +#include "log.h" +#include "ts_common.h" + +namespace SysTuning { +namespace TraceStdtype { +using namespace SysTuning::TraceCfg; +using namespace SysTuning::TraceStreamer; +class CacheBase { +public: + size_t Size() const + { + return std::max(timeStamps_.size(), ids_.size()); + } + const std::deque& IdsData() const + { + return ids_; + } + const std::deque& TimeStamData() const + { + return timeStamps_; + } + const std::deque& InternalTidsData() const + { + return internalTids_; + } + void Clear() + { + internalTids_.clear(); + timeStamps_.clear(); + ids_.clear(); + } +public: + std::deque internalTids_ = {}; + std::deque timeStamps_ = {}; + std::deque ids_ = {}; +}; + +class CpuCacheBase { +public: + const std::deque& DursData() const + { + return durs_; + } + + const std::deque& CpusData() const + { + return cpus_; + } + void Clear() + { + durs_.clear(); + cpus_.clear(); + } +public: + std::deque durs_; + std::deque cpus_; +}; +class Thread { +public: + explicit Thread(uint32_t t) : tid_(t) {} + InternalPid internalPid_ = 0; + uint32_t tid_ = 0; + DataIndex nameIndex_ = 0; + InternalTime startT_ = 0; + InternalTime endT_ = 0; +}; + +class Process { +public: + explicit Process(uint32_t p) : pid_(p) {} + std::string cmdLine_ = ""; + InternalTime startT_ = 0; + uint32_t pid_ = 0; +}; + +class ThreadState : public CacheBase, public CpuCacheBase { +public: + size_t AppendThreadState(uint64_t ts, uint64_t dur, uint64_t cpu, uint64_t internalTid, uint64_t state); + void SetDuration(size_t index, uint64_t duration); + uint64_t UpdateDuration(size_t index, uint64_t timestamp); + void UpdateState(size_t index, uint64_t state); + void UpdateDuration(size_t index, uint64_t timestamp, uint64_t state); + uint64_t UpdateDuration(size_t index, uint64_t timestamp, uint64_t cpu, uint64_t state); + const std::deque& StatesData() const + { + return states_; + } + void Clear() + { + CacheBase::Clear(); + CpuCacheBase::Clear(); + states_.clear(); + } + +private: + std::deque states_ = {}; +}; + +class SchedSlice : public CacheBase, public CpuCacheBase { +public: + size_t AppendSchedSlice(uint64_t ts, + uint64_t dur, + uint64_t cpu, + uint64_t internalTid, + uint64_t endState, + uint64_t priority); + void SetDuration(size_t index, uint64_t duration); + void Update(uint64_t index, uint64_t ts, uint64_t state, uint64_t pior); + + const std::deque& EndStatesData() const + { + return endStates_; + } + + const std::deque& PriorityData() const + { + return priority_; + } + void Clear() + { + CacheBase::Clear(); + CpuCacheBase::Clear(); + endStates_.clear(); + priority_.clear(); + } + +private: + std::deque endStates_ = {}; + std::deque priority_ = {}; +}; + +class CallStack : public CacheBase, public CpuCacheBase { +public: + size_t AppendInternalAsyncSlice(uint64_t startT, + uint64_t durationNs, + InternalTid internalTid, + DataIndex cat, + DataIndex name, + uint8_t depth, + uint64_t cookid, + const std::optional& parentId); + size_t AppendInternalSlice(uint64_t startT, + uint64_t durationNs, + InternalTid internalTid, + DataIndex cat, + DataIndex name, + uint8_t depth, + const std::optional& parentId); + void AppendDistributeInfo(const std::string& chainId, + const std::string& spanId, + const std::string& parentSpanId, + const std::string& flag, + const std::string& args); + void AppendArgSet(uint32_t argSetId); + void AppendDistributeInfo(); + void SetDuration(size_t index, uint64_t timestamp); + void SetDurationAndArg(size_t index, uint64_t timestamp, uint32_t argSetId); + void SetTimeStamp(size_t index, uint64_t timestamp); + void Clear() + { + CacheBase::Clear(); + CpuCacheBase::Clear(); + cats_.clear(); + cookies_.clear(); + callIds_.clear(); + names_.clear(); + depths_.clear(); + chainIds_.clear(); + spanIds_.clear(); + parentSpanIds_.clear(); + flags_.clear(); + args_.clear(); + argSet_.clear(); + } + + const std::deque>& ParentIdData() const; + const std::deque& CatsData() const; + const std::deque& NamesData() const; + const std::deque& Depths() const; + const std::deque& Cookies() const; + const std::deque& CallIds() const; + const std::deque& ChainIds() const; + const std::deque& SpanIds() const; + const std::deque& ParentSpanIds() const; + const std::deque& Flags() const; + const std::deque& ArgsData() const; + const std::deque& ArgSetIdsData() const; + +private: + void AppendCommonInfo(uint64_t startT, uint64_t durationNs, InternalTid internalTid); + void AppendCallStack(DataIndex cat, DataIndex name, uint8_t depth, std::optional parentId); + +private: + std::deque> parentIds_; + std::deque cats_ = {}; + std::deque cookies_ = {}; + std::deque callIds_ = {}; + std::deque names_ = {}; + std::deque depths_ = {}; + + std::deque chainIds_ = {}; + std::deque spanIds_ = {}; + std::deque parentSpanIds_ = {}; + std::deque flags_ = {}; + std::deque args_ = {}; + std::deque argSet_ = {}; +}; + +class Filter : public CacheBase { +public: + size_t AppendNewFilterData(std::string type, std::string name, uint64_t sourceArgSetId); + const std::deque& NameData() const + { + return nameDeque_; + } + const std::deque& TypeData() const + { + return typeDeque_; + } + const std::deque& SourceArgSetIdData() const + { + return sourceArgSetId_; + } + void Clear() + { + CacheBase::Clear(); + nameDeque_.clear(); + typeDeque_.clear(); + sourceArgSetId_.clear(); + } + +private: + std::deque nameDeque_ = {}; + std::deque typeDeque_ = {}; + std::deque sourceArgSetId_ = {}; +}; + +class Measure : public CacheBase { +public: + size_t AppendMeasureData(uint32_t type, uint64_t timestamp, int64_t value, uint32_t filterId); + const std::deque& TypeData() const + { + return typeDeque_; + } + const std::deque& ValuesData() const + { + return valuesDeque_; + } + const std::deque& FilterIdData() const + { + return filterIdDeque_; + } + void Clear() + { + CacheBase::Clear(); + typeDeque_.clear(); + valuesDeque_.clear(); + filterIdDeque_.clear(); + } + +private: + std::deque typeDeque_ = {}; + std::deque valuesDeque_ = {}; + std::deque filterIdDeque_ = {}; +}; + +class Raw : public CacheBase { +public: + size_t AppendRawData(uint32_t id, uint64_t timestamp, uint32_t name, uint32_t cpu, uint32_t internalTid); + const std::deque& NameData() const + { + return nameDeque_; + } + const std::deque& CpuData() const + { + return cpuDeque_; + } + const std::deque& InternalTidData() const + { + return itidDeque_; + } + void Clear() + { + CacheBase::Clear(); + nameDeque_.clear(); + cpuDeque_.clear(); + itidDeque_.clear(); + } + +private: + std::deque nameDeque_ = {}; + std::deque cpuDeque_ = {}; + std::deque itidDeque_ = {}; +}; + +class ThreadMeasureFilter { +public: + size_t AppendNewFilter(uint64_t filterId, uint32_t nameIndex, uint64_t internalTid); + size_t Size() const + { + return filterId_.size(); + } + const std::deque& FilterIdData() const + { + return filterId_; + } + const std::deque& InternalTidData() const + { + return internalTids_; + } + const std::deque& NameIndexData() const + { + return nameIndex_; + } + void Clear() + { + filterId_.clear(); + internalTids_.clear(); + nameIndex_.clear(); + } + +private: + std::deque filterId_ = {}; + std::deque internalTids_ = {}; + std::deque nameIndex_ = {}; +}; + +class CpuMeasureFilter : public CacheBase { +public: + inline size_t AppendNewFilter(uint64_t filterId, DataIndex name, uint64_t cpu) + { + ids_.emplace_back(filterId); + cpu_.emplace_back(cpu); + name_.emplace_back(name); + return Size() - 1; + } + + const std::deque& CpuData() const + { + return cpu_; + } + + const std::deque& TypeData() const + { + return type_; + } + + const std::deque& NameData() const + { + return name_; + } + void Clear() + { + CacheBase::Clear(); + cpu_.clear(); + type_.clear(); + name_.clear(); + } + +private: + std::deque cpu_ = {}; + std::deque type_ = {}; + std::deque name_ = {}; +}; + +class Instants : public CacheBase { +public: + size_t AppendInstantEventData(uint64_t timestamp, DataIndex nameIndex, int64_t internalTid); + + const std::deque& NameIndexsData() const + { + return NameIndexs_; + } + void Clear() + { + CacheBase::Clear(); + NameIndexs_.clear(); + } + +private: + std::deque NameIndexs_; +}; + +class ProcessMeasureFilter : public CacheBase { +public: + size_t AppendNewFilter(uint64_t id, DataIndex name, uint32_t internalPid); + + const std::deque& UpidsData() const + { + return internalPids_; + } + + const std::deque& NamesData() const + { + return names_; + } + void Clear() + { + CacheBase::Clear(); + internalPids_.clear(); + names_.clear(); + } + +private: + std::deque internalPids_ = {}; + std::deque names_ = {}; +}; +class ClockEventData : public CacheBase { +public: + size_t AppendNewFilter(uint64_t id, DataIndex type, DataIndex name, uint64_t cpu); + + const std::deque& CpusData() const + { + return cpus_; + } + + const std::deque& NamesData() const + { + return names_; + } + const std::deque& TypesData() const + { + return types_; + } + void Clear() + { + CacheBase::Clear(); + cpus_.clear(); + names_.clear(); + types_.clear(); + } + +private: + std::deque cpus_ = {}; // in clock_set_rate event, it save cpu + std::deque names_ = {}; + std::deque types_ = {}; +}; +class ClkEventData : public CacheBase { +public: + size_t AppendNewFilter(uint64_t id, uint64_t rate, DataIndex name, uint64_t cpu); + + const std::deque& NamesData() const + { + return names_; + } + const std::deque& RatesData() const + { + return rates_; + } + const std::deque& CpusData() const + { + return cpus_; + } + void Clear() + { + CacheBase::Clear(); + names_.clear(); + rates_.clear(); + cpus_.clear(); + } + +private: + std::deque names_; + std::deque rates_; + std::deque cpus_; +}; +class SysCall : public CacheBase { +public: + size_t AppendSysCallData(int64_t sysCallNum, DataIndex type, uint64_t ipid, uint64_t timestamp, int64_t ret); + const std::deque& SysCallsData() const + { + return sysCallNums_; + } + const std::deque& TypesData() const + { + return types_; + } + const std::deque& IpidsData() const + { + return ipids_; + } + const std::deque& RetsData() const + { + return rets_; + } + void Clear() + { + CacheBase::Clear(); + sysCallNums_.clear(); + types_.clear(); + ipids_.clear(); + rets_.clear(); + } + +private: + std::deque sysCallNums_ = {}; + std::deque types_ = {}; + std::deque ipids_ = {}; + std::deque rets_ = {}; +}; +class ArgSet : public CacheBase { +public: + size_t AppendNewArg(DataIndex nameId, BaseDataType dataType, int64_t value, size_t argSet); + const std::deque& DataTypes() const; + const std::deque& ValuesData() const; + const std::deque& ArgsData() const; + const std::deque& NamesData() const; + + void Clear() + { + CacheBase::Clear(); + names_.clear(); + dataTypes_.clear(); + values_.clear(); + argset_.clear(); + } +private: + std::deque names_ = {}; + std::deque dataTypes_ = {}; + std::deque values_ = {}; + std::deque argset_ = {}; +}; +class SysMeasureFilter : public CacheBase { +public: + size_t AppendNewFilter(uint64_t filterId, DataIndex type, DataIndex nameId); + const std::deque& NamesData() const; + const std::deque& TypesData() const; + void Clear() + { + CacheBase::Clear(); + types_.clear(); + names_.clear(); + } + +private: + std::deque types_ = {}; + std::deque names_ = {}; +}; +class DataType : public CacheBase { +public: + size_t AppendNewDataType(BaseDataType dataType, DataIndex dataDescIndex); + const std::deque& DataTypes() const; + const std::deque& DataDesc() const; + void Clear() + { + CacheBase::Clear(); + dataTypes_.clear(); + descs_.clear(); + } + +private: + std::deque dataTypes_ = {}; + std::deque descs_ = {}; +}; +class LogInfo : public CacheBase { +public: + size_t AppendNewLogInfo(uint64_t seq, + uint64_t timestamp, + uint32_t pid, + uint32_t tid, + DataIndex level, + DataIndex tag, + DataIndex context, + uint64_t originTs); + const std::deque& HilogLineSeqs() const; + const std::deque& Pids() const; + const std::deque& Tids() const; + const std::deque& Levels() const; + const std::deque& Tags() const; + const std::deque& Contexts() const; + const std::deque& OriginTimeStamData() const; + void Clear() + { + CacheBase::Clear(); + hilogLineSeqs_.clear(); + pids_.clear(); + levels_.clear(); + tags_.clear(); + contexts_.clear(); + originTs_.clear(); + } + +private: + std::deque hilogLineSeqs_ = {}; + std::deque pids_ = {}; + std::deque tids_ = {}; + std::deque levels_ = {}; + std::deque tags_ = {}; + std::deque contexts_ = {}; + std::deque originTs_ = {}; +}; + +class HeapInfo : public CacheBase { +public: + size_t AppendNewHeapInfo(uint64_t eventId, + uint32_t ipid, + uint32_t itid, + DataIndex eventType, + uint64_t timestamp, + uint64_t endTimestamp, + uint64_t duration, + uint64_t addr, + int64_t heapSize, + int64_t allHeapSize, + uint64_t currentSizeDur); + void UpdateHeapDuration(size_t row, uint64_t endTimestamp); + void UpdateCurrentSizeDur(size_t row, uint64_t nextStartTime); + const std::deque& EventIds() const; + const std::deque& Ipids() const; + const std::deque& Itids() const; + const std::deque& EventTypes() const; + const std::deque& EndTimeStamps() const; + const std::deque& Durations() const; + const std::deque& Addrs() const; + const std::deque& HeapSizes() const; + const std::deque& AllHeapSizes() const; + const std::deque& CurrentSizeDurs() const; + void Clear() + { + CacheBase::Clear(); + eventIds_.clear(); + ipids_.clear(); + itids_.clear(); + eventTypes_.clear(); + endTimestamps_.clear(); + durations_.clear(); + addrs_.clear(); + heapSizes_.clear(); + allHeapSizes_.clear(); + currentSizeDurs_.clear(); + } + +private: + std::deque eventIds_ = {}; + std::deque ipids_ = {}; + std::deque itids_ = {}; + std::deque eventTypes_ = {}; + std::deque endTimestamps_ = {}; + std::deque durations_ = {}; + std::deque addrs_ = {}; + std::deque heapSizes_ = {}; + std::deque allHeapSizes_ = {}; + std::deque currentSizeDurs_ = {}; + int64_t countHeapSizes_ = 0; +}; + +class HeapFrameInfo { +public: + size_t AppendNewHeapFrameInfo(uint64_t eventId, + uint64_t depth, + DataIndex ip, + DataIndex sp, + DataIndex symbolName, + DataIndex filePath, + DataIndex offset, + uint64_t symbolOffset); + const std::deque& EventIds() const; + const std::deque& Depths() const; + const std::deque& Ips() const; + const std::deque& Sps() const; + const std::deque& SymbolNames() const; + const std::deque& FilePaths() const; + const std::deque& Offsets() const; + const std::deque& SymbolOffsets() const; + size_t Size() const + { + return eventIds_.size(); + } + void Clear() + { + eventIds_.clear(); + depths_.clear(); + ips_.clear(); + sps_.clear(); + symbolNames_.clear(); + filePaths_.clear(); + offsets_.clear(); + symbolOffsets_.clear(); + } + +private: + std::deque eventIds_ = {}; + std::deque depths_ = {}; + std::deque ips_ = {}; + std::deque sps_ = {}; + std::deque symbolNames_ = {}; + std::deque filePaths_ = {}; + std::deque offsets_ = {}; + std::deque symbolOffsets_ = {}; +}; + +class Hidump : public CacheBase { +public: + size_t AppendNewHidumpInfo(uint64_t timestamp, uint32_t fps); + const std::deque& Fpss() const; +private: + std::deque fpss_ = {}; +}; + +class StatAndInfo { +public: + StatAndInfo(); + ~StatAndInfo() = default; + void IncreaseStat(SupportedTraceEventType eventType, StatType type); + const uint32_t& GetValue(SupportedTraceEventType eventType, StatType type) const; + const std::string& GetEvent(SupportedTraceEventType eventType) const; + const std::string& GetStat(StatType type) const; + const std::string& GetSeverityDesc(SupportedTraceEventType eventType, StatType type) const; + const StatSeverityLevel& GetSeverity(SupportedTraceEventType eventType, StatType type) const; + +private: + uint32_t statCount_[TRACE_EVENT_MAX][STAT_EVENT_MAX]; + std::string event_[TRACE_EVENT_MAX]; + std::string stat_[STAT_EVENT_MAX]; + std::string statSeverityDesc_[TRACE_EVENT_MAX][STAT_EVENT_MAX]; + StatSeverityLevel statSeverity_[TRACE_EVENT_MAX][STAT_EVENT_MAX]; + TraceStreamerConfig config_; +}; +class SymbolsData { +public: + SymbolsData() = default; + ~SymbolsData() = default; + uint64_t Size() const; + void InsertSymbol(const DataIndex& name, const uint64_t& addr); + const std::deque& GetConstFuncNames() const; + const std::deque& GetConstAddrs() const; + void Clear() + { + addrs_.clear(); + funcName_.clear(); + } +private: + std::deque addrs_ = {}; + std::deque funcName_ = {}; +}; +class MetaData { +public: + MetaData(); + ~MetaData() = default; + void SetTraceType(const std::string& traceType); + void SetSourceFileName(const std::string& fileName); + void SetOutputFileName(const std::string& fileName); + void SetParserToolVersion(const std::string& version); + void SetParserToolPublishDateTime(const std::string& datetime); + void SetTraceDataSize(uint64_t dataSize); + void SetTraceDuration(uint64_t dur); + const std::string& Value(uint64_t row) const; + const std::string& Name(uint64_t row) const; + void Clear() + { + columnNames_.clear(); + values_.clear(); + } +private: + const std::string METADATA_ITEM_DATASIZE_COLNAME = "datasize"; + const std::string METADATA_ITEM_PARSETOOL_NAME_COLNAME = "parse_tool"; + const std::string METADATA_ITEM_PARSERTOOL_VERSION_COLNAME = "tool_version"; + const std::string METADATA_ITEM_PARSERTOOL_PUBLISH_DATETIME_COLNAME = "tool_publish_time"; + const std::string METADATA_ITEM_SOURCE_FILENAME_COLNAME = "source_name"; + const std::string METADATA_ITEM_OUTPUT_FILENAME_COLNAME = "output_name"; + const std::string METADATA_ITEM_PARSERTIME_COLNAME = "runtime"; + const std::string METADATA_ITEM_TRACE_DURATION_COLNAME = "trace_duration"; + const std::string METADATA_ITEM_SOURCE_DATETYPE_COLNAME = "source_type"; + + std::deque columnNames_ = {}; + std::deque values_ = {}; +}; +class DataDict { +public: + size_t Size() const + { + return dataDict_.size(); + } + DataIndex GetStringIndex(std::string_view str); + const std::string& GetDataFromDict(DataIndex id) const + { + TS_ASSERT(id < dataDict_.size()); + return dataDict_[id]; + } + void Clear() + { + dataDict_.clear(); + } +public: + std::deque dataDict_; + std::unordered_map dataDictInnerMap_; + +private: + std::hash hashFun; +}; +} // namespace TraceStdtype +} // namespace SysTuning + +#endif // TRACE_STDTYPE_H diff --git a/host/trace_streamer/src/trace_streamer.pro b/host/trace_streamer/src/trace_streamer.pro new file mode 100644 index 0000000..45afeed --- /dev/null +++ b/host/trace_streamer/src/trace_streamer.pro @@ -0,0 +1,79 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +QT -= gui core +TEMPLATE = app +#TEMPLATE = lib +CONFIG += c++17 console +#CONFIG += c++17 lib +#CONFIG += c++17 +CONFIG += WITHRPC +TARGET = trace_streamer +DEFINES += SUPPORTTHREAD + +#CONFIG += release + +DEFINES += HAVE_PTHREAD +DEFINES += _LIBCPP_DISABLE_AVAILABILITY + +DEFINES += HAVE_PTHREAD +ROOTSRCDIR = $$PWD/../ + +#QMAKE_CXXFLAGS =-ftrapv -fPIE -fstack-protector-strong -fstack-protector-all -D_FORTIFY_SOURCE=2 -O2 +#QMAKE_CFLAGS=-ftrapv -fPIE -fstack-protector-strong -fstack-protector-all -D_FORTIFY_SOURCE=2 -O2 +!unix{ +#QMAKE_LFLAGS=-fpie -Wl,-rpath=\$ORIGIN/. +} else { +#QMAKE_LFLAGS=-fpie +#QMAKE_LFLAGS=-fpie -Wl,-z,noexecstack -Wl,-z,now -Wl,-rpath=\$ORIGIN/. -Wl,-z,relro +} +include($$PWD/multi_platform/global.pri) +INCLUDEPATH += $$PWD/include \ + $$PWD/../third_party/protobuf/src \ + $$PWD/../third_party/sqlite/include \ + $$PWD/../third_party/protogen/gen \ + $$PWD/../third_party/protogen/gen/types/plugins/memory_data \ + $$PWD/../third_party/protogen/gen/types/plugins/ftrace_data \ + $$PWD/../third_party/protogen/gen/types/plugins/hilog_data \ + $$PWD/../third_party/protogen/gen/types/plugins/native_hook \ + $$PWD/../third_party/protogen/gen/types/plugins/hidump_data + + +include($$PWD/trace_streamer/trace_streamer.pri) +include($$PWD/base/base.pri) +WITHRPC{ +DEFINES += WIN32_LEAN_AND_MEAN +include($$PWD/rpc/rpc.pri) +INCLUDEPATH += $$PWD/rpc +!unix{ +LIBS += -lws2_32 +} +} +include($$PWD/filter/filter.pri) +include($$PWD/parser/parser.pri) +include($$PWD/multi_platform/protogen.pri) +include($$PWD/table/table.pri) +include($$PWD/trace_data/trace_data.pri) +include($$PWD/cfg/cfg.pri) +include($$PWD/ext/sqlite_ext.pri) + +unix{ +LIBS += -L$$DESTDIR/ -lstdc++ \ + -L$${ROOTSRCDIR}/lib/$${DESTFOLDER} -lprotobuf -lsqlite -ldl +} else { +LIBS += -L$$DESTDIR/ -lstdc++ \ + -L$${ROOTSRCDIR}/lib/$${DESTFOLDER} -lprotobuf -lsqlite +} +INCLUDEPATH +=$$PWD/include + +SOURCES += \ + main.cpp diff --git a/host/trace_streamer/src/trace_streamer/trace_streamer.pri b/host/trace_streamer/src/trace_streamer/trace_streamer.pri new file mode 100644 index 0000000..9b33d31 --- /dev/null +++ b/host/trace_streamer/src/trace_streamer/trace_streamer.pri @@ -0,0 +1,21 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDEPATH += $$PWD +HEADERS += \ + $$PWD/trace_streamer_selector.h \ + $$PWD/trace_streamer_filters.h + +SOURCES += \ + $$PWD/trace_streamer_selector.cpp \ + $$PWD/trace_streamer_filters.cpp diff --git a/host/trace_streamer/src/trace_streamer/trace_streamer_filters.cpp b/host/trace_streamer/src/trace_streamer/trace_streamer_filters.cpp new file mode 100644 index 0000000..a39ee3f --- /dev/null +++ b/host/trace_streamer/src/trace_streamer/trace_streamer_filters.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "trace_streamer_filters.h" +#include "args_filter.h" +#include "binder_filter.h" +#include "clock_filter.h" +#include "cpu_filter.h" +#include "filter_filter.h" +#include "irq_filter.h" +#include "measure_filter.h" +#include "process_filter.h" +#include "slice_filter.h" +#include "stat_filter.h" +#include "symbols_filter.h" +#include "system_event_measure_filter.h" + +namespace SysTuning { +namespace TraceStreamer { +TraceStreamerFilters::TraceStreamerFilters() = default; +TraceStreamerFilters::~TraceStreamerFilters() = default; +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/trace_streamer/trace_streamer_filters.h b/host/trace_streamer/src/trace_streamer/trace_streamer_filters.h new file mode 100644 index 0000000..22e405a --- /dev/null +++ b/host/trace_streamer/src/trace_streamer/trace_streamer_filters.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TRACE_STREAMERTOKEN_H +#define TRACE_STREAMERTOKEN_H + +#include +namespace SysTuning { +namespace TraceStreamer { +class SliceFilter; +class ProcessFilter; +class CpuFilter; +class MeasureFilter; +class FilterFilter; +class ClockFilter; +class SymbolsFilter; +class StatFilter; +class BinderFilter; +class ArgsFilter; +class IrqFilter; +class SystemEventMeasureFilter; +class TraceStreamerFilters { +public: + TraceStreamerFilters(); + ~TraceStreamerFilters(); + + std::unique_ptr clockFilter_; + std::unique_ptr filterFilter_; + std::unique_ptr sliceFilter_; + std::unique_ptr processFilter_; + std::unique_ptr cpuFilter_; + std::unique_ptr cpuMeasureFilter_; + std::unique_ptr threadMeasureFilter_; + std::unique_ptr threadFilter_; + std::unique_ptr processMeasureFilter_; + std::unique_ptr processFilterFilter_; + std::unique_ptr symbolsFilter_; + std::unique_ptr statFilter_; + std::unique_ptr clockRateFilter_; + std::unique_ptr clockEnableFilter_; + std::unique_ptr clockDisableFilter_; + std::unique_ptr clkRateFilter_; + std::unique_ptr clkEnableFilter_; + std::unique_ptr clkDisableFilter_; + std::unique_ptr binderFilter_; + std::unique_ptr argsFilter_; + std::unique_ptr irqFilter_; + std::unique_ptr sysEventMemMeasureFilter_; + std::unique_ptr sysEventVMemMeasureFilter_; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // TRACE_STREAMERTOKEN_H diff --git a/host/trace_streamer/src/trace_streamer/trace_streamer_selector.cpp b/host/trace_streamer/src/trace_streamer/trace_streamer_selector.cpp new file mode 100644 index 0000000..c397859 --- /dev/null +++ b/host/trace_streamer/src/trace_streamer/trace_streamer_selector.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "trace_streamer_selector.h" +#include +#include +#include +#include +#include "args_filter.h" +#include "binder_filter.h" +#include "clock_filter.h" +#include "cpu_filter.h" +#include "file.h" +#include "filter_filter.h" +#include "irq_filter.h" +#include "measure_filter.h" +#include "parser/bytrace_parser/bytrace_parser.h" +#include "parser/htrace_parser/htrace_parser.h" +#include "process_filter.h" +#include "slice_filter.h" +#include "stat_filter.h" +#include "symbols_filter.h" +#include "system_event_measure_filter.h" + +using namespace SysTuning::base; +namespace SysTuning { +namespace TraceStreamer { +namespace { +TraceFileType GuessFileType(const uint8_t* data, size_t size) +{ + if (size == 0) { + return TRACE_FILETYPE_UN_KNOW; + } + std::string start(reinterpret_cast(data), std::min(size, 20)); + if (start.find("# tracer") != std::string::npos) { + return TRACE_FILETYPE_BY_TRACE; + } + if (start.find("# TRACE") != std::string::npos) { + return TRACE_FILETYPE_BY_TRACE; + } + if ((start.compare(0, std::string("").length(), "") == 0) || + (start.compare(0, std::string("").length(), "") == 0)) { + return TRACE_FILETYPE_BY_TRACE; + } + if (start.compare(0, std::string("\x0a").length(), "\x0a") == 0) { + return TRACE_FILETYPE_UN_KNOW; + } + if (start.compare(0, std::string("OHOSPROF").length(), "OHOSPROF") == 0) { + return TRACE_FILETYPE_H_TRACE; + } + const std::regex bytraceMatcher = std::regex(R"(-(\d+)\s+\(?\s*(\d+|-+)?\)?\s?\[(\d+)\]\s*)" + R"([a-zA-Z0-9.]{0,5}\s+(\d+\.\d+):\s+(\S+):)"); + std::smatch matcheLine; + std::string bytraceMode(reinterpret_cast(data), size); + if (std::regex_search(bytraceMode, matcheLine, bytraceMatcher)) { + return TRACE_FILETYPE_BY_TRACE; + } + return TRACE_FILETYPE_UN_KNOW; +} +} // namespace + +TraceStreamerSelector::TraceStreamerSelector() + : fileType_(TRACE_FILETYPE_UN_KNOW), bytraceParser_(nullptr), htraceParser_(nullptr) +{ + InitFilter(); +} +TraceStreamerSelector::~TraceStreamerSelector() {} + +void TraceStreamerSelector::InitFilter() +{ + streamFilters_ = std::make_unique(); + traceDataCache_ = std::make_unique(); + streamFilters_->cpuFilter_ = std::make_unique(traceDataCache_.get(), streamFilters_.get()); + streamFilters_->sliceFilter_ = std::make_unique(traceDataCache_.get(), streamFilters_.get()); + + streamFilters_->processFilter_ = std::make_unique(traceDataCache_.get(), streamFilters_.get()); + streamFilters_->clockFilter_ = std::make_unique(traceDataCache_.get(), streamFilters_.get()); + streamFilters_->filterFilter_ = std::make_unique(traceDataCache_.get(), streamFilters_.get()); + + streamFilters_->threadMeasureFilter_ = + std::make_unique(traceDataCache_.get(), streamFilters_.get(), E_THREADMEASURE_FILTER); + streamFilters_->threadFilter_ = + std::make_unique(traceDataCache_.get(), streamFilters_.get(), E_THREAD_FILTER); + streamFilters_->cpuMeasureFilter_ = + std::make_unique(traceDataCache_.get(), streamFilters_.get(), E_CPU_MEASURE_FILTER); + streamFilters_->processMeasureFilter_ = + std::make_unique(traceDataCache_.get(), streamFilters_.get(), E_PROCESS_MEASURE_FILTER); + streamFilters_->processFilterFilter_ = + std::make_unique(traceDataCache_.get(), streamFilters_.get(), E_PROCESS_FILTER_FILTER); + streamFilters_->symbolsFilter_ = std::make_unique(traceDataCache_.get(), streamFilters_.get()); + streamFilters_->statFilter_ = std::make_unique(traceDataCache_.get(), streamFilters_.get()); + streamFilters_->binderFilter_ = std::make_unique(traceDataCache_.get(), streamFilters_.get()); + streamFilters_->argsFilter_ = std::make_unique(traceDataCache_.get(), streamFilters_.get()); + streamFilters_->irqFilter_ = std::make_unique(traceDataCache_.get(), streamFilters_.get()); + streamFilters_->clockRateFilter_ = + std::make_unique(traceDataCache_.get(), streamFilters_.get(), E_CLOCK_RATE_FILTER); + streamFilters_->clockEnableFilter_ = + std::make_unique(traceDataCache_.get(), streamFilters_.get(), E_CLOCK_ENABLE_FILTER); + streamFilters_->clockDisableFilter_ = + std::make_unique(traceDataCache_.get(), streamFilters_.get(), E_CLOCK_DISABLE_FILTER); + streamFilters_->clkRateFilter_ = + std::make_unique(traceDataCache_.get(), streamFilters_.get(), E_CLK_RATE_FILTER); + streamFilters_->clkEnableFilter_ = + std::make_unique(traceDataCache_.get(), streamFilters_.get(), E_CLK_ENABLE_FILTER); + streamFilters_->clkDisableFilter_ = + std::make_unique(traceDataCache_.get(), streamFilters_.get(), E_CLK_DISABLE_FILTER); + streamFilters_->sysEventMemMeasureFilter_ = + std::make_unique(traceDataCache_.get(), streamFilters_.get(), E_SYS_MEMORY_FILTER); + streamFilters_->sysEventVMemMeasureFilter_ = std::make_unique( + traceDataCache_.get(), streamFilters_.get(), E_SYS_VIRTUAL_MEMORY_FILTER); +} + +void TraceStreamerSelector::WaitForParserEnd() +{ + if (fileType_ == TRACE_FILETYPE_H_TRACE) { + htraceParser_->WaitForParserEnd(); + } + if (fileType_ == TRACE_FILETYPE_BY_TRACE) { + bytraceParser_->WaitForParserEnd(); + } +} + +MetaData* TraceStreamerSelector::GetMetaData() +{ + return traceDataCache_->GetMetaData(); +} +bool TraceStreamerSelector::ParseTraceDataSegment(std::unique_ptr data, size_t size) +{ + if (size == 0) { + return true; + } + if (fileType_ == TRACE_FILETYPE_UN_KNOW) { + fileType_ = GuessFileType(data.get(), size); + if (fileType_ == TRACE_FILETYPE_H_TRACE) { + htraceParser_ = std::make_unique(traceDataCache_.get(), streamFilters_.get()); + } else if (fileType_ == TRACE_FILETYPE_BY_TRACE) { + bytraceParser_ = std::make_unique(traceDataCache_.get(), streamFilters_.get()); + } + if (fileType_ == TRACE_FILETYPE_UN_KNOW) { + SetAnalysisResult(TRACE_PARSER_FILE_TYPE_ERROR); + TS_LOGI( + "File type is not supported!,\nthe head content is:%s\n ---waring!!!---\n" + "File type is not supported!,\n", + data.get()); + return false; + } + } + if (fileType_ == TRACE_FILETYPE_H_TRACE) { + htraceParser_->ParseTraceDataSegment(std::move(data), size); + } + if (fileType_ == TRACE_FILETYPE_BY_TRACE) { + bytraceParser_->ParseTraceDataSegment(std::move(data), size); + } + SetAnalysisResult(TRACE_PARSER_NORMAL); + return true; +} +void TraceStreamerSelector::EnableMetaTable(bool enabled) +{ + traceDataCache_->EnableMetaTable(enabled); +} + +void TraceStreamerSelector::SetCleanMode(bool cleanMode) +{ + g_cleanMode = true; +} +int TraceStreamerSelector::ExportDatabase(const std::string& outputName) const +{ + traceDataCache_->UpdateTraceRange(); + return traceDataCache_->ExportDatabase(outputName); +} +void TraceStreamerSelector::Clear() +{ + traceDataCache_->Prepare(); + traceDataCache_->Clear(); +} +int TraceStreamerSelector::SearchData() +{ + return traceDataCache_->SearchData(); +} +int TraceStreamerSelector::OperateDatabase(const std::string& sql) +{ + return traceDataCache_->OperateDatabase(sql); +} +int TraceStreamerSelector::SearchDatabase(const std::string& sql, + TraceDataDB::ResultCallBack resultCallBack) +{ + return traceDataCache_->SearchDatabase(sql, resultCallBack); +} +int TraceStreamerSelector::SearchDatabase(const std::string& sql, uint8_t* out, int outLen) +{ + return traceDataCache_->SearchDatabase(sql, out, outLen); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/src/trace_streamer/trace_streamer_selector.h b/host/trace_streamer/src/trace_streamer/trace_streamer_selector.h new file mode 100644 index 0000000..69eb973 --- /dev/null +++ b/host/trace_streamer/src/trace_streamer/trace_streamer_selector.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TRACE_STREAMER_SELECTOR_H +#define TRACE_STREAMER_SELECTOR_H +#include +#include +#include "trace_data/trace_data_cache.h" +#include "trace_streamer_filters.h" + +namespace SysTuning { +namespace TraceStreamer { +class BytraceParser; +class HtraceParser; +enum TraceFileType { TRACE_FILETYPE_BY_TRACE, TRACE_FILETYPE_H_TRACE, TRACE_FILETYPE_UN_KNOW }; +class TraceStreamerSelector { +public: + TraceStreamerSelector(); + ~TraceStreamerSelector(); + bool ParseTraceDataSegment(std::unique_ptr data, size_t size); + void EnableMetaTable(bool enabled); + void SetCleanMode(bool cleanMode); + int ExportDatabase(const std::string& outputName) const; + int SearchData(); + int OperateDatabase(const std::string& sql); + int SearchDatabase(const std::string& sql, TraceDataDB::ResultCallBack resultCallBack); + int SearchDatabase(const std::string& sql, uint8_t* out, int outLen); + void WaitForParserEnd(); + void Clear(); + MetaData* GetMetaData(); + void SetDataType(TraceFileType type) + { + fileType_ = type; + } + TraceFileType DataType() + { + return fileType_; + } + +private: + void InitFilter(); + TraceFileType fileType_; + std::unique_ptr streamFilters_ = {}; + std::unique_ptr traceDataCache_ = {}; + + std::unique_ptr bytraceParser_; + std::unique_ptr htraceParser_; +}; +} // namespace TraceStreamer +} // namespace SysTuning + +#endif // TRACE_STREAMER_SELECTOR_H diff --git a/host/trace_streamer/src/ts.gni b/host/trace_streamer/src/ts.gni new file mode 100644 index 0000000..3dd4e2a --- /dev/null +++ b/host/trace_streamer/src/ts.gni @@ -0,0 +1,56 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +OHOS_PROTO_DIR = "" + +if (target_os == "linux" || target_os == "windows" || target_os == "wasm" || + target_os == "test") { + # OHOS_FTRACE_PROTO_DIR="//third_party/protogen" + OHOS_FTRACE_PROTO_DIR = "//src/multi_platform" + + # OHOS_MEMORY_PROTO_DIR="//third_party/protogen" + OHOS_MEMORY_PROTO_DIR = "//src/multi_platform" + + # OHOS_HILOG_PROTO_DIR="//third_party/protogen" + OHOS_HILOG_PROTO_DIR = "//src/multi_platform" + + # OHOS_NATIVE_HOOK_PROTO_DIR="//third_party/protogen" + OHOS_NATIVE_HOOK_PROTO_DIR = "//src/multi_platform" + + # OHOS_HIDUMP_PROTO_DIR="//third_party/protogen" + OHOS_HIDUMP_PROTO_DIR = "//src/multi_platform" + + # OHOS_SERVICE_PROTO_DIR = "//third_party/protogen" + OHOS_SERVICE_PROTO_DIR = "//src/multi_platform" + OHOS_PROTO_GEN = "//third_party/protogen" + if (target_os == "test") { + enable_ts_utest = true + } else { + enable_ts_utest = false + } +} else { + enable_ts_utest = true + use_wasm = false + OHOS_FTRACE_PROTO_DIR = + "//developtools/profiler/protos/types/plugins/ftrace_data" + OHOS_MEMORY_PROTO_DIR = + "//developtools/profiler/protos/types/plugins/memory_data" + OHOS_HILOG_PROTO_DIR = + "//developtools/profiler/protos/types/plugins/hilog_data" + OHOS_NATIVE_HOOK_PROTO_DIR = + "//developtools/profiler/protos/types/plugins/native_hook" + OHOS_HIDUMP_PROTO_DIR = + "//developtools/profiler/protos/types/plugins/hidump_data" + OHOS_SERVICE_PROTO_DIR = "//developtools/profiler/protos/services" + OHOS_PROTO_GEN = + "//developtools/profiler/trace_analyzer/third_party/protogen/" +} diff --git a/host/trace_streamer/test/BUILD.gn b/host/trace_streamer/test/BUILD.gn new file mode 100644 index 0000000..1d20471 --- /dev/null +++ b/host/trace_streamer/test/BUILD.gn @@ -0,0 +1,130 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("//build/test.gni") + +#import("../../device/base/config.gni") +import("test_ts.gni") + +#module_output_path = "${OHOS_PROFILER_TEST_MODULE_OUTPUT_PATH}/trace_analyzer" +config("module_private_config") { + visibility = [ ":*" ] + cflags_cc = [ "-std=c++17" ] + + # if (current_toolchain != host_toolchain) { + # defines = [ "HAVE_HILOG" ] + # } +} + +ohos_unittest("hiprofiler_ts_ut") { + # module_out_path = module_output_path + sources = [ + "unittest/binder_filter_test.cpp", + "unittest/bytrace_parser_test.cpp", + "unittest/clock_filter_test.cpp", + "unittest/cpu_filter_test.cpp", + "unittest/event_parser_test.cpp", + "unittest/filter_filter_test.cpp", + "unittest/hilog_parser_test.cpp", + "unittest/htrace_binder_event_test.cpp", + "unittest/htrace_event_parser_test.cpp", + "unittest/htrace_irq_event_test.cpp", + "unittest/htrace_mem_parser_test.cpp", + "unittest/htrace_sys_mem_parser_test.cpp", + "unittest/htrace_sys_vmem_parser_test.cpp", + "unittest/irq_filter_test.cpp", + "unittest/measure_filter_test.cpp", + "unittest/parser_test.cpp", + "unittest/process_filter_test.cpp", + "unittest/slice_filter_test.cpp", + ] + deps = [ + # "${OHOS_FTRACE_PROTO_DIR}:ftrace_data_cpp", + # "${OHOS_MEMORY_PROTO_DIR}:memory_data_cpp", + # "${OHOS_HILOG_PROTO_DIR}:hilog_data_cpp", + # "${OHOS_NATIVE_HOOK_PROTO_DIR}:native_hook_cpp", + # "${OHOS_HIDUMP_PROTO_DIR}:hidump_data_cpp", + # "${OHOS_SERVICE_PROTO_DIR}:proto_services_cpp", + "../src:trace_streamer_source", + "//prebuilts/protos:ts_proto_data_cpp", + "//third_party/googletest:gtest_main", + "//third_party/protobuf:protobuf", + "//third_party/protobuf:protobuf_lite", + "//third_party/sqlite:sqlite", + + # "//utils/native/base:utilsecurec", + ] + include_dirs = [ + "../src", + "../src/trace_data", + "../src/table", + "../src/filter", + "../src/base", + "../src/include", + "../src/trace_streamer", + "../src/parser/bytrace_parser", + "../src/parser", + "../src/cfg", + "../src/parser/htrace_parser", + "../src/parser/htrace_parser/htrace_event_parser", + "../src/parser/htrace_parser/htrace_cpu_parser", + "..", + "//third_party/googletest/googletest/include/gtest", + "//utils/native/base/include", + "//third_party/protobuf/src", + "${OHOS_PROTO_GEN}", + "${OHOS_FTRACE_PROTO_DIR}", + "${OHOS_MEMORY_PROTO_DIR}", + "${OHOS_HILOG_PROTO_DIR}", + "${OHOS_NATIVE_HOOK_PROTO_DIR}", + "${OHOS_HIDUMP_PROTO_DIR}", + "${OHOS_SERVICE_PROTO_DIR}", + ] + cflags = [ + "-Wno-inconsistent-missing-override", + "-Dprivate=public", #allow test code access private members + "-fprofile-arcs", + "-ftest-coverage", + "-Wno-unused-command-line-argument", + "-Wno-format", + "-Wno-unused-const-variable", + "-Wno-unused-variable", + "-Wno-used-but-marked-unused", + ] + ldflags = [ + "-fprofile-arcs", + "-ftest-coverage", + "--coverage", + ] + + # external_deps = [ "hiviewdfx_hilog_native:libhilog" ] + # public_configs = [ "${OHOS_PROFILER_DIR}/device/base:hiprofiler_test_config" ] + configs = [ ":module_private_config" ] + + # subsystem_name = "${OHOS_PROFILER_SUBSYS_NAME}" +} + +# this is the dest for ohos.build +group("unittest") { + testonly = true + deps = [ ":hiprofiler_ts_ut" ] +} +#group("fuzztest") { +# testonly = true +# deps = [ +# "test_fuzzer/bytrace_fuzzer:fuzztest", +# "test_fuzzer/htrace_fuzzer:fuzztest", +# "test_fuzzer/selector_fuzzer:fuzztest", +# ] +#} diff --git a/host/trace_streamer/test/test_ts.gni b/host/trace_streamer/test/test_ts.gni new file mode 100644 index 0000000..6b7c44b --- /dev/null +++ b/host/trace_streamer/test/test_ts.gni @@ -0,0 +1,48 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +OHOS_PROTO_DIR = "" + +if (target_os == "linux" || target_os == "windows" || target_os == "test") { + OHOS_FTRACE_PROTO_DIR = "//src/multi_platform" + OHOS_MEMORY_PROTO_DIR = "//src/multi_platform" + OHOS_SERVICE_PROTO_DIR = "//src/multi_platform" + OHOS_PROTO_GEN = "//third_party/protogen" + if (target_os == "test") { + OHOS_MEMORY_PROTO_DIR = "//third_party/protogen/types/plugins/memory_data" + enable_ts_utest = true + OHOS_HILOG_PROTO_DIR = "//src/multi_platform" + + # OHOS_NATIVE_HOOK_PROTO_DIR="//third_party/protogen" + OHOS_NATIVE_HOOK_PROTO_DIR = "//src/multi_platform" + + # OHOS_HIDUMP_PROTO_DIR="//third_party/protogen" + OHOS_HIDUMP_PROTO_DIR = "//src/multi_platform" + } +} else { + use_wasm = false + OHOS_FTRACE_PROTO_DIR = + "//developtools/profiler/protos/types/plugins/ftrace_data" + OHOS_MEMORY_PROTO_DIR = + "//developtools/profiler/protos/types/plugins/memory_data" + OHOS_HILOG_PROTO_DIR = + "//developtools/profiler/protos/types/plugins/hilog_data" + OHOS_NATIVE_HOOK_PROTO_DIR = + "//developtools/profiler/protos/types/plugins/native_hook" + OHOS_HIDUMP_PROTO_DIR = + "//developtools/profiler/protos/types/plugins/hidump_data" + OHOS_SERVICE_PROTO_DIR = "//developtools/profiler/protos/services" + OHOS_PROTO_GEN = + "//developtools/profiler/trace_analyzer/third_party/protogen/" + enable_ts_utest = true +} diff --git a/host/trace_streamer/test/unittest/README.md b/host/trace_streamer/test/unittest/README.md new file mode 100644 index 0000000..4bb1a39 --- /dev/null +++ b/host/trace_streamer/test/unittest/README.md @@ -0,0 +1,53 @@ +# 准备测试的硬件环境 + 1. 测试依赖hi3516DV300设备。 + 2. 烧录OHOS_STD代码。 + 3. 通过直连方式,使用USB线将设备连接到存储测试代码的主机。 + +# 准备测试的软件环境 + 1. 设置UT环境变量 + 在代码根目录执行:export BUILD_UT=true + 2. 连接设备 + 在代码根目录执行: root + remount + 3. 准备UT依赖的动态库 + 通过push 把测试依赖的libsqlite.z.so等动态库推到hi3516DV300设备的system/lib文件夹下。 + 4. 准备UT依赖的资源文件 + 将OHOS_STD/test/resource目录push到hi3516DV300设备的/data目录 + 在代码根目录执行: push ./test/resource/ /data/ + 5. 环境清理 + (a) 执行之前先清理OHOS_STD/out/xxx-arm-release/obj/developtools 临时文件。 + rm -rf ~/OHOS_STD/out/xxx-arm-release/obj/developtools + (b) 清理hi3516DV300设备上生成的中间文件/home/XXX/OHOS_STD/out。 + rm -rf /home/ohos/OHOS_STD/out* + (c) 清理UT环境残留的覆盖率报告。 + rm -rf ~/OHOS_STD/developtools/profiler/build/html + +# 测试步骤 + 1. 启动UT执行环境, 启动后根据提示信息输入hi3516DV300对应的设备编号。 + 在代码根目录执行:./test/developertest/start.sh + 2. 编译并执行hiprofiler_ts_ut。 + 在start.sh启动的交互式窗口执行:run -t ut -ss developtools -ts hiprofiler_ts_ut + +# 生成测试报告 + 1. pull设备上生成的gcda + 进入设备环境后: 执行cd /home/XXX/OHOS_STD + tar -cvf out.tar out + exit + 退出shell后:pull /home/XXX/OHOS_STD/out.tar ~/OHOS_STD/ + tar -xvf out.tar + pull /home/ohos/OHOS_STD/out/* /home/ohos/OHOS_STD/out/ + cd /home/ohos/OHOS_STD/developtools/profiler/build + 2. 生成UT覆盖率报告 + 在代码根目录执行:./developtools/profiler/build/lcov.sh + pull /data/test/hiprofiler_ts_ut.xml ~/OHOS_STD/developtools/profiler/build/html/ + 报告位置:~/OHOS_STD/developtools/profiler/build/html/index.html + +# 可能遇到的问题 + 1. gcno文件不存在问题 + gcno文件是在编译阶段生成,编译时如果out目录存在.o临时文件,则不会编译源码,需要删除out/ obj/developtools/目录的编译中间文件后重新编译 + 2. 运行时报缺少依赖库libsqlite.z.so + 需要把libsqlite.z.so push到设备的system/lib目录,在developtools/profiler/device/ohos_test.xml文件增加push命令 + 3. UT所有步骤执行完之后,html中没有生成覆盖率信息 + 权限问题,从设备中pull出来gcov文件后,需要关注文件权限 + 4. UT输出覆盖率世间点不是当前最新时间 + 清理掉原来的html文件,重新执行lcov.sh,生成新的html覆盖率报告 \ No newline at end of file diff --git a/host/trace_streamer/test/unittest/binder_filter_test.cpp b/host/trace_streamer/test/unittest/binder_filter_test.cpp new file mode 100644 index 0000000..4e028b4 --- /dev/null +++ b/host/trace_streamer/test/unittest/binder_filter_test.cpp @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "args_filter.h" +#include "binder_filter.h" +#include "process_filter.h" +#include "slice_filter.h" +#include "stat_filter.h" +#include "trace_plugin_result.pb.h" +#include "ts_common.h" + +using namespace testing::ext; +using namespace SysTuning::TraceStreamer; +namespace SysTuning { +namespace TraceStreamer { +class BinderFilterTest : public ::testing::Test { +public: + void SetUp() + { + streamFilters_.processFilter_ = std::make_unique(&traceDataCache_, &streamFilters_); + streamFilters_.argsFilter_ = std::make_unique(&traceDataCache_, &streamFilters_); + streamFilters_.binderFilter_ = std::make_unique(&traceDataCache_, &streamFilters_); + streamFilters_.statFilter_ = std::make_unique(&traceDataCache_, &streamFilters_); + streamFilters_.sliceFilter_ = std::make_unique(&traceDataCache_, &streamFilters_); + } + + void TearDown() {} + +public: + TraceStreamerFilters streamFilters_; + TraceDataCache traceDataCache_; +}; + +/** + * @tc.name: BinderSenderfilterNeedReply + * @tc.desc: Binder event needs reply to + * @tc.type: FUNC + */ +HWTEST_F(BinderFilterTest, BinderSenderfilterNeedReply, TestSize.Level1) +{ + TS_LOGI("test9-1"); + uint64_t ts1 = 100; + uint32_t tid1 = 1; + uint64_t transactionId1 = 1; + int32_t destNode1 = 1; + int32_t destTgid1 = 2; + int32_t destTid1 = 3; + bool isReply = false; + int32_t flags = 0x02; // if need reply bool needReply = !isReply && !(flags & noReturnMsgFlag_); 0x01 + int32_t code = 0; // not important + streamFilters_.binderFilter_->SendTraction(ts1, tid1, transactionId1, destNode1, destTgid1, + destTid1, isReply, flags, code); // start binder + streamFilters_.binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().Size() == 7); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().TimeStamData()[0] == ts1); +} + +/** + * @tc.name: BinderSenderfilterNeedReplyAndReceive + * @tc.desc: Complete event, Binder event needs reply to + * @tc.type: FUNC + */ +HWTEST_F(BinderFilterTest, BinderSenderfilterNeedReplyAndReceive, TestSize.Level1) +{ + TS_LOGI("test9-2"); + int64_t ts1 = 100; + uint32_t tid1 = 1; + uint64_t transactionId1 = 1; + int32_t destNode1 = 1; + int32_t destTgid1 = 2; + int32_t destTid1 = 3; + bool isReply = false; + int32_t flags = 0x02; // if need reply bool needReply = !isReply && !(flags & noReturnMsgFlag_) + int32_t code = 0; // not importent + streamFilters_.binderFilter_->SendTraction(ts1, tid1, transactionId1, destNode1, + destTgid1, destTid1, isReply, flags, code); // start binder + streamFilters_.binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().Size() == 7); + ts1 = 200; + uint32_t pid1 = 1; + streamFilters_.binderFilter_->ReceiveTraction(ts1, pid1, transactionId1); // receive binder + streamFilters_.binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 2); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().ArgSetIdsData()[1] == 1); + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().Size() == 11); + auto len = traceDataCache_.GetConstArgSetData().Size(); + for (uint64_t i = 0; i < len; i++) { + if (traceDataCache_.GetConstArgSetData().names_[i] == streamFilters_.binderFilter_->isReplayId_) { + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().values_[i] == static_cast(isReply)); + } else if (traceDataCache_.GetConstArgSetData().names_[i] == streamFilters_.binderFilter_->destNodeId_) { + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().values_[i] == static_cast(destNode1)); + } else if (traceDataCache_.GetConstArgSetData().names_[i] == streamFilters_.binderFilter_->destThreadId_) { + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().values_[i] == static_cast(pid1)); + } else if (traceDataCache_.GetConstArgSetData().names_[i] == streamFilters_.binderFilter_->callingTid_) { + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().values_[i] == static_cast(tid1)); + } else if (traceDataCache_.GetConstArgSetData().names_[i] == streamFilters_.binderFilter_->transId_) { + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().values_[i] == static_cast(transactionId1)); + } + } +} + +/** + * @tc.name: BinderSenderfilterNeedReplyAndReceiveWithAlloc + * @tc.desc: The binder test that needs to be replied and there is an allock event + * @tc.type: FUNC + */ +HWTEST_F(BinderFilterTest, BinderSenderfilterNeedReplyAndReceiveWithAlloc, TestSize.Level1) +{ + TS_LOGI("test9-3"); + int64_t ts1 = 100; + uint32_t tid1 = 1; + uint64_t transactionId1 = 1; + int32_t destNode1 = 1; + int32_t destTgid1 = 2; + int32_t destTid1 = 3; + bool isReply = false; + int32_t flags = 0x02; // if need reply bool needReply = !isReply && !(flags & noReturnMsgFlag_) + int32_t code = 0; // not importent + streamFilters_.binderFilter_->SendTraction(ts1, tid1, transactionId1, destNode1, destTgid1, + destTid1, isReply, flags, + code); // start binder + streamFilters_.binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().Size() == 7); + + ts1 = 150; + uint64_t dataSize = 100; + uint64_t offsetSize = 200; + streamFilters_.binderFilter_->TransactionAllocBuf(ts1, tid1, dataSize, offsetSize); + streamFilters_.binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().Size() == 9); + + ts1 = 200; + uint32_t pid1 = 1; + streamFilters_.binderFilter_->ReceiveTraction(ts1, pid1, transactionId1); // receive binder + streamFilters_.binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 2); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().ArgSetIdsData()[1] == 1); + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().Size() == 13); +} + +/** + * @tc.name: BinderSenderfilterNeedReplyAndReceiveNotmatch + * @tc.desc: The binder test that needs to be replied but not match + * @tc.type: FUNC + */ +HWTEST_F(BinderFilterTest, BinderSenderfilterNeedReplyAndReceiveNotmatch, TestSize.Level1) +{ + TS_LOGI("test9-4"); + int64_t ts1 = 100; + uint32_t tid1 = 1; + uint64_t transactionId1 = 1; + int32_t destNode1 = 1; + int32_t destTgid1 = 2; + int32_t destTid1 = 3; + bool isReply = false; + int32_t flags = 0x02; // if need reply bool needReply = !isReply && !(flags & noReturnMsgFlag_) + int32_t code = 0; // not importent + streamFilters_.binderFilter_->SendTraction(ts1, tid1, transactionId1, destNode1, + destTgid1, destTid1, isReply, flags, + code); // start binder + streamFilters_.binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + ts1 = 200; + uint32_t pid1 = 1; + uint64_t transactionId2 = 2; + streamFilters_.binderFilter_->ReceiveTraction(ts1, pid1, transactionId2); // receive binder + streamFilters_.binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); +} + +/** + * @tc.name: BinderSenderfilterNoNeedReply + * @tc.desc: The binder test that donot needs to be replied + * @tc.type: FUNC + */ +HWTEST_F(BinderFilterTest, BinderSenderfilterNoNeedReply, TestSize.Level1) +{ + TS_LOGI("test9-5"); + int64_t ts1 = 100; + uint32_t tid1 = 1; + uint64_t transactionId1 = 1; + int32_t destNode1 = 1; + int32_t destTgid1 = 2; + int32_t destTid1 = 3; + bool isReply = false; + int32_t flags = 0x01; // if need reply bool needReply = !isReply && !(flags & noReturnMsgFlag_) + int32_t code = 0; // not importent + streamFilters_.binderFilter_->SendTraction(ts1, tid1, transactionId1, destNode1, destTgid1, + destTid1, isReply, flags, code); // start binder + streamFilters_.binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); +} + +/** + * @tc.name: BinderSenderNoneedReplyAndReceivefilter + * @tc.desc: Complete event, The binder test that donot needs to be replied + * @tc.type: FUNC + */ +HWTEST_F(BinderFilterTest, BinderSenderNoneedReplyAndReceivefilter, TestSize.Level1) +{ + TS_LOGI("test9-6"); + int64_t ts1 = 100; + uint32_t tid1 = 1; + uint64_t transactionId1 = 1; + int32_t destNode1 = 1; + int32_t destTgid1 = 2; + int32_t destTid1 = 3; + bool isReply = false; + int32_t flags = 0x01; // if need reply bool needReply = !isReply && !(flags & noReturnMsgFlag_) + int32_t code = 0; // not importent + streamFilters_.binderFilter_->SendTraction(ts1, tid1, transactionId1, destNode1, destTgid1, + destTid1, isReply, flags, + code); // start binder + streamFilters_.binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + + ts1 = 200; + uint32_t pid1 = 1; + streamFilters_.binderFilter_->ReceiveTraction(ts1, pid1, transactionId1); // receive binder + streamFilters_.binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 2); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().ArgSetIdsData()[1] == 1); +} + +/** + * @tc.name: BinderSenderNoneedReplyAndReceivefilterNotmatch + * @tc.desc: Not Match, The binder test that donot needs to be replied + * @tc.type: FUNC + */ +HWTEST_F(BinderFilterTest, BinderSenderNoneedReplyAndReceivefilterNotmatch, TestSize.Level1) +{ + TS_LOGI("test9-7"); + int64_t ts1 = 100; + uint32_t tid1 = 1; + uint64_t transactionId1 = 1; + int32_t destNode1 = 1; + int32_t destTgid1 = 2; + int32_t destTid1 = 3; + bool isReply = false; + int32_t flags = 0x01; // if need reply bool needReply = !isReply && !(flags & noReturnMsgFlag_) + int32_t code = 0; // not importent + streamFilters_.binderFilter_->SendTraction(ts1, tid1, transactionId1, destNode1, destTgid1, + destTid1, isReply, flags, + code); // start binder + streamFilters_.binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + + ts1 = 200; + uint32_t pid1 = 1; + uint64_t transactionId2 = 2; + streamFilters_.binderFilter_->ReceiveTraction(ts1, pid1, transactionId2); // receive binder + streamFilters_.binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); +} + +/** + * @tc.name: BinderSenderfilterWrongReply + * @tc.desc: The binder test with wrong replie + * @tc.type: FUNC + */ +HWTEST_F(BinderFilterTest, BinderSenderfilterWrongReply, TestSize.Level1) +{ + TS_LOGI("test9-8"); + int64_t ts1 = 100; + uint32_t tid1 = 1; + uint64_t transactionId1 = 1; + int32_t destNode1 = 1; + int32_t destTgid1 = 2; + int32_t destTid1 = 3; + bool isReply = true; + int32_t flags = 0x01; // if need reply bool needReply = !isReply && !(flags & noReturnMsgFlag_) + int32_t code = 0; // not important + streamFilters_.binderFilter_->SendTraction(ts1, tid1, transactionId1, destNode1, + destTgid1, destTid1, isReply, flags, + code); // start binder + streamFilters_.binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 0); + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().Size() == 0); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/test/unittest/bytrace_parser_test.cpp b/host/trace_streamer/test/unittest/bytrace_parser_test.cpp new file mode 100644 index 0000000..b613d18 --- /dev/null +++ b/host/trace_streamer/test/unittest/bytrace_parser_test.cpp @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include "parser/bytrace_parser/bytrace_parser.h" +#include "parser/common_types.h" +#include "securec.h" +#include "trace_streamer_selector.h" + +using namespace testing::ext; +using namespace SysTuning::TraceStreamer; + +namespace SysTuning { +namespace TraceStreamer { +class BytraceParserTest : public ::testing::Test { +public: + void SetUp() + { + stream_.InitFilter(); + } + + void TearDown() {} + +public: + SysTuning::TraceStreamer::TraceStreamerSelector stream_ = {}; + const std::string dbPath_ = "/data/resource/out.db"; +}; + +/** + * @tc.name: ParseNoData + * @tc.desc: Test ParseTraceDataSegment interface Parse empty memory + * @tc.type: FUNC + */ +HWTEST_F(BytraceParserTest, ParseNoData, TestSize.Level1) +{ + TS_LOGI("test1-1"); + auto buf = std::make_unique(1); + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + printf("xxx\n"); + bytraceParser.ParseTraceDataSegment(std::move(buf), 1); + printf("xxx2\n"); + bytraceParser.WaitForParserEnd(); + printf("xxx3\n"); + EXPECT_TRUE(bytraceParser.TraceCommentLines() == 0); + EXPECT_TRUE(bytraceParser.ParsedTraceValidLines() == 0); + EXPECT_TRUE(bytraceParser.ParsedTraceInvalidLines() == 0); +} + +/** + * @tc.name: ParseNoDataWhithLineFlag + * @tc.desc: Test ParseTraceDataSegment interface Parse "\n" + * @tc.type: FUNC + */ +HWTEST_F(BytraceParserTest, ParseNoDataWhithLineFlag, TestSize.Level1) +{ + TS_LOGI("test1-2"); + constexpr uint32_t bufSize = 1024; + auto buf = std::make_unique(bufSize); + if (memcpy_s(buf.get(), bufSize, " \n", strlen(" \n"))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), bufSize); + bytraceParser.WaitForParserEnd(); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + EXPECT_TRUE(bytraceParser.TraceCommentLines() == 0); + EXPECT_TRUE(bytraceParser.ParsedTraceValidLines() == 0); + EXPECT_TRUE(bytraceParser.ParsedTraceInvalidLines() == 1); +} + +/** + * @tc.name: ParseInvalidData + * @tc.desc: Test ParseTraceDataSegment interface Parse invalid string + * @tc.type: FUNC + */ +HWTEST_F(BytraceParserTest, ParseInvalidData, TestSize.Level1) +{ + TS_LOGI("test1-3"); + constexpr uint32_t bufSize = 1024; + auto buf = std::make_unique(bufSize); + if (memcpy_s(buf.get(), bufSize, "0123456789\n", strlen("0123456789\n"))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), bufSize); + bytraceParser.WaitForParserEnd(); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + EXPECT_TRUE(bytraceParser.TraceCommentLines() == 0); + EXPECT_TRUE(bytraceParser.ParsedTraceValidLines() == 0); + EXPECT_TRUE(bytraceParser.ParsedTraceInvalidLines() == 1); +} + +/** + * @tc.name: ParseComment + * @tc.desc: Test ParseTraceDataSegment interface Parse Multiline data + * @tc.type: FUNC + */ +HWTEST_F(BytraceParserTest, ParseComment, TestSize.Level1) +{ + TS_LOGI("test1-4"); + constexpr uint32_t bufSize = 1024; + auto buf = std::make_unique(bufSize); + if (memcpy_s(buf.get(), bufSize, "TRACE: \n# tracer: nop \n# \n", strlen("TRACE: \n# tracer: nop \n# \n"))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), bufSize); + bytraceParser.WaitForParserEnd(); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + EXPECT_TRUE(bytraceParser.TraceCommentLines() == 2); + EXPECT_TRUE(bytraceParser.ParsedTraceValidLines() == 0); + EXPECT_TRUE(bytraceParser.ParsedTraceInvalidLines() == 1); +} + +/** + * @tc.name: ParseInvalidLines + * @tc.desc: Test ParseTraceDataSegment interface Parse Multiline Invalid data + * @tc.type: FUNC + */ +HWTEST_F(BytraceParserTest, ParseInvalidLines, TestSize.Level1) +{ + TS_LOGI("test1-5"); + constexpr uint32_t bufSize = 1024; + auto buf = std::make_unique(bufSize); + if (memcpy_s(buf.get(), bufSize, "\nafafda\n", strlen("\nafafda\n"))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), bufSize); + bytraceParser.WaitForParserEnd(); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + EXPECT_TRUE(bytraceParser.TraceCommentLines() == 0); + EXPECT_TRUE(bytraceParser.ParsedTraceValidLines() == 0); + EXPECT_TRUE(bytraceParser.ParsedTraceInvalidLines() == 2); +} + +/** + * @tc.name: ParseNormal + * @tc.desc: Test ParseTraceDataItem interface Parse normal data + * @tc.type: FUNC + */ +HWTEST_F(BytraceParserTest, ParseNormal, TestSize.Level1) +{ + TS_LOGI("test1-6"); + std::string str( + "ACCS0-2716 ( 2519) [000] ...1 168758.662861: binder_transaction: \ + transaction=25137708 dest_node=4336 dest_proc=924 dest_thread=0 reply=0 flags=0x10 code=0x3\n"); + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataItem(str); + bytraceParser.WaitForParserEnd(); + + EXPECT_TRUE(bytraceParser.TraceCommentLines() == 0); + EXPECT_TRUE(bytraceParser.ParsedTraceValidLines() == 1); + EXPECT_TRUE(bytraceParser.ParsedTraceInvalidLines() == 0); +} + +/** + * @tc.name: LineParser_abnormal_pid_err + * @tc.desc: Test ParseTraceDataItem interface Parse data with error pid + * @tc.type: FUNC + */ +HWTEST_F(BytraceParserTest, LineParser_abnormal_pid_err, TestSize.Level1) +{ + TS_LOGI("test1-7"); + std::string str( + "ACCS0-27X6 ( 2519) [000] ...1 168758.662861: binder_transaction: \ + transaction=25137708 dest_node=4336 dest_proc=924 dest_thread=0 reply=0 flags=0x10 code=0x3\n"); + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataItem(str); + bytraceParser.WaitForParserEnd(); + + EXPECT_TRUE(bytraceParser.TraceCommentLines() == 0); + EXPECT_TRUE(bytraceParser.ParsedTraceValidLines() == 0); + EXPECT_TRUE(bytraceParser.ParsedTraceInvalidLines() == 1); +} + +/** + * @tc.name: LineParserWithInvalidCpu + * @tc.desc: Test ParseTraceDataItem interface Parse data with invalid cpu + * @tc.type: FUNC + */ +HWTEST_F(BytraceParserTest, LineParserWithInvalidCpu, TestSize.Level1) +{ + TS_LOGI("test1-8"); + std::string str( + "ACCS0-2716 ( 2519) [00X] ...1 168758.662861: binder_transaction: \ + transaction=25137708 dest_node=4336 dest_proc=924 dest_thread=0 reply=0 flags=0x10 code=0x3\n"); + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataItem(str); + bytraceParser.WaitForParserEnd(); + + EXPECT_TRUE(bytraceParser.TraceCommentLines() == 0); + EXPECT_TRUE(bytraceParser.ParsedTraceValidLines() == 0); + EXPECT_TRUE(bytraceParser.ParsedTraceInvalidLines() == 1); +} + +/** + * @tc.name: LineParserWithInvalidTs + * @tc.desc: Test ParseTraceDataItem interface Parse data with invalid ts + * @tc.type: FUNC + */ +HWTEST_F(BytraceParserTest, LineParserWithInvalidTs, TestSize.Level1) +{ + TS_LOGI("test1-9"); + std::string str( + "ACCS0-2716 ( 2519) [000] ...1 168758.662X61: binder_transaction: \ + transaction=25137708 dest_node=4336 dest_proc=924 dest_thread=0 reply=0 flags=0x10 code=0x3\n"); + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataItem(str); + bytraceParser.WaitForParserEnd(); + + EXPECT_TRUE(bytraceParser.TraceCommentLines() == 0); + EXPECT_TRUE(bytraceParser.ParsedTraceValidLines() == 0); + EXPECT_TRUE(bytraceParser.ParsedTraceInvalidLines() == 1); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/test/unittest/clock_filter_test.cpp b/host/trace_streamer/test/unittest/clock_filter_test.cpp new file mode 100644 index 0000000..833a330 --- /dev/null +++ b/host/trace_streamer/test/unittest/clock_filter_test.cpp @@ -0,0 +1,507 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "clock_filter.h" +#include "trace_data_cache.h" +#include "trace_streamer_filters.h" + +using namespace testing::ext; +using namespace SysTuning::TraceStreamer; +namespace SysTuning { +namespace TraceStreamer { +class ClockFilterTest : public ::testing::Test { +public: + void SetUp() + { + streamFilters_.clockFilter_ = std::make_unique(&traceDataCache_, &streamFilters_); + } + + void TearDown() {} + +public: + SysTuning::TraceStreamer::TraceStreamerFilters streamFilters_; + SysTuning::TraceStreamer::TraceDataCache traceDataCache_; +}; + +/** + * @tc.name: ConvertBoottimeToMonitonicTime + * @tc.desc: convert Boottime to MonitonicTime + * @tc.type: FUNC + */ +HWTEST_F(ClockFilterTest, ConvertBoottimeToMonitonicTime, TestSize.Level1) +{ + TS_LOGI("test2-1"); + uint64_t tsBoottime = 100; + uint64_t tsMonotonicTime = 200; + std::vector snapShot0; + snapShot0.push_back({TS_CLOCK_BOOTTIME, tsBoottime}); + snapShot0.push_back({TS_MONOTONIC, tsMonotonicTime}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot0); + + uint64_t time1 = 150; + uint64_t expectTime1 = 250; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_BOOTTIME, time1, TS_MONOTONIC), expectTime1); +} + +/** + * @tc.name: ConvertBoottimeToMonitonicTime + * @tc.desc: convert twice Boottime to MonitonicTime + * @tc.type: FUNC + */ +HWTEST_F(ClockFilterTest, ConvertBoottimeToMonitonicTimeTwice, TestSize.Level1) +{ + TS_LOGI("test2-2"); + uint64_t tsBoottime = 100; + uint64_t tsMonotonicTime = 200; + std::vector snapShot0; + snapShot0.push_back({TS_CLOCK_BOOTTIME, tsBoottime}); + snapShot0.push_back({TS_MONOTONIC, tsMonotonicTime}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot0); + + uint64_t time1 = 150; + uint64_t expectTime1 = 250; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_BOOTTIME, time1, TS_MONOTONIC), expectTime1); + uint64_t time2 = 200; + uint64_t expectTime2 = 300; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_BOOTTIME, time2, TS_MONOTONIC), expectTime2); +} + +/** + * @tc.name: ConvertBoottimeToMonitonicTime + * @tc.desc: convert Boottime to MonitonicTime twice + * @tc.type: FUNC + */ +HWTEST_F(ClockFilterTest, ConvertTimestampInvalid, TestSize.Level1) +{ + TS_LOGI("test2-4"); + uint64_t tsBoottime = 100; + uint64_t tsMonotonicTime = 200; + std::vector snapShot0; + snapShot0.push_back({TS_CLOCK_BOOTTIME, tsBoottime}); + snapShot0.push_back({TS_MONOTONIC, tsMonotonicTime}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot0); + + std::vector snapShot1; + uint64_t tsBoottime2 = 200; + uint64_t tsMonotonicTime2 = 350; + snapShot1.push_back({TS_CLOCK_BOOTTIME, tsBoottime2}); + snapShot1.push_back({TS_MONOTONIC, tsMonotonicTime2}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot1); + uint64_t time2 = 200; + uint64_t expectTime2 = 200; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_BOOTTIME, time2, TS_CLOCK_REALTIME), expectTime2); +} + +/** + * @tc.name: ConvertTimestampBoottimeToRealtime + * @tc.desc: convert Boottime to Realtime + * @tc.type: FUNC + */ +HWTEST_F(ClockFilterTest, ConvertTimestampBoottimeToRealtime, TestSize.Level1) +{ + TS_LOGI("test2-5"); + uint64_t tsBoottime = 100; + uint64_t tsRealTime = 300; + std::vector snapShot0; + snapShot0.push_back({TS_CLOCK_BOOTTIME, tsBoottime}); + snapShot0.push_back({TS_CLOCK_REALTIME, tsRealTime}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot0); + + std::vector snapShot1; + uint64_t tsBoottime2 = 200; + uint64_t tsRealTime2 = 400; + snapShot1.push_back({TS_CLOCK_BOOTTIME, tsBoottime2}); + snapShot1.push_back({TS_CLOCK_REALTIME, tsRealTime2}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot1); + uint64_t time3 = 101; + uint64_t expectTime3 = 301; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_BOOTTIME, time3, TS_CLOCK_REALTIME), expectTime3); +} + +/** + * @tc.name: ConvertBoottimeToRealtimeTwiceWithTwoSnapShot + * @tc.desc: convert Boottime to Realtime twice with two snapShot + * @tc.type: FUNC + */ +HWTEST_F(ClockFilterTest, ConvertBoottimeToRealtimeTwiceWithTwoSnapShot, TestSize.Level1) +{ + TS_LOGI("test2-6"); + uint64_t tsBoottime = 100; + uint64_t tsRealTime = 300; + std::vector snapShot0; + snapShot0.push_back({TS_CLOCK_BOOTTIME, tsBoottime}); + snapShot0.push_back({TS_CLOCK_REALTIME, tsRealTime}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot0); + + std::vector snapShot1; + uint64_t tsBoottime2 = 200; + uint64_t tsRealTime2 = 400; + snapShot1.push_back({TS_CLOCK_BOOTTIME, tsBoottime2}); + snapShot1.push_back({TS_CLOCK_REALTIME, tsRealTime2}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot1); + uint64_t time3 = 101; + uint64_t expectTime3 = 301; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_BOOTTIME, time3, TS_CLOCK_REALTIME), expectTime3); + time3 = 201; + expectTime3 = 401; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_BOOTTIME, time3, TS_CLOCK_REALTIME), expectTime3); +} + +/** + * @tc.name: ConvertBoottimeToRealtimeWithSingleSnapShot + * @tc.desc: convert Boottime to Realtime twice with single snapShot + * @tc.type: FUNC + */ +HWTEST_F(ClockFilterTest, ConvertBoottimeToRealtimeWithSingleSnapShot, TestSize.Level1) +{ + TS_LOGI("test2-7"); + uint64_t tsBoottime = 100; + uint64_t tsRealTime = 300; + std::vector snapShot0; + snapShot0.push_back({TS_CLOCK_BOOTTIME, tsBoottime}); + snapShot0.push_back({TS_CLOCK_REALTIME, tsRealTime}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot0); + + uint64_t time3 = 101; + uint64_t expectTime3 = 301; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_BOOTTIME, time3, TS_CLOCK_REALTIME), expectTime3); + time3 = 201; + expectTime3 = 401; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_BOOTTIME, time3, TS_CLOCK_REALTIME), expectTime3); +} + +/** + * @tc.name: ConvertRealtimeToBoottime + * @tc.desc: convert Realtime to Boottime with single snapShot + * @tc.type: FUNC + */ +HWTEST_F(ClockFilterTest, ConvertRealtimeToBoottime, TestSize.Level1) +{ + TS_LOGI("test2-8"); + uint64_t tsBoottime = 100; + uint64_t tsRealTime = 300; + std::vector snapShot0; + snapShot0.push_back({TS_CLOCK_BOOTTIME, tsBoottime}); + snapShot0.push_back({TS_CLOCK_REALTIME, tsRealTime}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot0); + uint64_t time7 = 301; + uint64_t expectTime7 = 101; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_REALTIME, time7, TS_CLOCK_BOOTTIME), expectTime7); +} + +/** + * @tc.name: ConvertRealtimeToBoottimeTwice + * @tc.desc: convert Realtime to Boottime twice with single snapShot + * @tc.type: FUNC + */ +HWTEST_F(ClockFilterTest, ConvertRealtimeToBoottimeTwice, TestSize.Level1) +{ + TS_LOGI("test2-8"); + uint64_t tsBoottime = 100; + uint64_t tsRealTime = 300; + std::vector snapShot0; + snapShot0.push_back({TS_CLOCK_BOOTTIME, tsBoottime}); + snapShot0.push_back({TS_CLOCK_REALTIME, tsRealTime}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot0); + uint64_t time7 = 301; + uint64_t expectTime7 = 101; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_REALTIME, time7, TS_CLOCK_BOOTTIME), expectTime7); + time7 = 501; + expectTime7 = 301; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_REALTIME, time7, TS_CLOCK_BOOTTIME), expectTime7); +} + +/** + * @tc.name: ConvertRealtimeToBoottimeTwiceWithTwoSnapshot + * @tc.desc: convert Realtime to Boottime twice with two snapShot + * @tc.type: FUNC + */ +HWTEST_F(ClockFilterTest, ConvertRealtimeToBoottimeTwiceWithTwoSnapshot, TestSize.Level1) +{ + TS_LOGI("test2-9"); + uint64_t tsBoottime = 100; + uint64_t tsRealTime = 300; + std::vector snapShot0; + snapShot0.push_back({TS_CLOCK_BOOTTIME, tsBoottime}); + snapShot0.push_back({TS_CLOCK_REALTIME, tsRealTime}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot0); + + std::vector snapShot1; + uint64_t tsBoottime2 = 200; + uint64_t tsRealTime2 = 400; + snapShot1.push_back({TS_CLOCK_BOOTTIME, tsBoottime2}); + snapShot1.push_back({TS_CLOCK_REALTIME, tsRealTime2}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot1); + uint64_t time7 = 401; + uint64_t expectTime7 = 201; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_REALTIME, time7, TS_CLOCK_BOOTTIME), expectTime7); + time7 = 301; + expectTime7 = 101; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_REALTIME, time7, TS_CLOCK_BOOTTIME), expectTime7); +} + +/** + * @tc.name: ConvertTimestamp + * @tc.desc: muti type timestamp convert + * @tc.type: FUNC + */ +HWTEST_F(ClockFilterTest, ConvertTimestamp, TestSize.Level1) +{ + TS_LOGI("test2-10"); + uint64_t tsBoottime = 100; + uint64_t tsMonotonicTime = 200; + uint64_t tsRealTime = 300; + uint64_t tsRealTimeCoarseTime = 400; + std::vector snapShot0; + snapShot0.push_back({TS_CLOCK_BOOTTIME, tsBoottime}); + snapShot0.push_back({TS_MONOTONIC, tsMonotonicTime}); + snapShot0.push_back({TS_CLOCK_REALTIME, tsRealTime}); + snapShot0.push_back({TS_CLOCK_REALTIME_COARSE, tsRealTimeCoarseTime}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot0); + + std::vector snapShot1; + uint64_t tsBoottime2 = 200; + uint64_t tsMonotonicTime2 = 350; + uint64_t tsRealTime2 = 400; + uint64_t tsRealTimeCoarseTime2 = 800; + snapShot1.push_back({TS_CLOCK_BOOTTIME, tsBoottime2}); + snapShot1.push_back({TS_MONOTONIC, tsMonotonicTime2}); + snapShot1.push_back({TS_CLOCK_REALTIME, tsRealTime2}); + snapShot1.push_back({TS_CLOCK_REALTIME_COARSE, tsRealTimeCoarseTime2}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot1); + uint64_t time1 = 150; + uint64_t expectTime1 = 250; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_BOOTTIME, time1, TS_MONOTONIC), expectTime1); + uint64_t time2 = 200; + uint64_t expectTime2 = 350; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_BOOTTIME, time2, TS_MONOTONIC), expectTime2); + uint64_t time3 = 101; + uint64_t expectTime3 = 301; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_BOOTTIME, time3, TS_CLOCK_REALTIME), expectTime3); + uint64_t time4 = 102; + uint64_t expectTime4 = 402; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_BOOTTIME, time4, TS_CLOCK_REALTIME_COARSE), expectTime4); + uint64_t time5 = 102; + uint64_t expectTime5 = 202; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_MONOTONIC, time5, TS_CLOCK_REALTIME), expectTime5); + uint64_t time6 = 351; + uint64_t expectTime6 = 251; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_REALTIME, time6, TS_MONOTONIC), expectTime6); + uint64_t time7 = 401; + uint64_t expectTime7 = 351; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_CLOCK_REALTIME, time7, TS_MONOTONIC), expectTime7); + uint64_t time8 = 150; + uint64_t expectTime8 = 50; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_MONOTONIC, time8, TS_CLOCK_BOOTTIME), expectTime8); + uint64_t time9 = 250; + uint64_t expectTime9 = 150; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_MONOTONIC, time9, TS_CLOCK_BOOTTIME), expectTime9); + uint64_t time10 = 351; + uint64_t expectTime10 = 201; + EXPECT_EQ(streamFilters_.clockFilter_->Convert(TS_MONOTONIC, time10, TS_CLOCK_BOOTTIME), expectTime10); +} + +/** + * @tc.name: ConvertToPrimary + * @tc.desc: set realtime as primary time type, and convert boottime to Primary time type + * @tc.type: FUNC + */ +HWTEST_F(ClockFilterTest, ConvertToPrimary, TestSize.Level1) +{ + TS_LOGI("test2-11"); + std::vector snapShot0; + uint64_t tsBoottime = 100; + uint64_t tsRealTime = 300; + snapShot0.push_back({TS_CLOCK_BOOTTIME, tsBoottime}); + snapShot0.push_back({TS_CLOCK_REALTIME, tsRealTime}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot0); + + std::vector snapShot1; + uint64_t tsBoottime2 = 200; + uint64_t tsRealTime2 = 400; + snapShot1.push_back({TS_CLOCK_BOOTTIME, tsBoottime2}); + snapShot1.push_back({TS_CLOCK_REALTIME, tsRealTime2}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot1); + + streamFilters_.clockFilter_->SetPrimaryClock(TS_CLOCK_REALTIME); + uint64_t time1 = 150; + uint64_t expectTime1 = 350; + EXPECT_EQ(streamFilters_.clockFilter_->ToPrimaryTraceTime(TS_CLOCK_BOOTTIME, time1), expectTime1); +} + +/** + * @tc.name: ConvertToPrimaryTwice + * @tc.desc: set realtime as primary time type, and convert boottime to Primary time type twice + * @tc.type: FUNC + */ +HWTEST_F(ClockFilterTest, ConvertToPrimaryTwice, TestSize.Level1) +{ + TS_LOGI("test2-11"); + std::vector snapShot0; + uint64_t tsBoottime = 100; + uint64_t tsRealTime = 300; + snapShot0.push_back({TS_CLOCK_BOOTTIME, tsBoottime}); + snapShot0.push_back({TS_CLOCK_REALTIME, tsRealTime}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot0); + + std::vector snapShot1; + uint64_t tsBoottime2 = 200; + uint64_t tsRealTime2 = 400; + snapShot1.push_back({TS_CLOCK_BOOTTIME, tsBoottime2}); + snapShot1.push_back({TS_CLOCK_REALTIME, tsRealTime2}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot1); + + streamFilters_.clockFilter_->SetPrimaryClock(TS_CLOCK_BOOTTIME); + uint64_t time1 = 350; + uint64_t expectTime1 = 150; + EXPECT_EQ(streamFilters_.clockFilter_->ToPrimaryTraceTime(TS_CLOCK_REALTIME, time1), expectTime1); +} + +/** + * @tc.name: ConvertToPrimaryTimestampLessThanSnapShop + * @tc.desc: convert realtime to primary time type, and timestamp less than snapshop + * @tc.type: FUNC + */ +HWTEST_F(ClockFilterTest, ConvertToPrimaryTimestampLessThanSnapShop, TestSize.Level1) +{ + TS_LOGI("test2-13"); + std::vector snapShot0; + uint64_t tsBoottime = 100; + uint64_t tsRealTime = 300; + snapShot0.push_back({TS_CLOCK_BOOTTIME, tsBoottime}); + snapShot0.push_back({TS_CLOCK_REALTIME, tsRealTime}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot0); + + streamFilters_.clockFilter_->SetPrimaryClock(TS_CLOCK_BOOTTIME); + uint64_t time1 = 250; + uint64_t expectTime1 = 50; + EXPECT_EQ(streamFilters_.clockFilter_->ToPrimaryTraceTime(TS_CLOCK_REALTIME, time1), expectTime1); +} + +/** + * @tc.name: ConvertMonotonicTimeToPrimaryTwice + * @tc.desc: convert TS_MONOTONIC to primary time type with single snapshop + * @tc.type: FUNC + */ +HWTEST_F(ClockFilterTest, ConvertMonotonicTimeToPrimaryTwice, TestSize.Level1) +{ + TS_LOGI("test2-14"); + std::vector snapShot0; + uint64_t tsMonotonicTime = 200; + uint64_t tsRealTimeCoarseTime = 400; + snapShot0.push_back({TS_MONOTONIC, tsMonotonicTime}); + snapShot0.push_back({TS_CLOCK_REALTIME_COARSE, tsRealTimeCoarseTime}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot0); + + streamFilters_.clockFilter_->SetPrimaryClock(TS_CLOCK_REALTIME_COARSE); + uint64_t time1 = 250; + uint64_t expectTime1 = 450; + EXPECT_EQ(streamFilters_.clockFilter_->ToPrimaryTraceTime(TS_MONOTONIC, time1), expectTime1); + time1 = 550; + expectTime1 = 750; + EXPECT_EQ(streamFilters_.clockFilter_->ToPrimaryTraceTime(TS_MONOTONIC, time1), expectTime1); +} + +/** + * @tc.name: ConvertToPrimaryTwiceWithTwoSnapshop + * @tc.desc: convert TS_MONOTONIC & TS_CLOCK_BOOTTIME to primary time type with two snapshop + * @tc.type: FUNC + */ +HWTEST_F(ClockFilterTest, ConvertToPrimaryTwiceWithTwoSnapshop, TestSize.Level1) +{ + TS_LOGI("test2-15"); + std::vector snapShot0; + uint64_t tsMonotonicTime = 200; + uint64_t tsRealTimeCoarseTime = 400; + snapShot0.push_back({TS_MONOTONIC, tsMonotonicTime}); + snapShot0.push_back({TS_CLOCK_REALTIME_COARSE, tsRealTimeCoarseTime}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot0); + + std::vector snapShot1; + uint64_t tsBootTime2 = 350; + uint64_t tsRealTimeCoarseTime2 = 800; + snapShot1.push_back({TS_CLOCK_BOOTTIME, tsBootTime2}); + snapShot1.push_back({TS_CLOCK_REALTIME_COARSE, tsRealTimeCoarseTime2}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot1); + + streamFilters_.clockFilter_->SetPrimaryClock(TS_CLOCK_REALTIME_COARSE); + + uint64_t time1 = 250; + uint64_t expectTime1 = 450; + EXPECT_EQ(streamFilters_.clockFilter_->ToPrimaryTraceTime(TS_MONOTONIC, time1), expectTime1); + + uint64_t time2 = 450; + uint64_t expectTime2 = 900; + EXPECT_EQ(streamFilters_.clockFilter_->ToPrimaryTraceTime(TS_CLOCK_BOOTTIME, time2), expectTime2); +} + +/** + * @tc.name: MutiTimeTypeConvertWithMutiSnapshop + * @tc.desc: convert muti time type to primary time type with muti snapshop + * @tc.type: FUNC + */ +HWTEST_F(ClockFilterTest, MutiTimeTypeConvertWithMutiSnapshop, TestSize.Level1) +{ + TS_LOGI("test2-13"); + std::vector snapShot0; + uint64_t tsBoottime = 100; + uint64_t tsMonotonicTime = 200; + uint64_t tsRealTime = 300; + uint64_t tsRealTimeCoarseTime = 400; + snapShot0.push_back({TS_CLOCK_BOOTTIME, tsBoottime}); + snapShot0.push_back({TS_MONOTONIC, tsMonotonicTime}); + snapShot0.push_back({TS_CLOCK_REALTIME, tsRealTime}); + snapShot0.push_back({TS_CLOCK_REALTIME_COARSE, tsRealTimeCoarseTime}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot0); + + std::vector snapShot1; + uint64_t tsBoottime2 = 200; + uint64_t tsMonotonicTime2 = 350; + uint64_t tsRealTime2 = 400; + uint64_t tsRealTimeCoarseTime2 = 800; + snapShot1.push_back({TS_CLOCK_BOOTTIME, tsBoottime2}); + snapShot1.push_back({TS_MONOTONIC, tsMonotonicTime2}); + snapShot1.push_back({TS_CLOCK_REALTIME, tsRealTime2}); + snapShot1.push_back({TS_CLOCK_REALTIME_COARSE, tsRealTimeCoarseTime2}); + streamFilters_.clockFilter_->AddClockSnapshot(snapShot1); + + streamFilters_.clockFilter_->SetPrimaryClock(TS_CLOCK_REALTIME); + uint64_t time1 = 150; + uint64_t expectTime1 = 350; + EXPECT_EQ(streamFilters_.clockFilter_->ToPrimaryTraceTime(TS_CLOCK_BOOTTIME, time1), expectTime1); + uint64_t time2 = 101; + uint64_t expectTime2 = 301; + EXPECT_EQ(streamFilters_.clockFilter_->ToPrimaryTraceTime(TS_CLOCK_BOOTTIME, time2), expectTime2); + uint64_t time3 = 101; + uint64_t expectTime3 = 101; + EXPECT_EQ(streamFilters_.clockFilter_->ToPrimaryTraceTime(TS_CLOCK_REALTIME, time3), expectTime3); + uint64_t time4 = 351; + uint64_t expectTime4 = 351; + EXPECT_EQ(streamFilters_.clockFilter_->ToPrimaryTraceTime(TS_CLOCK_REALTIME, time4), expectTime4); + uint64_t time5 = 350; + uint64_t expectTime5 = 250; + EXPECT_EQ(streamFilters_.clockFilter_->ToPrimaryTraceTime(TS_CLOCK_REALTIME_COARSE, time5), expectTime5); + uint64_t time6 = 420; + uint64_t expectTime6 = 320; + EXPECT_EQ(streamFilters_.clockFilter_->ToPrimaryTraceTime(TS_CLOCK_REALTIME_COARSE, time6), expectTime6); + uint64_t time7 = 801; + uint64_t expectTime7 = 401; + EXPECT_EQ(streamFilters_.clockFilter_->ToPrimaryTraceTime(TS_CLOCK_REALTIME_COARSE, time7), expectTime7); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/test/unittest/cpu_filter_test.cpp b/host/trace_streamer/test/unittest/cpu_filter_test.cpp new file mode 100644 index 0000000..3239bf9 --- /dev/null +++ b/host/trace_streamer/test/unittest/cpu_filter_test.cpp @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "cpu_filter.h" +#include "ts_common.h" + +using namespace testing::ext; +using namespace SysTuning::TraceStreamer; +namespace SysTuning { +namespace TraceStreamer { +class CpuFilterTest : public ::testing::Test { +public: + void SetUp() + { + streamFilters_.cpuFilter_ = std::make_unique(&traceDataCache_, &streamFilters_); + } + + void TearDown() {} + +public: + TraceStreamerFilters streamFilters_; + TraceDataCache traceDataCache_; +}; + +/** + * @tc.name: CpufilterInsertWakeupTest + * @tc.desc: Test CpuFilter insert WakeupEvent + * @tc.type: FUNC + */ +HWTEST_F(CpuFilterTest, CpufilterInsertWakeupTest, TestSize.Level1) +{ + TS_LOGI("test3-1"); + /* InsertWakeupEvent ts, internalTid */ + uint64_t ts1 = 168758662877000; + uint64_t itid = 1; + streamFilters_.cpuFilter_->InsertWakeupEvent(ts1, itid); // 1st waking + streamFilters_.cpuFilter_->FinishCpuEvent(); + + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(itid) == INVALID_UINT64); + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itid) == TASK_INVALID); + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 0); // 0 thread state only +} + +/** + * @tc.name: CpufilterInsertSwitchTest + * @tc.desc: Test CpuFilter insert SwitchEvent + * @tc.type: FUNC + */ +HWTEST_F(CpuFilterTest, CpufilterInsertSwitchTest, TestSize.Level1) +{ + TS_LOGI("test3-2"); + uint64_t ts1 = 168758662919000; + uint64_t itidPre = 2; + uint64_t prePior = 120; + uint64_t cpu = 0; + uint64_t itidNext = 3; + uint64_t nextPior = 124; + + streamFilters_.cpuFilter_->InsertSwitchEvent(ts1, cpu, itidPre, prePior, TASK_INTERRUPTIBLE, itidNext, + nextPior); // 1st switch + streamFilters_.cpuFilter_->FinishCpuEvent(); + printf("state of pre itid: %llu\n", streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidPre)); + + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidPre) == TASK_INTERRUPTIBLE); + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidNext) == TASK_RUNNING); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(itidNext) == 0); + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 2); // 2 thread state +} + +/** + * @tc.name: CpufilterInsertSwitchFromZeroThread + * @tc.desc: Test CpuFilter insert SwitchEvent switch from zero thread + * @tc.type: FUNC + */ +HWTEST_F(CpuFilterTest, CpufilterInsertSwitchFromZeroThread, TestSize.Level1) +{ + TS_LOGI("test3-2-2"); + uint64_t ts1 = 168758662919000; + uint64_t itidPre = 0; + uint64_t prePior = 120; + uint64_t cpu = 0; + uint64_t itidNext = 3; + uint64_t nextPior = 124; + + streamFilters_.cpuFilter_->InsertSwitchEvent(ts1, cpu, itidPre, prePior, TASK_INTERRUPTIBLE, itidNext, + nextPior); // 1st switch + streamFilters_.cpuFilter_->FinishCpuEvent(); + printf("state of pre itid: %llu\n", streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidPre)); + + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidPre) == TASK_INVALID); + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidNext) == TASK_RUNNING); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(itidNext) == 0); + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 1); // 1 thread state +} + +/** + * @tc.name: CpufilterInsertSwitchToZeroThread + * @tc.desc: Test CpuFilter insert SwitchEvent switch to zero thread + * @tc.type: FUNC + */ +HWTEST_F(CpuFilterTest, CpufilterInsertSwitchToZeroThread, TestSize.Level1) +{ + TS_LOGI("test3-2-3"); + uint64_t ts1 = 168758662919000; + uint64_t itidPre = 2; + uint64_t prePior = 120; + uint64_t cpu = 0; + uint64_t itidNext = 0; + uint64_t nextPior = 124; + + streamFilters_.cpuFilter_->InsertSwitchEvent(ts1, cpu, itidPre, prePior, TASK_INTERRUPTIBLE, itidNext, + nextPior); // 1st switch + streamFilters_.cpuFilter_->FinishCpuEvent(); + printf("state of pre itid: %llu\n", streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidPre)); + + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidPre) == TASK_INTERRUPTIBLE); + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidNext) == TASK_INVALID); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(itidNext) == INVALID_UINT64); + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 1); // 1 thread state +} + +/** + * @tc.name: CpufilterInsertSwitchDoubleThread + * @tc.desc: Test CpuFilter insert SwitchEvent, A switch to B and B switch to A + * @tc.type: FUNC + */ +HWTEST_F(CpuFilterTest, CpufilterInsertSwitchDoubleThread, TestSize.Level1) +{ + TS_LOGI("test3-3"); + uint64_t ts1 = 168758662919000; + uint64_t itidPre = 2; + uint64_t prePior = 120; + uint64_t cpu = 0; + uint64_t itidNext = 3; + uint64_t nextPior = 124; + + streamFilters_.cpuFilter_->InsertSwitchEvent(ts1, cpu, itidPre, prePior, TASK_INTERRUPTIBLE, itidNext, + nextPior); // 1st switch + streamFilters_.cpuFilter_->FinishCpuEvent(); + + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidPre) == TASK_INTERRUPTIBLE); + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidNext) == TASK_RUNNING); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(itidNext) == 0); + + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 2); // 2 thread state + + ts1 = 168758663017000; + itidPre = 3; + prePior = 120; + cpu = 0; + itidNext = 4; + nextPior = 120; + streamFilters_.cpuFilter_->InsertSwitchEvent(ts1, cpu, itidPre, prePior, TASK_RUNNABLE, itidNext, + nextPior); // 2nd switch + streamFilters_.cpuFilter_->FinishCpuEvent(); + + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidNext) == TASK_RUNNING); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(itidNext) == 2); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(itidPre) == 3); + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 4); // 4 thread state +} + +/** + * @tc.name: CpufilterInsertSwitchThreeThread + * @tc.desc: Test CpuFilter insert SwitchEvent, A switch to B and B switch to C + * @tc.type: FUNC + */ +HWTEST_F(CpuFilterTest, CpufilterInsertSwitchThreeThread, TestSize.Level1) +{ + TS_LOGI("test3-3-2"); + uint64_t ts1 = 168758662919000; + uint64_t itidPre = 2; + uint64_t prePior = 120; + uint64_t cpu = 0; + uint64_t itidNext = 3; + uint64_t nextPior = 124; + + streamFilters_.cpuFilter_->InsertSwitchEvent(ts1, cpu, itidPre, prePior, TASK_INTERRUPTIBLE, itidNext, + nextPior); // 1st switch + streamFilters_.cpuFilter_->FinishCpuEvent(); + + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidPre) == TASK_INTERRUPTIBLE); + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidNext) == TASK_RUNNING); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(itidNext) == 0); + + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 2); // 2 thread state + + ts1 = 168758663017000; + itidPre = 3; + prePior = 120; + cpu = 0; + itidNext = 2; + nextPior = 120; + streamFilters_.cpuFilter_->InsertSwitchEvent(ts1, cpu, itidPre, prePior, TASK_RUNNABLE, itidNext, + nextPior); // 2nd switch + streamFilters_.cpuFilter_->FinishCpuEvent(); + + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidNext) == TASK_RUNNING); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(itidNext) == 2); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(itidPre) == 3); + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 4); // 4 thread state +} + +/** + * @tc.name: CpufilterInsertSwitchTestZero + * @tc.desc: Test CpuFilter insert SwitchEvent, A switch to B and B switch to zero thread + * @tc.type: FUNC + */ +HWTEST_F(CpuFilterTest, CpufilterInsertSwitchTestZero, TestSize.Level1) +{ + TS_LOGI("test3-3-3"); + uint64_t ts1 = 168758662919000; + uint64_t itidPre = 2; + uint64_t prePior = 120; + uint64_t cpu = 0; + uint64_t itidNext = 3; + uint64_t nextPior = 124; + + streamFilters_.cpuFilter_->InsertSwitchEvent(ts1, cpu, itidPre, prePior, TASK_INTERRUPTIBLE, itidNext, + nextPior); // 1st switch + streamFilters_.cpuFilter_->FinishCpuEvent(); + + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidPre) == TASK_INTERRUPTIBLE); + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidNext) == TASK_RUNNING); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(itidNext) == 0); + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 2); // 2 thread state + + ts1 = 168758663017000; + itidPre = 0; + prePior = 120; + cpu = 0; + itidNext = 4; + nextPior = 120; + streamFilters_.cpuFilter_->InsertSwitchEvent(ts1, cpu, itidPre, prePior, TASK_RUNNABLE, itidNext, + nextPior); // 2nd switch + streamFilters_.cpuFilter_->FinishCpuEvent(); + + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itidNext) == TASK_RUNNING); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(itidNext) == 2); + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 3); // 4 thread state +} + +/** + * @tc.name: CpufiltertWakeingTest + * @tc.desc: Test CpuFilter insert Waking Event + * @tc.type: FUNC + */ +HWTEST_F(CpuFilterTest, CpufiltertWakeupTest, TestSize.Level1) +{ + TS_LOGI("test3-5"); + uint64_t ts1 = 168758662919000; + uint64_t itid = 2; + + streamFilters_.cpuFilter_->InsertWakeupEvent(ts1, itid); + streamFilters_.cpuFilter_->FinishCpuEvent(); + + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itid) == TASK_INVALID); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(itid) == INVALID_UINT64); + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 0); // 0 thread state +} + +/** + * @tc.name: CpufiltertWakingTwice + * @tc.desc: Test CpuFilter insert Waking Event, one thread waking twice + * @tc.type: FUNC + */ +HWTEST_F(CpuFilterTest, CpufiltertWakingTwice, TestSize.Level1) +{ + TS_LOGI("test3-6"); + uint64_t ts1 = 168758662919000; + uint64_t itid = 2; + + streamFilters_.cpuFilter_->InsertWakeupEvent(ts1, itid); + streamFilters_.cpuFilter_->FinishCpuEvent(); + ts1 = 168758662929000; + itid = 4; + streamFilters_.cpuFilter_->InsertWakeupEvent(ts1, itid); + streamFilters_.cpuFilter_->FinishCpuEvent(); + + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(itid) == TASK_INVALID); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(itid) == INVALID_UINT64); + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 0); // 0 thread state +} + +/** + * @tc.name: CpufilterInsertSwitchTestFull + * @tc.desc: Parsing multiple switch and wakeup alternates + * @tc.type: FUNC + */ +HWTEST_F(CpuFilterTest, CpufilterInsertSwitchTestFull, TestSize.Level1) +{ + TS_LOGI("test3-7"); + /* InsertWakeupEvent ts, internalTid */ + /* InsertSwitchEvent ts, cpu, prevPid, prevPior, prevState, nextPid, nextPior */ + streamFilters_.cpuFilter_->InsertWakeupEvent(168758662877000, 1); // 1st waking + streamFilters_.cpuFilter_->FinishCpuEvent(); + + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(1) == INVALID_UINT64); + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(1) == TASK_INVALID); + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 0); // 0 thread state only + + streamFilters_.cpuFilter_->InsertSwitchEvent(168758662919000, 0, 1, 120, TASK_INTERRUPTIBLE, 2, 124); // 1st switch + streamFilters_.cpuFilter_->FinishCpuEvent(); + + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(1) == TASK_INTERRUPTIBLE); + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(2) == TASK_RUNNING); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(2) == 0); + // 2 thread state, the waking event add a runnable state + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 3); + streamFilters_.cpuFilter_->InsertSwitchEvent(168758663017000, 0, 0, 120, TASK_RUNNABLE, 4, 120); // 2nd switch + streamFilters_.cpuFilter_->FinishCpuEvent(); + + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(4) == TASK_RUNNING); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(4) == 3); + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 4); // 4 thread state + + streamFilters_.cpuFilter_->InsertWakeupEvent(168758663078000, 0); // 2nd waking + + streamFilters_.cpuFilter_->InsertWakeupEvent(168758663092000, 0); // 3rd waking + streamFilters_.cpuFilter_->FinishCpuEvent(); + + streamFilters_.cpuFilter_->InsertSwitchEvent(168758663107000, 0, 2, 124, TASK_RUNNABLE, 5, 98); // 3rd switch + streamFilters_.cpuFilter_->FinishCpuEvent(); + + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(5) == TASK_RUNNING); + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(2) == TASK_RUNNABLE); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(5) == 4); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(2) == 5); + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 6); // 6 thread state + + streamFilters_.cpuFilter_->InsertSwitchEvent(168758663126000, 0, 5, 98, TASK_INTERRUPTIBLE, 2, 124); // 4th switch + streamFilters_.cpuFilter_->FinishCpuEvent(); + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(2) == TASK_RUNNING); + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(5) == TASK_INTERRUPTIBLE); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(2) == 6); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(5) == 7); + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 8); // 8 thread state + + streamFilters_.cpuFilter_->InsertSwitchEvent(168758663136000, 3, 5, 120, TASK_RUNNABLE, 6, 120); // 5th switch + streamFilters_.cpuFilter_->FinishCpuEvent(); + + EXPECT_TRUE(streamFilters_.cpuFilter_->StateOfInternalTidInStateTable(6) == TASK_RUNNING); + EXPECT_TRUE(streamFilters_.cpuFilter_->RowOfInternalTidInStateTable(6) == 8); + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->Size() == 10); // 10 thread state + + // after 3rd switch + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->DursData()[0] == 168758663107000 - 168758662919000); + // after 4th switch + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->DursData()[4] == 168758663126000 - 168758663107000); + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->DursData()[5] == 168758663126000 - 168758663107000); + // after 5th switch + EXPECT_TRUE(traceDataCache_.GetThreadStateData()->DursData()[7] == 168758663136000 - 168758663126000); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/test/unittest/event_parser_test.cpp b/host/trace_streamer/test/unittest/event_parser_test.cpp new file mode 100644 index 0000000..56a8776 --- /dev/null +++ b/host/trace_streamer/test/unittest/event_parser_test.cpp @@ -0,0 +1,1214 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include "cpu_filter.h" +#include "parser/bytrace_parser/bytrace_event_parser.h" +#include "parser/bytrace_parser/bytrace_parser.h" +#include "parser/common_types.h" +#include "securec.h" +#include "trace_streamer_selector.h" + +using namespace testing::ext; +using namespace SysTuning::TraceStreamer; +namespace SysTuning { +namespace TraceStreamer { +const uint32_t G_BUF_SIZE = 1024; +// TestSuite: +class EventParserTest : public ::testing::Test { +public: + void SetUp() + { + stream_.InitFilter(); + } + + void TearDown() + { + if (access(dbPath_.c_str(), F_OK) == 0) { + remove(dbPath_.c_str()); + } + } + +public: + TraceStreamerSelector stream_ = {}; + const std::string dbPath_ = "/data/resource/out.db"; +}; + +/** + * @tc.name: ParseLine + * @tc.desc: Parse a complete sched_switch event in bytrace format + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseLine, TestSize.Level1) +{ + TS_LOGI("test4-1"); + BytraceLine bytraceLine; + bytraceLine.ts = 1616439852302; + bytraceLine.pid = 1; + bytraceLine.cpu = 0; + bytraceLine.task = "ACCS0-2716"; + bytraceLine.pidStr = "12"; + bytraceLine.tGidStr = "12"; + bytraceLine.eventName = "sched_switch"; + ArgsMap args; + args.insert(std::make_pair("prev_comm", "ACCS0")); + args.insert(std::make_pair("prev_pid", "2716")); + args.insert(std::make_pair("prev_prio", "120")); + args.insert(std::make_pair("prev_state", "R")); + args.insert(std::make_pair("next_comm", "kworker/0:0")); + args.insert(std::make_pair("next_pid", "8326")); + args.insert(std::make_pair("next_prio", "120")); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.ParseDataItem(bytraceLine, args, 2519); + EXPECT_EQ(result, true); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + auto readStatIndex = stream_.traceDataCache_->GetConstSchedSliceData().EndStatesData()[0]; + EXPECT_EQ(TASK_RUNNABLE, readStatIndex); + auto realTimeStamp = stream_.traceDataCache_->GetConstSchedSliceData().TimeStamData()[0]; + EXPECT_TRUE(bytraceLine.ts == realTimeStamp); + auto realCpu = stream_.traceDataCache_->GetConstSchedSliceData().CpusData()[0]; + EXPECT_TRUE(bytraceLine.cpu == realCpu); +} + +/** + * @tc.name: ParseLineNotEnoughArgs + * @tc.desc: Parse a sched_switch event which has not enough args in bytrace format + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseLineNotEnoughArgs, TestSize.Level1) +{ + TS_LOGI("test4-1"); + BytraceLine bytraceLine; + bytraceLine.ts = 1616439852302; + bytraceLine.pid = 1; + bytraceLine.cpu = 0; + bytraceLine.task = "ACCS0-2716"; + bytraceLine.pidStr = "12"; + bytraceLine.tGidStr = "12"; + bytraceLine.eventName = "sched_switch"; + ArgsMap args; + args.insert(std::make_pair("prev_prio", "120")); + args.insert(std::make_pair("prev_state", "R")); + args.insert(std::make_pair("next_comm", "kworker/0:0")); + args.insert(std::make_pair("next_pid", "8326")); + args.insert(std::make_pair("next_prio", "120")); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.ParseDataItem(bytraceLine, args, 2519); + + EXPECT_EQ(result, false); +} + +/** + * @tc.name: ParseLineUnCognizableEventname + * @tc.desc: Parse a UnCognizable Eventname event in bytrace format + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseLineUnCognizableEventname, TestSize.Level1) +{ + TS_LOGI("test4-2"); + BytraceLine bytraceLine; + bytraceLine.ts = 1616439852302; + bytraceLine.pid = 1; + bytraceLine.cpu = 0; + bytraceLine.task = "ACCS0-2716"; + bytraceLine.pidStr = "12"; + bytraceLine.tGidStr = "12"; + bytraceLine.eventName = "ThisEventNameDoNotExist"; // UnRecognizable event name + ArgsMap args; + args.insert(std::make_pair("prev_comm", "ACCS0")); + args.insert(std::make_pair("prev_pid", "2716")); + args.insert(std::make_pair("prev_prio", "120")); + args.insert(std::make_pair("prev_state", "R")); + args.insert(std::make_pair("next_comm", "kworker/0:0")); + args.insert(std::make_pair("next_pid", "8326")); + args.insert(std::make_pair("next_prio", "120")); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.ParseDataItem(bytraceLine, args, 2519); + + EXPECT_EQ(result, false); +} + +/** + * @tc.name: ParseSchedSwitchNoArgs + * @tc.desc: Parse a SchedSwitch event which has no args in bytrace format + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseSchedSwitchNoArgs, TestSize.Level1) +{ + TS_LOGI("test4-4"); + BytraceLine bytraceLine; + bytraceLine.ts = 1616439852302; + bytraceLine.pid = 1; + bytraceLine.cpu = 0; + bytraceLine.task = "ACCS0-2716"; + bytraceLine.pidStr = "12"; + bytraceLine.tGidStr = "12"; + bytraceLine.eventName = "sched_switch"; + ArgsMap args; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.ParseDataItem(bytraceLine, args, 2519); + + EXPECT_EQ(result, false); +} + +/** + * @tc.name: ParseSchedWakeupNoArgs + * @tc.desc: Parse a SchedWakeup event which has no args in bytrace format + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseSchedWakeupNoArgs, TestSize.Level1) +{ + TS_LOGI("test4-4"); + BytraceLine bytraceLine; + bytraceLine.ts = 1616439852302; + bytraceLine.pid = 1; + bytraceLine.cpu = 0; + bytraceLine.task = "ACCS0-2716"; + bytraceLine.pidStr = "12"; + bytraceLine.tGidStr = "12"; + bytraceLine.eventName = "sched_wakeup"; + ArgsMap args; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.ParseDataItem(bytraceLine, args, 2519); + + EXPECT_EQ(result, false); +} + +/** + * @tc.name: ParseTracingMarkWriteC + * @tc.desc: Parse a TracingMarkWrite C event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseTracingMarkWriteC, TestSize.Level1) +{ + TS_LOGI("test4-7"); + + const uint8_t str[] = + "ACCS0-2716 ( 2519) [000] ...1 174330.284808: tracing_mark_write: C|2519|Heap size (KB)|2906\n"; + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(1)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); +} + +/** + * @tc.name: ParseTracingMarkWriteBE + * @tc.desc: Parse a TracingMarkWrite BE event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseTracingMarkWriteBE, TestSize.Level1) +{ + TS_LOGI("test4-8"); + const uint8_t str[] = + "system-1298 ( 1298) [001] ...1 174330.287420: tracing_mark_write: B|1298|Choreographer#doFrame\n \ + system - 1298(1298)[001]... 1 174330.287622 : tracing_mark_write : E | 1298\n"; // E | 1298 wrong format + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(1)); + EXPECT_EQ(bytraceParser.ParsedTraceInvalidLines(), static_cast(1)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); +} + +/** + * @tc.name: ParseTracingMarkWriteSF + * @tc.desc: Parse a TracingMarkWrite SF event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseTracingMarkWriteSF, TestSize.Level1) +{ + TS_LOGI("test4-9"); + + const uint8_t str[] = + "system-1298 ( 1298) [001] ...1 174330.287478: tracing_mark_write: S|1298|animator:\ + translateX|18888109\n system-1298(1298)[001]... 1 174330.287514 : tracing_mark_write : \ + F | 1298 | animator : translateX | 18888109\n"; + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(1)); + EXPECT_EQ(bytraceParser.ParsedTraceInvalidLines(), static_cast(1)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); +} + +/** + * @tc.name: ParseTracingMarkWriteErrorPoint + * @tc.desc: Parse a TracingMarkWrite event with error point info + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseTracingMarkWriteErrorPoint, TestSize.Level1) +{ + TS_LOGI("test4-10"); + const uint8_t str[] = + "system-1298 ( 1298) [001] ...1 174330.287478: tracing_mark_write: G|1298|animator: \ + translateX|18888109\n system-1298(1298)[001]... 1 174330.287514 : tracing_mark_write : \ + F | 1298 | animator : translateX | 18888109\n"; + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(1)); + EXPECT_EQ(bytraceParser.ParsedTraceInvalidLines(), static_cast(1)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); +} + +/** + * @tc.name: ParseCpuIdle + * @tc.desc: Parse a CpuIdle event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseCpuIdle, TestSize.Level1) +{ + TS_LOGI("test4-14"); + const uint8_t str[] = "-0 (-----) [003] d..2 174330.280761: cpu_idle: state=2 cpu_id=3\n"; + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(1)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); +} + +/** + * @tc.name: ParseIrqHandlerEntry + * @tc.desc: Parse a IrqHandlerEntry event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseIrqHandlerEntry, TestSize.Level1) +{ + TS_LOGI("test4-15"); + const uint8_t str[] = + "ACCS0-2716 ( 2519) [000] d.h1 174330.280362: irq_handler_entry: irq=19 name=408000.qcom,cpu-bwmon\n"; + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(1)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); +} + +/** + * @tc.name: ParseIrqHandlerExit + * @tc.desc: Parse a IrqHandlerExit event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseIrqHandlerExit, TestSize.Level1) +{ + TS_LOGI("test4-16"); + const uint8_t str[] = + "ACCS0-2716 ( 2519) [000] d.h1 174330.280382: irq_handler_exit: irq=19 ret=handled\n"; + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(1)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); +} + +/** + * @tc.name: ParseSchedWaking + * @tc.desc: Parse a SchedWaking event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseSchedWaking, TestSize.Level1) +{ + TS_LOGI("test4-17"); + const uint8_t str[] = + "ACCS0-2716 ( 2519) [000] d..5 174330.280567: sched_waking: \ + comm=Binder:924_6 pid=1332 prio=120 target_cpu=000\n"; + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(1)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); +} + +/** + * @tc.name: ParseSchedWakeup + * @tc.desc: Parse a SchedWakeup event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseSchedWakeup, TestSize.Level1) +{ + TS_LOGI("test4-18"); + const uint8_t str[] = + "ACCS0-2716 ( 2519) [000] d..6 174330.280575: sched_wakeup: \ + comm=Binder:924_6 pid=1332 prio=120 target_cpu=000\n"; + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(1)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); +} + +/** + * @tc.name: ParseTraceEventClockSync + * @tc.desc: Parse a TraceEventClockSync event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseTraceEventClockSync, TestSize.Level1) +{ + TS_LOGI("test4-19"); + const uint8_t str[] = + "sampletrace-12728 (12728) [003] ...1 174330.280300: tracing_mark_write: \ + trace_event_clock_sync:parent_ts=23139.998047\n"; + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(1)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); +} + +/** + * @tc.name: ParseSchedSwitch + * @tc.desc: Parse a SchedSwitch event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseSchedSwitch, TestSize.Level1) +{ + TS_LOGI("test4-27"); + const uint8_t str[] = + "ACCS0-2716 ( 2519) [000] d..3 174330.289220: sched_switch: prev_comm=ACCS0 prev_pid=2716 prev_prio=120 \ + prev_state=R+ ==> next_comm=Binder:924_6 next_pid=1332 next_prio=120\n"; + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(1)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); +} + +/** + * @tc.name: ParseTaskRename + * @tc.desc: Parse a TaskRename event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseTaskRename, TestSize.Level1) +{ + TS_LOGI("test4-28"); + const uint8_t str[] = + "<...>-2093 (-----) [001] ...2 174332.792290: task_rename: pid=12729 oldcomm=perfd \ + newcomm=POSIX timer 249 oom_score_adj=-1000\n"; + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(1)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); +} + +/** + * @tc.name: ParseTaskNewtask + * @tc.desc: Parse a TaskNewtask event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseTaskNewtask, TestSize.Level1) +{ + TS_LOGI("test4-29"); + const uint8_t str[] = + "<...>-2 (-----) [003] ...1 174332.825588: task_newtask: pid=12730 \ + comm=kthreadd clone_flags=800711 oom_score_adj=0\n"; + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(1)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); +} + +/** + * @tc.name: ParseWorkqueueExecuteStart + * @tc.desc: Parse a WorkqueueExecuteStart event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseWorkqueueExecuteStart, TestSize.Level1) +{ + TS_LOGI("test4-30"); + const uint8_t str[] = + "<...>-12180 (-----) [001] ...1 174332.827595: workqueue_execute_start: \ + work struct 0000000000000000: function pm_runtime_work\n"; + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(1)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); +} + +/** + * @tc.name: ParseWorkqueueExecuteEnd + * @tc.desc: Parse a WorkqueueExecuteEnd event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseWorkqueueExecuteEnd, TestSize.Level1) +{ + TS_LOGI("test4-31"); + const uint8_t str[] = + "<...>-12180 (-----) [001] ...1 174332.828056: workqueue_execute_end: work struct 0000000000000000\n"; + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(1)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); +} + +/** + * @tc.name: ParsDistribute + * @tc.desc: Parse a Distribute event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParsDistribute, TestSize.Level1) +{ + TS_LOGI("test4-31-1"); + const uint8_t str[] = + "system-1298 ( 1298) [001] ...1 174330.287420: tracing_mark_write: B|1298|[8b00e96b2,2,1]:C$#decodeFrame$#" + "{\"Process\":\"DecodeVideoFrame\",\"frameTimestamp\":37313484466} \ + system - 1298(1298)[001]... 1 174330.287622 : tracing_mark_write : E | 1298 \n"; + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(1)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ChainIds()[0] == "8b00e96b2"); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().SpanIds()[0] == "2"); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ParentSpanIds()[0] == "1"); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Flags()[0] == "C"); +} + +/** + * @tc.name: ParsPairsOfDistributeEvent + * @tc.desc: Parse a pair of Distribute event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParsPairsOfDistributeEvent, TestSize.Level1) +{ + TS_LOGI("test4-31-2"); + const uint8_t str[] = + "system-1298 ( 1298) [001] ...1 174330.287420: tracing_mark_write: B|1298|[8b00e96b2,2,1]:C$#decodeFrame$#" + "{\"Process\":\"DecodeVideoFrame\",\"frameTimestamp\":37313484466} \ + system - 1298(1298)[001]... 1 174330.287622 : tracing_mark_write : E | 1298 \n" + "startVC-7601 ( 7601) [002] ...1 174330.387420: tracing_mark_write: B|7601|[8b00e96b2,2,1]:S$#startVCFrame$#" + "{\"Process\":\"DecodeVideoFrame\",\"frameTimestamp\":37313484466} \ + startVC-7601 (7601)[002]... 1 174330.487622 : tracing_mark_write : E | 7601 \n"; + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(2)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 2); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ChainIds()[0] == "8b00e96b2"); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().SpanIds()[0] == "2"); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ParentSpanIds()[0] == "1"); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Flags()[0] == "C"); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ChainIds()[1] == "8b00e96b2"); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().SpanIds()[1] == "2"); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ParentSpanIds()[1] == "1"); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Flags()[1] == "S"); +} + +/** + * @tc.name: ParsDistributeWithNoFlag + * @tc.desc: Parse a Distribute event with no flag + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParsDistributeWithNoFlag, TestSize.Level1) +{ + TS_LOGI("test4-31-3"); + const uint8_t str[] = + "system-1298 ( 1298) [001] ...1 174330.287420: tracing_mark_write: B|1298|[8b00e96b2,2,1]$#decodeFrame$#" + "{\"Process\":\"DecodeVideoFrame\",\"frameTimestamp\":37313484466} \ + system - 1298(1298)[001]... 1 174330.287622 : tracing_mark_write : E | 1298 \n"; + auto buf = std::make_unique(G_BUF_SIZE); + if (memcpy_s(buf.get(), G_BUF_SIZE, str, sizeof(str))) { + EXPECT_TRUE(false); + return; + } + BytraceParser bytraceParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + bytraceParser.ParseTraceDataSegment(std::move(buf), G_BUF_SIZE); + bytraceParser.WaitForParserEnd(); + + EXPECT_EQ(bytraceParser.ParsedTraceValidLines(), static_cast(1)); + stream_.traceDataCache_->ExportDatabase(dbPath_); + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ChainIds()[0] == "8b00e96b2"); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().SpanIds()[0] == "2"); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ParentSpanIds()[0] == "1"); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Flags()[0] == ""); +} + +/** + * @tc.name: ParseSchedSwitchByInitParam + * @tc.desc: Parse a SchedSwitch event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseSchedSwitchByInitParam, TestSize.Level1) +{ + TS_LOGI("test4-32"); + BytraceLine bytraceLine; + static std::unordered_map args{{"prev_comm", "ACCS0"}, {"next_comm", "HeapTaskDaemon"}, + {"prev_prio", "120"}, {"next_prio", "124"}, + {"prev_pid", "2716"}, {"next_pid", "2532"}, + {"prev_state", "S"}}; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.SchedSwitchEvent(args, bytraceLine); + + EXPECT_EQ(result, true); +} + +/** + * @tc.name: ParseSchedSwitchByAbnormalInitParam + * @tc.desc: Parse a SchedSwitch event with some Null parameter + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseSchedSwitchByAbnormalInitParam, TestSize.Level1) +{ + TS_LOGI("test4-33"); + BytraceLine bytraceLine; + static std::unordered_map args{{"prev_comm", "ACCS0"}, {"next_comm", "HeapTaskDaemon"}, + {"prev_prio", ""}, {"next_prio", ""}, + {"prev_pid", ""}, {"next_pid", ""}, + {"prev_state", "S"}}; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.SchedSwitchEvent(args, bytraceLine); + + EXPECT_EQ(result, false); +} + +/** + * @tc.name: ParseTaskRenameEventByInitParam + * @tc.desc: Parse a TaskRename event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseTaskRenameByInitParam, TestSize.Level1) +{ + TS_LOGI("test4-34"); + BytraceLine bytraceLine; + static std::unordered_map args{{"newcomm", "POSIX"}, {"pid", "8542"}}; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.TaskRenameEvent(args, bytraceLine); + + EXPECT_EQ(result, true); +} + +/** + * @tc.name: ParseTaskNewtaskByInitParam + * @tc.desc: Parse a TaskNew event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseTaskNewtaskByInitParam, TestSize.Level1) +{ + TS_LOGI("test4-35"); + BytraceLine bytraceLine; + bytraceLine.tGidStr = "12"; + static std::unordered_map args{{"comm", "POSIX"}, {"pid", "8542"}, {"clone_flags", "1"}}; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.TaskNewtaskEvent(args, bytraceLine); + + EXPECT_EQ(result, true); +} + +/** + * @tc.name: ParseTracingMarkWriteByInitParam + * @tc.desc: Parse a TracingMarkWriteEvent event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseTracingMarkWriteByInitParam, TestSize.Level1) +{ + TS_LOGI("test4-36"); + BytraceLine bytraceLine; + bytraceLine.ts = 1616439852302; + bytraceLine.pid = 1; + bytraceLine.argsStr = "vec=9 [action=RCU]"; + static std::unordered_map args{}; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.TracingMarkWriteEvent(args, bytraceLine); + + EXPECT_EQ(result, true); +} + +/** + * @tc.name: ParseSchedWakeupByInitParam + * @tc.desc: Parse a SchedWakeup event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseSchedWakeupByInitParam, TestSize.Level1) +{ + TS_LOGI("test4-37"); + BytraceLine bytraceLine; + bytraceLine.ts = 1616439852302; + bytraceLine.pid = 1; + static std::unordered_map args{{"pid", "1200"}, {"target_cpu", "1"}}; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.SchedWakeupEvent(args, bytraceLine); + + EXPECT_EQ(result, true); +} + +/** + * @tc.name: ParseSchedWakeupByAbromalInitParam + * @tc.desc: Parse a SchedWakeup event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseSchedWakeupByAbromalInitParam, TestSize.Level1) +{ + TS_LOGI("test4-38"); + BytraceLine bytraceLine; + bytraceLine.ts = 1616439852302; + bytraceLine.pid = 1; + static std::unordered_map args{{"pid", ""}, {"target_cpu", "1"}}; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.SchedWakeupEvent(args, bytraceLine); + + EXPECT_EQ(result, false); +} + +/** + * @tc.name: ParseSchedWakingByInitParam + * @tc.desc: Parse a SchedWaking event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseSchedWakingByInitParam, TestSize.Level1) +{ + TS_LOGI("test4-39"); + BytraceLine bytraceLine; + bytraceLine.ts = 1616439852302; + bytraceLine.pid = 1; + static std::unordered_map args{{"pid", "1200"}, {"target_cpu", "1"}}; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.SchedWakingEvent(args, bytraceLine); + + EXPECT_EQ(result, true); +} + +/** + * @tc.name: ParseSchedWakingByAbnormalInitParam + * @tc.desc: Parse a SchedWaking event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseSchedWakingByAbnormalInitParam, TestSize.Level1) +{ + TS_LOGI("test4-40"); + BytraceLine bytraceLine; + bytraceLine.ts = 1616439852302; + bytraceLine.pid = 1; + static std::unordered_map args{{"pid", ""}, {"target_cpu", "1"}}; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.SchedWakingEvent(args, bytraceLine); + + EXPECT_EQ(result, false); +} + +/** + * @tc.name: ParseCpuIdleByInitParam + * @tc.desc: Parse a CpuIdle event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseCpuIdleByInitParam, TestSize.Level1) +{ + TS_LOGI("test4-41"); + BytraceLine bytraceLine; + bytraceLine.ts = 1616439852302; + bytraceLine.eventName = "POSIX"; + static std::unordered_map args{{"cpu_id", "3"}, {"state", "4294967295"}}; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.CpuIdleEvent(args, bytraceLine); + + EXPECT_EQ(result, true); +} + +/** + * @tc.name: ParseCpuIdleByAbnormalInitParam + * @tc.desc: Parse a CpuIdle event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseCpuIdleByAbnormalInitParam, TestSize.Level1) +{ + TS_LOGI("test4-42"); + BytraceLine bytraceLine; + bytraceLine.ts = 1616439852302; + bytraceLine.eventName = "POSIX"; + static std::unordered_map args{{"cpu_id", ""}, {"state", "4294967295"}}; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.CpuIdleEvent(args, bytraceLine); + + EXPECT_EQ(result, false); +} + +/** + * @tc.name: ParseCpuFrequencyNormal + * @tc.desc: Parse a CpuFrequency event normally + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseCpuFrequencyNormal, TestSize.Level1) +{ + TS_LOGI("test4-44"); + BytraceLine bytraceLine; + bytraceLine.ts = 1616439852302; + bytraceLine.eventName = "POSIX"; + static std::unordered_map args{{"cpu_id", "3"}, {"state", "4294967295"}}; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.CpuFrequencyEvent(args, bytraceLine); + + EXPECT_EQ(result, true); +} + +/** + * @tc.name: ParseCpuFrequencyByAbnormalInitEmptyCpuId + * @tc.desc: Parse a CpuFrequency event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseCpuFrequencyByAbnormalInitEmptyCpuId, TestSize.Level1) +{ + TS_LOGI("test4-45"); + BytraceLine bytraceLine; + bytraceLine.ts = 1616439852302; + bytraceLine.eventName = "POSIX"; + static std::unordered_map args{{"cpu_id", ""}, {"state", "4294967295"}}; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.CpuFrequencyEvent(args, bytraceLine); + + EXPECT_EQ(result, false); +} + +/** + * @tc.name: ParseCpuFrequencyByAbnormalInitEmptyStateValue + * @tc.desc: Parse a CpuFrequency event, empty state value + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseCpuFrequencyByAbnormalInitEmptyStateValue, TestSize.Level1) +{ + TS_LOGI("test4-46"); + BytraceLine bytraceLine; + bytraceLine.ts = 1616439852302; + bytraceLine.eventName = "POSIX"; + static std::unordered_map args{{"cpu_id", "3"}, {"state", ""}}; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.CpuFrequencyEvent(args, bytraceLine); + + EXPECT_EQ(result, false); +} + +/** + * @tc.name: ParseWorkqueueExecuteStartByInitParam + * @tc.desc: Parse a WorkqueueExecuteStart event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseWorkqueueExecuteStartByInitParam, TestSize.Level1) +{ + TS_LOGI("test4-47"); + BytraceLine bytraceLine; + bytraceLine.ts = 1616439852302; + bytraceLine.pid = 1355; + bytraceLine.argsStr = "vec=9 [action=RCU]"; + static std::unordered_map args{}; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.WorkqueueExecuteStartEvent(args, bytraceLine); + + EXPECT_EQ(result, true); +} + +/** + * @tc.name: ParseWorkqueueExecuteEndByInitParam + * @tc.desc: Parse a WorkqueueExecuteEnd event + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, ParseWorkqueueExecuteEndByInitParam, TestSize.Level1) +{ + TS_LOGI("test4-48"); + BytraceLine bytraceLine; + bytraceLine.ts = 1616439852302; + bytraceLine.pid = 1355; + static std::unordered_map args{}; + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.WorkqueueExecuteEndEvent(args, bytraceLine); + + EXPECT_EQ(result, false); +} + +/** + * @tc.name: CheckTracePoint + * @tc.desc: Judge whether the "tracepoint information conforming to the specification" in a text format conforms to the + * tracepoint specification + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, CheckTracePoint, TestSize.Level1) +{ + TS_LOGI("test4-49"); + std::string str("B|924|FullSuspendCheck"); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.printEventParser_.CheckTracePoint(str); + + EXPECT_TRUE(result == SUCCESS); +} + +/** + * @tc.name: CheckTracePointEmptyString + * @tc.desc: Judge whether the Empty string conforms to the tracepoint specification + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, CheckTracePointEmptyString, TestSize.Level1) +{ + TS_LOGI("test4-50"); + std::string str(""); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.printEventParser_.CheckTracePoint(str); + + EXPECT_TRUE(result == ERROR); +} + +/** + * @tc.name: CheckTracePointNoSplit + * @tc.desc: Judge whether the string No Split conforms to the tracepoint specification + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, CheckTracePointNoSplit, TestSize.Level1) +{ + TS_LOGI("test4-51"); + std::string str("trace_event_clock_sync"); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.printEventParser_.CheckTracePoint(str); + + EXPECT_TRUE(result == ERROR); +} + +/** + * @tc.name: CheckTracePointMultiType + * @tc.desc: Judge whether the string has multipul Case type conforms to the tracepoint specification + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, CheckTracePointMultiType, TestSize.Level1) +{ + TS_LOGI("test4-52"); + std::string str("BECSF|924|FullSuspendCheck"); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.printEventParser_.CheckTracePoint(str); + + EXPECT_TRUE(result == ERROR); +} + +/** + * @tc.name: CheckTracePointCheckSingleCharacter + * @tc.desc: Check whether a single character conforms to tracepoint format + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, CheckTracePointCheckSingleCharacter, TestSize.Level1) +{ + TS_LOGI("test4-53"); + std::string str("X"); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.printEventParser_.CheckTracePoint(str); + + EXPECT_TRUE(result == ERROR); +} + +/** + * @tc.name: CheckTracePointCheckErrorSplit + * @tc.desc: Check error split + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, CheckTracePointCheckErrorSplit, TestSize.Level1) +{ + TS_LOGI("test4-54"); + std::string str("B&924|FullSuspendCheck"); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.printEventParser_.CheckTracePoint(str); + + EXPECT_TRUE(result == ERROR); +} + +/** + * @tc.name: GetTracePoint + * @tc.desc: Test GetTracePoint interface + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, GetTracePoint, TestSize.Level1) +{ + TS_LOGI("test4-55"); + TracePoint point; + std::string str("B|924|SuspendThreadByThreadId suspended Binder:924_8 id=39"); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.printEventParser_.GetTracePoint(str, point); + + EXPECT_TRUE(result == SUCCESS); +} + +/** + * @tc.name: GetTracePointParseEmptyString + * @tc.desc: Test GetTracePoint interface parse empty string + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, GetTracePointParseEmptyString, TestSize.Level1) +{ + TS_LOGI("test4-56"); + TracePoint point; + std::string str(""); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.printEventParser_.GetTracePoint(str, point); + + EXPECT_TRUE(result == ERROR); +} + +/** + * @tc.name: GetTracePointParseErrorSubEventType + * @tc.desc: Test GetTracePoint interface parse error Sub event type + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, GetTracePointParseErrorSubEventType, TestSize.Level1) +{ + TS_LOGI("test4-57"); + TracePoint point; + std::string str("X|924|SuspendThreadByThreadId suspended Binder:924_8 id=39"); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.printEventParser_.GetTracePoint(str, point); + + EXPECT_TRUE(result == ERROR); +} + +/** + * @tc.name: GetThreadGroupId + * @tc.desc: Test GetThreadGroupId interface + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, GetThreadGroupId, TestSize.Level1) +{ + TS_LOGI("test4-58"); + size_t length{0}; + std::string str("E|924"); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.printEventParser_.GetThreadGroupId(str, length); + + EXPECT_TRUE(result == 924); +} + +/** + * @tc.name: GetThreadGroupIdParseErrorPid + * @tc.desc: Test GetThreadGroupId interface parse error pid + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, GetThreadGroupIdParseErrorPid, TestSize.Level1) +{ + TS_LOGI("test4-59"); + size_t length{0}; + std::string str("E|abc"); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.printEventParser_.GetThreadGroupId(str, length); + + EXPECT_TRUE(result == ERROR); +} + +/** + * @tc.name: HandlerB + * @tc.desc: Test HandlerB interface + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, HandlerB, TestSize.Level1) +{ + TS_LOGI("test4-60"); + size_t length{3}; + TracePoint outPoint; + std::string str("B|924|HID::ISensors::batch::client"); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.printEventParser_.HandlerB(str, outPoint, length); + + EXPECT_TRUE(result == SUCCESS); +} + +/** + * @tc.name: HandlerB + * @tc.desc: Test HandlerBAbnormal interface using Abnormal format + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, HandlerBAbnormal, TestSize.Level1) +{ + TS_LOGI("test4-61"); + size_t length{3}; + TracePoint outPoint; + std::string str("B|924|"); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.printEventParser_.HandlerB(str, outPoint, length); + + EXPECT_TRUE(result == ERROR); +} + +/** + * @tc.name: HandlerCsf + * @tc.desc: Test HandlerCSF interface + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, HandlerCsf, TestSize.Level1) +{ + TS_LOGI("test4-62"); + size_t length{4}; + TracePoint outPoint; + std::string str("C|2519|Heap size (KB)|2363"); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.printEventParser_.HandlerCSF(str, outPoint, length); + + EXPECT_TRUE(result == SUCCESS); +} + +/** + * @tc.name: HandlerCsfParseEmptyString + * @tc.desc: Parse empty string using HandlerCSF interface + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, HandlerCsfParseEmptyString, TestSize.Level1) +{ + TS_LOGI("test4-63"); + size_t length{4}; + TracePoint outPoint; + std::string str(""); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.printEventParser_.HandlerCSF(str, outPoint, length); + + EXPECT_TRUE(result == ERROR); +} + +/** + * @tc.name: HandlerCsfParseErrorFormate + * @tc.desc: Parse "C|2519|Heap size (KB)|" using HandlerCSF interface + * @tc.type: FUNC + */ +HWTEST_F(EventParserTest, HandlerCsfParseErrorFormate, TestSize.Level1) +{ + TS_LOGI("test4-64"); + size_t length{4}; + TracePoint outPoint; + std::string str("C|2519|Heap size (KB)|"); + BytraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + int result = eventParser.printEventParser_.HandlerCSF(str, outPoint, length); + + EXPECT_TRUE(result == ERROR); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/test/unittest/filter_filter_test.cpp b/host/trace_streamer/test/unittest/filter_filter_test.cpp new file mode 100644 index 0000000..732dbb0 --- /dev/null +++ b/host/trace_streamer/test/unittest/filter_filter_test.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "filter_filter.h" +#include "trace_data_cache.h" +#include "trace_streamer_filters.h" + +using namespace testing::ext; +using namespace SysTuning::TraceStreamer; +namespace SysTuning { +namespace TraceStreamer { +class FilterFilterTest : public ::testing::Test { +public: + void SetUp() + { + streamFilters_.filterFilter_ = std::make_unique(&traceDataCache_, &streamFilters_); + } + + void TearDown() {} + +public: + TraceStreamerFilters streamFilters_; + TraceDataCache traceDataCache_; +}; + +/** + * @tc.name: AddCpuCounterFilter + * @tc.desc: Add cpu_counter_filter through AddFilter interface + * @tc.type: FUNC + */ +HWTEST_F(FilterFilterTest, AddCpuCounterFilter, TestSize.Level1) +{ + TS_LOGI("test5-1"); + uint32_t filterId = streamFilters_.filterFilter_->AddFilter("cpu_counter_filter", "cpu1", 1); + EXPECT_EQ(filterId, static_cast(0)); + + filterId = streamFilters_.filterFilter_->AddFilter("cpu_counter_filter", "cpu2", 2); + EXPECT_EQ(filterId, static_cast(1)); + + Filter* filterTable = traceDataCache_.GetFilterData(); + EXPECT_EQ(filterTable->Size(), static_cast(2)); +} + +/** + * @tc.name: AddCpuCounterFilter + * @tc.desc: Add thread_counter_filter & thread_filter through AddFilter interface + * @tc.type: FUNC + */ +HWTEST_F(FilterFilterTest, AddThreadFilter, TestSize.Level1) +{ + TS_LOGI("test5-2"); + uint32_t threadFilterId = streamFilters_.filterFilter_->AddFilter("thread_counter_filter", "threadCount1", 1); + EXPECT_EQ(threadFilterId, static_cast(0)); + + threadFilterId = streamFilters_.filterFilter_->AddFilter("thread_counter_filter", "threadCount2", 2); + EXPECT_EQ(threadFilterId, static_cast(1)); + + Filter* filterTable = traceDataCache_.GetFilterData(); + EXPECT_EQ(filterTable->Size(), static_cast(2)); + + threadFilterId = streamFilters_.filterFilter_->AddFilter("thread_filter", "thread1", 1); + EXPECT_EQ(threadFilterId, static_cast(2)); + + threadFilterId = streamFilters_.filterFilter_->AddFilter("thread_filter", "thread2", 2); + EXPECT_EQ(threadFilterId, static_cast(3)); + + EXPECT_EQ(filterTable->Size(), static_cast(4)); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/test/unittest/hilog_parser_test.cpp b/host/trace_streamer/test/unittest/hilog_parser_test.cpp new file mode 100644 index 0000000..69f71d6 --- /dev/null +++ b/host/trace_streamer/test/unittest/hilog_parser_test.cpp @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "htrace_hilog_parser.h" +#include "parser/bytrace_parser/bytrace_parser.h" +#include "parser/common_types.h" +#include "trace_streamer_selector.h" + +using namespace testing::ext; +using namespace SysTuning::TraceStreamer; + +namespace SysTuning { +namespace TraceStreamer { +class HilogParserTest : public ::testing::Test { +public: + void SetUp() + { + stream_.InitFilter(); + } + + void TearDown() {} + +public: + SysTuning::TraceStreamer::TraceStreamerSelector stream_ = {}; +}; + +/** + * @tc.name: HilogParseWithoutHilogLine + * @tc.desc: Parse a HilogInfo that does not contain any hiloglines + * @tc.type: FUNC + */ +HWTEST_F(HilogParserTest, ParseHilogInfoWithoutHilogLine, TestSize.Level1) +{ + HilogInfo* hilogInfo = new HilogInfo(); + HtraceHiLogParser htraceHiLogParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + htraceHiLogParser.Parse(*hilogInfo); + auto size = stream_.traceDataCache_->GetConstHilogData().Size(); + EXPECT_FALSE(size); +} + +/** + * @tc.name: ParseHilogInfoWithOneHilogLine + * @tc.desc: Parse a HilogInfo with only one Hilogline + * @tc.type: FUNC + */ +HWTEST_F(HilogParserTest, ParseHilogInfoWithOneHilogLine, TestSize.Level1) +{ + const uint64_t TV_SEC = 1632675525; + const uint64_t TV_NSEC = 996560700; + const std::string LOG_TAG = "HwMSDPMovementService"; + const std::string LOG_CONTEXT = "handleGetSupportedModule"; + const uint32_t LOG_LEVEL_D = 68; + const uint32_t PID = 2716; + const uint32_t TID = 1532; + const uint64_t LOG_ID = 1; + + HilogDetails* hilogDetails = new HilogDetails(); + hilogDetails->set_tv_sec(TV_SEC); + hilogDetails->set_tv_nsec(TV_NSEC); + hilogDetails->set_pid(PID); + hilogDetails->set_tid(TID); + hilogDetails->set_level(LOG_LEVEL_D); + hilogDetails->set_tag(LOG_TAG); + + HilogInfo* hilogInfo = new HilogInfo(); + auto hilogLine = hilogInfo->add_info(); + hilogLine->set_allocated_detail(hilogDetails); + hilogLine->set_context(LOG_CONTEXT); + hilogLine->set_id(LOG_ID); + + HtraceHiLogParser htraceHiLogParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + htraceHiLogParser.Parse(*hilogInfo); + + auto seq = stream_.traceDataCache_->GetConstHilogData().HilogLineSeqs()[0]; + EXPECT_EQ(seq, LOG_ID); + + auto timestamp = stream_.traceDataCache_->GetConstHilogData().TimeStamData()[0]; + EXPECT_EQ(timestamp, (TV_NSEC + TV_SEC * SEC_TO_NS)); + + auto pid = stream_.traceDataCache_->GetConstHilogData().Pids()[0]; + EXPECT_EQ(pid, PID); + + auto tid = stream_.traceDataCache_->GetConstHilogData().Tids()[0]; + EXPECT_EQ(tid, TID); + + auto level = stream_.traceDataCache_->GetConstHilogData().Levels()[0]; + auto iter = htraceHiLogParser.logLevelString_.find(LOG_LEVEL_D); + if (iter == htraceHiLogParser.logLevelString_.end()) { + EXPECT_FALSE(0); + } + DataIndex levelDIndex = stream_.traceDataCache_->dataDict_.GetStringIndex(iter->second.c_str()); + EXPECT_EQ(level, levelDIndex); + + auto readTagIndex = stream_.traceDataCache_->GetConstHilogData().Tags()[0]; + DataIndex writeTagIndex = stream_.traceDataCache_->dataDict_.GetStringIndex(LOG_TAG); + EXPECT_EQ(readTagIndex, writeTagIndex); + + auto readContextIndex = stream_.traceDataCache_->GetConstHilogData().Contexts()[0]; + DataIndex writeContextIndex = stream_.traceDataCache_->dataDict_.GetStringIndex(LOG_CONTEXT); + EXPECT_EQ(readContextIndex, writeContextIndex); + + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_HILOG, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseHilogInfoWithMultipleHilogLine + * @tc.desc: Parse a HilogInfo with multiple Hiloglines + * @tc.type: FUNC + */ +HWTEST_F(HilogParserTest, ParseHilogInfoWithMultipleHilogLine, TestSize.Level1) +{ + const uint64_t TV_SEC_01 = 1632675525; + const uint64_t TV_NSEC_01 = 996560700; + const uint32_t PID_01 = 2716; + const uint32_t TID_01 = 1532; + const uint32_t LOG_LEVEL_D = 68; + const std::string LOG_TAG_01 = "HwMSDPMovementService"; + const std::string LOG_CONTEXT_01 = "handleGetSupportedModule"; + const uint64_t LOG_ID_01 = 1; + + HilogDetails* hilogDetailsFirst = new HilogDetails(); + hilogDetailsFirst->set_tv_sec(TV_SEC_01); + hilogDetailsFirst->set_tv_nsec(TV_NSEC_01); + hilogDetailsFirst->set_pid(PID_01); + hilogDetailsFirst->set_tid(TID_01); + hilogDetailsFirst->set_level(LOG_LEVEL_D); + hilogDetailsFirst->set_tag(LOG_TAG_01); + + const uint64_t TV_SEC_02 = 1632688888; + const uint64_t TV_NSEC_02 = 996588888; + const uint32_t PID_02 = 2532; + const uint32_t TID_02 = 1716; + const uint32_t LOG_LEVEL_E = 69; + const std::string LOG_TAG_02 = "ProfilerService"; + const std::string LOG_CONTEXT_02 = "POST_RECV_MESSAGE method: /IProfilerService/CreateSession"; + const uint64_t LOG_ID_02 = 2; + + HilogDetails* hilogDetailsSecond = new HilogDetails(); + hilogDetailsSecond->set_tv_sec(TV_SEC_02); + hilogDetailsSecond->set_tv_nsec(TV_NSEC_02); + hilogDetailsSecond->set_pid(PID_02); + hilogDetailsSecond->set_tid(TID_02); + hilogDetailsSecond->set_level(LOG_LEVEL_E); + hilogDetailsSecond->set_tag(LOG_TAG_02); + + HilogInfo* hilogInfo = new HilogInfo(); + auto hilogLineFirst = hilogInfo->add_info(); + hilogLineFirst->set_allocated_detail(hilogDetailsFirst); + hilogLineFirst->set_context(LOG_CONTEXT_01); + hilogLineFirst->set_id(LOG_ID_01); + + auto hilogLineSecond = hilogInfo->add_info(); + hilogLineSecond->set_allocated_detail(hilogDetailsSecond); + hilogLineSecond->set_context(LOG_CONTEXT_02); + hilogLineSecond->set_id(LOG_ID_02); + + HtraceHiLogParser htraceHiLogParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + htraceHiLogParser.Parse(*hilogInfo); + + auto seqFirst = stream_.traceDataCache_->GetConstHilogData().HilogLineSeqs()[0]; + auto seqSecond = stream_.traceDataCache_->GetConstHilogData().HilogLineSeqs()[1]; + EXPECT_EQ(seqFirst, LOG_ID_01); + EXPECT_EQ(seqSecond, LOG_ID_02); + + auto timestampFirst = stream_.traceDataCache_->GetConstHilogData().TimeStamData()[0]; + auto timestampSecond = stream_.traceDataCache_->GetConstHilogData().TimeStamData()[1]; + EXPECT_EQ(timestampFirst, (TV_NSEC_01 + TV_SEC_01 * SEC_TO_NS)); + EXPECT_EQ(timestampSecond, (TV_NSEC_02 + TV_SEC_02 * SEC_TO_NS)); + + auto pidFirst = stream_.traceDataCache_->GetConstHilogData().Pids()[0]; + auto pidSecond = stream_.traceDataCache_->GetConstHilogData().Pids()[1]; + EXPECT_EQ(pidFirst, PID_01); + EXPECT_EQ(pidSecond, PID_02); + + auto tidFirst = stream_.traceDataCache_->GetConstHilogData().Tids()[0]; + auto tidSecond = stream_.traceDataCache_->GetConstHilogData().Tids()[1]; + EXPECT_EQ(tidFirst, TID_01); + EXPECT_EQ(tidSecond, TID_02); + + auto levelFirst = stream_.traceDataCache_->GetConstHilogData().Levels()[0]; + auto iterFirst = htraceHiLogParser.logLevelString_.find(LOG_LEVEL_D); + if (iterFirst == htraceHiLogParser.logLevelString_.end()) { + EXPECT_FALSE(0); + } + DataIndex levelDIndex = stream_.traceDataCache_->dataDict_.GetStringIndex(iterFirst->second.c_str()); + EXPECT_EQ(levelFirst, levelDIndex); + + auto levelSecond = stream_.traceDataCache_->GetConstHilogData().Levels()[1]; + auto iterSecond = htraceHiLogParser.logLevelString_.find(LOG_LEVEL_E); + if (iterSecond == htraceHiLogParser.logLevelString_.end()) { + EXPECT_FALSE(0); + } + DataIndex levelEIndex = stream_.traceDataCache_->dataDict_.GetStringIndex(iterSecond->second.c_str()); + EXPECT_EQ(levelSecond, levelEIndex); + + auto readTagIndexFirst = stream_.traceDataCache_->GetConstHilogData().Tags()[0]; + auto readTagIndexSecond = stream_.traceDataCache_->GetConstHilogData().Tags()[1]; + DataIndex writeTagIndexFirst = stream_.traceDataCache_->dataDict_.GetStringIndex(LOG_TAG_01); + DataIndex writeTagIndexSecond = stream_.traceDataCache_->dataDict_.GetStringIndex(LOG_TAG_02); + EXPECT_EQ(readTagIndexFirst, writeTagIndexFirst); + EXPECT_EQ(readTagIndexSecond, writeTagIndexSecond); + + auto readContextIndexFirst = stream_.traceDataCache_->GetConstHilogData().Contexts()[0]; + auto readContextIndexSecond = stream_.traceDataCache_->GetConstHilogData().Contexts()[1]; + DataIndex writeContextIndexFirst = stream_.traceDataCache_->dataDict_.GetStringIndex(LOG_CONTEXT_01); + DataIndex writeContextIndexSecond = stream_.traceDataCache_->dataDict_.GetStringIndex(LOG_CONTEXT_02); + EXPECT_EQ(readContextIndexFirst, writeContextIndexFirst); + EXPECT_EQ(readContextIndexSecond, writeContextIndexSecond); + + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_HILOG, STAT_EVENT_RECEIVED); + EXPECT_TRUE(2 == eventCount); +} + +/** + * @tc.name: ParseHilogInfoWithErrLevelHilogLine + * @tc.desc: Parse a HilogInfo with error level Hiloglines + * @tc.type: FUNC + */ +HWTEST_F(HilogParserTest, ParseHilogInfoWithErrLevelHilogLine, TestSize.Level1) +{ + const uint64_t TV_SEC = 1632675525; + const uint64_t TV_NSEC = 996560700; + const std::string LOG_TAG = "HwMSDPMovementService"; + const std::string LOG_CONTEXT = "handleGetSupportedModule"; + const uint32_t LOG_LEVEL_ILLEGAL = 0; + const uint32_t PID = 2716; + const uint32_t TID = 1532; + const uint64_t LOG_ID = 1; + + HilogDetails* hilogDetails = new HilogDetails(); + hilogDetails->set_tv_sec(TV_SEC); + hilogDetails->set_tv_nsec(TV_NSEC); + hilogDetails->set_pid(PID); + hilogDetails->set_tid(TID); + hilogDetails->set_level(LOG_LEVEL_ILLEGAL); + hilogDetails->set_tag(LOG_TAG); + + HilogInfo* hilogInfo = new HilogInfo(); + auto hilogLine = hilogInfo->add_info(); + hilogLine->set_allocated_detail(hilogDetails); + hilogLine->set_context(LOG_CONTEXT); + hilogLine->set_id(LOG_ID); + + HtraceHiLogParser htraceHiLogParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + htraceHiLogParser.Parse(*hilogInfo); + + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_HILOG, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); + eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_HILOG, STAT_EVENT_DATA_INVALID); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseHilogInfoLostHilogLine + * @tc.desc: Parse a HilogInfo that lost a Hiloglines + * @tc.type: FUNC + */ +HWTEST_F(HilogParserTest, ParseHilogInfoLostHilogLine, TestSize.Level1) +{ + const uint64_t TV_SEC = 1632675525; + const uint64_t TV_NSEC = 996560700; + const std::string LOG_TAG = "HwMSDPMovementService"; + const std::string LOG_CONTEXT = "handleGetSupportedModule"; + const uint32_t LOG_LEVEL_D = 68; + const uint32_t PID = 2716; + const uint32_t TID = 1532; + const uint64_t LOG_ID = 2; + + HilogDetails* hilogDetails = new HilogDetails(); + hilogDetails->set_tv_sec(TV_SEC); + hilogDetails->set_tv_nsec(TV_NSEC); + hilogDetails->set_pid(PID); + hilogDetails->set_tid(TID); + hilogDetails->set_level(LOG_LEVEL_D); + hilogDetails->set_tag(LOG_TAG); + + HilogInfo* hilogInfo = new HilogInfo(); + auto hilogLine = hilogInfo->add_info(); + hilogLine->set_allocated_detail(hilogDetails); + hilogLine->set_context(LOG_CONTEXT); + hilogLine->set_id(LOG_ID); + + HtraceHiLogParser htraceHiLogParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + htraceHiLogParser.Parse(*hilogInfo); + + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_HILOG, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); + eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_HILOG, STAT_EVENT_DATA_LOST); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseHilogInfoHasDuplicateHilogLine + * @tc.desc: Parse a HilogInfo has duplicate HilogLine + * @tc.type: FUNC + */ +HWTEST_F(HilogParserTest, ParseHilogInfoHasDuplicateHilogLine, TestSize.Level1) +{ + const uint64_t TV_SEC = 1632675525; + const uint64_t TV_NSEC = 996560700; + const std::string LOG_TAG = "HwMSDPMovementService"; + const std::string LOG_CONTEXT = "handleGetSupportedModule"; + const uint32_t LOG_LEVEL_D = 68; + const uint32_t PID = 2716; + const uint32_t TID = 1532; + const uint64_t LOG_ID = 1; + + HilogDetails* hilogDetails = new HilogDetails(); + hilogDetails->set_tv_sec(TV_SEC); + hilogDetails->set_tv_nsec(TV_NSEC); + hilogDetails->set_pid(PID); + hilogDetails->set_tid(TID); + hilogDetails->set_level(LOG_LEVEL_D); + hilogDetails->set_tag(LOG_TAG); + + HilogInfo* hilogInfo = new HilogInfo(); + auto hilogLineFirst = hilogInfo->add_info(); + hilogLineFirst->set_allocated_detail(hilogDetails); + hilogLineFirst->set_context(LOG_CONTEXT); + hilogLineFirst->set_id(LOG_ID); + auto hilogLineSecond = hilogInfo->add_info(); + hilogLineSecond->set_allocated_detail(hilogDetails); + hilogLineSecond->set_context(LOG_CONTEXT); + hilogLineSecond->set_id(LOG_ID); + + HtraceHiLogParser htraceHiLogParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + htraceHiLogParser.Parse(*hilogInfo); + + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_HILOG, STAT_EVENT_RECEIVED); + EXPECT_TRUE(2 == eventCount); + eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_HILOG, STAT_EVENT_NOTMATCH); + EXPECT_TRUE(1 == eventCount); +} +} // namespace TraceStreamer +} // namespace SysTuning \ No newline at end of file diff --git a/host/trace_streamer/test/unittest/htrace_binder_event_test.cpp b/host/trace_streamer/test/unittest/htrace_binder_event_test.cpp new file mode 100644 index 0000000..32c7c9e --- /dev/null +++ b/host/trace_streamer/test/unittest/htrace_binder_event_test.cpp @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "binder_filter.h" +#include "htrace_cpu_detail_parser.h" +#include "htrace_event_parser.h" +#include "trace_plugin_result.pb.h" +#include "trace_streamer_selector.h" +#include "ts_common.h" + +using namespace testing::ext; +using namespace SysTuning::TraceStreamer; +namespace SysTuning { +namespace TraceStreamer { +class HtraceBinderEventTest : public ::testing::Test { +public: + void SetUp() + { + stream_.InitFilter(); + } + + void TearDown() {} + +public: + SysTuning::TraceStreamer::TraceStreamerSelector stream_ = {}; +}; + +/** + * @tc.name: BinderSenderfilterNeedReply + * @tc.desc: Binary formate binder event test, The binder event needs reply + * @tc.type: FUNC + */ +HWTEST_F(HtraceBinderEventTest, BinderSenderfilterNeedReply, TestSize.Level1) +{ + std::string appName = "app1"; + int64_t ts1 = 100; + uint32_t tid1 = 1; + uint64_t transactionId1 = 1; + int32_t destNode1 = 1; + int32_t destTgid1 = 2; + int32_t destTid1 = 3; + bool isReply = false; + uint32_t flags = 0x02; // if need reply bool needReply = !isReply && !(flags & noReturnMsgFlag_); 0x01 + uint32_t code = 0; // not important + BinderTransactionFormat* binderEvent = new BinderTransactionFormat(); + binderEvent->set_to_proc(destTgid1); + binderEvent->set_target_node(destNode1); + binderEvent->set_to_thread(destTid1); + binderEvent->set_debug_id(transactionId1); + binderEvent->set_reply(static_cast(isReply)); + binderEvent->set_code(code); + binderEvent->set_flags(flags); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_binder_transaction_format(binderEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); + EXPECT_TRUE(stream_.traceDataCache_->GetConstArgSetData().Size() == 7); +} + +/** + * @tc.name: BinderSenderfilterNeedReplyAndReceive + * @tc.desc: Binary formate binder event test, The binder event needs reply and received reply + * @tc.type: FUNC + */ +HWTEST_F(HtraceBinderEventTest, BinderSenderfilterNeedReplyAndReceive, TestSize.Level1) +{ + TS_LOGI("test9-2"); + std::string appName = "app1"; + int64_t ts1 = 100; + uint32_t tid1 = 1; + uint64_t transactionId1 = 1; + int32_t destNode1 = 1; + int32_t destTgid1 = 2; + int32_t destTid1 = 3; + bool isReply = false; + uint32_t flags = 0x02; // if need reply bool needReply = !isReply && !(flags & noReturnMsgFlag_); 0x01 + uint32_t code = 0; // not important + BinderTransactionFormat* binderEvent = new BinderTransactionFormat(); + binderEvent->set_to_proc(destTgid1); + binderEvent->set_target_node(destNode1); + binderEvent->set_to_thread(destTid1); + binderEvent->set_debug_id(transactionId1); + binderEvent->set_reply(static_cast(isReply)); + binderEvent->set_code(code); + binderEvent->set_flags(flags); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_binder_transaction_format(binderEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstArgSetData().Size() == 7); + + ts1 = 200; + uint32_t pid1 = 1; + FtraceCpuDetailMsg ftraceCpuDetail2; + ftraceCpuDetail2.set_cpu(0); + ftraceCpuDetail2.set_overwrite(0); + auto ftraceEvent2 = ftraceCpuDetail2.add_event(); + + ftraceEvent2->set_timestamp(ts1); + ftraceEvent2->set_tgid(pid1); + std::string appName2 = "app2"; + ftraceEvent2->set_comm(appName2); + BinderTransactionReceivedFormat* binderReceivedEvent = new BinderTransactionReceivedFormat(); + binderReceivedEvent->set_debug_id(transactionId1); + ftraceEvent2->set_allocated_binder_transaction_received_format(binderReceivedEvent); + eventParser.ParseDataItem(&ftraceCpuDetail2, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 2); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ArgSetIdsData()[1] == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstArgSetData().Size() == 11); +} + +/** + * @tc.name: BinderSenderfilterNeedReplyAndReceiveWithAlloc + * @tc.desc: Binary formate BinderTransactionAllocBuf event test, The binder event needs reply and received reply + * @tc.type: FUNC + */ +HWTEST_F(HtraceBinderEventTest, BinderSenderfilterNeedReplyAndReceiveWithAlloc, TestSize.Level1) +{ + int64_t ts1 = 100; + std::string appName = "app1"; + uint32_t tid1 = 1; + uint64_t transactionId1 = 1; + int32_t destNode1 = 1; + int32_t destTgid1 = 2; + int32_t destTid1 = 3; + bool isReply = false; + uint32_t flags = 0x02; // if need reply bool needReply = !isReply && !(flags & noReturnMsgFlag_) + uint32_t code = 0; // not important + BinderTransactionFormat* binderEvent = new BinderTransactionFormat(); + binderEvent->set_to_proc(destTgid1); + binderEvent->set_target_node(destNode1); + binderEvent->set_to_thread(destTid1); + binderEvent->set_debug_id(transactionId1); + binderEvent->set_reply(static_cast(isReply)); + binderEvent->set_code(code); + binderEvent->set_flags(flags); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_binder_transaction_format(binderEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstArgSetData().Size() == 7); + + ts1 = 150; + uint64_t dataSize = 100; + uint64_t offsetSize = 200; + BinderTransactionAllocBufFormat* binderAllocEvent = new BinderTransactionAllocBufFormat(); + binderAllocEvent->set_data_size(dataSize); + binderAllocEvent->set_offsets_size(offsetSize); + + FtraceCpuDetailMsg ftraceCpuDetail2; + ftraceCpuDetail2.set_cpu(0); + ftraceCpuDetail2.set_overwrite(0); + auto ftraceEvent2 = ftraceCpuDetail2.add_event(); + + ftraceEvent2->set_timestamp(ts1); + ftraceEvent2->set_tgid(tid1); + ftraceEvent2->set_comm(appName); + ftraceEvent2->set_allocated_binder_transaction_alloc_buf_format(binderAllocEvent); + eventParser.ParseDataItem(&ftraceCpuDetail2, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(stream_.traceDataCache_->GetConstArgSetData().Size() == 9); + + ts1 = 200; + uint32_t pid1 = 1; + FtraceCpuDetailMsg ftraceCpuDetail3; + ftraceCpuDetail3.set_cpu(0); + ftraceCpuDetail3.set_overwrite(0); + auto ftraceEvent3 = ftraceCpuDetail3.add_event(); + + ftraceEvent3->set_timestamp(ts1); + ftraceEvent3->set_tgid(pid1); + std::string appName2 = "app2"; + ftraceEvent3->set_comm(appName2); + BinderTransactionReceivedFormat* binderReceivedEvent = new BinderTransactionReceivedFormat(); + binderReceivedEvent->set_debug_id(transactionId1); + ftraceEvent3->set_allocated_binder_transaction_received_format(binderReceivedEvent); + eventParser.ParseDataItem(&ftraceCpuDetail3, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 2); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ArgSetIdsData()[1] == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstArgSetData().Size() == 13); +} + +/** + * @tc.name: BinderSenderfilterNeedReplyAndReceiveNotmatch + * @tc.desc: Binary formate BinderTransaction event test, The binder event needs reply but received not match + * @tc.type: FUNC + */ +HWTEST_F(HtraceBinderEventTest, BinderSenderfilterNeedReplyAndReceiveNotmatch, TestSize.Level1) +{ + TS_LOGI("test9-4"); + std::string appName = "app1"; + int64_t ts1 = 100; + uint32_t tid1 = 1; + uint64_t transactionId1 = 1; + int32_t destNode1 = 1; + int32_t destTgid1 = 2; + int32_t destTid1 = 3; + bool isReply = false; + uint32_t flags = 0x02; // if need reply bool needReply = !isReply && !(flags & noReturnMsgFlag_) + uint32_t code = 0; // not important + BinderTransactionFormat* binderEvent = new BinderTransactionFormat(); + binderEvent->set_to_proc(destTgid1); + binderEvent->set_target_node(destNode1); + binderEvent->set_to_thread(destTid1); + binderEvent->set_debug_id(transactionId1); + binderEvent->set_reply(static_cast(isReply)); + binderEvent->set_code(code); + binderEvent->set_flags(flags); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_binder_transaction_format(binderEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + + ts1 = 200; + uint32_t pid1 = 1; + uint64_t transactionId2 = 2; + FtraceCpuDetailMsg ftraceCpuDetail2; + ftraceCpuDetail2.set_cpu(0); + ftraceCpuDetail2.set_overwrite(0); + auto ftraceEvent2 = ftraceCpuDetail2.add_event(); + + ftraceEvent2->set_timestamp(ts1); + ftraceEvent2->set_tgid(pid1); + std::string appName2 = "app2"; + ftraceEvent2->set_comm(appName2); + BinderTransactionReceivedFormat* binderReceivedEvent = new BinderTransactionReceivedFormat(); + binderReceivedEvent->set_debug_id(transactionId2); + ftraceEvent2->set_allocated_binder_transaction_received_format(binderReceivedEvent); + eventParser.ParseDataItem(&ftraceCpuDetail2, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); +} + +/** + * @tc.name: BinderSenderfilterNoNeedReply + * @tc.desc: Binary formate binder event test, The binder event needs no reply + * @tc.type: FUNC + */ +HWTEST_F(HtraceBinderEventTest, BinderSenderfilterNoNeedReply, TestSize.Level1) +{ + TS_LOGI("test9-5"); + std::string appName = "app1"; + int64_t ts1 = 100; + uint32_t tid1 = 1; + uint64_t transactionId1 = 1; + int32_t destNode1 = 1; + int32_t destTgid1 = 2; + int32_t destTid1 = 3; + bool isReply = false; + uint32_t flags = 0x01; // if need reply bool needReply = !isReply && !(flags & noReturnMsgFlag_) + uint32_t code = 0; // not important + BinderTransactionFormat* binderEvent = new BinderTransactionFormat(); + binderEvent->set_to_proc(destTgid1); + binderEvent->set_target_node(destNode1); + binderEvent->set_to_thread(destTid1); + binderEvent->set_debug_id(transactionId1); + binderEvent->set_reply(static_cast(isReply)); + binderEvent->set_code(code); + binderEvent->set_flags(flags); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_binder_transaction_format(binderEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); +} + +/** + * @tc.name: BinderSenderNoneedReplyAndReceivefilter + * @tc.desc: Binary formate binder event test, other party received and no need reply。 + * @tc.type: FUNC + */ +HWTEST_F(HtraceBinderEventTest, BinderSenderNoneedReplyAndReceivefilter, TestSize.Level1) +{ + TS_LOGI("test9-6"); + std::string appName = "app1"; + int64_t ts1 = 100; + uint32_t tid1 = 1; + uint64_t transactionId1 = 1; + int32_t destNode1 = 1; + int32_t destTgid1 = 2; + int32_t destTid1 = 3; + bool isReply = false; + uint32_t flags = 0x01; // if need reply bool needReply = !isReply && !(flags & noReturnMsgFlag_) + uint32_t code = 0; // not important + BinderTransactionFormat* binderEvent = new BinderTransactionFormat(); + binderEvent->set_to_proc(destTgid1); + binderEvent->set_target_node(destNode1); + binderEvent->set_to_thread(destTid1); + binderEvent->set_debug_id(transactionId1); + binderEvent->set_reply(static_cast(isReply)); + binderEvent->set_code(code); + binderEvent->set_flags(flags); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_binder_transaction_format(binderEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + + ts1 = 200; + uint32_t pid1 = 1; + FtraceCpuDetailMsg ftraceCpuDetail2; + ftraceCpuDetail2.set_cpu(0); + ftraceCpuDetail2.set_overwrite(0); + auto ftraceEvent2 = ftraceCpuDetail2.add_event(); + + ftraceEvent2->set_timestamp(ts1); + ftraceEvent2->set_tgid(pid1); + std::string appName2 = "app2"; + ftraceEvent2->set_comm(appName2); + BinderTransactionReceivedFormat* binderReceivedEvent = new BinderTransactionReceivedFormat(); + binderReceivedEvent->set_debug_id(transactionId1); + ftraceEvent2->set_allocated_binder_transaction_received_format(binderReceivedEvent); + eventParser.ParseDataItem(&ftraceCpuDetail2, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 2); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ArgSetIdsData()[1] == 1); +} + +/** + * @tc.name: BinderSenderNoneedReplyAndReceivefilterNotmatch + * @tc.desc: Binary formate binder event test, other party received but not match + * @tc.type: FUNC + */ +HWTEST_F(HtraceBinderEventTest, BinderSenderNoneedReplyAndReceivefilterNotmatch, TestSize.Level1) +{ + TS_LOGI("test9-7"); + std::string appName = "app1"; + int64_t ts1 = 100; + uint32_t tid1 = 1; + uint64_t transactionId1 = 1; + int32_t destNode1 = 1; + int32_t destTgid1 = 2; + int32_t destTid1 = 3; + bool isReply = false; + uint32_t flags = 0x01; // if need reply bool needReply = !isReply && !(flags & noReturnMsgFlag_) + uint32_t code = 0; // not importent + BinderTransactionFormat* binderEvent = new BinderTransactionFormat(); + binderEvent->set_to_proc(destTgid1); + binderEvent->set_target_node(destNode1); + binderEvent->set_to_thread(destTid1); + binderEvent->set_debug_id(transactionId1); + binderEvent->set_reply(static_cast(isReply)); + binderEvent->set_code(code); + binderEvent->set_flags(flags); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_binder_transaction_format(binderEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + + ts1 = 200; + uint32_t pid1 = 1; + uint64_t transactionId2 = 2; + FtraceCpuDetailMsg ftraceCpuDetail2; + ftraceCpuDetail2.set_cpu(0); + ftraceCpuDetail2.set_overwrite(0); + auto ftraceEvent2 = ftraceCpuDetail2.add_event(); + + ftraceEvent2->set_timestamp(ts1); + ftraceEvent2->set_tgid(pid1); + std::string appName2 = "app2"; + ftraceEvent2->set_comm(appName2); + BinderTransactionReceivedFormat* binderReceivedEvent = new BinderTransactionReceivedFormat(); + binderReceivedEvent->set_debug_id(transactionId2); + ftraceEvent2->set_allocated_binder_transaction_received_format(binderReceivedEvent); + eventParser.ParseDataItem(&ftraceCpuDetail2, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); +} + +/** + * @tc.name: BinderSenderfilterWrongReply + * @tc.desc: Binary formate binder event test, other party replyed wrong Info + * @tc.type: FUNC + */ +HWTEST_F(HtraceBinderEventTest, BinderSenderfilterWrongReply, TestSize.Level1) +{ + TS_LOGI("test9-8"); + std::string appName = "app1"; + int64_t ts1 = 100; + uint32_t tid1 = 1; + uint64_t transactionId1 = 1; + int32_t destNode1 = 1; + int32_t destTgid1 = 2; + int32_t destTid1 = 3; + bool isReply = true; + uint32_t flags = 0x01; // if need reply bool needReply = !isReply && !(flags & noReturnMsgFlag_) + uint32_t code = 0; // not important + BinderTransactionFormat* binderEvent = new BinderTransactionFormat(); + binderEvent->set_to_proc(destTgid1); + binderEvent->set_target_node(destNode1); + binderEvent->set_to_thread(destTid1); + binderEvent->set_debug_id(transactionId1); + binderEvent->set_reply(static_cast(isReply)); + binderEvent->set_code(code); + binderEvent->set_flags(flags); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_binder_transaction_format(binderEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->binderFilter_->FinishBinderEvent(); + stream_.streamFilters_->binderFilter_->FinishBinderEvent(); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 0); + EXPECT_TRUE(stream_.traceDataCache_->GetConstArgSetData().Size() == 0); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/test/unittest/htrace_event_parser_test.cpp b/host/trace_streamer/test/unittest/htrace_event_parser_test.cpp new file mode 100644 index 0000000..220fe6f --- /dev/null +++ b/host/trace_streamer/test/unittest/htrace_event_parser_test.cpp @@ -0,0 +1,609 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "cpu_filter.h" +#include "htrace_cpu_detail_parser.h" +#include "parser/common_types.h" +#include "trace_streamer_filters.h" +#include "trace_streamer_selector.h" + +using namespace testing::ext; +using namespace SysTuning::TraceStreamer; +namespace SysTuning { +namespace TraceStreamer { +const uint64_t TIMESTAMP = 1616439852302; +const std::string THREAD_NAME_01 = "ACCS0"; +const std::string THREAD_NAME_02 = "HeapTaskDaemon"; +const uint32_t PRIORITY_01 = 120; +const uint32_t PRIORITY_02 = 124; +const uint32_t PID_01 = 2716; +const uint32_t PID_02 = 2532; +class HtraceEventParserTest : public ::testing::Test { +public: + void SetUp() + { + stream_.InitFilter(); + } + + void TearDown() {} + +public: + SysTuning::TraceStreamer::TraceStreamerSelector stream_ = {}; +}; + +/** + * @tc.name: ParseSchedSwitchEvent + * @tc.desc: Parse a schedswitch event in htrace format + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseSchedSwitchEvent, TestSize.Level1) +{ + SchedSwitchFormat* event = new SchedSwitchFormat(); + event->set_prev_prio(PRIORITY_01); + event->set_next_prio(PRIORITY_02); + event->set_prev_pid(PID_01); + event->set_next_pid(PID_02); + event->set_prev_comm(THREAD_NAME_01); + event->set_next_comm(THREAD_NAME_02); + event->set_prev_state(1); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(1); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->unsafe_arena_set_allocated_sched_switch_format(event); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + EXPECT_TRUE(1); + auto realTimeStamp = stream_.traceDataCache_->GetConstSchedSliceData().TimeStamData()[0]; + EXPECT_TRUE(TIMESTAMP == realTimeStamp); + auto realCpu = stream_.traceDataCache_->GetConstSchedSliceData().CpusData()[0]; + EXPECT_TRUE(0 == realCpu); +} + +/** + * @tc.name: ParseFtraceCpuDetailMsgHasNoEvent + * @tc.desc: FtraceCpuDetailMsg has no ftrace event + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseFtraceCpuDetailMsgHasNoEvent, TestSize.Level1) +{ + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_OTHER, STAT_EVENT_DATA_LOST); + EXPECT_TRUE(0 == eventCount); +} + +/** + * @tc.name: ParseFtraceCpuDetailMsgOverwriteTrue + * @tc.desc: FtraceCpuDetailMsg overwrit is true + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseFtraceCpuDetailMsgOverwriteTrue, TestSize.Level1) +{ + SchedSwitchFormat* event = new SchedSwitchFormat(); + event->set_prev_prio(PRIORITY_01); + event->set_next_prio(PRIORITY_02); + event->set_prev_pid(PID_01); + event->set_next_pid(PID_02); + event->set_prev_comm(THREAD_NAME_01); + event->set_next_comm(THREAD_NAME_02); + event->set_prev_state(1); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(1); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(1); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->set_allocated_sched_switch_format(event); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_OTHER, STAT_EVENT_DATA_LOST); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseTaskRenameEvent + * @tc.desc: Parse a task_rename event in htrace format + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseTaskRenameEvent, TestSize.Level1) +{ + TaskRenameFormat* taskRenameEvent = new TaskRenameFormat(); + taskRenameEvent->set_pid(PID_01); + taskRenameEvent->set_oldcomm(THREAD_NAME_01); + taskRenameEvent->set_newcomm(THREAD_NAME_02); + taskRenameEvent->set_oom_score_adj(1); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(1); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->set_allocated_task_rename_format(taskRenameEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_TASK_RENAME, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseTaskNewtaskEvent + * @tc.desc: Parse a task_newtask event in htrace format + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseTaskNewtaskEvent, TestSize.Level1) +{ + TaskNewtaskFormat* newTaskEvent = new TaskNewtaskFormat(); + newTaskEvent->set_pid(PID_01); + newTaskEvent->set_comm(THREAD_NAME_01); + newTaskEvent->set_clone_flags(0); + newTaskEvent->set_oom_score_adj(1); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(1); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->set_allocated_task_newtask_format(newTaskEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_TASK_NEWTASK, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseSchedWakeupEvent + * @tc.desc: Parse a sched_wakeup event in htrace format + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseSchedWakeupEvent, TestSize.Level1) +{ + SchedWakeupFormat* wakeupEvent = new SchedWakeupFormat(); + wakeupEvent->set_comm(THREAD_NAME_01); + wakeupEvent->set_pid(PRIORITY_02); + wakeupEvent->set_prio(PID_01); + wakeupEvent->set_target_cpu(1); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(1); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->set_allocated_sched_wakeup_format(wakeupEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_SCHED_WAKEUP, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseSchedWakingEvent + * @tc.desc: Parse a sched_waking event in htrace format + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseSchedWakingEvent, TestSize.Level1) +{ + SchedWakingFormat* wakingEvent = new SchedWakingFormat(); + wakingEvent->set_comm(THREAD_NAME_01); + wakingEvent->set_pid(PRIORITY_02); + wakingEvent->set_prio(PID_01); + wakingEvent->set_target_cpu(1); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(1); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->set_allocated_sched_waking_format(wakingEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_SCHED_WAKING, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseCpuIdleEvent + * @tc.desc: Parse a cpuIdle event in htrace format + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseCpuIdleEvent, TestSize.Level1) +{ + CpuIdleFormat* cpuIdleEvent = new CpuIdleFormat(); + cpuIdleEvent->set_cpu_id(0); + cpuIdleEvent->set_state(1); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(1); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->set_allocated_cpu_idle_format(cpuIdleEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_CPU_IDLE, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseCpuFrequencyEvent + * @tc.desc: Parse a CpuFrequency event in htrace format + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseCpuFrequencyEvent, TestSize.Level1) +{ + CpuFrequencyFormat* cpuFrequencyEvent = new CpuFrequencyFormat(); + cpuFrequencyEvent->set_cpu_id(0); + cpuFrequencyEvent->set_state(1); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(2); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->set_allocated_cpu_frequency_format(cpuFrequencyEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_CPU_FREQUENCY, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseWorkqueueExecuteStartEvent + * @tc.desc: Parse a WorkqueueExecuteStart event in htrace format + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseWorkqueueExecuteStartEvent, TestSize.Level1) +{ + WorkqueueExecuteStartFormat* workqueueExecuteStartEvent = new WorkqueueExecuteStartFormat(); + workqueueExecuteStartEvent->set_work(0); + workqueueExecuteStartEvent->set_function(1); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(1); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->set_allocated_workqueue_execute_start_format(workqueueExecuteStartEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_WORKQUEUE_EXECUTE_START, + STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseWorkqueueExecuteEndEvent + * @tc.desc: Parse a WorkqueueExecuteEnd event in htrace format + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseWorkqueueExecuteEndEvent, TestSize.Level1) +{ + WorkqueueExecuteEndFormat* workqueueExecuteEndEvent = new WorkqueueExecuteEndFormat(); + workqueueExecuteEndEvent->set_work(0); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(1); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->set_allocated_workqueue_execute_end_format(workqueueExecuteEndEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_WORKQUEUE_EXECUTE_END, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseClockDisableEvent + * @tc.desc: Parse a clock_Disable event in htrace format + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseClockDisableEvent, TestSize.Level1) +{ + ClockDisableFormat* clockDisableEvent = new ClockDisableFormat(); + clockDisableEvent->set_name(THREAD_NAME_02); + clockDisableEvent->set_cpu_id(0); + clockDisableEvent->set_state(1); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(2); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->set_allocated_clock_disable_format(clockDisableEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_CLOCK_DISABLE, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseClockEnableEvent + * @tc.desc: Parse a clock_Enable event in htrace format + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseClockEnableEvent, TestSize.Level1) +{ + ClockEnableFormat* clockEnableEvent = new ClockEnableFormat(); + clockEnableEvent->set_name(THREAD_NAME_02); + clockEnableEvent->set_cpu_id(0); + clockEnableEvent->set_state(1); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(2); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->set_allocated_clock_enable_format(clockEnableEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_CLOCK_ENABLE, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseClockSetRateEvent + * @tc.desc: Parse a clock_set_rate event in htrace format + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseClockSetRateEvent, TestSize.Level1) +{ + ClockSetRateFormat* clockSetRateEvent = new ClockSetRateFormat(); + clockSetRateEvent->set_name(THREAD_NAME_02); + clockSetRateEvent->set_cpu_id(0); + clockSetRateEvent->set_state(1); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(2); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->set_allocated_clock_set_rate_format(clockSetRateEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_CLOCK_SET_RATE, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseClkDisableEvent + * @tc.desc: Parse a clk_Disable event in htrace format + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseClkDisableEvent, TestSize.Level1) +{ + ClkDisableFormat* clkDisableEvent = new ClkDisableFormat(); + clkDisableEvent->set_name(THREAD_NAME_02); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(2); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->set_allocated_clk_disable_format(clkDisableEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_CLK_DISABLE, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseClkEnableEvent + * @tc.desc: Parse a clk_Enable event in htrace format + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseClkEnableEvent, TestSize.Level1) +{ + ClkEnableFormat* clkEnableEvent = new ClkEnableFormat(); + clkEnableEvent->set_name(THREAD_NAME_02); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(2); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->set_allocated_clk_enable_format(clkEnableEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_CLK_ENABLE, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseClkSetRateEvent + * @tc.desc: Parse a clk_set_rate event in htrace format + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseClkSetRateEvent, TestSize.Level1) +{ + ClkSetRateFormat* clkSetRateEvent = new ClkSetRateFormat(); + clkSetRateEvent->set_name(THREAD_NAME_02); + clkSetRateEvent->set_rate(1); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(2); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->set_allocated_clk_set_rate_format(clkSetRateEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_CLK_SET_RATE, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseSysEnterEvent + * @tc.desc: Parse a sysEnter event in htrace format + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseSysEnterEvent, TestSize.Level1) +{ + SysEnterFormat* sysEnterEvent = new SysEnterFormat(); + sysEnterEvent->set_id(1); + sysEnterEvent->set_args(THREAD_NAME_02); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(2); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->set_allocated_sys_enter_format(sysEnterEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_SYS_ENTRY, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} +/** + * @tc.name: ParseSystemExitEvent + * @tc.desc: Parse a system_exit event in htrace format + * @tc.type: FUNC + */ +HWTEST_F(HtraceEventParserTest, ParseSystemExitEvent, TestSize.Level1) +{ + SysExitFormat* sysExitEvent = new SysExitFormat(); + sysExitEvent->set_id(1); + sysExitEvent->set_ret(1); + + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(0); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(TIMESTAMP); + ftraceEvent->set_tgid(2); + ftraceEvent->set_comm(THREAD_NAME_02); + ftraceEvent->set_allocated_sys_exit_format(sysExitEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + stream_.streamFilters_->cpuFilter_->FinishCpuEvent(); + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_SYS_EXIT, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/test/unittest/htrace_irq_event_test.cpp b/host/trace_streamer/test/unittest/htrace_irq_event_test.cpp new file mode 100644 index 0000000..79c265a --- /dev/null +++ b/host/trace_streamer/test/unittest/htrace_irq_event_test.cpp @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "htrace_cpu_detail_parser.h" +#include "htrace_event_parser.h" +#include "irq_filter.h" +#include "trace_streamer_selector.h" +#include "ts_common.h" +#include "types/plugins/ftrace_data/trace_plugin_result.pb.h" + +using namespace testing::ext; +using namespace SysTuning::TraceStreamer; +namespace SysTuning { +namespace TraceStreamer { +class HtraceIrqEventTest : public ::testing::Test { +public: + void SetUp() + { + stream_.InitFilter(); + } + + void TearDown() {} + +public: + SysTuning::TraceStreamer::TraceStreamerSelector stream_ = {}; +}; + +/** + * @tc.name: IrqHandlerEntryTest + * @tc.desc: Binary formate IrqHandlerEntry Normal TEST + * @tc.type: FUNC + */ +HWTEST_F(HtraceIrqEventTest, IrqHandlerEntryTest, TestSize.Level1) +{ + TS_LOGI("test10-1"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + std::string appName = "app1"; + uint32_t tid1 = 1; + int32_t irq = 12; + IrqHandlerEntryFormat* irqHandlerEvent = new IrqHandlerEntryFormat(); + irqHandlerEvent->set_irq(irq); + irqHandlerEvent->set_name("user_irq"); + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(cpu1); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_irq_handler_entry_format(irqHandlerEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); +} + +/** + * @tc.name: IrqHandlerEntryTestNotMatch + * @tc.desc: Binary formate IrqHandlerEntry, only start, no end + * @tc.type: FUNC + */ +HWTEST_F(HtraceIrqEventTest, IrqHandlerEntryTestNotMatch, TestSize.Level1) +{ + TS_LOGI("test10-2"); + int64_t ts1 = 120; + uint32_t cpu1 = 1; + std::string appName = "app1"; + uint32_t tid1 = 1; + int32_t irq = 12; + IrqHandlerEntryFormat* irqHandlerEvent = new IrqHandlerEntryFormat(); + irqHandlerEvent->set_irq(irq); + irqHandlerEvent->set_name("user_irq"); + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(cpu1); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_irq_handler_entry_format(irqHandlerEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + ts1 = 110; + IrqHandlerEntryFormat* irqHandlerEvent2 = new IrqHandlerEntryFormat(); + irqHandlerEvent2->set_irq(irq); + irqHandlerEvent2->set_name("user_irq"); + FtraceCpuDetailMsg ftraceCpuDetail2; + ftraceCpuDetail2.set_cpu(cpu1); + ftraceCpuDetail2.set_overwrite(0); + auto ftraceEvent2 = ftraceCpuDetail2.add_event(); + + ftraceEvent2->set_timestamp(ts1); + ftraceEvent2->set_tgid(tid1); + ftraceEvent2->set_comm(appName); + ftraceEvent2->set_allocated_irq_handler_entry_format(irqHandlerEvent2); + eventParser.ParseDataItem(&ftraceCpuDetail2, TS_CLOCK_BOOTTIME); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 2); + + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_IRQ_HANDLER_ENTRY, STAT_EVENT_DATA_LOST); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: IrqHandlerExitTestEmpty + * @tc.desc: Binary formate IrqHandlerExit, Interrupt only ends, not starts + * @tc.type: FUNC + */ +HWTEST_F(HtraceIrqEventTest, IrqHandlerExitTestEmpty, TestSize.Level1) +{ + TS_LOGI("test10-3"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + uint32_t ret = 1; + std::string appName = "app1"; + uint32_t tid1 = 1; + int32_t irq = 12; // 1 for handled, else for unhandled + + IrqHandlerExitFormat* irqHandlerExitEvent = new IrqHandlerExitFormat(); + irqHandlerExitEvent->set_irq(irq); + irqHandlerExitEvent->set_ret(ret); + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(cpu1); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_irq_handler_exit_format(irqHandlerExitEvent); + + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 0); + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_IRQ_HANDLER_EXIT, STAT_EVENT_NOTMATCH); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: IrqHandlerEnterAndExitTest + * @tc.desc: Binary formate IrqHandlerEnter, Interrupt normal start and end + * @tc.type: FUNC + */ +HWTEST_F(HtraceIrqEventTest, IrqHandlerEnterAndExitTest, TestSize.Level1) +{ + TS_LOGI("test10-4"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + std::string appName = "app1"; + uint32_t tid1 = 1; + int32_t irq = 12; + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + IrqHandlerEntryFormat* irqHandlerEvent = new IrqHandlerEntryFormat(); + irqHandlerEvent->set_irq(irq); + irqHandlerEvent->set_name("user_irq"); + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(cpu1); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_irq_handler_entry_format(irqHandlerEvent); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + uint32_t ret = 1; // 1 for handled, else for unhandled + + IrqHandlerExitFormat* irqHandlerExitEvent = new IrqHandlerExitFormat(); + irqHandlerExitEvent->set_irq(irq); + irqHandlerExitEvent->set_ret(ret); + FtraceCpuDetailMsg ftraceCpuDetail2; + ftraceCpuDetail2.set_cpu(cpu1); + ftraceCpuDetail2.set_overwrite(0); + auto ftraceEvent2 = ftraceCpuDetail2.add_event(); + + ftraceEvent2->set_timestamp(ts1); + ftraceEvent2->set_tgid(tid1); + ftraceEvent2->set_comm(appName); + ftraceEvent2->set_allocated_irq_handler_exit_format(irqHandlerExitEvent); + + eventParser.ParseDataItem(&ftraceCpuDetail2, TS_CLOCK_BOOTTIME); + + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); +} + +/** + * @tc.name: IrqHandlerEnterAndExitTestTwice + * @tc.desc: Binary formate IrqHandlerEnter and Exit, Interrupt normal start and end Twice + * @tc.type: FUNC + */ +HWTEST_F(HtraceIrqEventTest, IrqHandlerEnterAndExitTestTwice, TestSize.Level1) +{ + TS_LOGI("test10-4-2"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + std::string appName = "app1"; + uint32_t tid1 = 1; + int32_t irq = 12; + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + IrqHandlerEntryFormat* irqHandlerEvent = new IrqHandlerEntryFormat(); + irqHandlerEvent->set_irq(irq); + irqHandlerEvent->set_name("user_irq"); + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(cpu1); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_irq_handler_entry_format(irqHandlerEvent); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + uint32_t ret = 1; // 1 for handled, else for unhandled + cpu1 = 2; + ts1 = 150; + + IrqHandlerExitFormat* irqHandlerExitEvent = new IrqHandlerExitFormat(); + irqHandlerExitEvent->set_irq(irq); + irqHandlerExitEvent->set_ret(ret); + FtraceCpuDetailMsg ftraceCpuDetail2; + ftraceCpuDetail2.set_cpu(cpu1); + ftraceCpuDetail2.set_overwrite(0); + auto ftraceEvent2 = ftraceCpuDetail2.add_event(); + + ftraceEvent2->set_timestamp(ts1); + ftraceEvent2->set_tgid(tid1); + ftraceEvent2->set_comm(appName); + ftraceEvent2->set_allocated_irq_handler_exit_format(irqHandlerExitEvent); + + eventParser.ParseDataItem(&ftraceCpuDetail2, TS_CLOCK_BOOTTIME); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_IRQ_HANDLER_EXIT, + STAT_EVENT_NOTMATCH) == 1); + cpu1 = 1; + ts1 = 200; + + IrqHandlerExitFormat* irqHandlerExitEvent2 = new IrqHandlerExitFormat(); + irqHandlerExitEvent2->set_irq(irq); + irqHandlerExitEvent2->set_ret(ret); + FtraceCpuDetailMsg ftraceCpuDetail3; + ftraceCpuDetail3.set_cpu(cpu1); + ftraceCpuDetail3.set_overwrite(0); + auto ftraceEvent3 = ftraceCpuDetail3.add_event(); + + ftraceEvent3->set_timestamp(ts1); + ftraceEvent3->set_tgid(tid1); + ftraceEvent3->set_comm(appName); + ftraceEvent3->set_allocated_irq_handler_exit_format(irqHandlerExitEvent2); + + eventParser.ParseDataItem(&ftraceCpuDetail3, TS_CLOCK_BOOTTIME); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); +} + +/** + * @tc.name: SoftIrqEntryTest + * @tc.desc: Binary format Soft interrupt normal test + * @tc.type: FUNC + */ +HWTEST_F(HtraceIrqEventTest, SoftIrqEntryTest, TestSize.Level1) +{ + TS_LOGI("test10-6"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + uint32_t vec = 1; + std::string appName = "app1"; + uint32_t tid1 = 1; + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + SoftirqEntryFormat* softirqEntryEvent = new SoftirqEntryFormat(); + softirqEntryEvent->set_vec(vec); + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(cpu1); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_softirq_entry_format(softirqEntryEvent); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); +} + +/** + * @tc.name: SoftIrqEntryNotMatch + * @tc.desc: The binary format soft interrupts do not match. The two interrupts have only the beginning and no end + * @tc.type: FUNC + */ +HWTEST_F(HtraceIrqEventTest, SoftIrqEntryNotMatch, TestSize.Level1) +{ + TS_LOGI("test10-7"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + uint32_t vec = 1; + std::string appName = "app1"; + uint32_t tid1 = 1; + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + SoftirqEntryFormat* softirqEntryEvent = new SoftirqEntryFormat(); + softirqEntryEvent->set_vec(vec); + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(cpu1); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_softirq_entry_format(softirqEntryEvent); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + ts1 = 150; + + SoftirqEntryFormat* softirqEntryEvent2 = new SoftirqEntryFormat(); + softirqEntryEvent2->set_vec(vec); + FtraceCpuDetailMsg ftraceCpuDetail2; + ftraceCpuDetail2.set_cpu(cpu1); + ftraceCpuDetail2.set_overwrite(0); + auto ftraceEvent2 = ftraceCpuDetail2.add_event(); + + ftraceEvent2->set_timestamp(ts1); + ftraceEvent2->set_tgid(tid1); + ftraceEvent2->set_comm(appName); + ftraceEvent2->set_allocated_softirq_entry_format(softirqEntryEvent2); + eventParser.ParseDataItem(&ftraceCpuDetail2, TS_CLOCK_BOOTTIME); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 2); + EXPECT_TRUE( + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_SOFTIRQ_ENTRY, STAT_EVENT_DATA_LOST) == 1); +} + +/** + * @tc.name: SoftIrqExitEmptyTest + * @tc.desc: The binary format soft interrupt only ends without starting + * @tc.type: FUNC + */ +HWTEST_F(HtraceIrqEventTest, SoftIrqExitEmptyTest, TestSize.Level1) +{ + TS_LOGI("test10-8"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + uint32_t vec = 1; + std::string appName = "app1"; + uint32_t tid1 = 1; + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + SoftirqExitFormat* softirqExitEvent = new SoftirqExitFormat(); + softirqExitEvent->set_vec(vec); + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(cpu1); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_softirq_exit_format(softirqExitEvent); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 0); + EXPECT_TRUE( + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_SOFTIRQ_EXIT, STAT_EVENT_DATA_LOST) == 1); +} + +/** + * @tc.name: SoftIrqTest + * @tc.desc: The binary format soft interrupt normal test + * @tc.type: FUNC + */ +HWTEST_F(HtraceIrqEventTest, SoftIrqTest, TestSize.Level1) +{ + TS_LOGI("test10-9"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + uint32_t vec = 1; + std::string appName = "app1"; + uint32_t tid1 = 1; + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + SoftirqEntryFormat* softirqEntryEvent = new SoftirqEntryFormat(); + softirqEntryEvent->set_vec(vec); + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(cpu1); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_softirq_entry_format(softirqEntryEvent); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + ts1 = 150; + SoftirqExitFormat* softirqExitEvent = new SoftirqExitFormat(); + softirqExitEvent->set_vec(vec); + FtraceCpuDetailMsg ftraceCpuDetail2; + ftraceCpuDetail2.set_cpu(cpu1); + ftraceCpuDetail2.set_overwrite(0); + auto ftraceEvent2 = ftraceCpuDetail2.add_event(); + + ftraceEvent2->set_timestamp(ts1); + ftraceEvent2->set_tgid(tid1); + ftraceEvent2->set_comm(appName); + ftraceEvent2->set_allocated_softirq_exit_format(softirqExitEvent); + eventParser.ParseDataItem(&ftraceCpuDetail2, TS_CLOCK_BOOTTIME); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); +} + +/** + * @tc.name: SoftIrqTestNotMatch + * @tc.desc: The binary soft interrupt test not match + * @tc.type: FUNC + */ +HWTEST_F(HtraceIrqEventTest, SoftIrqTestNotMatch, TestSize.Level1) +{ + TS_LOGI("test10-10"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + uint32_t vec = 1; + std::string appName = "app1"; + uint32_t tid1 = 1; + HtraceEventParser eventParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + SoftirqEntryFormat* softirqEntryEvent = new SoftirqEntryFormat(); + softirqEntryEvent->set_vec(vec); + FtraceCpuDetailMsg ftraceCpuDetail; + ftraceCpuDetail.set_cpu(cpu1); + ftraceCpuDetail.set_overwrite(0); + auto ftraceEvent = ftraceCpuDetail.add_event(); + + ftraceEvent->set_timestamp(ts1); + ftraceEvent->set_tgid(tid1); + ftraceEvent->set_comm(appName); + ftraceEvent->set_allocated_softirq_entry_format(softirqEntryEvent); + eventParser.ParseDataItem(&ftraceCpuDetail, TS_CLOCK_BOOTTIME); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + ts1 = 150; + cpu1 = 2; + + SoftirqExitFormat* softirqExitEvent = new SoftirqExitFormat(); + softirqExitEvent->set_vec(vec); + FtraceCpuDetailMsg ftraceCpuDetail2; + ftraceCpuDetail2.set_cpu(cpu1); + ftraceCpuDetail2.set_overwrite(0); + auto ftraceEvent2 = ftraceCpuDetail2.add_event(); + + ftraceEvent2->set_timestamp(ts1); + ftraceEvent2->set_tgid(tid1); + ftraceEvent2->set_comm(appName); + ftraceEvent2->set_allocated_softirq_exit_format(softirqExitEvent); + eventParser.ParseDataItem(&ftraceCpuDetail2, TS_CLOCK_BOOTTIME); + EXPECT_TRUE(stream_.traceDataCache_->GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_EVENT_SOFTIRQ_EXIT, + STAT_EVENT_DATA_LOST) == 1); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/test/unittest/htrace_mem_parser_test.cpp b/host/trace_streamer/test/unittest/htrace_mem_parser_test.cpp new file mode 100644 index 0000000..822a0f2 --- /dev/null +++ b/host/trace_streamer/test/unittest/htrace_mem_parser_test.cpp @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + + +#include "htrace_mem_parser.h" +#include "parser/common_types.h" +#include "trace_streamer_selector.h" + +using namespace testing::ext; +using namespace SysTuning::TraceStreamer; + +namespace SysTuning { +namespace TraceStreamer { +class HtraceMemParserTest : public ::testing::Test { +public: + void SetUp() + { + stream_.InitFilter(); + } + + void TearDown() + { + if (access(dbPath_.c_str(), F_OK) == 0) { + remove(dbPath_.c_str()); + } + } + +public: + SysTuning::TraceStreamer::TraceStreamerSelector stream_ = {}; + const std::string dbPath_ = "/data/resource/out.db"; +}; + +/** + * @tc.name: ParseMemParse + * @tc.desc: Parse MemoryData object and export database + * @tc.type: FUNC + */ +HWTEST_F(HtraceMemParserTest, ParseMemParse, TestSize.Level1) +{ + TS_LOGI("test7-1"); + HtraceMemParser* memParser = new HtraceMemParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + MemoryData tracePacket; + ProcessMemoryInfo* memoryInfo = tracePacket.add_processesinfo(); + EXPECT_TRUE(memoryInfo != nullptr); + int size = tracePacket.processesinfo_size(); + EXPECT_TRUE(size == 1); + uint64_t timeStamp = 1616439852302; + BuiltinClocks clock = TS_CLOCK_REALTIME; + + memParser->Parse(tracePacket, timeStamp, clock); + memParser->Finish(); + stream_.traceDataCache_->ExportDatabase(dbPath_); + + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + tracePacket.clear_processesinfo(); + delete memParser; + + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_MEMORY, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: ParseMemParseTestMeasureDataSize + * @tc.desc: Parse MemoryData object and count StatInfo + * @tc.type: FUNC + */ +HWTEST_F(HtraceMemParserTest, ParseMemParseTestMeasureDataSize, TestSize.Level1) +{ + TS_LOGI("test7-1"); + HtraceMemParser* memParser = new HtraceMemParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + MemoryData tracePacket; + ProcessMemoryInfo* memoryInfo = tracePacket.add_processesinfo(); + EXPECT_TRUE(memoryInfo != nullptr); + int size = tracePacket.processesinfo_size(); + EXPECT_TRUE(size == 1); + uint64_t timeStamp = 1616439852302; + BuiltinClocks clock = TS_CLOCK_REALTIME; + uint32_t pid = 12; + int64_t memKb = 1024; + int64_t memRssKb = 512; + int64_t memAnonKb = 128; + int64_t memFileKb = 2048; + memoryInfo->set_pid(pid); + memoryInfo->set_name("Process1"); + memoryInfo->set_vm_size_kb(memKb); + memoryInfo->set_vm_rss_kb(memRssKb); + memoryInfo->set_rss_anon_kb(memAnonKb); + memoryInfo->set_rss_file_kb(memFileKb); + + memParser->Parse(tracePacket, timeStamp, clock); + memParser->Finish(); + stream_.traceDataCache_->ExportDatabase(dbPath_); + + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + tracePacket.clear_processesinfo(); + delete memParser; + + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_MEMORY, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); + + EXPECT_TRUE(stream_.traceDataCache_->GetConstProcessData(1).pid_ == pid); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().Size() == MEM_MAX * 1); + + for (auto i = 0; i < MEM_MAX; i++) { + if (stream_.traceDataCache_->GetConstMeasureData().filterIdDeque_[i] == + memParser->memNameDictMap_.at(MEM_VM_SIZE)) { + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().valuesDeque_[i] == memKb); + } else if (stream_.traceDataCache_->GetConstMeasureData().filterIdDeque_[i] == + memParser->memNameDictMap_.at(MEM_VM_RSS)) { + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().valuesDeque_[i] == memRssKb); + } else if (stream_.traceDataCache_->GetConstMeasureData().filterIdDeque_[i] == + memParser->memNameDictMap_.at(MEM_VM_ANON)) { + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().valuesDeque_[i] == memAnonKb); + } else if (stream_.traceDataCache_->GetConstMeasureData().filterIdDeque_[i] == + memParser->memNameDictMap_.at(MEM_RSS_FILE)) { + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().valuesDeque_[i] == memFileKb); + } + } +} + +/** + * @tc.name: ParseMemParseTestMutiMeasureData + * @tc.desc: Parse muti MemoryData object and count StatInfo + * @tc.type: FUNC + */ +HWTEST_F(HtraceMemParserTest, ParseMemParseTestMutiMeasureData, TestSize.Level1) +{ + TS_LOGI("test7-1"); + HtraceMemParser* memParser = new HtraceMemParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + MemoryData tracePacket; + ProcessMemoryInfo* memoryInfo = tracePacket.add_processesinfo(); + EXPECT_TRUE(memoryInfo != nullptr); + int size = tracePacket.processesinfo_size(); + EXPECT_TRUE(size == 1); + uint64_t timeStamp = 1616439852302; + BuiltinClocks clock = TS_CLOCK_REALTIME; + uint32_t pid = 12; + memoryInfo->set_pid(12); + memoryInfo->set_name("Process1"); + memoryInfo->set_vm_size_kb(1024); + memoryInfo->set_vm_rss_kb(512); + memoryInfo->set_rss_anon_kb(128); + memoryInfo->set_rss_file_kb(128); + + ProcessMemoryInfo* memoryInfo2 = tracePacket.add_processesinfo(); + EXPECT_TRUE(memoryInfo2 != nullptr); + size = tracePacket.processesinfo_size(); + EXPECT_TRUE(size == 2); + timeStamp = 1616439852402; + uint32_t pid2 = 13; + memoryInfo2->set_pid(pid2); + memoryInfo2->set_name("Process2"); + memoryInfo2->set_vm_size_kb(1024); + memoryInfo2->set_vm_rss_kb(512); + memoryInfo2->set_rss_anon_kb(128); + memoryInfo2->set_rss_file_kb(128); + + memParser->Parse(tracePacket, timeStamp, clock); + memParser->Finish(); + stream_.traceDataCache_->ExportDatabase(dbPath_); + + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + tracePacket.clear_processesinfo(); + delete memParser; + + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_MEMORY, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); + + EXPECT_TRUE(stream_.traceDataCache_->GetConstProcessData(1).pid_ == pid); + EXPECT_TRUE(stream_.traceDataCache_->GetConstProcessData(2).pid_ == pid2); +} + +/** + * @tc.name: ParseMultiEmptyProcessMemoryInfo + * @tc.desc: Parse muti Empty ProcessMemoryInfo object and count StatInfo + * @tc.type: FUNC + */ +HWTEST_F(HtraceMemParserTest, ParseMultiEmptyProcessMemoryInfo, TestSize.Level1) +{ + TS_LOGI("test7-1"); + HtraceMemParser* memParser = new HtraceMemParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + MemoryData tracePacket; + ProcessMemoryInfo* memoryInfo = tracePacket.add_processesinfo(); + EXPECT_TRUE(memoryInfo != nullptr); + int size = tracePacket.processesinfo_size(); + EXPECT_TRUE(size == 1); + uint64_t timeStamp = 1616439852302; + BuiltinClocks clock = TS_CLOCK_REALTIME; + + ProcessMemoryInfo* memoryInfo2 = tracePacket.add_processesinfo(); + EXPECT_TRUE(memoryInfo2 != nullptr); + size = tracePacket.processesinfo_size(); + EXPECT_TRUE(size == 2); + + memParser->Parse(tracePacket, timeStamp, clock); + memParser->Finish(); + stream_.traceDataCache_->ExportDatabase(dbPath_); + + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + tracePacket.clear_processesinfo(); + delete memParser; + + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_MEMORY, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); + + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().Size() == MEM_MAX * 2); +} + +/** + * @tc.name: ParseEmptyMemoryData + * @tc.desc: Parse Empty MemoryData + * @tc.type: FUNC + */ +HWTEST_F(HtraceMemParserTest, ParseEmptyMemoryData, TestSize.Level1) +{ + TS_LOGI("test7-1"); + HtraceMemParser* memParser = new HtraceMemParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + MemoryData tracePacket; + int size = tracePacket.processesinfo_size(); + EXPECT_TRUE(size == 0); + uint64_t timeStamp = 1616439852302; + BuiltinClocks clock = TS_CLOCK_REALTIME; + + memParser->Parse(tracePacket, timeStamp, clock); + memParser->Finish(); + delete memParser; + + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_MEMORY, STAT_EVENT_RECEIVED); + EXPECT_TRUE(0 == eventCount); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/test/unittest/htrace_sys_mem_parser_test.cpp b/host/trace_streamer/test/unittest/htrace_sys_mem_parser_test.cpp new file mode 100644 index 0000000..560f22d --- /dev/null +++ b/host/trace_streamer/test/unittest/htrace_sys_mem_parser_test.cpp @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include "htrace_mem_parser.h" +#include "parser/common_types.h" +#include "trace_streamer_selector.h" + +using namespace testing::ext; +using namespace SysTuning::TraceStreamer; + +namespace SysTuning { +namespace TraceStreamer { +class HtraceSysMemParserTest : public ::testing::Test { +public: + void SetUp() + { + stream_.InitFilter(); + } + + void TearDown() + { + if (access(dbPath_.c_str(), F_OK) == 0) { + remove(dbPath_.c_str()); + } + } + +public: + SysTuning::TraceStreamer::TraceStreamerSelector stream_ = {}; + const std::string dbPath_ = "/data/resource/out.db"; +}; + +/** + * @tc.name: ParseSysMemParseInputEmpty + * @tc.desc: Kernel memory parsing test, input empty + * @tc.type: FUNC + */ +HWTEST_F(HtraceSysMemParserTest, ParseSysMemParseInputEmpty, TestSize.Level1) +{ + TS_LOGI("test12-1"); + HtraceMemParser* memParser = new HtraceMemParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + MemoryData tracePacket; + SysMeminfo* mem = tracePacket.add_meminfo(); + EXPECT_TRUE(mem != nullptr); + int size = tracePacket.meminfo_size(); + EXPECT_TRUE(size == 1); + mem->set_key(SysMeminfoType::PMEM_MEM_TOTAL); + uint64_t value = random(); + mem->set_value(value); + uint64_t timeStamp = 1616439852302; + BuiltinClocks clock = TS_CLOCK_REALTIME; + + memParser->Parse(tracePacket, timeStamp, clock); + stream_.traceDataCache_->ExportDatabase(dbPath_); + + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + tracePacket.clear_meminfo(); + delete memParser; + + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_SYS_MEMORY, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().ValuesData()[0] == static_cast(value)); +} + +/** + * @tc.name: ParseSysMemParseNormal + * @tc.desc: Kernel memory parsing test normal + * @tc.type: FUNC + */ +HWTEST_F(HtraceSysMemParserTest, ParseSysMemParseNormal, TestSize.Level1) +{ + TS_LOGI("test12-2"); + HtraceMemParser* memParser = new HtraceMemParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + MemoryData tracePacket; + SysMeminfo* mem = tracePacket.add_meminfo(); + EXPECT_TRUE(mem != nullptr); + int size = tracePacket.meminfo_size(); + EXPECT_TRUE(size == 1); + mem->set_key(SysMeminfoType::PMEM_MEM_TOTAL); + uint64_t value = random(); + mem->set_value(value); + + mem = tracePacket.add_meminfo(); + EXPECT_TRUE(mem != nullptr); + size = tracePacket.meminfo_size(); + EXPECT_TRUE(size == 2); + mem->set_key(SysMeminfoType::PMEM_MEM_FREE); + uint64_t value2 = random(); + mem->set_value(value2); + + uint64_t timeStamp = 1616439852302; + BuiltinClocks clock = TS_CLOCK_REALTIME; + + memParser->Parse(tracePacket, timeStamp, clock); + stream_.traceDataCache_->ExportDatabase(dbPath_); + + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + tracePacket.clear_meminfo(); + delete memParser; + + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_SYS_MEMORY, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().Size() == 2); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().ValuesData()[0] == static_cast(value)); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().ValuesData()[1] == static_cast(value2)); +} + +/** + * @tc.name: ParseSysMemParseAbnomal + * @tc.desc: Kernel memory parsing test abnomal + * @tc.type: FUNC + */ +HWTEST_F(HtraceSysMemParserTest, ParseSysMemParseAbnomal, TestSize.Level1) +{ + TS_LOGI("test12-3"); + HtraceMemParser* memParser = new HtraceMemParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + MemoryData tracePacket; + SysMeminfo* mem = tracePacket.add_meminfo(); + EXPECT_TRUE(mem != nullptr); + int size = tracePacket.meminfo_size(); + EXPECT_TRUE(size == 1); + mem->set_key(SysMeminfoType::PMEM_MEM_TOTAL); + uint64_t value = random(); + mem->set_value(value); + + mem = tracePacket.add_meminfo(); + EXPECT_TRUE(mem != nullptr); + size = tracePacket.meminfo_size(); + EXPECT_TRUE(size == 2); + mem->set_key(static_cast(199999)); // invalid data + uint64_t value2 = random(); + mem->set_value(value2); + + uint64_t timeStamp = 1616439852302; + BuiltinClocks clock = TS_CLOCK_REALTIME; + + memParser->Parse(tracePacket, timeStamp, clock); + stream_.traceDataCache_->ExportDatabase(dbPath_); + + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + tracePacket.clear_meminfo(); + delete memParser; + + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_SYS_MEMORY, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); + eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_SYS_MEMORY, STAT_EVENT_DATA_INVALID); + EXPECT_TRUE(1 == eventCount); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().ValuesData()[0] == static_cast(value)); +} + +/** + * @tc.name: ParseSysMemParseMutiNomal + * @tc.desc: Kernel memory parsing test with muti nomal + * @tc.type: FUNC + */ +HWTEST_F(HtraceSysMemParserTest, ParseSysMemParseMutiNomal, TestSize.Level1) +{ + TS_LOGI("test12-4"); + HtraceMemParser* memParser = new HtraceMemParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + MemoryData tracePacket; + SysMeminfo* mem = tracePacket.add_meminfo(); + EXPECT_TRUE(mem != nullptr); + int size = tracePacket.meminfo_size(); + EXPECT_TRUE(size == 1); + mem->set_key(SysMeminfoType::PMEM_CMA_FREE); + uint64_t value = random(); + mem->set_value(value); + + mem = tracePacket.add_meminfo(); + EXPECT_TRUE(mem != nullptr); + size = tracePacket.meminfo_size(); + EXPECT_TRUE(size == 2); + mem->set_key(SysMeminfoType::SysMeminfoType_INT_MIN_SENTINEL_DO_NOT_USE_); + uint64_t value2 = random(); + mem->set_value(value2); + + mem = tracePacket.add_meminfo(); + EXPECT_TRUE(mem != nullptr); + size = tracePacket.meminfo_size(); + EXPECT_TRUE(size == 3); + mem->set_key(SysMeminfoType::SysMeminfoType_INT_MAX_SENTINEL_DO_NOT_USE_); + uint64_t value3 = random(); + mem->set_value(value3); + + uint64_t timeStamp = 1616439852302; + BuiltinClocks clock = TS_CLOCK_REALTIME; + + memParser->Parse(tracePacket, timeStamp, clock); + stream_.traceDataCache_->ExportDatabase(dbPath_); + + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + tracePacket.clear_meminfo(); + delete memParser; + + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_SYS_MEMORY, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); + eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_SYS_MEMORY, STAT_EVENT_DATA_INVALID); + EXPECT_TRUE(2 == eventCount); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().ValuesData()[0] == static_cast(value)); +} + +/** + * @tc.name: ParseSysMemParseWithRandomValue + * @tc.desc: Kernel memory parsing test, input a random reasonable value + * @tc.type: FUNC + */ +HWTEST_F(HtraceSysMemParserTest, ParseSysMemParseWithRandomValue, TestSize.Level1) +{ + TS_LOGI("test12-5"); + HtraceMemParser* memParser = new HtraceMemParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + MemoryData tracePacket; + + std::map sysMemValueMap_ = {}; + for (auto i = 0; i < SysMeminfoType::PMEM_CMA_FREE + 1; i++) { + uint64_t value = random(); + sysMemValueMap_.insert(std::make_pair(static_cast(i), value)); + SysMeminfo* mem = tracePacket.add_meminfo(); + EXPECT_TRUE(mem != nullptr); + mem->set_key(static_cast(i)); + mem->set_value(value); + int size = tracePacket.meminfo_size(); + EXPECT_TRUE(size == i + 1); + } + + uint64_t timeStamp = 1616439852302; + BuiltinClocks clock = TS_CLOCK_REALTIME; + + memParser->Parse(tracePacket, timeStamp, clock); + stream_.traceDataCache_->ExportDatabase(dbPath_); + + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + tracePacket.clear_meminfo(); + delete memParser; + + auto eventCount = stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_SYS_MEMORY, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); + + for (auto i = 0; i < SysMeminfoType::PMEM_CMA_FREE + 1; i++) { + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().ValuesData()[i] == + sysMemValueMap_.at(static_cast(i))); + } +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/test/unittest/htrace_sys_vmem_parser_test.cpp b/host/trace_streamer/test/unittest/htrace_sys_vmem_parser_test.cpp new file mode 100644 index 0000000..da4d406 --- /dev/null +++ b/host/trace_streamer/test/unittest/htrace_sys_vmem_parser_test.cpp @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include "htrace_mem_parser.h" +#include "parser/common_types.h" +#include "trace_streamer_selector.h" + +using namespace testing::ext; +using namespace SysTuning::TraceStreamer; + +namespace SysTuning { +namespace TraceStreamer { +class HtraceSysVMemParserTest : public ::testing::Test { +public: + void SetUp() + { + stream_.InitFilter(); + } + + void TearDown() + { + if (access(dbPath_.c_str(), F_OK) == 0) { + remove(dbPath_.c_str()); + } + } + +public: + SysTuning::TraceStreamer::TraceStreamerSelector stream_ = {}; + const std::string dbPath_ = "/data/resource/out.db"; +}; + +/** + * @tc.name: ParseSysMemParseWithRandomValue + * @tc.desc: Virtual memory parsing test, input a random reasonable value + * @tc.type: FUNC + */ +HWTEST_F(HtraceSysVMemParserTest, ParseSysVMemParse, TestSize.Level1) +{ + TS_LOGI("test13-1"); + HtraceMemParser* memParser = new HtraceMemParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + MemoryData tracePacket; + SysVMeminfo* vmem = tracePacket.add_vmeminfo(); + EXPECT_TRUE(vmem != nullptr); + int size = tracePacket.vmeminfo_size(); + EXPECT_TRUE(size == 1); + vmem->set_key(SysVMeminfoType::VMEMINFO_UNSPECIFIED); + uint64_t value = random(); + vmem->set_value(value); + uint64_t timeStamp = 1616439852302; + BuiltinClocks clock = TS_CLOCK_REALTIME; + + memParser->Parse(tracePacket, timeStamp, clock); + stream_.traceDataCache_->ExportDatabase(dbPath_); + + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + tracePacket.clear_meminfo(); + delete memParser; + + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_SYS_VIRTUAL_MEMORY, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().ValuesData()[0] == static_cast(value)); +} + +/** + * @tc.name: ParseSysVMemNomal + * @tc.desc: Virtual memory parsing test nomal + * @tc.type: FUNC + */ +HWTEST_F(HtraceSysVMemParserTest, ParseSysVMemNomal, TestSize.Level1) +{ + TS_LOGI("test13-2"); + HtraceMemParser* memParser = new HtraceMemParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + MemoryData tracePacket; + SysVMeminfo* vmem = tracePacket.add_vmeminfo(); + EXPECT_TRUE(vmem != nullptr); + int size = tracePacket.vmeminfo_size(); + EXPECT_TRUE(size == 1); + vmem->set_key(SysVMeminfoType::VMEMINFO_NR_FREE_PAGES); + uint64_t value = random(); + vmem->set_value(value); + + vmem = tracePacket.add_vmeminfo(); + EXPECT_TRUE(vmem != nullptr); + size = tracePacket.vmeminfo_size(); + EXPECT_TRUE(size == 2); + vmem->set_key(SysVMeminfoType::VMEMINFO_NR_ALLOC_BATCH); + uint64_t value2 = random(); + vmem->set_value(value2); + + uint64_t timeStamp = 1616439852302; + BuiltinClocks clock = TS_CLOCK_REALTIME; + + memParser->Parse(tracePacket, timeStamp, clock); + stream_.traceDataCache_->ExportDatabase(dbPath_); + + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + tracePacket.clear_meminfo(); + delete memParser; + + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_SYS_VIRTUAL_MEMORY, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().Size() == 2); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().ValuesData()[0] == static_cast(value)); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().ValuesData()[1] == static_cast(value2)); +} + +/** + * @tc.name: ParseSysVMemAbnomal + * @tc.desc: Virtual memory parsing test abnomal + * @tc.type: FUNC + */ +HWTEST_F(HtraceSysVMemParserTest, ParseSysVMemAbnomal, TestSize.Level1) +{ + TS_LOGI("test13-3"); + HtraceMemParser* memParser = new HtraceMemParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + MemoryData tracePacket; + SysVMeminfo* vmem = tracePacket.add_vmeminfo(); + EXPECT_TRUE(vmem != nullptr); + int size = tracePacket.vmeminfo_size(); + EXPECT_TRUE(size == 1); + vmem->set_key(SysVMeminfoType::VMEMINFO_NR_FREE_PAGES); + uint64_t value = random(); + vmem->set_value(value); + + vmem = tracePacket.add_vmeminfo(); + EXPECT_TRUE(vmem != nullptr); + size = tracePacket.vmeminfo_size(); + EXPECT_TRUE(size == 2); + uint64_t value2 = random(); + vmem->set_value(value2); + + uint64_t timeStamp = 1616439852302; + BuiltinClocks clock = TS_CLOCK_REALTIME; + + memParser->Parse(tracePacket, timeStamp, clock); + stream_.traceDataCache_->ExportDatabase(dbPath_); + + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + tracePacket.clear_meminfo(); + delete memParser; + + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_SYS_VIRTUAL_MEMORY, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); + eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_SYS_VIRTUAL_MEMORY, STAT_EVENT_DATA_INVALID); + EXPECT_TRUE(0 == eventCount); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().Size() == 2); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().ValuesData()[0] == static_cast(value)); +} + +/** + * @tc.name: ParseSysVMemWithMutiNomal + * @tc.desc: Virtual memory parsing test with muti nomal + * @tc.type: FUNC + */ +HWTEST_F(HtraceSysVMemParserTest, ParseSysVMemWithMutiNomal, TestSize.Level1) +{ + TS_LOGI("test13-4"); + HtraceMemParser* memParser = new HtraceMemParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + MemoryData tracePacket; + SysVMeminfo* vmem = tracePacket.add_vmeminfo(); + EXPECT_TRUE(vmem != nullptr); + int size = tracePacket.vmeminfo_size(); + EXPECT_TRUE(size == 1); + vmem->set_key(SysVMeminfoType::VMEMINFO_WORKINGSET_RESTORE); + uint64_t value = random(); + vmem->set_value(value); + + vmem = tracePacket.add_vmeminfo(); + EXPECT_TRUE(vmem != nullptr); + size = tracePacket.vmeminfo_size(); + EXPECT_TRUE(size == 2); + vmem->set_key(SysVMeminfoType::SysVMeminfoType_INT_MIN_SENTINEL_DO_NOT_USE_); + uint64_t value2 = random(); + vmem->set_value(value2); + + vmem = tracePacket.add_vmeminfo(); + EXPECT_TRUE(vmem != nullptr); + size = tracePacket.vmeminfo_size(); + EXPECT_TRUE(size == 3); + vmem->set_key(SysVMeminfoType::SysVMeminfoType_INT_MAX_SENTINEL_DO_NOT_USE_); + uint64_t value3 = random(); + vmem->set_value(value3); + + uint64_t timeStamp = 1616439852302; + BuiltinClocks clock = TS_CLOCK_REALTIME; + + memParser->Parse(tracePacket, timeStamp, clock); + stream_.traceDataCache_->ExportDatabase(dbPath_); + + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + tracePacket.clear_vmeminfo(); + delete memParser; + + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_SYS_VIRTUAL_MEMORY, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); + eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_SYS_VIRTUAL_MEMORY, STAT_EVENT_DATA_INVALID); + EXPECT_TRUE(2 == eventCount); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().ValuesData()[0] == static_cast(value)); +} + +/** + * @tc.name: ParseSysVMemWithRandomValue + * @tc.desc: Virtual memory parsing test, input a random reasonable value + * @tc.type: FUNC + */ +HWTEST_F(HtraceSysVMemParserTest, ParseSysVMemWithRandomValue, TestSize.Level1) +{ + TS_LOGI("test13-5"); + HtraceMemParser* memParser = new HtraceMemParser(stream_.traceDataCache_.get(), stream_.streamFilters_.get()); + + MemoryData tracePacket; + + std::map sysVMemValueMap_ = {}; + for (auto i = 0; i < SysVMeminfoType::VMEMINFO_WORKINGSET_RESTORE + 1; i++) { + uint64_t value = random(); + sysVMemValueMap_.insert(std::make_pair(static_cast(i), value)); + SysVMeminfo* vmem = tracePacket.add_vmeminfo(); + EXPECT_TRUE(vmem != nullptr); + vmem->set_key(static_cast(i)); + vmem->set_value(value); + int size = tracePacket.vmeminfo_size(); + EXPECT_TRUE(size == i + 1); + } + + uint64_t timeStamp = 1616439852302; + BuiltinClocks clock = TS_CLOCK_REALTIME; + + memParser->Parse(tracePacket, timeStamp, clock); + stream_.traceDataCache_->ExportDatabase(dbPath_); + + EXPECT_TRUE(access(dbPath_.c_str(), F_OK) == 0); + tracePacket.clear_vmeminfo(); + delete memParser; + + auto eventCount = + stream_.traceDataCache_->GetConstStatAndInfo().GetValue(TRACE_SYS_VIRTUAL_MEMORY, STAT_EVENT_RECEIVED); + EXPECT_TRUE(1 == eventCount); + + for (auto i = 0; i < SysVMeminfoType::VMEMINFO_WORKINGSET_RESTORE + 1; i++) { + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().ValuesData()[i] == + sysVMemValueMap_.at(static_cast(i))); + } +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/test/unittest/irq_filter_test.cpp b/host/trace_streamer/test/unittest/irq_filter_test.cpp new file mode 100644 index 0000000..33ebaa9 --- /dev/null +++ b/host/trace_streamer/test/unittest/irq_filter_test.cpp @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "args_filter.h" +#include "irq_filter.h" +#include "slice_filter.h" +#include "stat_filter.h" +#include "ts_common.h" + +using namespace testing::ext; +using namespace SysTuning::TraceStreamer; +namespace SysTuning { +namespace TraceStreamer { +class IrqFilterTest : public ::testing::Test { +public: + void SetUp() + { + streamFilters_.argsFilter_ = std::make_unique(&traceDataCache_, &streamFilters_); + streamFilters_.irqFilter_ = std::make_unique(&traceDataCache_, &streamFilters_); + streamFilters_.sliceFilter_ = std::make_unique(&traceDataCache_, &streamFilters_); + streamFilters_.statFilter_ = std::make_unique(&traceDataCache_, &streamFilters_); + streamFilters_.statFilter_ = std::make_unique(&traceDataCache_, &streamFilters_); + } + + void TearDown() {} + +public: + TraceStreamerFilters streamFilters_; + TraceDataCache traceDataCache_; +}; + +/** + * @tc.name: IrqHandlerEntryTest + * @tc.desc: IrqHandlerEntry Normal TEST + * @tc.type: FUNC + */ +HWTEST_F(IrqFilterTest, IrqHandlerEntryTest, TestSize.Level1) +{ + TS_LOGI("test10-1"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + DataIndex nameId1 = 1; + streamFilters_.irqFilter_->IrqHandlerEntry(ts1, cpu1, nameId1); // IrqHandlerEntry + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); +} + +/** + * @tc.name: IrqHandlerEntryTestNotMatch + * @tc.desc: Test two interrupts, only start, no end + * @tc.type: FUNC + */ +HWTEST_F(IrqFilterTest, IrqHandlerEntryTestNotMatch, TestSize.Level1) +{ + TS_LOGI("test10-2"); + int64_t ts1 = 120; + uint32_t cpu1 = 1; + DataIndex nameId1 = 1; + streamFilters_.irqFilter_->IrqHandlerEntry(ts1, cpu1, nameId1); // IrqHandlerEntry + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + ts1 = 110; + uint32_t irqRet = 1; + streamFilters_.irqFilter_->IrqHandlerEntry(ts1, cpu1, irqRet); // IrqHandlerEntry + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 2); + // TRACE_EVENT_IRQ_HANDLER_ENTRY STAT_EVENT_DATA_LOST + auto eventCount = + traceDataCache_.GetConstStatAndInfo().GetValue(TRACE_EVENT_IRQ_HANDLER_ENTRY, STAT_EVENT_DATA_LOST); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: IrqHandlerExitTestEmpty + * @tc.desc:Interrupt only ends, not starts + * @tc.type: FUNC + */ +HWTEST_F(IrqFilterTest, IrqHandlerExitTestEmpty, TestSize.Level1) +{ + TS_LOGI("test10-3"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + uint32_t ret = 1; // 1 for handled, else for unhandled + streamFilters_.irqFilter_->IrqHandlerExit(ts1, cpu1, ret); // IrqHandlerExit + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 0); + // TRACE_EVENT_IRQ_HANDLER_EXIT STAT_EVENT_NOTMATCH + auto eventCount = + traceDataCache_.GetConstStatAndInfo().GetValue(TRACE_EVENT_IRQ_HANDLER_EXIT, STAT_EVENT_NOTMATCH); + EXPECT_TRUE(1 == eventCount); +} + +/** + * @tc.name: IrqHandlerEnterAndExitTest + * @tc.desc: Interrupt normal start and end + * @tc.type: FUNC + */ +HWTEST_F(IrqFilterTest, IrqHandlerEnterAndExitTest, TestSize.Level1) +{ + TS_LOGI("test10-4"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + DataIndex nameId1 = 1; + streamFilters_.irqFilter_->IrqHandlerEntry(ts1, cpu1, nameId1); // IrqHandlerEntry + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + uint32_t irqRet = 1; // 1 for handled, else for unhandled + streamFilters_.irqFilter_->IrqHandlerExit(ts1, cpu1, irqRet); // IrqHandlerExit + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().Size() == 1); + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().values_[0] == + static_cast(streamFilters_.irqFilter_->irqHandled_)); + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().names_[0] == streamFilters_.irqFilter_->irqRet_); + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().argset_[0] == 0); +} + +/** + * @tc.name: IrqHandlerDoubleEnterAndExitTest + * @tc.desc: Interrupt normal test, 2 interrupts and exits + * @tc.type: FUNC + */ +HWTEST_F(IrqFilterTest, IrqHandlerDoubleEnterAndExitTest, TestSize.Level1) +{ + TS_LOGI("test10-4-2"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + DataIndex nameId1 = 1; + streamFilters_.irqFilter_->IrqHandlerEntry(ts1, cpu1, nameId1); // IrqHandlerEntry + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + uint32_t ret = 1; // 1 for handled, else for unhandled + cpu1 = 2; + ts1 = 150; + streamFilters_.irqFilter_->IrqHandlerExit(ts1, cpu1, ret); // IrqHandlerExit + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(traceDataCache_.GetConstStatAndInfo().GetValue(TRACE_EVENT_IRQ_HANDLER_EXIT, + STAT_EVENT_NOTMATCH) == 1); + cpu1 = 1; + ts1 = 200; + streamFilters_.irqFilter_->IrqHandlerExit(ts1, cpu1, ret); // IrqHandlerExit + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); +} + +/** + * @tc.name: IrqHandlerTripleEnterAndExitTest + * @tc.desc: Interrupt normal test, 3 interrupts and exits + * @tc.type: FUNC + */ +HWTEST_F(IrqFilterTest, IrqHandlerTripleEnterAndExitTest, TestSize.Level1) +{ + TS_LOGI("test10-5"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + DataIndex nameId1 = 1; + streamFilters_.irqFilter_->IrqHandlerEntry(ts1, cpu1, nameId1); // IrqHandlerEntry + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + uint32_t ret = 1; // 1 for handled, else for unhandled + ts1 = 150; + streamFilters_.irqFilter_->IrqHandlerExit(ts1, cpu1, ret); // IrqHandlerExit + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + // check args + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); + + ts1 = 200; + cpu1 = 1; + nameId1 = 1; + streamFilters_.irqFilter_->IrqHandlerEntry(ts1, cpu1, nameId1); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 2); + ret = 1; // 1 for handled, else for unhandled + ts1 = 250; + streamFilters_.irqFilter_->IrqHandlerExit(ts1, cpu1, ret); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 2); + // check args + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().ArgSetIdsData()[1] == 1); +} + +/** + * @tc.name: SoftIrqEntryTest + * @tc.desc: Soft interrupt normal test + * @tc.type: FUNC + */ +HWTEST_F(IrqFilterTest, SoftIrqEntryTest, TestSize.Level1) +{ + TS_LOGI("test10-6"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + uint32_t vec = 1; + streamFilters_.irqFilter_->SoftIrqEntry(ts1, cpu1, vec); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); +} + +/** + * @tc.name: SoftIrqEntryNotMatch + * @tc.desc: The soft interrupts do not match. The two interrupts have only the beginning and no end + * @tc.type: FUNC + */ +HWTEST_F(IrqFilterTest, SoftIrqEntryNotMatch, TestSize.Level1) +{ + TS_LOGI("test10-7"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + uint32_t vec = 1; + streamFilters_.irqFilter_->SoftIrqEntry(ts1, cpu1, vec); // SoftIrqEntry + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + ts1 = 150; + streamFilters_.irqFilter_->SoftIrqEntry(ts1, cpu1, vec); // SoftIrqEntry + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 2); + EXPECT_TRUE( + traceDataCache_.GetConstStatAndInfo().GetValue(TRACE_EVENT_SOFTIRQ_ENTRY, STAT_EVENT_DATA_LOST) == 1); +} + +/** + * @tc.name: SoftIrqExitEmptyTest + * @tc.desc: The soft interrupt only ends without starting + * @tc.type: FUNC + */ +HWTEST_F(IrqFilterTest, SoftIrqExitEmptyTest, TestSize.Level1) +{ + TS_LOGI("test10-8"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + uint32_t vec = 1; + streamFilters_.irqFilter_->SoftIrqExit(ts1, cpu1, vec); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 0); + EXPECT_TRUE( + traceDataCache_.GetConstStatAndInfo().GetValue(TRACE_EVENT_SOFTIRQ_EXIT, STAT_EVENT_DATA_LOST) == 1); +} + +/** + * @tc.name: SoftIrqTest + * @tc.desc: The soft interrupt normal test + * @tc.type: FUNC + */ +HWTEST_F(IrqFilterTest, SoftIrqTest, TestSize.Level1) +{ + TS_LOGI("test10-9"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + uint32_t vec = 1; + streamFilters_.irqFilter_->SoftIrqEntry(ts1, cpu1, vec); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + ts1 = 150; + uint32_t irqRet = 1; + streamFilters_.irqFilter_->SoftIrqExit(ts1, cpu1, irqRet); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().ArgSetIdsData()[0] == 0); + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().Size() == 1); + printf("%lld\n", traceDataCache_.GetConstArgSetData().values_[0]); + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().values_[0] == + static_cast(streamFilters_.irqFilter_->irqActionNameIds_[irqRet])); + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().names_[0] == streamFilters_.irqFilter_->irqRet_); + EXPECT_TRUE(traceDataCache_.GetConstArgSetData().argset_[0] == 0); +} + +/** + * @tc.name: SoftIrqTestWithIrqEntryAndExit + * @tc.desc: The soft interrupt normal test + * @tc.type: FUNC + */ +HWTEST_F(IrqFilterTest, SoftIrqTestWithIrqEntryAndExit, TestSize.Level1) +{ + TS_LOGI("test10-10"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + uint32_t vec = 1; + streamFilters_.irqFilter_->SoftIrqEntry(ts1, cpu1, vec); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + ts1 = 150; + cpu1 = 2; + streamFilters_.irqFilter_->SoftIrqExit(ts1, cpu1, vec); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE( + traceDataCache_.GetConstStatAndInfo().GetValue(TRACE_EVENT_SOFTIRQ_EXIT, STAT_EVENT_DATA_LOST) == 1); +} + +/** + * @tc.name: SoftIrqTestOneEntryTwoNotMatchExit + * @tc.desc: The soft interrupt test with onece entry and twice Not Match exit + * @tc.type: FUNC + */ +HWTEST_F(IrqFilterTest, SoftIrqTestOneEntryTwoNotMatchExit, TestSize.Level1) +{ + TS_LOGI("test10-11"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + uint32_t vec = 1; + streamFilters_.irqFilter_->SoftIrqEntry(ts1, cpu1, vec); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + ts1 = 150; + cpu1 = 2; + streamFilters_.irqFilter_->SoftIrqExit(ts1, cpu1, vec); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE( + traceDataCache_.GetConstStatAndInfo().GetValue(TRACE_EVENT_SOFTIRQ_EXIT, STAT_EVENT_DATA_LOST) == 1); + ts1 = 200; + cpu1 = 3; + streamFilters_.irqFilter_->SoftIrqExit(ts1, cpu1, vec); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE( + traceDataCache_.GetConstStatAndInfo().GetValue(TRACE_EVENT_SOFTIRQ_EXIT, STAT_EVENT_DATA_LOST) == 2); +} + +/** + * @tc.name: SoftIrqTestWithSingleNotMatchExit + * @tc.desc: The soft interrupt test with single not Match Exit + * @tc.type: FUNC + */ +HWTEST_F(IrqFilterTest, SoftIrqTestWithSingleNotMatchExit, TestSize.Level1) +{ + TS_LOGI("test10-12"); + int64_t ts1 = 100; + uint32_t cpu1 = 1; + uint32_t vec = 1; + streamFilters_.irqFilter_->SoftIrqEntry(ts1, cpu1, vec); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + ts1 = 150; + cpu1 = 2; + streamFilters_.irqFilter_->SoftIrqExit(ts1, cpu1, vec); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE( + traceDataCache_.GetConstStatAndInfo().GetValue(TRACE_EVENT_SOFTIRQ_EXIT, STAT_EVENT_DATA_LOST) == 1); + ts1 = 200; + cpu1 = 1; + streamFilters_.irqFilter_->SoftIrqExit(ts1, cpu1, vec); + EXPECT_TRUE(traceDataCache_.GetConstInternalSlicesData().Size() == 1); + EXPECT_TRUE( + traceDataCache_.GetConstStatAndInfo().GetValue(TRACE_EVENT_SOFTIRQ_EXIT, STAT_EVENT_DATA_LOST) == 1); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/test/unittest/measure_filter_test.cpp b/host/trace_streamer/test/unittest/measure_filter_test.cpp new file mode 100644 index 0000000..6e20f9a --- /dev/null +++ b/host/trace_streamer/test/unittest/measure_filter_test.cpp @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "filter_filter.h" +#include "measure_filter.h" +#include "trace_streamer_selector.h" + +using namespace testing::ext; +using namespace SysTuning::TraceStreamer; +namespace SysTuning { +namespace TraceStreamer { +constexpr int CPU_ID_0 = 0; +constexpr int CPU_ID_1 = 1; +constexpr std::string_view CPU_TYPE_0 = "cpu_idle"; +constexpr std::string_view CPU_TYPE_1 = "cpu_frequency"; +constexpr int INTERNAL_THREAD_ID_0 = 1; +constexpr int INTERNAL_THREAD_ID_1 = 2; +constexpr int INTERNAL_PROCESS_ID_0 = 1; +constexpr int INTERNAL_PROCESS_ID_1 = 2; +constexpr std::string_view TASK_NAME_0 = "softbus_server"; +constexpr std::string_view TASK_NAME_1 = "hiprofilerd"; + +class MeasureFilterTest : public ::testing::Test { +public: + void SetUp() + { + stream_.InitFilter(); + } + + void TearDown() {} + +public: + SysTuning::TraceStreamer::TraceStreamerSelector stream_ = {}; +}; + +/** + * @tc.name: ThreadMeasureFilter + * @tc.desc: Test whether the GetOrCreateFilterId interface generated filterid and threadmeasure Info is correct + * @tc.type: FUNC + */ +HWTEST_F(MeasureFilterTest, ThreadMeasureFilter, TestSize.Level1) +{ + TS_LOGI("test8-1"); + auto nameIndex0 = stream_.traceDataCache_->GetDataIndex(TASK_NAME_0); + uint32_t filterId = + stream_.streamFilters_->threadMeasureFilter_->GetOrCreateFilterId(INTERNAL_THREAD_ID_0, nameIndex0); + EXPECT_TRUE(filterId == 0); + + auto nameIndex1 = stream_.traceDataCache_->GetDataIndex(TASK_NAME_1); + filterId = stream_.streamFilters_->threadMeasureFilter_->GetOrCreateFilterId(INTERNAL_THREAD_ID_1, nameIndex1); + EXPECT_TRUE(filterId == 1); + + Filter* filterTable = stream_.traceDataCache_->GetFilterData(); + EXPECT_TRUE(filterTable->Size() == 2); + + ThreadMeasureFilter* threadMeasureTable = stream_.traceDataCache_->GetThreadMeasureFilterData(); + EXPECT_TRUE(threadMeasureTable->Size() == 2); + EXPECT_TRUE(threadMeasureTable->FilterIdData()[0] == 0); + EXPECT_TRUE(threadMeasureTable->FilterIdData()[1] == 1); + EXPECT_TRUE(threadMeasureTable->InternalTidData()[0] == INTERNAL_THREAD_ID_0); + EXPECT_TRUE(threadMeasureTable->InternalTidData()[1] == INTERNAL_THREAD_ID_1); +} + +/** + * @tc.name: ThreadFilter + * @tc.desc: Test whether the GetOrCreateFilterId interface generated filterid and thread Info is correct + * @tc.type: FUNC + */ +HWTEST_F(MeasureFilterTest, ThreadFilter, TestSize.Level1) +{ + TS_LOGI("test8-2"); + auto nameIndex0 = stream_.traceDataCache_->GetDataIndex(TASK_NAME_0); + uint32_t filterId = stream_.streamFilters_->threadFilter_->GetOrCreateFilterId(INTERNAL_THREAD_ID_0, nameIndex0); + EXPECT_TRUE(filterId == 0); + + auto nameIndex1 = stream_.traceDataCache_->GetDataIndex(TASK_NAME_1); + filterId = stream_.streamFilters_->threadFilter_->GetOrCreateFilterId(INTERNAL_THREAD_ID_1, nameIndex1); + EXPECT_TRUE(filterId == 1); + + Filter* filterTable = stream_.traceDataCache_->GetFilterData(); + EXPECT_TRUE(filterTable->Size() == 2); + + ThreadMeasureFilter* threadTable = stream_.traceDataCache_->GetThreadFilterData(); + EXPECT_TRUE(threadTable->Size() == 2); + EXPECT_TRUE(threadTable->FilterIdData()[0] == 0); + EXPECT_TRUE(threadTable->FilterIdData()[1] == 1); + EXPECT_TRUE(threadTable->InternalTidData()[0] == INTERNAL_THREAD_ID_0); + EXPECT_TRUE(threadTable->InternalTidData()[1] == INTERNAL_THREAD_ID_1); +} + +/** + * @tc.name: CpuFilter + * @tc.desc: Test GetOrCreateFilterId interface of class CpuFilter + * @tc.type: FUNC + */ +HWTEST_F(MeasureFilterTest, CpuFilter, TestSize.Level1) +{ + TS_LOGI("test8-3"); + auto nameIndex_0 = stream_.traceDataCache_->GetDataIndex(CPU_TYPE_0); + uint32_t filterId = stream_.streamFilters_->cpuMeasureFilter_->GetOrCreateFilterId(CPU_ID_0, nameIndex_0); + EXPECT_TRUE(filterId == 0); + + auto nameIndex_1 = stream_.traceDataCache_->GetDataIndex(CPU_TYPE_1); + filterId = stream_.streamFilters_->cpuMeasureFilter_->GetOrCreateFilterId(CPU_ID_1, nameIndex_1); + EXPECT_TRUE(filterId == 1); + + Filter* filterTable = stream_.traceDataCache_->GetFilterData(); + EXPECT_TRUE(filterTable->Size() == 2); + + CpuMeasureFilter* cpuMeasureTable = stream_.traceDataCache_->GetCpuMeasuresData(); + EXPECT_TRUE(cpuMeasureTable->Size() == 2); + EXPECT_TRUE(cpuMeasureTable->IdsData()[0] == 0); + EXPECT_TRUE(cpuMeasureTable->IdsData()[1] == 1); + EXPECT_TRUE(cpuMeasureTable->CpuData()[0] == CPU_ID_0); + EXPECT_TRUE(cpuMeasureTable->CpuData()[1] == CPU_ID_1); +} + +/** + * @tc.name: ProcessFilter + * @tc.desc: Test GetOrCreateFilterId interface of class ProcessFilter + * @tc.type: FUNC + */ +HWTEST_F(MeasureFilterTest, ProcessFilter, TestSize.Level1) +{ + TS_LOGI("test8-4"); + auto nameIndex_0 = stream_.traceDataCache_->GetDataIndex(TASK_NAME_0); + uint32_t filterId = + stream_.streamFilters_->processFilterFilter_->GetOrCreateFilterId(INTERNAL_PROCESS_ID_0, nameIndex_0); + EXPECT_TRUE(filterId == 0); + + auto nameIndex_1 = stream_.traceDataCache_->GetDataIndex(TASK_NAME_1); + filterId = stream_.streamFilters_->processFilterFilter_->GetOrCreateFilterId(INTERNAL_PROCESS_ID_1, nameIndex_1); + EXPECT_TRUE(filterId == 1); + + Filter* filterTable = stream_.traceDataCache_->GetFilterData(); + EXPECT_TRUE(filterTable->Size() == 2); + + ProcessMeasureFilter* processFilterTable = stream_.traceDataCache_->GetProcessFilterData(); + EXPECT_TRUE(processFilterTable->Size() == 2); +} + +/** + * @tc.name: ClockRateFilter + * @tc.desc: Test GetOrCreateFilterId interface of class ClockRateFilter + * @tc.type: FUNC + */ +HWTEST_F(MeasureFilterTest, ClockRateFilter, TestSize.Level1) +{ + TS_LOGI("test8-5"); + auto nameIndex_0 = stream_.traceDataCache_->GetDataIndex(TASK_NAME_0); + uint32_t filterId = stream_.streamFilters_->clockRateFilter_->GetOrCreateFilterId(CPU_ID_0, nameIndex_0); + EXPECT_TRUE(filterId == 0); + + auto nameIndex_1 = stream_.traceDataCache_->GetDataIndex(TASK_NAME_1); + filterId = stream_.streamFilters_->clockRateFilter_->GetOrCreateFilterId(CPU_ID_1, nameIndex_1); + EXPECT_TRUE(filterId == 1); + + Filter* filterTable = stream_.traceDataCache_->GetFilterData(); + EXPECT_TRUE(filterTable->Size() == 2); + + ClockEventData* clockEventTable = stream_.traceDataCache_->GetClockEventFilterData(); + EXPECT_TRUE(clockEventTable->Size() == 2); + EXPECT_TRUE(clockEventTable->CpusData()[0] == CPU_ID_0); + EXPECT_TRUE(clockEventTable->CpusData()[1] == CPU_ID_1); + EXPECT_TRUE(clockEventTable->NamesData()[0] == nameIndex_0); + EXPECT_TRUE(clockEventTable->NamesData()[1] == nameIndex_1); +} + +/** + * @tc.name: ClockEnableFilter + * @tc.desc: Test GetOrCreateFilterId interface of class ClockEnableFilter + * @tc.type: FUNC + */ +HWTEST_F(MeasureFilterTest, ClockEnableFilter, TestSize.Level1) +{ + TS_LOGI("test8-6"); + auto nameIndex_0 = stream_.traceDataCache_->GetDataIndex(TASK_NAME_0); + uint32_t filterId = stream_.streamFilters_->clockEnableFilter_->GetOrCreateFilterId(CPU_ID_0, nameIndex_0); + EXPECT_TRUE(filterId == 0); + + auto nameIndex_1 = stream_.traceDataCache_->GetDataIndex(TASK_NAME_1); + filterId = stream_.streamFilters_->clockEnableFilter_->GetOrCreateFilterId(CPU_ID_1, nameIndex_1); + EXPECT_TRUE(filterId == 1); + + Filter* filterTable = stream_.traceDataCache_->GetFilterData(); + EXPECT_TRUE(filterTable->Size() == 2); + + ClockEventData* clockEventTable = stream_.traceDataCache_->GetClockEventFilterData(); + EXPECT_TRUE(clockEventTable->Size() == 2); + EXPECT_TRUE(clockEventTable->CpusData()[0] == CPU_ID_0); + EXPECT_TRUE(clockEventTable->CpusData()[1] == CPU_ID_1); + EXPECT_TRUE(clockEventTable->NamesData()[0] == nameIndex_0); + EXPECT_TRUE(clockEventTable->NamesData()[1] == nameIndex_1); +} + +/** + * @tc.name: ClockDisableFilter + * @tc.desc: Test GetOrCreateFilterId interface of class ClockDisableFilter + * @tc.type: FUNC + */ +HWTEST_F(MeasureFilterTest, ClockDisableFilter, TestSize.Level1) +{ + TS_LOGI("test8-7"); + auto nameIndex_0 = stream_.traceDataCache_->GetDataIndex(TASK_NAME_0); + uint32_t filterId = stream_.streamFilters_->clockDisableFilter_->GetOrCreateFilterId(CPU_ID_0, nameIndex_0); + EXPECT_TRUE(filterId == 0); + + auto nameIndex_1 = stream_.traceDataCache_->GetDataIndex(TASK_NAME_1); + filterId = stream_.streamFilters_->clockDisableFilter_->GetOrCreateFilterId(CPU_ID_1, nameIndex_1); + EXPECT_TRUE(filterId == 1); + + Filter* filterTable = stream_.traceDataCache_->GetFilterData(); + EXPECT_TRUE(filterTable->Size() == 2); + + ClockEventData* clockEventTable = stream_.traceDataCache_->GetClockEventFilterData(); + EXPECT_TRUE(clockEventTable->Size() == 2); + EXPECT_TRUE(clockEventTable->CpusData()[0] == CPU_ID_0); + EXPECT_TRUE(clockEventTable->CpusData()[1] == CPU_ID_1); + EXPECT_TRUE(clockEventTable->NamesData()[0] == nameIndex_0); + EXPECT_TRUE(clockEventTable->NamesData()[1] == nameIndex_1); +} + +/** + * @tc.name: MeasureFilterTest + * @tc.desc: Test GetOrCreateFilterId interface of class MeasureFilterTest + * @tc.type: FUNC + */ +HWTEST_F(MeasureFilterTest, MeasureFilterTest, TestSize.Level1) +{ + TS_LOGI("test8-8"); + uint64_t itid = 1; + const std::string_view MEASURE_ITEM_NAME = "mem_rss"; + auto nameIndex0 = stream_.traceDataCache_->GetDataIndex(MEASURE_ITEM_NAME); + auto threadMeasureFilter = stream_.streamFilters_->processMeasureFilter_.get(); + threadMeasureFilter->AppendNewMeasureData(itid, nameIndex0, 168758682476000, 1200); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().Size() == 1); +} + +/** + * @tc.name: MeasureFilterAddMultiMemToSingleThread + * @tc.desc: Test GetOrCreateFilterId interface of class MeasureFilterTest, Adding multiple memory information tests to + * the same thread + * @tc.type: FUNC + */ +HWTEST_F(MeasureFilterTest, MeasureFilterAddMultiMemToSingleThread, TestSize.Level1) +{ + TS_LOGI("test8-9"); + uint64_t itid = 1; + auto threadMeasureFilter = stream_.streamFilters_->processMeasureFilter_.get(); + const std::string_view MEASURE_ITEM_NAME = "mem_rss"; + auto nameIndex0 = stream_.traceDataCache_->GetDataIndex(MEASURE_ITEM_NAME); + threadMeasureFilter->AppendNewMeasureData(itid, nameIndex0, 168758682476000, 1200); + const std::string_view MEASURE_ITEM_NAME2 = "mem_vm"; + auto nameIndex1 = stream_.traceDataCache_->GetDataIndex(MEASURE_ITEM_NAME2); + threadMeasureFilter->AppendNewMeasureData(itid, nameIndex1, 168758682477000, 9200); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().Size() == 2); +} + +/** + * @tc.name: MeasureFilterAddMultiMemToMultiThread + * @tc.desc: Test GetOrCreateFilterId interface of class MeasureFilterTest, Adding multiple memory information to multi + * thread + * @tc.type: FUNC + */ +HWTEST_F(MeasureFilterTest, MeasureFilterAddMultiMemToMultiThread, TestSize.Level1) +{ + TS_LOGI("test8-10"); + uint64_t itid = 1; + uint64_t itid2 = 2; + auto threadMeasureFilter = stream_.streamFilters_->processMeasureFilter_.get(); + const std::string_view MEASURE_ITEM_NAME = "mem_rss"; + auto nameIndex0 = stream_.traceDataCache_->GetDataIndex(MEASURE_ITEM_NAME); + threadMeasureFilter->AppendNewMeasureData(itid, nameIndex0, 168758682476000, 1200); + const std::string_view MEASURE_ITEM_NAME2 = "mem_vm"; + auto nameIndex1 = stream_.traceDataCache_->GetDataIndex(MEASURE_ITEM_NAME2); + threadMeasureFilter->AppendNewMeasureData(itid2, nameIndex1, 168758682477000, 9200); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().Size() == 2); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().ValuesData()[0] == 1200); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().ValuesData()[1] == 9200); +} + +/** + * @tc.name: MeasureFilterAddPerfclLfMux + * @tc.desc: Add perfcl_ lf_mux status test + * @tc.type: FUNC + */ +HWTEST_F(MeasureFilterTest, MeasureFilterAddPerfclLfMux, TestSize.Level1) +{ + TS_LOGI("test8-11"); + uint64_t cpuId = 1; + int64_t state = 0; + auto threadMeasureFilter = stream_.streamFilters_->clockDisableFilter_.get(); + const std::string_view MEASURE_ITEM_NAME = "perfcl_lf_mux"; + auto nameIndex0 = stream_.traceDataCache_->GetDataIndex(MEASURE_ITEM_NAME); + threadMeasureFilter->AppendNewMeasureData(cpuId, nameIndex0, 168758682476000, state); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().ValuesData()[0] == state); +} + +/** + * @tc.name: MeasureFilterAddPerfclLfMux + * @tc.desc: Add perfcl_pll status test + * @tc.type: FUNC + */ +HWTEST_F(MeasureFilterTest, MeasureFilterAddPerfclPll, TestSize.Level1) +{ + TS_LOGI("test8-12"); + uint64_t cpuId = 1; + int64_t state = 1747200000; + auto threadMeasureFilter = stream_.streamFilters_->clockRateFilter_.get(); + const std::string_view MEASURE_ITEM_NAME = "perfcl_pll"; + auto nameIndex0 = stream_.traceDataCache_->GetDataIndex(MEASURE_ITEM_NAME); + threadMeasureFilter->AppendNewMeasureData(cpuId, nameIndex0, 168758682476000, state); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().Size() == 1); + EXPECT_TRUE(stream_.traceDataCache_->GetConstMeasureData().ValuesData()[0] == state); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/test/unittest/parser_test.cpp b/host/trace_streamer/test/unittest/parser_test.cpp new file mode 100644 index 0000000..f6511fb --- /dev/null +++ b/host/trace_streamer/test/unittest/parser_test.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#include "file.h" +#include "trace_streamer_selector.h" +constexpr size_t G_FILE_PERMISSION = 664; + +using namespace testing::ext; +using namespace SysTuning; +using namespace SysTuning::TraceStreamer; +namespace SysTuning { +namespace TraceStreamer { +class ParserTest : public testing::Test { +protected: + static void SetUpTestCase() {} + static void TearDownTestCase() {} +}; + +/** + * @tc.name: BytraceParserTest + * @tc.desc: Test bytrace parsing TXT file to export database + * @tc.type: FUNC + */ +HWTEST_F(ParserTest, BytraceParserTest, TestSize.Level1) +{ + TS_LOGI("test9-1"); + const std::string tracePath = "/data/resource/ut_bytrace_input_full.txt"; + const std::string utGoldDb = "/data/resource/ut_bytrace_input_full_gold.db"; + const std::string dbPath = "/data/resource/out_db1.db"; + constexpr size_t readSize = 1024 * 1024; + constexpr uint32_t lineLength = 256; + + if (access(tracePath.c_str(), F_OK) == 0) { + std::unique_ptr ta = + std::make_unique(); + ta->EnableMetaTable(false); + ta->SetCleanMode(false); + int fd(base::OpenFile(tracePath, O_RDONLY, G_FILE_PERMISSION)); + while (true) { + std::unique_ptr buf = std::make_unique(std::move(readSize)); + auto rsize = base::Read(fd, buf.get(), readSize); + if (rsize == 0) { + break; + } + if (rsize < 0) { + TS_LOGD("Reading trace file failed (errno: %d, %s)", errno, strerror(errno)); + break; + } + if (!ta->ParseTraceDataSegment(std::move(buf), rsize)) { + break; + }; + } + ta->WaitForParserEnd(); + close(fd); + ta->ExportDatabase(dbPath); + ta->Clear(); + EXPECT_TRUE(access(dbPath.c_str(), F_OK) == 0); + } else { + EXPECT_TRUE(false); + } + + if (access(utGoldDb.c_str(), F_OK) == 0) { + FILE* file1 = nullptr; + FILE* file2 = nullptr; + char line1[lineLength]; + char line2[lineLength]; + const std::string command1 = "md5sum /data/resource/ut_bytrace_input_full_gold.db"; + const std::string md5DbPath = "md5sum "+ dbPath; + file1 = popen(command1.c_str(), "r"); + file2 = popen(md5DbPath.c_str(), "r"); + if (file1 && file2) { + if (fgets(line1, lineLength, file1) != nullptr && fgets(line2, lineLength, file2) != nullptr) { + std::string str1(line1); + std::string str2(line2); + str1 = str1.substr(0, str1.find_first_of(' ')); + str2 = str2.substr(0, str2.find_first_of(' ')); + EXPECT_TRUE(str1.compare(str2) == 0); + } + } + } else { + EXPECT_TRUE(false); + } + + if (access(dbPath.c_str(), F_OK) == 0) { + remove(dbPath.c_str()); + } +} + +/** + * @tc.name: HtraceParserTest + * @tc.desc: Test htrace parsing binary file export database + * @tc.type: FUNC + */ +HWTEST_F(ParserTest, HtraceParserTest, TestSize.Level1) +{ + TS_LOGI("test9-2"); + const std::string tracePath = "/data/resource/htrace.bin"; + const std::string utGoldDb = "/data/resource/htrace_gold.db"; + const std::string dbPath = "/data/resource/out_db2.db"; + constexpr size_t readSize = 1024; + constexpr uint32_t lineLength = 256; + + if (access(tracePath.c_str(), F_OK) == 0) { + std::unique_ptr ta = + std::make_unique(); + ta->EnableMetaTable(false); + ta->SetCleanMode(false); + int fd(base::OpenFile(tracePath, O_RDONLY, G_FILE_PERMISSION)); + while (true) { + std::unique_ptr buf = std::make_unique(std::move(readSize)); + auto rsize = base::Read(fd, buf.get(), readSize); + + if (rsize == 0) { + break; + } + if (rsize < 0) { + TS_LOGD("Reading trace file over (errno: %d, %s)", errno, strerror(errno)); + break; + } + if (!ta->ParseTraceDataSegment(std::move(buf), rsize)) { + break; + }; + } + ta->WaitForParserEnd(); + close(fd); + ta->ExportDatabase(dbPath); + ta->Clear(); + EXPECT_TRUE(access(dbPath.c_str(), F_OK) == 0); + } else { + EXPECT_TRUE(false); + } + + if (access(utGoldDb.c_str(), F_OK) == 0) { + FILE* file1 = nullptr; + FILE* file2 = nullptr; + char line1[lineLength]; + char line2[lineLength]; + const std::string command1 = "md5sum /data/resource/htrace_gold.db"; + const std::string md5DbPath = "md5sum "+ dbPath; + file1 = popen(command1.c_str(), "r"); + file2 = popen(md5DbPath.c_str(), "r"); + if (file1 && file2) { + if (fgets(line1, lineLength, file1) != nullptr && fgets(line2, lineLength, file2) != nullptr) { + std::string str1(line1); + std::string str2(line2); + str1 = str1.substr(0, str1.find_first_of(' ')); + str2 = str2.substr(0, str2.find_first_of(' ')); + EXPECT_TRUE(str1.compare(str2) == 0); + } + } + } else { + EXPECT_TRUE(false); + } + + if (access(dbPath.c_str(), F_OK) == 0) { + remove(dbPath.c_str()); + } +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/test/unittest/process_filter_test.cpp b/host/trace_streamer/test/unittest/process_filter_test.cpp new file mode 100644 index 0000000..db31d5e --- /dev/null +++ b/host/trace_streamer/test/unittest/process_filter_test.cpp @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "process_filter.h" + +using namespace testing::ext; +using namespace SysTuning::TraceStreamer; +namespace SysTuning { +namespace TraceStreamer { +class ProcessFilterTest : public ::testing::Test { +public: + void SetUp() + { + streamFilters_.processFilter_ = std::make_unique(&traceDataCache_, &streamFilters_); + } + + void TearDown() {} + +public: + TraceStreamerFilters streamFilters_; + TraceDataCache traceDataCache_; +}; + +/** + * @tc.name: UpdateOrCreateThread + * @tc.desc: Test UpdateOrCreateThread interface + * @tc.type: FUNC + */ +HWTEST_F(ProcessFilterTest, UpdateOrCreateThread, TestSize.Level1) +{ + TS_LOGI("test10-1"); + uint64_t ts = 168758662877000; + uint32_t tid = 2716; + uint32_t iTid0 = streamFilters_.processFilter_->UpdateOrCreateThread(ts, tid); + EXPECT_TRUE(iTid0 == 1); + + uint32_t tid2 = 2519; + uint32_t iTid1 = streamFilters_.processFilter_->UpdateOrCreateThread(0, 2519); + EXPECT_TRUE(iTid1 == 2); + + Thread* thread = traceDataCache_.GetThreadData(iTid0); + EXPECT_TRUE(thread->tid_ == tid); + + thread = traceDataCache_.GetThreadData(iTid1); + EXPECT_TRUE(thread->tid_ == tid2); + EXPECT_TRUE(thread->internalPid_ == 0); +} + +/** + * @tc.name: UpdateOrCreateProcessWithName + * @tc.desc: Test UpdateOrCreateProcessWithName interface + * @tc.type: FUNC + */ +HWTEST_F(ProcessFilterTest, UpdateOrCreateProcessWithName, TestSize.Level1) +{ + TS_LOGI("test10-2"); + uint32_t pid = 8629; + std::string_view processName = "RenderThread"; + uint32_t iPid0 = streamFilters_.processFilter_->UpdateOrCreateProcessWithName(pid, processName); + EXPECT_TRUE(iPid0 == 1); + + uint32_t pid2 = 8709; + uint32_t iPid1 = streamFilters_.processFilter_->UpdateOrCreateProcessWithName(pid2, processName); + EXPECT_TRUE(iPid1 == 2); + + Process* process = traceDataCache_.GetProcessData(iPid0); + EXPECT_TRUE(process->pid_ == pid); + + process = traceDataCache_.GetProcessData(iPid1); + EXPECT_TRUE(process->pid_ == pid2); +} + +/** + * @tc.name: UpdateOrCreateProcessWithNameSingleIpid + * @tc.desc: Test whether the internal PID of the generated single process is as expected. + * @tc.type: FUNC + */ +HWTEST_F(ProcessFilterTest, UpdateOrCreateProcessWithNameSingleIpid, TestSize.Level1) +{ + TS_LOGI("test10-3"); + uint32_t pid = 8629; + std::string_view processName = "RenderThread"; + uint32_t iPid0 = streamFilters_.processFilter_->UpdateOrCreateProcessWithName(pid, processName); + EXPECT_TRUE(iPid0 == 1); +} + +/** + * @tc.name: UpdateOrCreateProcessWithNameMultiIpid + * @tc.desc: Test genarated multi ipid with UpdateOrCreateProcessWithName interface + * @tc.type: FUNC + */ +HWTEST_F(ProcessFilterTest, UpdateOrCreateProcessWithNameMultiIpid, TestSize.Level1) +{ + TS_LOGI("test10-4"); + uint32_t pid = 8629; + std::string_view processName = "RenderThread"; + uint32_t iPid0 = streamFilters_.processFilter_->UpdateOrCreateProcessWithName(pid, processName); + EXPECT_TRUE(iPid0 == 1); + + uint32_t pid2 = 8709; + uint32_t iPid1 = streamFilters_.processFilter_->UpdateOrCreateProcessWithName(pid2, processName); + EXPECT_TRUE(iPid1 == 2); +} + +/** + * @tc.name: UpdateOrCreateProcessWithNameSinglePid + * @tc.desc: Test genarated single pid with UpdateOrCreateProcessWithName interface + * @tc.type: FUNC + */ +HWTEST_F(ProcessFilterTest, UpdateOrCreateProcessWithNameSinglePid, TestSize.Level1) +{ + TS_LOGI("test10-5"); + uint32_t pid = 8629; + std::string_view processName = "RenderThread"; + uint32_t iPid0 = streamFilters_.processFilter_->UpdateOrCreateProcessWithName(pid, processName); + EXPECT_TRUE(iPid0 == 1); + + Process* process = traceDataCache_.GetProcessData(iPid0); + EXPECT_TRUE(process->pid_ == pid); +} + +/** + * @tc.name: UpdateOrCreateProcessWithNameMultiPid + * @tc.desc: est genarated multi pid with UpdateOrCreateProcessWithName interface + * @tc.type: FUNC + */ +HWTEST_F(ProcessFilterTest, UpdateOrCreateProcessWithNameMultiPid, TestSize.Level1) +{ + TS_LOGI("test10-6"); + uint32_t pid = 8629; + std::string_view processName = "RenderThread"; + uint32_t iPid0 = streamFilters_.processFilter_->UpdateOrCreateProcessWithName(pid, processName); + EXPECT_TRUE(iPid0 == 1); + + uint32_t pid2 = 8709; + uint32_t iPid1 = streamFilters_.processFilter_->UpdateOrCreateProcessWithName(pid2, processName); + EXPECT_TRUE(iPid1 == 2); + + uint32_t pid3 = 87091; + uint32_t iPid2 = streamFilters_.processFilter_->UpdateOrCreateProcessWithName(87091, processName); + EXPECT_TRUE(iPid2 == 3); + + Process* process = traceDataCache_.GetProcessData(iPid0); + EXPECT_TRUE(process->pid_ == pid); + + process = traceDataCache_.GetProcessData(iPid1); + EXPECT_TRUE(process->pid_ == pid2); + + process = traceDataCache_.GetProcessData(iPid2); + EXPECT_TRUE(process->pid_ == pid3); +} + +/** + * @tc.name: UpdateOrCreateThreadWithName + * @tc.desc: Test UpdateOrCreateThreadWithName interface + * @tc.type: FUNC + */ +HWTEST_F(ProcessFilterTest, UpdateOrCreateThreadWithName, TestSize.Level1) +{ + TS_LOGI("test10-7"); + uint64_t ts = 168758662957020; + uint32_t tid = 123; + std::string_view threadName = "RenderThread"; + uint32_t iTid0 = streamFilters_.processFilter_->UpdateOrCreateThreadWithName(ts, tid, threadName); + EXPECT_TRUE(iTid0 == 1); + uint64_t ts2 = 168758663957020; + uint32_t tid2 = 2519; + std::string_view threadName2 = "RenderThread2"; + uint32_t iTid1 = streamFilters_.processFilter_->UpdateOrCreateThreadWithName(ts2, tid2, threadName2); + EXPECT_TRUE(iTid1 == 2); + + Thread* thread = traceDataCache_.GetThreadData(iTid0); + EXPECT_TRUE(thread->tid_ == tid); + EXPECT_TRUE(thread->nameIndex_ == traceDataCache_.GetDataIndex(threadName)); + + thread = traceDataCache_.GetThreadData(iTid1); + EXPECT_TRUE(thread->tid_ == tid2); + EXPECT_TRUE(thread->internalPid_ == 0); + EXPECT_TRUE(thread->nameIndex_ == traceDataCache_.GetDataIndex(threadName2)); +} + +/** + * @tc.name: UpdateOrCreateThreadWithNameSingleItid + * @tc.desc: Test genarated single itid with UpdateOrCreateThreadWithName + * @tc.type: FUNC + */ +HWTEST_F(ProcessFilterTest, UpdateOrCreateThreadWithNameSingleItid, TestSize.Level1) +{ + TS_LOGI("test10-8"); + uint64_t ts = 168758662957020; + uint32_t tid = 123; + std::string_view threadName = "RenderThread"; + uint32_t iTid0 = streamFilters_.processFilter_->UpdateOrCreateThreadWithName(ts, tid, threadName); + EXPECT_TRUE(iTid0 == 1); +} + +/** + * @tc.name: UpdateOrCreateThreadWithNameGenarateTidAndItid + * @tc.desc: Test genarated single itid and tid with UpdateOrCreateThreadWithName + * @tc.type: FUNC + */ +HWTEST_F(ProcessFilterTest, UpdateOrCreateThreadWithNameGenarateTidAndItid, TestSize.Level1) +{ + TS_LOGI("test10-9"); + uint64_t ts = 168758662957020; + uint32_t tid = 123; + std::string_view threadName = "RenderThread2"; + uint32_t iTid0 = streamFilters_.processFilter_->UpdateOrCreateThreadWithName(ts, tid, threadName); + EXPECT_TRUE(iTid0 == 1); + Thread* thread = traceDataCache_.GetThreadData(iTid0); + EXPECT_TRUE(thread->tid_ == tid); + EXPECT_TRUE(thread->nameIndex_ == traceDataCache_.GetDataIndex(threadName)); +} + +/** + * @tc.name: UpdateOrCreateThreadWithNameMultiItid + * @tc.desc: Test genarate double itid with UpdateOrCreateThreadWithName interface + * @tc.type: FUNC + */ +HWTEST_F(ProcessFilterTest, UpdateOrCreateThreadWithNameDoubleItid, TestSize.Level1) +{ + TS_LOGI("test10-10"); + uint64_t ts = 168758662957020; + uint32_t tid = 123; + std::string_view threadName = "RenderThread"; + uint32_t iTid0 = streamFilters_.processFilter_->UpdateOrCreateThreadWithName(ts, tid, threadName); + EXPECT_TRUE(iTid0 == 1); + uint64_t ts2 = 168758663957020; + uint32_t tid2 = 2519; + std::string_view threadName2 = "RenderThread2"; + uint32_t iTid1 = streamFilters_.processFilter_->UpdateOrCreateThreadWithName(ts2, tid2, threadName2); + EXPECT_TRUE(iTid1 == 2); + auto thread = traceDataCache_.GetThreadData(iTid1); + EXPECT_TRUE(thread->tid_ == tid2); + EXPECT_TRUE(thread->internalPid_ == 0); + EXPECT_TRUE(thread->nameIndex_ == traceDataCache_.GetDataIndex(threadName2)); +} + +/** + * @tc.name: UpdateOrCreateThreadWithNameTripleItid + * @tc.desc: Test genarate triple itid with UpdateOrCreateThreadWithName interface + * @tc.type: FUNC + */ +HWTEST_F(ProcessFilterTest, UpdateOrCreateThreadWithNameTripleItid, TestSize.Level1) +{ + TS_LOGI("test10-11"); + uint64_t ts = 168758662957020; + uint32_t tid = 123; + std::string_view threadName = "RenderThread"; + uint32_t iTid0 = streamFilters_.processFilter_->UpdateOrCreateThreadWithName(ts, tid, threadName); + EXPECT_TRUE(iTid0 == 1); + uint64_t ts2 = 168758663957020; + uint32_t tid2 = 2519; + std::string_view threadName2 = "RenderThread2"; + uint32_t iTid1 = streamFilters_.processFilter_->UpdateOrCreateThreadWithName(ts2, tid2, threadName2); + EXPECT_TRUE(iTid1 == 2); + uint64_t ts3 = 168758663957020; + uint32_t tid3 = 25191; + std::string_view threadName3 = "RenderThread3"; + uint32_t iTid2 = streamFilters_.processFilter_->UpdateOrCreateThreadWithName(ts3, tid3, threadName3); + EXPECT_TRUE(iTid2 == 3); + auto thread = traceDataCache_.GetThreadData(iTid2); + EXPECT_TRUE(thread->tid_ == tid3); + EXPECT_TRUE(thread->internalPid_ == 0); + EXPECT_TRUE(thread->nameIndex_ == traceDataCache_.GetDataIndex(threadName3)); +} + +/** + * @tc.name: UpdateOrCreateThreadWithPidAndName + * @tc.desc: Test UpdateOrCreateThreadWithPidAndName interface + * @tc.type: FUNC + */ +HWTEST_F(ProcessFilterTest, UpdateOrCreateThreadWithPidAndName, TestSize.Level1) +{ + TS_LOGI("test10-12"); + uint32_t tid = 869; + uint32_t pid = 123; + std::string_view threadName = "RenderThread"; + streamFilters_.processFilter_->UpdateOrCreateThreadWithPidAndName(tid, pid, threadName); + auto itid = streamFilters_.processFilter_->GetInternalTid(tid); + EXPECT_TRUE(itid != INVALID_ID); + + Thread* thread = traceDataCache_.GetThreadData(itid); + EXPECT_TRUE(thread->nameIndex_ == traceDataCache_.GetDataIndex(threadName)); +} + +/** + * @tc.name: UpdateOrCreateThreadWithPidAndNameAbnomal + * @tc.desc: Test UpdateOrCreateThreadWithPidAndName interface + * @tc.type: FUNC + */ +HWTEST_F(ProcessFilterTest, UpdateOrCreateThreadWithPidAndNameAbnomal, TestSize.Level1) +{ + TS_LOGI("test10-13"); + uint32_t tid = 869; + uint32_t pid = 123; + std::string_view threadName = "RenderThread"; + streamFilters_.processFilter_->UpdateOrCreateThreadWithPidAndName(tid, pid, threadName); + uint32_t tid2 = 969; + auto itid = streamFilters_.processFilter_->GetInternalTid(tid2); + EXPECT_TRUE(itid == INVALID_ID); +} + +/** + * @tc.name: UpdateOrCreateThreadWithPidAndNameSingleItid + * @tc.desc: Test UpdateOrCreateThreadWithPidAndName interface + * @tc.type: FUNC + */ +HWTEST_F(ProcessFilterTest, UpdateOrCreateThreadWithPidAndNameSingleItid, TestSize.Level1) +{ + TS_LOGI("test10-14"); + uint32_t tid = 869; + uint32_t pid = 123; + std::string_view threadName = "RenderThread"; + streamFilters_.processFilter_->UpdateOrCreateThreadWithPidAndName(tid, pid, threadName); + auto itid = streamFilters_.processFilter_->GetInternalPid(pid); + EXPECT_TRUE(itid != INVALID_ID); +} + +/** + * @tc.name: UpdateOrCreateThreadWithPidAndNameAbnomalPid + * @tc.desc: Test UpdateOrCreateThreadWithPidAndName interface + * @tc.type: FUNC + */ +HWTEST_F(ProcessFilterTest, UpdateOrCreateThreadWithPidAndNameAbnomalPid, TestSize.Level1) +{ + TS_LOGI("test10-15"); + uint32_t tid = 869; + uint32_t pid = 123; + std::string_view threadName = "RenderThread"; + streamFilters_.processFilter_->UpdateOrCreateThreadWithPidAndName(tid, pid, threadName); + uint32_t pid2 = 124; + auto itid = streamFilters_.processFilter_->GetInternalPid(pid2); + EXPECT_TRUE(itid == INVALID_ID); +} + +/** + * @tc.name: UpdateThreadWithName + * @tc.desc: Test update thread name + * @tc.type: FUNC + */ +HWTEST_F(ProcessFilterTest, UpdateThreadWithName, TestSize.Level1) +{ + TS_LOGI("test10-16"); + uint32_t tid = 869; + uint64_t timestamp = 168758662957020; + std::string_view threadName = "RenderThread"; + streamFilters_.processFilter_->UpdateOrCreateThread(timestamp, tid); + streamFilters_.processFilter_->UpdateOrCreateThreadWithName(timestamp, tid, threadName); + auto itid = streamFilters_.processFilter_->GetInternalTid(tid); + EXPECT_TRUE(itid != INVALID_ID); + Thread* thread = traceDataCache_.GetThreadData(itid); + EXPECT_TRUE(thread->nameIndex_ == traceDataCache_.GetDataIndex(threadName)); +} + +/** + * @tc.name: UpdateProcessWithName + * @tc.desc: Test update process name + * @tc.type: FUNC + */ +HWTEST_F(ProcessFilterTest, UpdateProcessWithName, TestSize.Level1) +{ + TS_LOGI("test10-17"); + uint32_t pid = 869; + uint64_t timestamp = 168758662957020; + std::string_view processName = "RenderProcess"; + auto ipid = streamFilters_.processFilter_->GetOrCreateInternalPid(timestamp, pid); + EXPECT_TRUE(ipid != INVALID_ID); + streamFilters_.processFilter_->UpdateOrCreateProcessWithName(pid, processName); + Process* process = traceDataCache_.GetProcessData(ipid); + EXPECT_TRUE(process->cmdLine_ == processName); +} +} // namespace TraceStreamer +} // namespace SysTuning diff --git a/host/trace_streamer/test/unittest/slice_filter_test.cpp b/host/trace_streamer/test/unittest/slice_filter_test.cpp new file mode 100644 index 0000000..6846cba --- /dev/null +++ b/host/trace_streamer/test/unittest/slice_filter_test.cpp @@ -0,0 +1,414 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "filter_filter.h" +#include "measure_filter.h" +#include "process_filter.h" +#include "slice_filter.h" +#include "trace_streamer_selector.h" + +using namespace testing::ext; +using namespace SysTuning::TraceStreamer; +namespace SysTuning { +namespace TraceStreamer { +class SliceFilterTest : public ::testing::Test { +public: + void SetUp() + { + stream_.InitFilter(); + } + + void TearDown() {} + +public: + TraceStreamerSelector stream_; +}; + +/** + * @tc.name: SliceTestOnceCall + * @tc.desc: Parse once method call stack + * @tc.type: FUNC + */ +HWTEST_F(SliceFilterTest, SliceTestOnceCall, TestSize.Level1) +{ + TS_LOGI("test1"); + uint64_t ts = 168758662957000; + uint64_t ts2 = 168758663011000; + + uint32_t pid1 = 2532; + uint32_t threadGroupId1 = 2519; + DataIndex cat = stream_.traceDataCache_->GetDataIndex("Catalog"); + DataIndex splitStrIndex = stream_.traceDataCache_->GetDataIndex("call_function_one"); + stream_.streamFilters_->sliceFilter_->BeginSlice(ts, pid1, threadGroupId1, cat, splitStrIndex); + stream_.streamFilters_->sliceFilter_->EndSlice(ts2, pid1, threadGroupId1); + auto slices = stream_.traceDataCache_->GetInternalSlicesData(); + EXPECT_TRUE(slices->Size() == 1); + EXPECT_TRUE(slices->DursData()[0] == ts2 - ts); +} + +/** + * @tc.name: SliceTestNestedTwoMethod + * @tc.desc: Parse Nested call stack of two methods + * @tc.type: FUNC + */ +HWTEST_F(SliceFilterTest, SliceTestNestedTwoMethod, TestSize.Level1) +{ + TS_LOGI("test2"); + uint64_t ts1 = 168758670506000; + uint32_t pid1 = 1298; + uint32_t threadGroupId1 = 1298; + DataIndex cat = stream_.traceDataCache_->GetDataIndex("Catalog"); + DataIndex splitStrIndex = stream_.traceDataCache_->GetDataIndex("call_function_one"); + stream_.streamFilters_->sliceFilter_->BeginSlice(ts1, pid1, threadGroupId1, cat, splitStrIndex); + splitStrIndex = stream_.traceDataCache_->GetDataIndex("call_function_two"); + uint64_t ts2 = 168758670523000; + stream_.streamFilters_->sliceFilter_->BeginSlice(ts2, pid1, threadGroupId1, cat, splitStrIndex); + uint64_t ts3 = 168758670720000; + stream_.streamFilters_->sliceFilter_->EndSlice(ts3, pid1, threadGroupId1); + uint64_t ts4 = 168758670732000; + stream_.streamFilters_->sliceFilter_->EndSlice(ts4, pid1, threadGroupId1); + auto slices = stream_.traceDataCache_->GetInternalSlicesData(); + EXPECT_TRUE(slices->Size() == 2); + EXPECT_TRUE(slices->DursData()[0] == ts4 - ts1); + EXPECT_TRUE(slices->DursData()[1] == ts3 - ts2); + EXPECT_TRUE(slices->Depths()[1] == 1); +} + +/** + * @tc.name: SliceTestNestedTwoMethodStackAndOneMethodStack + * @tc.desc: Parse Nested call stack of two methods and one method call stack + * @tc.type: FUNC + */ +HWTEST_F(SliceFilterTest, SliceTestNestedTwoMethodStackAndOneMethodStack, TestSize.Level1) +{ + TS_LOGI("test3"); + uint64_t ts1 = 168758663018000; + uint32_t pid1 = 2532; + uint32_t threadGroupId1 = 2519; + DataIndex cat = stream_.traceDataCache_->GetDataIndex("Catalog"); + DataIndex splitStrIndex = stream_.traceDataCache_->GetDataIndex("thread_one_call_function_one"); + stream_.streamFilters_->sliceFilter_->BeginSlice(ts1, pid1, threadGroupId1, cat, splitStrIndex); // slice 0 + splitStrIndex = stream_.traceDataCache_->GetDataIndex("thread_two_call_function_one"); + uint64_t ts2 = 168758663028000; + uint32_t pid2 = 2533; + uint32_t threadGroupId2 = 2529; + stream_.streamFilters_->sliceFilter_->BeginSlice(ts2, pid2, threadGroupId2, cat, splitStrIndex); + splitStrIndex = stream_.traceDataCache_->GetDataIndex("thread_one_call_function_two"); + uint64_t ts3 = 168758679303000; + stream_.streamFilters_->sliceFilter_->BeginSlice(ts3, pid1, threadGroupId1, cat, splitStrIndex); // slice 2 + // end thread_one_call_function_two + uint64_t ts4 = 168758682466000; + stream_.streamFilters_->sliceFilter_->EndSlice(ts4, pid1, threadGroupId1); + // end thread_one_call_function_one + uint64_t ts5 = 168758682476000; + stream_.streamFilters_->sliceFilter_->EndSlice(ts5, pid1, threadGroupId1); + // end thread_two_call_function_one slice 1 + uint64_t ts6 = 168758689323000; + stream_.streamFilters_->sliceFilter_->EndSlice(ts6, pid2, threadGroupId2); + auto slices = stream_.traceDataCache_->GetInternalSlicesData(); + EXPECT_TRUE(slices->Size() == 3); + EXPECT_TRUE(slices->DursData()[0] == ts5 - ts1); // slice 0 + EXPECT_TRUE(slices->Depths()[0] == 0); + EXPECT_TRUE(slices->DursData()[1] == ts6 - ts2); // slice 1 + EXPECT_TRUE(slices->Depths()[1] == 0); + EXPECT_TRUE(slices->DursData()[2] == ts4 - ts3); // slice 2 + EXPECT_TRUE(slices->Depths()[2] == 1); +} + +/** + * @tc.name: SliceTestWithoutBeginSlice + * @tc.desc: Test EndSlice without BeginSlice + * @tc.type: FUNC + */ +HWTEST_F(SliceFilterTest, SliceTestWithoutBeginSlice, TestSize.Level1) +{ + TS_LOGI("test3"); + uint64_t ts1 = 168758663018000; + uint32_t pid1 = 2532; + uint32_t threadGroupId1 = 2519; + stream_.streamFilters_->sliceFilter_->EndSlice(ts1, pid1, threadGroupId1); + auto slices = stream_.traceDataCache_->GetInternalSlicesData(); + EXPECT_TRUE(slices->Size() == 0); +} + +/** + * @tc.name: SliceTestWithMultiNestedCall + * @tc.desc: Parse multi nested call stack + * @tc.type: FUNC + */ +HWTEST_F(SliceFilterTest, SliceTestWithMultiNestedCall, TestSize.Level1) +{ + TS_LOGI("test3"); + uint64_t ts1 = 168758663018000; + uint32_t pid1 = 2532; + uint32_t threadGroupId1 = 2519; + DataIndex cat = stream_.traceDataCache_->GetDataIndex("Catalog"); + DataIndex splitStrIndex = stream_.traceDataCache_->GetDataIndex("thread_one_call_function_one"); + stream_.streamFilters_->sliceFilter_->BeginSlice(ts1, pid1, threadGroupId1, cat, splitStrIndex); // slice 0 + splitStrIndex = stream_.traceDataCache_->GetDataIndex("thread_two_call_function_one"); + uint64_t ts2 = 168758663028000; + uint32_t pid2 = 2533; + uint32_t threadGroupId2 = 2529; + stream_.streamFilters_->sliceFilter_->BeginSlice(ts2, pid2, threadGroupId2, cat, splitStrIndex); + splitStrIndex = stream_.traceDataCache_->GetDataIndex("thread_one_call_function_two"); + uint64_t ts3 = 168758679303000; + stream_.streamFilters_->sliceFilter_->BeginSlice(ts3, pid1, threadGroupId1, cat, splitStrIndex); // slice 2 + splitStrIndex = stream_.traceDataCache_->GetDataIndex("thread_two_call_function_two"); + uint64_t ts4 = 168758679312000; + stream_.streamFilters_->sliceFilter_->BeginSlice(ts4, pid2, threadGroupId2, cat, splitStrIndex); + splitStrIndex = stream_.traceDataCache_->GetDataIndex("thread_one_call_function_three"); + uint64_t ts5 = 168758679313000; + stream_.streamFilters_->sliceFilter_->BeginSlice(ts5, pid1, threadGroupId1, cat, splitStrIndex); // slice 4 + splitStrIndex = stream_.traceDataCache_->GetDataIndex("thread_two_call_function_three"); + uint64_t ts6 = 168758679323000; + stream_.streamFilters_->sliceFilter_->BeginSlice(ts6, pid2, threadGroupId2, cat, splitStrIndex); + // end thread_one_call_function_three + uint64_t ts7 = 168758682456000; + stream_.streamFilters_->sliceFilter_->EndSlice(ts7, pid1, threadGroupId1); + // end thread_one_call_function_two + uint64_t ts8 = 168758682466000; + stream_.streamFilters_->sliceFilter_->EndSlice(ts8, pid1, threadGroupId1); + // end thread_one_call_function_one + uint64_t ts9 = 168758682476000; + stream_.streamFilters_->sliceFilter_->EndSlice(ts9, pid1, threadGroupId1); + // end thread_two_call_function_three slice 5 + uint64_t ts10 = 168758679343000; + stream_.streamFilters_->sliceFilter_->EndSlice(ts10, pid2, threadGroupId2); + // end thread_two_call_function_two slice 3 + uint64_t ts11 = 168758679344000; + stream_.streamFilters_->sliceFilter_->EndSlice(ts11, pid2, threadGroupId2); + // end thread_two_call_function_one slice 1 + uint64_t ts12 = 168758689323000; + stream_.streamFilters_->sliceFilter_->EndSlice(ts12, pid2, threadGroupId2); + auto slices = stream_.traceDataCache_->GetInternalSlicesData(); + EXPECT_TRUE(slices->Size() == 6); + EXPECT_TRUE(slices->DursData()[0] == ts9 - ts1); // slice 0 + EXPECT_TRUE(slices->Depths()[0] == 0); + EXPECT_TRUE(slices->DursData()[1] == ts12 - ts2); // slice 1 + EXPECT_TRUE(slices->Depths()[1] == 0); + EXPECT_TRUE(slices->DursData()[2] == ts8 - ts3); // slice 2 + EXPECT_TRUE(slices->Depths()[2] == 1); + EXPECT_TRUE(slices->DursData()[3] == ts11 - ts4); // slice 3 + EXPECT_TRUE(slices->Depths()[3] == 1); + EXPECT_TRUE(slices->DursData()[4] == ts7 - ts5); // slice 4 + EXPECT_TRUE(slices->DursData()[5] == ts10 - ts6); // slice 5 +} + +/** + * @tc.name: AsyncTest + * @tc.desc: Test once asynchronous method call stack + * @tc.type: FUNC + */ +HWTEST_F(SliceFilterTest, AsyncTest, TestSize.Level1) +{ + TS_LOGI("test3"); + uint64_t ts1 = 168758663018000; + uint32_t pid1 = 2532; + uint32_t threadGroupId1 = 2519; + DataIndex cat = stream_.traceDataCache_->GetDataIndex("Catalog"); + DataIndex splitStrIndex = stream_.traceDataCache_->GetDataIndex("async_call_function_one"); + stream_.streamFilters_->sliceFilter_->StartAsyncSlice(ts1, pid1, threadGroupId1, cat, splitStrIndex); // slice 0 + splitStrIndex = stream_.traceDataCache_->GetDataIndex("async_call_function_one"); + // end thread_one_call_function_three + uint64_t ts2 = 168758682456000; + stream_.streamFilters_->sliceFilter_->FinishAsyncSlice(ts2, pid1, threadGroupId1, cat, splitStrIndex); + auto slices = stream_.traceDataCache_->GetInternalSlicesData(); + EXPECT_TRUE(slices->Size() == 1); + EXPECT_TRUE(slices->DursData()[0] == ts2 - ts1); // slice 0 +} + +/** + * @tc.name: FinishAsyncSliceWithoutStart + * @tc.desc: Finish async slice without start + * @tc.type: FUNC + */ +HWTEST_F(SliceFilterTest, FinishAsyncSliceWithoutStart, TestSize.Level1) +{ + TS_LOGI("test3"); + uint64_t ts = 100; + uint32_t pid = 2532; + uint32_t threadGroupId = 2519; + DataIndex cat = stream_.traceDataCache_->GetDataIndex("Catalog"); + DataIndex splitStrIndex = stream_.traceDataCache_->GetDataIndex("async_call_function_one"); + stream_.streamFilters_->sliceFilter_->FinishAsyncSlice(ts, pid, threadGroupId, cat, splitStrIndex); + auto slices = stream_.traceDataCache_->GetInternalSlicesData(); + EXPECT_TRUE(slices->Size() == 0); +} + +/** + * @tc.name: AsyncTestTwiceCallStack + * @tc.desc: Test Twice asynchronous call stack + * @tc.type: FUNC + */ +HWTEST_F(SliceFilterTest, AsyncTestTwiceCallStack, TestSize.Level1) +{ + TS_LOGI("test3"); + uint64_t ts1 = 168758663018000; + uint32_t pid1 = 2532; + uint32_t threadGroupId1 = 2519; + DataIndex cat = stream_.traceDataCache_->GetDataIndex("Catalog"); + DataIndex splitStrIndex = stream_.traceDataCache_->GetDataIndex("thread_one_call_function_one"); + stream_.streamFilters_->sliceFilter_->StartAsyncSlice(ts1, pid1, threadGroupId1, cat, splitStrIndex); // slice 0 + DataIndex splitStrIndex2 = stream_.traceDataCache_->GetDataIndex("thread_two_call_function_one"); + uint64_t ts2 = 168758663028000; + uint32_t pid2 = 2533; + uint32_t threadGroupId2 = 2529; + stream_.streamFilters_->sliceFilter_->StartAsyncSlice(ts2, pid2, threadGroupId2, cat, splitStrIndex2); + // end thread_one_call_function_three + uint64_t ts3 = 168758682456000; + stream_.streamFilters_->sliceFilter_->FinishAsyncSlice(ts3, pid1, threadGroupId1, cat, splitStrIndex); + // end thread_two_call_function_three slice 5 + uint64_t ts4 = 168758679343000; + stream_.streamFilters_->sliceFilter_->FinishAsyncSlice(ts4, pid2, threadGroupId2, cat, splitStrIndex2); + auto slices = stream_.traceDataCache_->GetInternalSlicesData(); + EXPECT_TRUE(slices->Size() == 2); + EXPECT_TRUE(slices->DursData()[0] == ts3 - ts1); // slice 0 + EXPECT_TRUE(slices->Depths()[0] == 0); + EXPECT_TRUE(slices->DursData()[1] == ts4 - ts2); // slice 1 +} + +/** + * @tc.name: BeginAsyncSliceThreeTimes + * @tc.desc: Test asynchronous call three times + * @tc.type: FUNC + */ +HWTEST_F(SliceFilterTest, BeginAsyncSliceThreeTimes, TestSize.Level1) +{ + TS_LOGI("test3"); + uint64_t ts1 = 168758663018000; + uint32_t pid1 = 2532; + uint32_t threadGroupId1 = 2519; + DataIndex cat = stream_.traceDataCache_->GetDataIndex("Catalog"); + DataIndex splitStrIndex = stream_.traceDataCache_->GetDataIndex("thread_one_call_function_one"); + stream_.streamFilters_->sliceFilter_->StartAsyncSlice(ts1, pid1, threadGroupId1, cat, splitStrIndex); // slice 0 + DataIndex splitStrIndex2 = stream_.traceDataCache_->GetDataIndex("thread_two_call_function_one"); + uint64_t ts2 = 168758663028000; + uint32_t pid2 = 2533; + uint32_t threadGroupId2 = 2529; + stream_.streamFilters_->sliceFilter_->StartAsyncSlice(ts2, pid2, threadGroupId2, cat, splitStrIndex2); + DataIndex splitStrIndex3 = stream_.traceDataCache_->GetDataIndex("thread_three_call_function_two"); + DataIndex cat2 = stream_.traceDataCache_->GetDataIndex("Catalog2"); + uint64_t ts3 = 168758679303000; + stream_.streamFilters_->sliceFilter_->StartAsyncSlice(ts3, pid1, threadGroupId1, cat2, splitStrIndex3); // slice 2 + // end thread_one_call_function_three + uint64_t ts4 = 168758682456000; + stream_.streamFilters_->sliceFilter_->FinishAsyncSlice(ts4, pid1, threadGroupId1, cat, splitStrIndex); + // end thread_one_call_function_two + uint64_t ts5 = 168758682466000; + stream_.streamFilters_->sliceFilter_->FinishAsyncSlice(ts5, pid1, threadGroupId1, cat2, splitStrIndex3); + // end thread_two_call_function_three slice 5 + uint64_t ts6 = 168758679343000; + stream_.streamFilters_->sliceFilter_->FinishAsyncSlice(ts6, pid2, threadGroupId2, cat, splitStrIndex2); + auto slices = stream_.traceDataCache_->GetInternalSlicesData(); + EXPECT_TRUE(slices->Size() == 3); + EXPECT_TRUE(slices->DursData()[0] == ts4 - ts1); // slice 0 + EXPECT_TRUE(slices->Depths()[0] == 0); + EXPECT_TRUE(slices->DursData()[1] == ts6 - ts2); // slice 1 + EXPECT_TRUE(slices->Depths()[1] == 0); + EXPECT_TRUE(slices->DursData()[2] == ts5 - ts3); // slice 2 +} + +/** + * @tc.name: BeginAsyncSliceThreeTimes + * @tc.desc: Test asynchronous call muti times + * @tc.type: FUNC + */ +HWTEST_F(SliceFilterTest, BeginSliceMultiTimes, TestSize.Level1) +{ + TS_LOGI("test3"); + uint64_t ts1 = 168758663018000; + uint32_t pid1 = 2532; + uint32_t threadGroupId1 = 2519; + DataIndex cat = stream_.traceDataCache_->GetDataIndex("Catalog"); + DataIndex splitStrIndex = stream_.traceDataCache_->GetDataIndex("thread_one_call_function_one"); + stream_.streamFilters_->sliceFilter_->StartAsyncSlice(ts1, pid1, threadGroupId1, cat, splitStrIndex); // slice 0 + + DataIndex splitStrIndex2 = stream_.traceDataCache_->GetDataIndex("thread_two_call_function_one"); + uint64_t ts2 = 168758663028000; + uint32_t pid2 = 2533; + uint32_t threadGroupId2 = 2529; + stream_.streamFilters_->sliceFilter_->StartAsyncSlice(ts2, pid2, threadGroupId2, cat, splitStrIndex2); // slice 1 + + DataIndex splitStrIndex3 = stream_.traceDataCache_->GetDataIndex("thread_one_call_function_two"); + DataIndex cat2 = stream_.traceDataCache_->GetDataIndex("Catalog2"); + uint64_t ts3 = 168758679303000; + stream_.streamFilters_->sliceFilter_->StartAsyncSlice(ts3, pid1, threadGroupId1, cat2, splitStrIndex3); // slice 2 + + DataIndex splitStrIndex4 = stream_.traceDataCache_->GetDataIndex("thread_two_call_function_two"); + uint64_t ts4 = 168758679312000; + stream_.streamFilters_->sliceFilter_->StartAsyncSlice(ts4, pid2, threadGroupId2, cat2, splitStrIndex4); // slice 3 + + DataIndex splitStrIndex5 = stream_.traceDataCache_->GetDataIndex("thread_one_call_function_three"); + uint64_t ts5 = 168758679313000; + DataIndex cat3 = stream_.traceDataCache_->GetDataIndex("Catalog3"); + stream_.streamFilters_->sliceFilter_->StartAsyncSlice(ts5, pid1, threadGroupId1, cat3, splitStrIndex5); // slice 4 + + DataIndex splitStrIndex6 = stream_.traceDataCache_->GetDataIndex("thread_two_call_function_three"); + uint64_t ts6 = 168758679323000; + stream_.streamFilters_->sliceFilter_->StartAsyncSlice(ts6, pid2, threadGroupId2, cat2, splitStrIndex6); // slice 5 + + // end thread_one_call_function_three + uint64_t ts7 = 168758682456000; + stream_.streamFilters_->sliceFilter_->FinishAsyncSlice(ts7, pid1, threadGroupId1, cat, + splitStrIndex); // end slice 0 + + // end thread_one_call_function_two + uint64_t ts8 = 168758682466000; + stream_.streamFilters_->sliceFilter_->FinishAsyncSlice(ts8, pid1, threadGroupId2, cat, + splitStrIndex2); // end slice 1 + + // end thread_one_call_function_one + uint64_t ts9 = 168758682476000; + stream_.streamFilters_->sliceFilter_->FinishAsyncSlice(ts9, pid1, threadGroupId1, cat2, + splitStrIndex3); // end slice 2 + + // end thread_two_call_function_three slice 5 + uint64_t ts10 = 168758679343000; + stream_.streamFilters_->sliceFilter_->FinishAsyncSlice(ts10, pid2, threadGroupId2, cat2, + splitStrIndex4); // end slice 3 + + // end thread_two_call_function_two slice 3 + uint64_t ts11 = 168758679344000; + stream_.streamFilters_->sliceFilter_->FinishAsyncSlice(ts11, pid2, threadGroupId1, cat3, + splitStrIndex5); // end slice 4 + + // end thread_two_call_function_one slice 1 + uint64_t ts12 = 168758689323000; + stream_.streamFilters_->sliceFilter_->FinishAsyncSlice(ts12, pid2, threadGroupId2, cat2, + splitStrIndex6); // end slice 5 + + auto slices = stream_.traceDataCache_->GetInternalSlicesData(); + EXPECT_TRUE(slices->Size() == 6); + EXPECT_TRUE(slices->DursData()[0] == ts7 - ts1); // slice 0 + EXPECT_TRUE(slices->Depths()[0] == 0); + + EXPECT_TRUE(slices->DursData()[1] == ts8 - ts2); // slice 1 + EXPECT_TRUE(slices->Depths()[1] == 0); + + EXPECT_TRUE(slices->DursData()[2] == ts9 - ts3); // slice 2 + EXPECT_TRUE(slices->Depths()[2] == 1); + + EXPECT_TRUE(slices->DursData()[3] == ts10 - ts4); // slice 3 + EXPECT_TRUE(slices->Depths()[3] == 1); + + EXPECT_TRUE(slices->DursData()[4] == ts11 - ts5); // slice 4 + + EXPECT_TRUE(slices->DursData()[5] == ts12 - ts6); // slice 5 +} +} // namespace TraceStreamer +} // namespace SysTuning -- Gitee From e46da4efe0baa8ee513eace5a398351f593d944e Mon Sep 17 00:00:00 2001 From: zhaohui Date: Thu, 12 May 2022 10:24:51 +0800 Subject: [PATCH 2/4] Modify Device Code Signed-off-by: zhaohui --- README_zh.md | 11 ++- device/device_command/ByTrace.cpp | 10 +-- device/device_command/README_zh.md | 2 +- device/device_command/include/ByTrace.h | 2 +- .../device_command/include/socket_profiler.h | 11 ++- device/device_command/profiler.cpp | 4 +- device/device_command/socket_profiler.cpp | 17 ++--- device/device_ui/README_zh.md | 73 +++++++++++++++++++ 8 files changed, 101 insertions(+), 29 deletions(-) create mode 100644 device/device_ui/README_zh.md diff --git a/README_zh.md b/README_zh.md index 5c4be42..281e916 100644 --- a/README_zh.md +++ b/README_zh.md @@ -6,14 +6,14 @@ - [相关文档](#相关文档) ## 简介 - SmartPerf是一款基于OpenHarmony系统开发的性能功耗测试分析工具,支持两个组成部分: SP-Device端和SP-Host端。 - SP-Device端:是一款初级的,粗粒度的数据采集分析的性能功耗测试工具。支持FPS、功耗、热、Soc信息的实时采集、实时展示、数据报告生成。 - SP-Host端: 是一款深入挖掘数据、细粒度的展示数据的性能功耗测试工具。支持CPU调度、频点、进程线程时间片、堆内存、FPS数据采集和展示。支持在泳道图中展示非实时的采集数据,支持GUI操作数据分析。 +- **SmartPerf** 是一款基于OpenHarmony系统开发的性能功耗测试分析工具,支持两个组成部分: SP-Device端和SP-Host端。 +- **SP-Device端:** 是一款初级的,粗粒度的数据采集分析的性能功耗测试工具。支持FPS、功耗、热、Soc信息的实时采集、实时展示、数据报告生成。 +- **SP-Host端:** 是一款深入挖掘数据、细粒度的展示数据的性能功耗测试工具。支持CPU调度、频点、进程线程时间片、堆内存、FPS数据采集和展示。支持在泳道图中展示非实时的采集数据,支持GUI操作数据分析。 其主要的结构如下图所示: -![系统架构图](http://image.huawei.com/tiny-lts/v1/images/83a570e4c58496abb6b1e6bbe36da9d8_660x541.png) +![系統架构图](http://image.huawei.com/tiny-lts/v1/images/5997c5d67ac2eb79823c7b124005f160_815x450.png) ## 目录 @@ -46,12 +46,15 @@ ## 约束 +host端构建约束 - 语言版本 - C++11或以上 - TypeScript 4.2.3 ## 相关文档 +- [device_command_line_collection](https://gitee.com/openharmony/developtools_profiler/blob/master/host/smartperf/doc/quickstart_trace_streamer.md) +- [device_ui_collection](https://gitee.com/openharmony/developtools_profiler/blob/master/host/smartperf/doc/quickstart_trace_streamer.md) - [quickstart_trace_streamer](https://gitee.com/openharmony/developtools_profiler/blob/master/host/smartperf/doc/quickstart_trace_streamer.md) - [quickstart_smartperf](https://gitee.com/openharmony/developtools_profiler/blob/master/host/smartperf/doc/quickstart_smartperf.md) - [quickstart_hiprofiler_cmd](https://gitee.com/openharmony/developtools_profiler/blob/master/host/smartperf/doc/quickstart_hiprofiler_cmd.md) diff --git a/device/device_command/ByTrace.cpp b/device/device_command/ByTrace.cpp index f790429..e445884 100644 --- a/device/device_command/ByTrace.cpp +++ b/device/device_command/ByTrace.cpp @@ -42,9 +42,9 @@ void ByTrace::thread_finish_trace(std::string &pathName) TraceStatus ByTrace::init_trace(bool isStart) { if (isStart) { - return TRACE_START; + return TraceStatus::TRACE_START; } else { - return TRACE_NO; + return TraceStatus::TRACE_NO; } } @@ -56,7 +56,7 @@ TraceStatus ByTrace::check_fps_jitters(std::vector jitters, int curPr if (normalJitter > threshold) { if ((flagProfilerNum != -1) && curProfilerNum < (flagProfilerNum + interval)) { // 如果不是第一次抓取 并且 小于抓取周期间隔 则放弃抓取 - return TRACE_NO; + return TraceStatus::TRACE_NO; } curNum++; flagProfilerNum = curProfilerNum; @@ -66,11 +66,11 @@ TraceStatus ByTrace::check_fps_jitters(std::vector jitters, int curPr std::endl; std::cout << "***************************************************************************" << std::endl; std::cout << "***************************************************************************" << std::endl; - return TRACE_FINISH; + return TraceStatus::TRACE_FINISH; } } } - return TRACE_NO; + return TraceStatus::TRACE_NO; } } } diff --git a/device/device_command/README_zh.md b/device/device_command/README_zh.md index c9e0b9c..255edab 100644 --- a/device/device_command/README_zh.md +++ b/device/device_command/README_zh.md @@ -8,7 +8,7 @@ - 支持RK3568、Hi3516; - 支持Shell启动; -- 支持采集整机CPU、GPU、DDR、POWER、TEMPERATURE、应用的FPS、RAM; +- 支持采集整机CPU、GPU、POWER、TEMPERATURE、应用的FPS、RAM; ## 使用方式 >1、首先检查系统是否默认预制了SP_daemon,如打印如下日志,系统已内置SP_daemon diff --git a/device/device_command/include/ByTrace.h b/device/device_command/include/ByTrace.h index 3c5c749..1a451f9 100644 --- a/device/device_command/include/ByTrace.h +++ b/device/device_command/include/ByTrace.h @@ -18,7 +18,7 @@ #include "singleton.h" namespace OHOS { namespace SmartPerf { -enum TraceStatus { +enum class TraceStatus { TRACE_START, TRACE_FINISH, TRACE_NO diff --git a/device/device_command/include/socket_profiler.h b/device/device_command/include/socket_profiler.h index eefd123..84a91f5 100644 --- a/device/device_command/include/socket_profiler.h +++ b/device/device_command/include/socket_profiler.h @@ -26,15 +26,10 @@ #include "ByTrace.h" namespace OHOS { namespace SmartPerf { -enum SockConstant { - SOCK_PORT = 8283, - BUFF_SIZE_RECV = 256, - BUFF_SIZE_SEND = 2048 -}; class SocketProfiler : public DelayedSingleton { public: void initSocketProfiler(); - int bufsendto(int sockLocal, const char *bufsend, int length, struct sockaddr *clientLocal, socklen_t len); + void bufsendto(int sockLocal, const char *bufsend, int length, struct sockaddr *clientLocal, socklen_t len); void callSend(std::stringstream &sstream, std::string &str1, std::string &str2); void thread_udp_server(); void initSocket(); @@ -51,6 +46,10 @@ public: int sock; struct sockaddr_in local; struct sockaddr_in client; + const size_t SOCK_PORT = 8283; + const size_t BUFF_SIZE_RECV = 256; + const size_t BUFF_SIZE_SEND = 2048; + }; } } diff --git a/device/device_command/profiler.cpp b/device/device_command/profiler.cpp index f6d598e..62cf658 100644 --- a/device/device_command/profiler.cpp +++ b/device/device_command/profiler.cpp @@ -35,7 +35,7 @@ void Profiler::initProfiler() mTemperature->init_temperature(); mGpu->init_gpu_node(); mPower->init_power(); - if (mByTrace->init_trace(true) == TRACE_START) { + if (mByTrace->init_trace(true) == TraceStatus::TRACE_START) { std::thread pInitTrace(&ByTrace::thread_get_trace, mByTrace); } } @@ -89,7 +89,7 @@ void Profiler::createFps(int isVideo, int isCamera, int isCatchTrace, int curPro gpMap.insert(std::pair(std::string(desc), std::to_string(gfpsInfo.fps))); } if (isCatchTrace > 0) { - if (mByTrace->check_fps_jitters(gfpsInfo.jitters, curProfilerNum) == TRACE_FINISH) { + if (mByTrace->check_fps_jitters(gfpsInfo.jitters, curProfilerNum) == TraceStatus::TRACE_FINISH) { std::string profilerNum = std::to_string(curProfilerNum); std::thread pFinishTrace(&ByTrace::thread_finish_trace, mByTrace, std::ref(profilerNum)); } diff --git a/device/device_command/socket_profiler.cpp b/device/device_command/socket_profiler.cpp index ecd6694..08cb87c 100644 --- a/device/device_command/socket_profiler.cpp +++ b/device/device_command/socket_profiler.cpp @@ -17,7 +17,6 @@ #include #include #include "include/gp_utils.h" -#include "include/gp_constant.h" #include "include/socket_profiler.h" namespace OHOS { namespace SmartPerf { @@ -37,15 +36,13 @@ void SocketProfiler::initSocketProfiler() mPower->init_power(); } -int SocketProfiler::bufsendto(int sockLocal, const char *bufsend, int length, +void SocketProfiler::bufsendto(int sockLocal, const char *bufsend, int length, struct sockaddr *clientLocal, socklen_t len) { - ssize_t echo_size = sendto(sockLocal, bufsend, length, ZERO, clientLocal, len); - if (echo_size < ZERO) { + ssize_t echo_size = sendto(sockLocal, bufsend, length, 0, clientLocal, len); + if (echo_size < 0) { printf("sendto error, buf is %s\n", bufsend); - return ERROR_MINUX; } - return SUCCESS_ZERO; } void SocketProfiler::callSend(std::stringstream &sstream, std::string &str1, std::string &str2) @@ -60,14 +57,14 @@ void SocketProfiler::callSend(std::stringstream &sstream, std::string &str1, std void SocketProfiler::initSocket() { - sock = socket(AF_INET, SOCK_DGRAM, ZERO); - if (sock < ZERO) { + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { perror("socket error"); } local.sin_family = AF_INET; local.sin_port = htons(SOCK_PORT); local.sin_addr.s_addr = htonl(INADDR_ANY); - if (::bind(sock, reinterpret_cast(&local), sizeof(local)) < ZERO) { + if (::bind(sock, reinterpret_cast(&local), sizeof(local)) < 0) { perror("bind error"); } } @@ -124,7 +121,7 @@ void SocketProfiler::thread_udp_server() } else if (recv.find("get_fps_and_jitters") != std::string::npos) { std::vector sps; GPUtils::mSplit(recv, "::", sps); - if (sps.size() > TWO) { + if (sps.size() > 2) { int is_video = atoi(sps[1].c_str()); int is_camera = atoi(sps[2].c_str()); diff --git a/device/device_ui/README_zh.md b/device/device_ui/README_zh.md new file mode 100644 index 0000000..0772ae0 --- /dev/null +++ b/device/device_ui/README_zh.md @@ -0,0 +1,73 @@ +## 一、简介 +- OpenHarmony性能测试工具 SmartPerf UI版本,可采集CPU、GPU、Temperature、Power、应用RAM、FPS等指标,可使用悬浮窗口实时展示测试数据并支持本地测试报告图表展示。 + +## 二、如何使用 + +- 步骤1:打开桌面预制的Smartperf工具;
      +- 步骤2:点击登录,进入首页,点击开始测试页,选择被测应用,配置采集项(目前支持CPU、GPU、FPS、POWER、TEMP、RAM、截图能力、trace);
      +- 步骤3:选择应用后根据应用类型选择是否是视频应用、是否是相机应用,点击开始测试、启动悬浮窗(左上角展示),会自动拉起应用,左上角悬浮窗展示start后点击即可开始,展示会变为计时状态,显示当前采集计时;(双击展开、关闭详情悬浮窗;长按结束任务保存数据;单击暂停、继续任务;点击详情悬浮窗后任意采集项会展开相应采集折线图)
      +- 步骤4:长按计时器,可以保存采集项数据,悬浮框消失;
      +- 步骤5:报告列表页查看报告,原始数据可在概览页对应路径自行使用hdc命令pull出来
      + +## 三、功能介绍 +**1、采集指标说明**
      + +| 采集项 | 说明 | +| :----- | :--------------------- | +| FPS | 1秒内游戏画面或者应用界面真实刷新次数 | +| CPU频率 | 每1秒读取一次设备节点下各CPU的频点信息 | +| CPU负载 | 每1秒读取一次设备节点下各CPU的负载信息 | +| GPU频率 | 每1秒读取一次设备节点下GPU的频点信息 | +| GPU负载 | 每1秒读取一次设备节点下GPU的负载信息 | +| DDR频点 |每1秒读取一次设备节点下DDR的频点信息 | +| RAM |每1秒读取一次设备节点下DDR的频点信息 | +| 温度 | 每1秒读取一次读取一次设备节点下的soc温度等信息 | +| 电流 | 每1秒读取一次设备节点下的电流信息 | +| 电压 | 每1秒读取一次设备节点下电池的电压信息 | +| 截图 |每1秒截取一张截图 | +|trace采集|当帧绘制时间超过100ms以上会自动抓取trace,1min内只抓取1次 | + + +**2、计算指标说明**
      + +| 计算指标 | 说明 | +| :----- | :--------------------- | +| 归一化电流 | 归一化电流=电压 * 电流 / 3.8。 | +| 平均帧率 | 有效时间内的帧率之和除以时间 | +| 最高帧率 | 测试数据中帧率的最大值 | +| 低帧率 | 根据测试数据帧率数据最大数据计算满帧,然后满帧对应的的低帧基线,计算低于基线数量除以总的帧率个数 | +| 抖动率 | 根据测试数据帧率数据的最大数据计算满帧,按照满帧对应抖动率基线,然后计算得到抖动率 | +| 卡顿次数 | 目前卡顿次数依赖于每帧的绘制时间,帧绘制时间>100ms的计算为卡顿 | + +**3、原始数据说明**
      + +| 原始数据 | 说明 | +| :----- | :--------------------- | +| timestamp | 当前时间戳,对应于采集时间 | +| taskId | 任务Id,对应于网站端报告的任务ID| +| cpu0Frequency | cpu0核心的频率 单位一般是HZ| +| cpu0Load | cpu0核心的负载占比,计算得出 单位%| +| gpuFrequency | gpu频率| +| gpuLoad | gpu负载占比 单位%| +| ddrFrequency | ddr频率 | +| socThermalTemp | soc温度| +| gpuTemp | gpu温度| +| batteryTemp |电池温度| +| currentNow | 当前读到的电流值,单位一般是mA| +| voltageNow | 当前读到的电压值,单位一般是uV(微伏)| +| pss | 应用实际使用的物理内存, 单位一般是KB| +| fps | 帧率| +| fpsJitters | 每一帧绘制间隔,单位ns| + + +## 四、其他一些说明 +**1、关于截图、FPS、RAM、trace采集能力**
      +- 截图目录默认在/data/local/tmp/capture下。
      + - **Trace抓取:当帧绘制时间超过100ms以上会自动抓取trace,1min内只抓取1次,目录在/data目录下,trace文件较大,建议定期清除。** + **文件名称为:mynewtrace + 当前采集s数 + s:mynewtrace950s.ftrace** + + +**2、fps采集不到如何解决**
      +- 方法一:后台必须只能存在一个被测试应用,同应用不能挂许多后台(如果有多个,需清除所有应用,重新启动测试);
      +- 方法二:执行hidumper -s 10是否出现connect error等error错误,有error报错的话(尝试kill 掉 hidumper_servic进程,系统会自动再次拉起)
      + -- Gitee From ff9f3ec850a05cf499525c95920f530409d525ea Mon Sep 17 00:00:00 2001 From: zhaohui Date: Thu, 12 May 2022 11:09:51 +0800 Subject: [PATCH 3/4] Modify Device-ui Code Signed-off-by: zhaohui --- device/device_ui/entry/src/main/cpp/FPS.cpp | 3 +- device/device_ui/entry/src/main/cpp/RAM.cpp | 5 +- device/device_ui/entry/src/main/cpp/RAM.h | 27 +++++---- .../device_ui/entry/src/main/cpp/gp_utils.cpp | 58 +++++++++---------- .../device_ui/entry/src/main/cpp/profiler.cpp | 17 +++--- 5 files changed, 55 insertions(+), 55 deletions(-) diff --git a/device/device_ui/entry/src/main/cpp/FPS.cpp b/device/device_ui/entry/src/main/cpp/FPS.cpp index db60aed..4fda886 100644 --- a/device/device_ui/entry/src/main/cpp/FPS.cpp +++ b/device/device_ui/entry/src/main/cpp/FPS.cpp @@ -14,12 +14,11 @@ **/ #include -#include #include #include #include -#include "FPS.h" #include "gp_utils.h" +#include "FPS.h" pthread_mutex_t FPS::mutex; FPS *FPS::instance = nullptr; diff --git a/device/device_ui/entry/src/main/cpp/RAM.cpp b/device/device_ui/entry/src/main/cpp/RAM.cpp index e05bde5..a5f3b42 100644 --- a/device/device_ui/entry/src/main/cpp/RAM.cpp +++ b/device/device_ui/entry/src/main/cpp/RAM.cpp @@ -1,10 +1,10 @@ /* - * Copyright (C) 2021 Huawei Device Co., Ltd. + * Copyright (C) 2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,7 +13,6 @@ * limitations under the License. */ #include -#include #include #include #include diff --git a/device/device_ui/entry/src/main/cpp/RAM.h b/device/device_ui/entry/src/main/cpp/RAM.h index d0a94c2..997119f 100644 --- a/device/device_ui/entry/src/main/cpp/RAM.h +++ b/device/device_ui/entry/src/main/cpp/RAM.h @@ -1,22 +1,21 @@ /* -* Copyright (C) 2021 Huawei Device Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #ifndef RAM_H #define RAM_H #include #include -#include "pthread.h" class RAM { public: diff --git a/device/device_ui/entry/src/main/cpp/gp_utils.cpp b/device/device_ui/entry/src/main/cpp/gp_utils.cpp index 6846b93..3845f21 100644 --- a/device/device_ui/entry/src/main/cpp/gp_utils.cpp +++ b/device/device_ui/entry/src/main/cpp/gp_utils.cpp @@ -13,7 +13,7 @@ * limitations under the License. **/ #include -#include +#include #include #include #include "gp_utils.h" @@ -21,40 +21,40 @@ #define LOG_DOMAIN 0x200 // 标识业务领域,范围0x0~0xFFFFF #define LOG_TAG "gpUtils" -void gpUtils::mSplit(const std::string &content, const std::string &sp, std::vector &out) { - int index = 0; - while (index != std::string::npos) { - int t_end = content.find_first_of(sp, index); - std::string tmp = content.substr(index, t_end - index); - if (tmp != "" && tmp != " ") - out.push_back(tmp); - if (t_end == std::string::npos) - break; - index = t_end + 1; +namespace gpUtils { + void mSplit(const std::string &content, const std::string &sp, std::vector &out) { + int index = 0; + while (index != std ::string::npos) { + int t_end = content.find_first_of(sp, index); + std::string tmp = content.substr(index, t_end - index); + if (tmp != "" && tmp != " ") + out.push_back(tmp); + if (t_end == std::string::npos) + break; + index = t_end + 1; + } } -} -bool gpUtils::canOpen(const std::string &path) { - FILE* fp; - fp = fopen(path.c_str(), "r"); - if(fp == nullptr){ - return false; + bool canOpen(const std::string &path) { + FILE* fp; + fp = fopen(path.c_str(), "r"); + if(fp == nullptr){ + return false; + } + fclose(fp); + return true; } - fclose(fp); - return true; -} -bool gpUtils::canCmd(const std::string &cmd) { - FILE* pp; - pp = popen(cmd.c_str(), "r"); - if(pp == nullptr){ - return false; + bool canCmd(const std::string &cmd) { + FILE* pp; + pp = popen(cmd.c_str(), "r"); + if(pp == nullptr){ + return false; + } + pclose(pp); + return true; } - pclose(pp); - return true; } - - // popen std::string gpUtils::readCmd(const std::string &cmd) { diff --git a/device/device_ui/entry/src/main/cpp/profiler.cpp b/device/device_ui/entry/src/main/cpp/profiler.cpp index 50b95c1..f96aba3 100644 --- a/device/device_ui/entry/src/main/cpp/profiler.cpp +++ b/device/device_ui/entry/src/main/cpp/profiler.cpp @@ -11,31 +11,34 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - **/ + */ #include #include #include #include #include #include -#include -#include +#include +#include #include -#include "napi/native_api.h" #include #include #include #include +#include "napi/native_api.h" #include "gp_utils.h" #define LOG_DOMAIN 0x200 // 标识业务领域,范围0x0~0xFFFFF #define LOG_TAG "PROFILER" -void collectFpsThread(std::promise &promiseObj){ - FpsInfo fpsInfo = FPS::getInstance()->getFpsInfo(); - promiseObj.set_value(fpsInfo); +namespace { + void collectFpsThread(std::promise &promiseObj){ + FpsInfo fpsInfo = FPS::getInstance()->getFpsInfo(); + promiseObj.set_value(fpsInfo); + } } + static napi_value getFpsData(napi_env env, napi_callback_info info) { size_t argc = 1; -- Gitee From d6cc1365b6c117b857ec37a988f0749519f9bc2f Mon Sep 17 00:00:00 2001 From: zhaohui Date: Thu, 12 May 2022 17:15:48 +0800 Subject: [PATCH 4/4] Modify host licence Signed-off-by: zhaohui --- README_zh.md | 2 +- host/ide/src/trace/grpc/HiProfilerClient.ts | 14 ++++++++++++++ host/ide/src/trace/grpc/ProfilerClient.ts | 14 ++++++++++++++ host/ide/src/trace/grpc/ProfilerController.ts | 14 ++++++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/README_zh.md b/README_zh.md index 281e916..38e2ceb 100644 --- a/README_zh.md +++ b/README_zh.md @@ -13,7 +13,7 @@ 其主要的结构如下图所示: -![系統架构图](http://image.huawei.com/tiny-lts/v1/images/5997c5d67ac2eb79823c7b124005f160_815x450.png) +![系统架构图](http://image.huawei.com/tiny-lts/v1/images/cc51c24a361d47dd1d10b23f089a0faa_877x491.png) ## 目录 diff --git a/host/ide/src/trace/grpc/HiProfilerClient.ts b/host/ide/src/trace/grpc/HiProfilerClient.ts index 09d6090..c4bbdb0 100644 --- a/host/ide/src/trace/grpc/HiProfilerClient.ts +++ b/host/ide/src/trace/grpc/HiProfilerClient.ts @@ -1,3 +1,17 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import {Address, ProfilerClient} from "./ProfilerClient.js"; export class HiProfilerClient { diff --git a/host/ide/src/trace/grpc/ProfilerClient.ts b/host/ide/src/trace/grpc/ProfilerClient.ts index c27e1b6..73f0199 100644 --- a/host/ide/src/trace/grpc/ProfilerClient.ts +++ b/host/ide/src/trace/grpc/ProfilerClient.ts @@ -1,3 +1,17 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import * as path from 'path'; const profilerServicePath = path.join(__dirname,'../proto', 'profiler_service.proto'); diff --git a/host/ide/src/trace/grpc/ProfilerController.ts b/host/ide/src/trace/grpc/ProfilerController.ts index eb927bf..bbc186a 100644 --- a/host/ide/src/trace/grpc/ProfilerController.ts +++ b/host/ide/src/trace/grpc/ProfilerController.ts @@ -1,3 +1,17 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ export class ClientContainer { // private _credentials: rpc.ChannelCredentials | undefined; // private _clients: { service: any, client?: rpc.Client, target: any }[] = []; -- Gitee

      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%0DX4LJ7VF}tyMu;@BdQmyjCF&Pc%xp-4QHjc zDc1%2hX8{dHO*;zl?7^S#G%{1WEl@c;)y~UkdW5X$vw#GYU^;QwBDz#rFKd-Rt?f( Vs{``pG&qN+Ic4AnJn+}S{|83CIxzqM literal 0 HcmV?d00001 diff --git a/host/figures/process_thread.png b/host/figures/process_thread.png new file mode 100644 index 0000000000000000000000000000000000000000..abc3867130c10413197482d0156ce0ee00aca255 GIT binary patch literal 1841 zcmbuAX;9PG7Qp{52xSRTQ&d8WfOUC@s6ZnD*$f2m0fLXPG|FN?0|tnR6bJ;Qj}prA zKpqrX5-p_^g9HWHG*AeTrYuoW0wg8^DPhw9DS;#q8rwH--i#mm;m!QcJ$KH``Ecgm z`@1<8e31sa#<~Ci7WqVN39Q|QIU2J@# zS4IL)FKgXNsPdUy-k2+*303-IoUcrs$r)y>c7_*UXz<>u-c#v zJex?os108A&VQCS9W%nfQc`OAlfkARBlq<9vStyv%Q-d2t#tA_vXnc4-^%%@ASA-0 zOf8=;z9LFYucu*6mlnWzbKS6TA!k5H^~YsZo`xouU|{}^v9tPJ@4ocmUu)* zXBX37u1H91Lpwb$7Vovu16KC>6g>48Y&MmwuH@vUQ*`pcvQ(`Iu&XxUH9Tw{R2VpS zILhz#q}Fb5*4O~&e0JEjqMH4q$Ia(5&@FubdvQq_n3n4{Pt48Fm@7MVcX&n{QD&-d zKh5@qY=03jF5GW>Yb<;>w_+*9zHW4Y5^>p{qzgvjn0Uu z9CZGaTlicr3G{f5cbBNA3%E24PQXZ~n+Z2r7fwXuffz#YDertYZ3>sWZ8oY71DOW^ z=FB`0hwPo{HeUM{lG~?ZlC8+jhc}KhEv>0k`+}x%yR18sL62}zGdOY!Rnb|MXyCAY z4Q4g=KaAQti<~dC;4S$mg?dxP$~GKnn2K98JqisYRB?F-@zT;l`Ele04AsQIeqxshU{OgTls3z>#n&SYO&~9P`y-XZ~^>1PZsB+a6 zu}o&;K;w!Wo#S%SdpuS)$IKOup?y;jV8x*JVVk=IDy7%f*7i9~<&y>8)xATm8{{oK zeXPfd?Xkp}KQCcc<9A3u1 zK21{=l}QHeKJ>w#6*pBu%4aNN98TfidY8z2U`5l-RDc}v-LT*vE`8mmGgF~(M<_{W zFQ26fi|(Oa80~M*0xmBt?S^;!p%6dwdm-nT3$8e&ox$GN&+lBdcR6{|d*5V0g44wy z=|ahCQ}fl<(H5MIUVEgfh3|_=TJ2iuPfUAK$665Nr7)cAmF( zBjqN^)jAo`r8Q?J@u;3xw!wo4d9R>bZG@#V5VxDQJ~jNx^57!~tAiZ#xor9p02KP1 zjtZQJd^l(ER7x}FkO~7yXBKA4{MQ^Qt@0!b+MzpDM|XbDr!VSe4iUVBEWtCi-|T%O zba2&CvMv99{1TiK0wo5rzW)~ETMw6t>Z<9+hf5fze t*=L%g&S!W{k<(|4sgh>R>;FsEp%`}UM(2S;KAP_i@N)M>G`NLl{tn0ixo-df literal 0 HcmV?d00001 diff --git a/host/figures/smartperf_framework.png b/host/figures/smartperf_framework.png new file mode 100644 index 0000000000000000000000000000000000000000..62c1bc6cd61e07ba3014141f70941e6134d75681 GIT binary patch literal 19831 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 R>&*Ri2LAs&0}OxW{|7)PXv_cr literal 0 HcmV?d00001 diff --git a/host/figures/threadinfo.jpg b/host/figures/threadinfo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d2b6be2acf86bcc2612ff4ab1a6b820e6551b9b6 GIT binary patch literal 169830 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