├── .bazelignore ├── .bazelrc ├── .github └── workflows │ └── build_test_schedviz.yml ├── .gitignore ├── BUILD.bazel ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── WORKSPACE ├── analysis ├── BUILD.bazel ├── sched_analysis.go ├── sched_analysis_test.go ├── sched_collection.go ├── sched_collection_options.go ├── sched_collection_queries.go ├── sched_collection_queries_test.go ├── sched_cpu_span_set.go ├── sched_cpu_span_set_test.go ├── sched_elementary_intervals.go ├── sched_elementary_intervals_test.go ├── sched_event_loader.go ├── sched_event_loader_test.go ├── sched_event_loaders.go ├── sched_event_loaders.proto ├── sched_metrics.go ├── sched_metrics_test.go ├── sched_per_cpu_events.go ├── sched_per_cpu_events_intervals_test.go ├── sched_per_cpu_events_test.go ├── sched_query_filter.go ├── sched_test_common.go ├── sched_thread_inferrer.go ├── sched_thread_inferrer_test.go ├── sched_thread_span.go ├── sched_thread_span_set.go ├── sched_thread_span_set_test.go ├── sched_thread_span_test.go ├── sched_thread_transition.go ├── sched_thread_transition_builder.go ├── sched_thread_transition_test.go ├── sched_types.go ├── string_bank.go └── string_bank_test.go ├── angular-metadata.tsconfig.json ├── client ├── BUILD.bazel ├── app │ ├── BUILD.bazel │ ├── app.css │ ├── app.ng.html │ ├── app_root.ts │ ├── app_root_module.ts │ ├── app_root_test.ts │ ├── app_routing_module.ts │ ├── collections │ │ ├── BUILD.bazel │ │ ├── collections.css │ │ ├── collections.ng.html │ │ ├── collections.ts │ │ ├── collections_module.ts │ │ ├── collections_table.css │ │ ├── collections_table.ng.html │ │ ├── collections_table.ts │ │ ├── collections_test.ts │ │ ├── collections_toolbar.css │ │ ├── collections_toolbar.ng.html │ │ ├── collections_toolbar.ts │ │ └── selectable_anchor.ts │ ├── dashboard │ │ ├── BUILD.bazel │ │ ├── dashboard.css │ │ ├── dashboard.ng.html │ │ ├── dashboard.ts │ │ ├── dashboard_module.ts │ │ ├── dashboard_test.ts │ │ ├── dashboard_toolbar.css │ │ ├── dashboard_toolbar.ng.html │ │ └── dashboard_toolbar.ts │ ├── dialog_shortcuts.ts │ ├── heatmap │ │ ├── BUILD.bazel │ │ ├── cpu_axes │ │ │ ├── BUILD.bazel │ │ │ ├── cpu_axes.css │ │ │ ├── cpu_axes_module.ts │ │ │ ├── cpu_axes_test.ts │ │ │ ├── cpu_axis_layer.ts │ │ │ ├── index.ts │ │ │ └── topological_cpu_axis_layer.ts │ │ ├── heatmap.css │ │ ├── heatmap.ng.html │ │ ├── heatmap.ts │ │ ├── heatmap_module.ts │ │ ├── heatmap_test.ts │ │ ├── index.ts │ │ ├── intervals_layer.ts │ │ ├── metrics_overlay │ │ │ ├── BUILD.bazel │ │ │ ├── dialog_metrics_help.ng.html │ │ │ ├── index.ts │ │ │ ├── metrics_overlay.css │ │ │ ├── metrics_overlay.ng.html │ │ │ ├── metrics_overlay.ts │ │ │ ├── metrics_overlay_module.ts │ │ │ └── metrics_overlay_test.ts │ │ ├── preview_layer.ts │ │ ├── preview_layer_test.ts │ │ ├── timeline_zoom_brush.ts │ │ ├── timeline_zoom_brush_test.ts │ │ └── x_axis_layer.ts │ ├── models │ │ ├── BUILD.bazel │ │ ├── checkpoint.ts │ │ ├── collection.ts │ │ ├── collection_data_services.ts │ │ ├── collections_filter.ts │ │ ├── cpu_intervals.ts │ │ ├── cpu_layers.ts │ │ ├── events.ts │ │ ├── ftrace_interval.ts │ │ ├── ftrace_interval_test.ts │ │ ├── index.ts │ │ ├── interval.ts │ │ ├── layer.ts │ │ ├── metrics_services.ts │ │ ├── render_data_services.ts │ │ ├── sched_event.ts │ │ ├── thread.ts │ │ ├── thread_intervals.ts │ │ └── utilization_metrics.ts │ ├── services │ │ ├── BUILD.bazel │ │ ├── collection_data_service.ts │ │ ├── color_service.ts │ │ ├── index.ts │ │ ├── metrics_service.ts │ │ ├── render_data_service.ts │ │ ├── shortcut_service.ts │ │ └── shortcut_service_test.ts │ ├── sidebar │ │ ├── BUILD.bazel │ │ ├── settings_menu │ │ │ ├── BUILD.bazel │ │ │ ├── settings_menu.css │ │ │ ├── settings_menu.ng.html │ │ │ ├── settings_menu.ts │ │ │ ├── settings_menu_module.ts │ │ │ └── settings_menu_test.ts │ │ ├── sidebar.css │ │ ├── sidebar.ng.html │ │ ├── sidebar.ts │ │ ├── sidebar_module.ts │ │ ├── sidebar_test.ts │ │ └── thread_table │ │ │ ├── BUILD.bazel │ │ │ ├── antagonist_table.ng.html │ │ │ ├── antagonist_table.ts │ │ │ ├── antagonist_table_test.ts │ │ │ ├── event_table.ng.html │ │ │ ├── event_table.ts │ │ │ ├── event_table_test.ts │ │ │ ├── interval_table.ng.html │ │ │ ├── interval_table.ts │ │ │ ├── interval_table_test.ts │ │ │ ├── jump_to_time.ts │ │ │ ├── jump_to_time_test.ts │ │ │ ├── layer_toggle.css │ │ │ ├── layer_toggle.ts │ │ │ ├── sched_events_table.ng.html │ │ │ ├── sched_events_table.ts │ │ │ ├── sched_events_table_test.ts │ │ │ ├── selectable_table.ts │ │ │ ├── table_helpers_test.ts │ │ │ ├── thread_table.css │ │ │ ├── thread_table.ng.html │ │ │ ├── thread_table.ts │ │ │ ├── thread_table_module.ts │ │ │ └── thread_table_test.ts │ └── util │ │ ├── BUILD.bazel │ │ ├── clipboard.ts │ │ ├── complex_system_topology.ts │ │ ├── duration.ts │ │ ├── duration_test.ts │ │ ├── duration_validator.ts │ │ ├── error_snackbar.ts │ │ ├── hash_compressor.ts │ │ ├── hash_compressor_test.ts │ │ ├── hash_keys.ts │ │ ├── helpers.ts │ │ ├── index.ts │ │ ├── system_topology.ts │ │ ├── util_module.ts │ │ └── viewport.ts ├── environments │ ├── BUILD.bazel │ ├── environment.dev.ts │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── material-theme.scss ├── module-id.js ├── rxjs_shims.js ├── sched.html └── tsconfig.app.json ├── doc ├── images │ └── walkthrough │ │ ├── cib.png │ │ ├── collections_page.png │ │ ├── cpu_swimlane.png │ │ ├── events_tab.png │ │ ├── expanded_thread.png │ │ ├── layers_tab.png │ │ ├── layers_tab_preview.png │ │ ├── manipulated_layers.png │ │ ├── metrics.png │ │ ├── metrics_pane.png │ │ ├── migrations_selected.png │ │ ├── round_robin.png │ │ ├── sleep_time.png │ │ ├── trace_view.png │ │ ├── unzoomed_heatmap.png │ │ ├── unzoomed_heatmap_preview.png │ │ ├── zoombrush.png │ │ ├── zoomed_heatmap.png │ │ └── zoomed_heatmap_preview.png ├── sitemap.md └── walkthrough.md ├── ebpf ├── BUILD.bazel ├── collect.sh ├── sched.bt ├── schedbt.go └── schedbt_test.go ├── package.json ├── server ├── BUILD.bazel ├── api_service.go ├── collection_data_services.go ├── events.go ├── fs_storage.go ├── fs_storage_test.go ├── fs_upload_file.go ├── metrics_services.go ├── render_data_services.go ├── server.go ├── server_test.go ├── storage_proto_converters.go ├── storage_service.go └── testdata │ ├── ebpf_trace.tar.gz │ ├── test.tar.gz │ └── test_no_metadata.tar.gz ├── testhelpers ├── BUILD.bazel └── testhelpers.go ├── tracedata ├── BUILD.bazel ├── clipping.go ├── clipping_test.go ├── event_set_builder.go ├── event_set_builder_test.go ├── events.proto ├── sched_event.go ├── test_event_set_builder.go ├── trace_event.go └── trace_event_test.go ├── traceparser ├── BUILD.bazel ├── event_set_builder.go ├── event_set_builder_test.go ├── eventformat.go ├── example │ ├── BUILD.bazel │ └── trace_to_proto_converter.go ├── formatparser.go ├── formatparser_test.go ├── path.go ├── ringbuffer.go ├── testdata │ ├── input │ │ ├── cpu0 │ │ └── cpu0-32 │ └── output │ │ ├── trace-32.gob │ │ └── trace.gob ├── trace_parser.go ├── trace_parser_test.go └── traceevent.go ├── util ├── BUILD.bazel ├── gcloud_trace.sh ├── log.go ├── status.h ├── trace.cc ├── trace.h └── trace.sh └── yarn.lock /.bazelignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | # For docker rules 2 | build --host_force_python=PY2 3 | test --host_force_python=PY2 4 | run --host_force_python=PY2 5 | 6 | # Use Clang with C++17 7 | build --crosstool_top=@llvm_toolchain//:toolchain 8 | run --crosstool_top=@llvm_toolchain//:toolchain 9 | test --crosstool_top=@llvm_toolchain//:toolchain 10 | 11 | build --incompatible_strict_action_env --incompatible_new_actions_api=false --experimental_allow_incremental_repository_updates --incompatible_depset_is_not_iterable=false 12 | run --incompatible_strict_action_env --incompatible_new_actions_api=false --experimental_allow_incremental_repository_updates --incompatible_depset_is_not_iterable=false 13 | test --test_output=errors --incompatible_strict_action_env --incompatible_new_actions_api=false --experimental_allow_incremental_repository_updates --incompatible_depset_is_not_iterable=false 14 | -------------------------------------------------------------------------------- /.github/workflows/build_test_schedviz.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of SchedViz dependencies, build, and test the source code. 2 | 3 | name: Build and Test SchedViz 4 | 5 | on: [push] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Use Node.js 14.x 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: 14.x 18 | - name: Install dependencies 19 | run: yarn install 20 | - name: Build the server 21 | run: yarn bazel build server 22 | - name: Run tests 23 | run: yarn bazel test //... 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bazel-bin 2 | bazel-genfiles 3 | bazel-out 4 | bazel-schedviz 5 | bazel-testlogs 6 | node_modules/ 7 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@bazel_gazelle//:def.bzl", "gazelle") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | licenses(["notice"]) # Apache License 2.0 6 | 7 | exports_files(["LICENSE"]) 8 | 9 | config_setting( 10 | name = "environment", 11 | values = { 12 | "define": "environment=dev", 13 | }, 14 | ) 15 | 16 | alias( 17 | name = "tsconfig.json", 18 | actual = "//client:tsconfig.app.json", 19 | ) 20 | 21 | package_group( 22 | name = "schedviz", 23 | packages = [ 24 | "//analysis/...", 25 | "//client/...", 26 | "//tracedata/...", 27 | "//traceparser/...", 28 | ], 29 | ) 30 | 31 | # gazelle:prefix github.com/google/schedviz 32 | gazelle(name = "gazelle") 33 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code Reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows 28 | [Google's Open Source Community Guidelines](https://opensource.google.com/conduct/). 29 | -------------------------------------------------------------------------------- /analysis/sched_cpu_span_set.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | package sched 18 | 19 | import ( 20 | "sort" 21 | 22 | "github.com/Workiva/go-datastructures/augmentedtree" 23 | "google.golang.org/grpc/codes" 24 | "google.golang.org/grpc/status" 25 | ) 26 | 27 | // cpuSpans stores per-CPU sets of running, sleeping, and waiting threadSpans. 28 | type cpuSpans struct { 29 | runningSpans []*threadSpan 30 | sleepingSpans []*threadSpan 31 | waitingSpans []*threadSpan 32 | } 33 | 34 | func (cs *cpuSpans) addSpan(span *threadSpan) { 35 | switch span.state { 36 | case RunningState: 37 | cs.runningSpans = append(cs.runningSpans, span) 38 | case SleepingState: 39 | cs.sleepingSpans = append(cs.sleepingSpans, span) 40 | case WaitingState: 41 | cs.waitingSpans = append(cs.waitingSpans, span) 42 | } 43 | } 44 | 45 | func sortSpans(tss []*threadSpan) { 46 | sort.Slice(tss, func(a, b int) bool { 47 | return tss[a].less(tss[b]) 48 | }) 49 | } 50 | 51 | // sort sorts each group of spans in the receiver by increasing start 52 | // timestamp. 53 | func (cs *cpuSpans) sort() { 54 | sortSpans(cs.runningSpans) 55 | sortSpans(cs.waitingSpans) 56 | sortSpans(cs.sleepingSpans) 57 | } 58 | 59 | // finalize sorts the cpuSpans, then confirms that there are no 60 | // anomalies in it. Any anomalies result in returned errors. 61 | func (cs *cpuSpans) finalize() error { 62 | cs.sort() 63 | // Ensure that no CPU ever has more than one running thread. 64 | var lastSpan *threadSpan 65 | for _, ts := range cs.runningSpans { 66 | if lastSpan != nil && lastSpan.endTimestamp > ts.startTimestamp { 67 | return status.Errorf(codes.InvalidArgument, "multiple running threads on %s at timestamp %d: [%s %s]", ts.cpu, ts.startTimestamp, lastSpan, ts) 68 | } 69 | lastSpan = ts 70 | } 71 | return nil 72 | } 73 | 74 | type cpuSpanSet struct { 75 | cpuSpansByCPU map[CPUID]*cpuSpans 76 | } 77 | 78 | func newCPUSpanSet() *cpuSpanSet { 79 | return &cpuSpanSet{ 80 | cpuSpansByCPU: map[CPUID]*cpuSpans{}, 81 | } 82 | } 83 | 84 | func (css *cpuSpanSet) cpuSpans(cpu CPUID) *cpuSpans { 85 | cs, ok := css.cpuSpansByCPU[cpu] 86 | if !ok { 87 | cs = &cpuSpans{} 88 | css.cpuSpansByCPU[cpu] = cs 89 | } 90 | return cs 91 | } 92 | 93 | // addSpan adds the provided span to its appropriate cpuSpans. 94 | func (css *cpuSpanSet) addSpan(span *threadSpan) { 95 | css.cpuSpans(span.cpu).addSpan(span) 96 | } 97 | 98 | func (css *cpuSpanSet) cpuTrees() (runningSpansByCPU map[CPUID][]*threadSpan, sleepingSpansByCPU, waitingSpansByCPU map[CPUID]augmentedtree.Tree, err error) { 99 | runningSpansByCPU = map[CPUID][]*threadSpan{} 100 | sleepingSpansByCPU = map[CPUID]augmentedtree.Tree{} 101 | waitingSpansByCPU = map[CPUID]augmentedtree.Tree{} 102 | for cpu, css := range css.cpuSpansByCPU { 103 | if err = css.finalize(); err != nil { 104 | return 105 | } 106 | runningSpansByCPU[cpu] = css.runningSpans 107 | sleeping := augmentedtree.New(1) 108 | for _, span := range css.sleepingSpans { 109 | sleeping.Add(span) 110 | } 111 | sleepingSpansByCPU[cpu] = sleeping 112 | waiting := augmentedtree.New(1) 113 | for _, span := range css.waitingSpans { 114 | waiting.Add(span) 115 | } 116 | waitingSpansByCPU[cpu] = waiting 117 | } 118 | return 119 | } 120 | -------------------------------------------------------------------------------- /analysis/sched_event_loaders.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package schedviz.analysis.event_loaders; 4 | 5 | enum LoadersType { 6 | // A set of default event loaders operating on sched_migrate_task, 7 | // sched_switch, sched_wakeup, and sched_wakeup_new. sched_wakeup* events 8 | // that cannot be reconciled are dropped. 9 | DEFAULT = 0; 10 | // A set of event loaders operating on only sched_switch. CPU and thread 11 | // state transitions are inferred and may not be entirely accurate. Running 12 | // intervals will be entirely accurate, but waiting intervals and CPU wait 13 | // queues may be approximate. 14 | SWITCH_ONLY = 1; 15 | // A set of event loaders operating on sched_migrate_task, sched_switch, 16 | // sched_wakeup, and sched_wakeup_new, and suitable for use on traces that may 17 | // be incomplete or have out-of-order events. sched_migrate events whose CPUs 18 | // cannot be reconciled are dropped; sched_wakeup* events that cannot be 19 | // reconciled are dropped, and unattested CPU and thread state transitions 20 | // between sched_switch events are inferred. 21 | FAULT_TOLERANT = 2; 22 | } 23 | -------------------------------------------------------------------------------- /analysis/string_bank_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | package sched 18 | 19 | import ( 20 | "strings" 21 | "testing" 22 | ) 23 | 24 | func TestStringBank(t *testing.T) { 25 | strs := []string{"a", "b", "such a long string amaze wow", "ελληνικά"} 26 | sb := newStringBank() 27 | var strIDs = []stringID{} 28 | // Generate a known-absent index by summing all real indices and adding 1 29 | var absentID = stringID(1) 30 | for _, str := range strs { 31 | id := sb.stringIDByString(str) 32 | strIDs = append(strIDs, id) 33 | absentID += id 34 | } 35 | // Ensure each index maps to the expected string 36 | for i, str := range strs { 37 | fetchedStr, err := sb.stringByID(stringID(i)) 38 | if err != nil { 39 | t.Fatalf("Expected index %d to return string %s, but got error %v", i, str, err) 40 | } 41 | if strings.Compare(fetchedStr, str) != 0 { 42 | t.Errorf("stringByID(%d) = %s, want %s", i, fetchedStr, str) 43 | } 44 | } 45 | // Ensure that the absent index is in fact absent. 46 | fetchedStr, err := sb.stringByID(absentID) 47 | if err == nil { 48 | t.Errorf("stringByID(%d) = %s, but should have been absent", absentID, fetchedStr) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /angular-metadata.tsconfig.json: -------------------------------------------------------------------------------- 1 | // WORKAROUND https://github.com/angular/angular/issues/18810 2 | { 3 | "compilerOptions": { 4 | "lib": [ 5 | "dom", 6 | "es2015" 7 | ], 8 | "experimentalDecorators": true, 9 | "types": [], 10 | "module": "amd", 11 | "moduleResolution": "node" 12 | }, 13 | "angularCompilerOptions": { 14 | "enableSummariesForJit": true 15 | }, 16 | "include": [ 17 | "node_modules/@angular/**/*" 18 | ], 19 | "exclude": [ 20 | "node_modules/@angular/cdk/schematics/**", 21 | "node_modules/@angular/cdk/typings/schematics/**", 22 | "node_modules/@angular/material/schematics/**", 23 | "node_modules/@angular/material/typings/schematics/**", 24 | "node_modules/@angular/common/upgrade*", 25 | "node_modules/@angular/router/upgrade*", 26 | "node_modules/@angular/bazel/**", 27 | "node_modules/@angular/compiler-cli/**", 28 | "node_modules/@angular/**/testing/**" 29 | 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /client/app/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm_bazel_typescript//:defs.bzl", "ts_library") 2 | load("@npm_angular_bazel//:index.bzl", "ng_module") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | licenses(["notice"]) # Apache License 2.0 7 | 8 | ng_module( 9 | name = "app_root", 10 | srcs = [ 11 | "app_root.ts", 12 | "app_root_module.ts", 13 | "app_routing_module.ts", 14 | "dialog_shortcuts.ts", 15 | ], 16 | assets = [ 17 | "app.css", 18 | "app.ng.html", 19 | ], 20 | deps = [ 21 | "//client/app/collections", 22 | "//client/app/dashboard", 23 | "//client/app/services", 24 | "//client/app/util", 25 | "//client/environments", 26 | "@npm//@angular/core", 27 | "@npm//@angular/forms", 28 | "@npm//@angular/material", 29 | "@npm//@angular/router", 30 | "@npm//rxjs", 31 | ], 32 | ) 33 | 34 | ts_library( 35 | name = "app_tests", 36 | testonly = True, 37 | srcs = ["app_root_test.ts"], 38 | deps = [ 39 | ":app_root", 40 | "//client/app/services", 41 | "//client/app/services:services_tests", 42 | "//client/app/util", 43 | "@npm//@angular/core", 44 | "@npm//@angular/material", 45 | "@npm//@angular/router", 46 | "@npm//@types/jasmine", 47 | ], 48 | ) 49 | -------------------------------------------------------------------------------- /client/app/app.css: -------------------------------------------------------------------------------- 1 | .toolbar-small { 2 | height: 34px; 3 | padding: 0; 4 | } 5 | -------------------------------------------------------------------------------- /client/app/app.ng.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /client/app/app_root.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {Component, Inject, OnDestroy} from '@angular/core'; 18 | import {MatDialog} from '@angular/material/dialog'; 19 | import {Router, NavigationEnd} from '@angular/router'; 20 | import {DialogShortcuts} from './dialog_shortcuts'; 21 | import {ShortcutId, ShortcutService, DeregistrationCallback} from './services/shortcut_service'; 22 | import {parseHashFragment} from './util'; 23 | import {COLLECTION_NAME_KEY} from './util/hash_keys'; 24 | import {Subject} from 'rxjs'; 25 | import {takeUntil} from 'rxjs/operators'; 26 | 27 | /** 28 | * The SchedViz app point of entry. 29 | */ 30 | @Component({ 31 | selector: 'app-root', 32 | templateUrl: './app.ng.html', 33 | styleUrls: ['./app.css'], 34 | }) 35 | export class AppRoot implements OnDestroy { 36 | private readonly unsub$ = new Subject(); 37 | private readonly deregisterShortcut: DeregistrationCallback; 38 | 39 | constructor( 40 | private readonly router: Router, private readonly dialog: MatDialog, 41 | private readonly shortcutService: ShortcutService, 42 | ) { 43 | 44 | this.deregisterShortcut = this.registerShortcut(); 45 | 46 | let {hash} = window.location; 47 | const hashMap = parseHashFragment(hash); 48 | if (hashMap[COLLECTION_NAME_KEY]) { 49 | // Strip off leading #. 50 | // This is safe because we previously checked that the hash was valid. 51 | hash = hash.substring(1); 52 | this.router.navigate(['/dashboard'], {fragment: hash}); 53 | } 54 | } 55 | 56 | private registerShortcut(): DeregistrationCallback { 57 | return this.shortcutService.register( 58 | ShortcutId.SHOW_SHORTCUTS, 59 | () => this.dialog.open(DialogShortcuts, {width: '600px'})); 60 | } 61 | 62 | 63 | ngOnDestroy() { 64 | this.deregisterShortcut(); 65 | this.unsub$.next(); 66 | this.unsub$.complete(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /client/app/app_root_module.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {NgModule} from '@angular/core'; 18 | import {FormsModule} from '@angular/forms'; 19 | import {MatButtonModule} from '@angular/material/button'; 20 | import {MatDialogModule} from '@angular/material/dialog'; 21 | import {MatDividerModule} from '@angular/material/divider'; 22 | import {MatIconModule} from '@angular/material/icon'; 23 | import {MatTableModule} from '@angular/material/table'; 24 | import {MatToolbarModule} from '@angular/material/toolbar'; 25 | 26 | // Prevent unused import error 27 | import {production} from '../environments/environment'; 28 | 29 | import {AppRoot} from './app_root'; 30 | import {AppRoutingModule} from './app_routing_module'; 31 | import {DialogShortcuts} from './dialog_shortcuts'; 32 | import {HttpCollectionDataService, LocalCollectionDataService} from './services/collection_data_service'; 33 | import {HttpMetricsService, LocalMetricsService} from './services/metrics_service'; 34 | import {HttpRenderDataService, LocalRenderDataService} from './services/render_data_service'; 35 | 36 | const collectionDataService = 37 | production ? HttpCollectionDataService : LocalCollectionDataService; 38 | const renderDataService = 39 | production ? HttpRenderDataService : LocalRenderDataService; 40 | const metricsService = production ? HttpMetricsService : LocalMetricsService; 41 | 42 | @NgModule({ 43 | declarations: [AppRoot, DialogShortcuts], 44 | exports: [AppRoot], 45 | imports: [ 46 | AppRoutingModule, 47 | FormsModule, 48 | MatButtonModule, 49 | MatDialogModule, 50 | MatDividerModule, 51 | MatIconModule, 52 | MatTableModule, 53 | MatToolbarModule, 54 | ], 55 | bootstrap: [AppRoot], 56 | providers: [ 57 | {provide: 'RenderDataService', useClass: renderDataService}, 58 | {provide: 'MetricsService', useClass: metricsService}, 59 | {provide: 'CollectionDataService', useClass: collectionDataService}, 60 | ], 61 | entryComponents: [DialogShortcuts] 62 | }) 63 | export class AppRootModule { 64 | } 65 | -------------------------------------------------------------------------------- /client/app/app_root_test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {TestBed, waitForAsync} from '@angular/core/testing'; 18 | import {MatDialog} from '@angular/material/dialog'; 19 | import {Router} from '@angular/router'; 20 | import {RouterTestingModule} from '@angular/router/testing'; 21 | 22 | import {AppRoot} from './app_root'; 23 | import {AppRootModule} from './app_root_module'; 24 | import {routes} from './app_routing_module'; 25 | import {ShortcutId, ShortcutService} from './services/shortcut_service'; 26 | import {triggerShortcut} from './services/shortcut_service_test'; 27 | import {serializeHashFragment} from './util'; 28 | 29 | describe('AppRoot', () => { 30 | beforeEach(waitForAsync(() => { 31 | TestBed 32 | .configureTestingModule({ 33 | imports: [AppRootModule, RouterTestingModule.withRoutes(routes)], 34 | providers: [ 35 | {provide: 'ShortcutService', useClass: ShortcutService}, 36 | ], 37 | }) 38 | .compileComponents(); 39 | })); 40 | 41 | it('should create component', () => { 42 | const fixture = TestBed.createComponent(AppRoot); 43 | const component = fixture.componentInstance; 44 | fixture.detectChanges(); 45 | expect(component).toBeTruthy(); 46 | }); 47 | 48 | it('should register show shortcut handler', () => { 49 | const fixture = TestBed.createComponent(AppRoot); 50 | fixture.detectChanges(); 51 | const dialog = TestBed.get(MatDialog) as MatDialog; 52 | const shortcutService = fixture.debugElement.injector.get(ShortcutService); 53 | 54 | const dialogOpenSpy = spyOn(dialog, 'open'); 55 | const shortcut = shortcutService.getShortcuts()[ShortcutId.SHOW_SHORTCUTS]; 56 | 57 | expect(shortcut.isEnabled).toBe(true); 58 | triggerShortcut(shortcut); 59 | expect(dialogOpenSpy).toHaveBeenCalledTimes(1); 60 | }); 61 | 62 | }); 63 | -------------------------------------------------------------------------------- /client/app/app_routing_module.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {NgModule} from '@angular/core'; 18 | import {RouterModule, Routes} from '@angular/router'; 19 | 20 | import {Collections} from './collections/collections'; 21 | import {CollectionsModule} from './collections/collections_module'; 22 | import {CollectionsToolbar} from './collections/collections_toolbar'; 23 | import {Dashboard} from './dashboard/dashboard'; 24 | import {DashboardModule} from './dashboard/dashboard_module'; 25 | import {DashboardToolbar} from './dashboard/dashboard_toolbar'; 26 | 27 | /** 28 | * Routes used for app navigation 29 | */ 30 | export const routes: Routes = [ 31 | {path: '', redirectTo: '/collections', pathMatch: 'full'}, 32 | { 33 | path: 'collections', 34 | children: [ 35 | {path: '', component: Collections}, 36 | {path: '', component: CollectionsToolbar, outlet: 'toolbar'}, 37 | ] 38 | }, 39 | { 40 | path: 'dashboard', 41 | children: [ 42 | {path: '', component: Dashboard}, 43 | {path: '', component: DashboardToolbar, outlet: 'toolbar'}, 44 | ] 45 | }, 46 | ]; 47 | 48 | @NgModule({ 49 | imports: [CollectionsModule, DashboardModule, RouterModule.forRoot(routes)], 50 | exports: [RouterModule] 51 | }) 52 | export class AppRoutingModule { 53 | } 54 | -------------------------------------------------------------------------------- /client/app/collections/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm_bazel_typescript//:defs.bzl", "ts_library") 2 | load("@npm_angular_bazel//:index.bzl", "ng_module") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | licenses(["notice"]) # Apache License 2.0 7 | 8 | ng_module( 9 | name = "collections", 10 | srcs = [ 11 | "collections.ts", 12 | "collections_module.ts", 13 | "collections_table.ts", 14 | "collections_toolbar.ts", 15 | "selectable_anchor.ts", 16 | ], 17 | assets = [ 18 | "collections.ng.html", 19 | "collections.css", 20 | "collections_table.css", 21 | "collections_table.ng.html", 22 | "collections_toolbar.css", 23 | "collections_toolbar.ng.html", 24 | ], 25 | deps = [ 26 | "//client/app/models", 27 | "//client/app/models:collections_filter", 28 | "//client/app/services", 29 | "//client/app/util", 30 | "@npm//@angular/cdk", 31 | "@npm//@angular/common", 32 | "@npm//@angular/core", 33 | "@npm//@angular/forms", 34 | "@npm//@angular/material", 35 | "@npm//@angular/platform-browser", 36 | "@npm//@angular/router", 37 | "@npm//@types/node", 38 | "@npm//d3", 39 | "@npm//rxjs", 40 | ], 41 | ) 42 | 43 | ts_library( 44 | name = "collections_tests", 45 | testonly = True, 46 | srcs = ["collections_test.ts"], 47 | deps = [ 48 | ":collections", 49 | "//client/app:app_root", 50 | "//client/app/dashboard", 51 | "//client/app/models", 52 | "//client/app/services", 53 | "//client/app/util", 54 | "@npm//@angular/common", 55 | "@npm//@angular/core", 56 | "@npm//@angular/forms", 57 | "@npm//@angular/material", 58 | "@npm//@angular/platform-browser", 59 | "@npm//@angular/router", 60 | "@npm//@types/jasmine", 61 | "@npm//rxjs", 62 | ], 63 | ) 64 | -------------------------------------------------------------------------------- /client/app/collections/collections.css: -------------------------------------------------------------------------------- 1 | .collection-forms { 2 | background-color: #dbddeb; 3 | padding-left: 5px; 4 | padding-right: 5px; 5 | } 6 | 7 | .collection-forms span, .collection-forms label { 8 | margin-left: 8px; 9 | margin-right: 8px; 10 | font-size: 18px; 11 | font-family: sans-serif; 12 | vertical-align: middle; 13 | } 14 | 15 | .collection-forms span.upload-file-label { 16 | margin-right: 0; 17 | } 18 | 19 | mat-form-field { 20 | margin-left: 5px; 21 | margin-right: 5px; 22 | } 23 | 24 | form { 25 | display: inline-block; 26 | } 27 | 28 | .name { 29 | font-size: 14px; 30 | height: 14px; 31 | } 32 | 33 | .description { 34 | font-size: 10px; 35 | } 36 | 37 | -------------------------------------------------------------------------------- /client/app/collections/collections.ng.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 10 | 14 |
15 |
16 | or 17 | 18 | 21 | 22 | 25 |
26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /client/app/collections/collections_module.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {HttpClientModule} from '@angular/common/http'; 18 | import {NgModule} from '@angular/core'; 19 | import {FormsModule} from '@angular/forms'; 20 | import {MatButtonModule} from '@angular/material/button'; 21 | import {MatCheckboxModule} from '@angular/material/checkbox'; 22 | import {MatDialogModule} from '@angular/material/dialog'; 23 | import {MatIconModule} from '@angular/material/icon'; 24 | import {MatInputModule} from '@angular/material/input'; 25 | import {MatPaginatorModule} from '@angular/material/paginator'; 26 | import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; 27 | import {MatSelectModule} from '@angular/material/select'; 28 | import {MatSnackBarModule} from '@angular/material/snack-bar'; 29 | import {MatSortModule} from '@angular/material/sort'; 30 | import {MatTableModule} from '@angular/material/table'; 31 | import {MatTooltipModule} from '@angular/material/tooltip'; 32 | import {BrowserModule} from '@angular/platform-browser'; 33 | import {RouterModule} from '@angular/router'; 34 | 35 | import {Collections} from './collections'; 36 | import {CollectionsTable, DialogDeleteConfirm} from './collections_table'; 37 | import {CollectionsToolbar} from './collections_toolbar'; 38 | import {SelectableAnchor} from './selectable_anchor'; 39 | 40 | @NgModule({ 41 | declarations: [ 42 | CollectionsTable, 43 | CollectionsToolbar, 44 | Collections, 45 | DialogDeleteConfirm, 46 | SelectableAnchor, 47 | ], 48 | exports: [CollectionsTable, CollectionsToolbar, Collections], 49 | imports: [ 50 | BrowserModule, 51 | RouterModule.forChild([]), 52 | FormsModule, 53 | HttpClientModule, 54 | MatDialogModule, 55 | MatSortModule, 56 | MatIconModule, 57 | MatButtonModule, 58 | MatCheckboxModule, 59 | MatPaginatorModule, 60 | MatTableModule, 61 | MatInputModule, 62 | MatSelectModule, 63 | MatSnackBarModule, 64 | MatProgressSpinnerModule, 65 | MatTooltipModule, 66 | ], 67 | entryComponents: [DialogDeleteConfirm] 68 | }) 69 | export class CollectionsModule { 70 | } 71 | -------------------------------------------------------------------------------- /client/app/collections/collections_table.css: -------------------------------------------------------------------------------- 1 | .table-container { 2 | height: calc(100% - 100px); 3 | overflow: hidden; 4 | } 5 | 6 | .mat-table { 7 | height: calc(100% - 93px); 8 | overflow: auto; 9 | } 10 | 11 | .mat-row { 12 | min-height: 36px; 13 | cursor: pointer; 14 | position: relative; 15 | user-select: none; 16 | } 17 | 18 | .mat-row-link { 19 | position: absolute; 20 | height: 100%; 21 | width: calc(100% - 50px); 22 | top: 0; 23 | right: 0; 24 | } 25 | 26 | mat-cell:first-of-type { 27 | pointer-events: auto; 28 | padding-left: 10px; 29 | } 30 | 31 | mat-header-cell:first-of-type { 32 | padding-left: 10px; 33 | } 34 | 35 | mat-header-cell:last-of-type { 36 | padding-right: 0px; 37 | } 38 | 39 | mat-cell { 40 | pointer-events: none; 41 | font-size: 12px; 42 | } 43 | 44 | mat-cell:last-of-type { 45 | padding-right: 10px; 46 | } 47 | 48 | .table-container-events { 49 | height: 100%; 50 | margin-bottom: 10px; 51 | } 52 | 53 | .mat-icon { 54 | text-align: center; 55 | cursor: pointer; 56 | margin-right: 8px; 57 | vertical-align: middle; 58 | } 59 | 60 | .mat-header-cell { 61 | font-size: 10px; 62 | } 63 | 64 | .mat-header-row { 65 | min-height: 50px; 66 | } 67 | 68 | .no-data-label { 69 | position: absolute; 70 | width: 100%; 71 | top: 240px; 72 | font-family: Roboto; 73 | text-align: center; 74 | } 75 | 76 | .spinner-container { 77 | display: flex; 78 | justify-content: center; 79 | align-items: center; 80 | top: -100%; 81 | background-color: white; 82 | position: relative; 83 | /* Material Drop down menus have z-index 1000, so set to be one lower so they can still be seen */ 84 | z-index: 999; 85 | width: 100%; 86 | height: 100%; 87 | } 88 | 89 | .metadata-row { 90 | cursor: pointer; 91 | } 92 | 93 | .metadata-row:hover { 94 | background: #f5f5f5; 95 | } 96 | 97 | .metadata-row:active { 98 | background: #efefef; 99 | } 100 | 101 | mat-form-field.mat-form-field { 102 | font-size: 14px; 103 | } 104 | 105 | .mat-column-select { 106 | overflow: initial; 107 | flex: 0 0 40px; 108 | } 109 | 110 | .selection-info { 111 | font-family: sans-serif; 112 | vertical-align: middle; 113 | margin-left: 10px; 114 | } 115 | 116 | selectable-anchor { 117 | z-index: 1; 118 | } 119 | -------------------------------------------------------------------------------- /client/app/collections/collections_toolbar.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | align-items: flex-start; 4 | justify-content: space-between; 5 | width: 100%; 6 | height: 100%; 7 | padding-left: 16px; 8 | } 9 | 10 | .toolbar-buttons { 11 | margin-top: -3px; 12 | } 13 | -------------------------------------------------------------------------------- /client/app/collections/collections_toolbar.ng.html: -------------------------------------------------------------------------------- 1 |

SchedViz

2 |
3 | 9 |
10 | -------------------------------------------------------------------------------- /client/app/collections/collections_toolbar.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 18 | 19 | /** 20 | * A toolbar for the collections page 21 | */ 22 | @Component({ 23 | selector: 'collections-toolbar', 24 | styleUrls: ['collections_toolbar.css'], 25 | templateUrl: './collections_toolbar.ng.html', 26 | changeDetection: ChangeDetectionStrategy.OnPush, 27 | }) 28 | export class CollectionsToolbar { 29 | constructor() {} 30 | } 31 | -------------------------------------------------------------------------------- /client/app/collections/selectable_anchor.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {Component, Input, OnInit} from '@angular/core'; 18 | 19 | /** 20 | * An anchor component which supports text selection without following the link. 21 | */ 22 | @Component({ 23 | selector: 'selectable-anchor', 24 | template: ` 25 | 29 | 30 | 31 | `, 32 | styles: [` 33 | .selectable-text { 34 | text-decoration: inherit; 35 | color: inherit; 36 | user-select: text; 37 | pointer-events: auto; 38 | } 39 | `] 40 | }) 41 | export class SelectableAnchor implements OnInit { 42 | @Input() href!: string; 43 | 44 | ngOnInit() { 45 | if (!this.href) { 46 | throw new Error('Must provide href to SelectableAnchor'); 47 | } 48 | } 49 | 50 | onClick() { 51 | // Stop the event if there is text selected 52 | return !this.isTextSelected(); 53 | } 54 | 55 | isTextSelected() { 56 | const selection = window.getSelection(); 57 | if (!selection) { 58 | return false; 59 | } 60 | 61 | return selection.toString().length > 0; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /client/app/dashboard/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm_bazel_typescript//:defs.bzl", "ts_library") 2 | load("@npm_angular_bazel//:index.bzl", "ng_module") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | licenses(["notice"]) # Apache License 2.0 7 | 8 | ng_module( 9 | name = "dashboard", 10 | srcs = [ 11 | "dashboard.ts", 12 | "dashboard_module.ts", 13 | "dashboard_toolbar.ts", 14 | ], 15 | assets = [ 16 | "dashboard.css", 17 | "dashboard.ng.html", 18 | "dashboard_toolbar.css", 19 | "dashboard_toolbar.ng.html", 20 | ], 21 | deps = [ 22 | "//client/app/heatmap", 23 | "//client/app/models", 24 | "//client/app/services", 25 | "//client/app/sidebar", 26 | "//client/app/util", 27 | "@npm//@angular/common", 28 | "@npm//@angular/core", 29 | "@npm//@angular/forms", 30 | "@npm//@angular/material", 31 | "@npm//@angular/platform-browser", 32 | "@npm//@angular/router", 33 | "@npm//@types/node", 34 | "@npm//rxjs", 35 | ], 36 | ) 37 | 38 | ts_library( 39 | name = "dashboard_tests", 40 | testonly = True, 41 | srcs = ["dashboard_test.ts"], 42 | deps = [ 43 | ":dashboard", 44 | "//client/app/heatmap", 45 | "//client/app/models", 46 | "//client/app/services", 47 | "//client/app/sidebar", 48 | "//client/app/util", 49 | "@npm//@angular/common", 50 | "@npm//@angular/core", 51 | "@npm//@angular/forms", 52 | "@npm//@angular/material", 53 | "@npm//@angular/platform-browser", 54 | "@npm//@types/jasmine", 55 | "@npm//rxjs", 56 | ], 57 | ) 58 | -------------------------------------------------------------------------------- /client/app/dashboard/dashboard.css: -------------------------------------------------------------------------------- 1 | #heatmapContainer { 2 | margin: 4px; 3 | display: inline-block; 4 | width: calc(100% - 10px); 5 | height: calc(100% - 48px); 6 | overflow: hidden; 7 | border: 1px solid lightgray; 8 | } 9 | 10 | #sidebarContainer { 11 | display: inline-block; 12 | width: 50%; 13 | height: calc(100% - 20px); 14 | vertical-align: top; 15 | overflow: hidden; 16 | } 17 | 18 | .container { 19 | position: absolute; 20 | width: 100%; 21 | height: 100%; 22 | display: flex; 23 | top: 34px; 24 | bottom: 0; 25 | left: 0; 26 | right: 0; 27 | flex-direction: initial; 28 | } 29 | 30 | .spinner-container { 31 | display: flex; 32 | justify-content: center; 33 | align-items: center; 34 | top: 33px; 35 | background-color: white; 36 | position: absolute; 37 | z-index: 2000; 38 | width: 100%; 39 | height: 100%; 40 | } 41 | 42 | .sidenav-container { 43 | /* When the sidenav is not fixed, stretch the sidenav container to fill the 44 | * available space. This causes `` to act as our 45 | * scrolling element for desktop layouts. */ 46 | flex: 1; 47 | } 48 | 49 | heatmap { 50 | width: 100%; 51 | height: 100%; 52 | } 53 | 54 | sidebar { 55 | max-width: 600px; 56 | min-width: 600px; 57 | width: 600px; 58 | } 59 | -------------------------------------------------------------------------------- /client/app/dashboard/dashboard.ng.html: -------------------------------------------------------------------------------- 1 |
2 | 15 | 16 |
17 | 29 | 30 |
31 |
32 |
33 | 34 | 35 |
36 | 37 | -------------------------------------------------------------------------------- /client/app/dashboard/dashboard_module.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {CommonModule} from '@angular/common'; 18 | import {HttpClientJsonpModule, HttpClientModule} from '@angular/common/http'; 19 | import {NgModule} from '@angular/core'; 20 | import {FormsModule} from '@angular/forms'; 21 | import {MatButtonModule} from '@angular/material/button'; 22 | import {MatDialogModule} from '@angular/material/dialog'; 23 | import {MatIconModule} from '@angular/material/icon'; 24 | import {MatInputModule} from '@angular/material/input'; 25 | import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; 26 | import {MatSidenavModule} from '@angular/material/sidenav'; 27 | import {MatSnackBarModule} from '@angular/material/snack-bar'; 28 | import {MatTooltipModule} from '@angular/material/tooltip'; 29 | import {BrowserModule} from '@angular/platform-browser'; 30 | 31 | import {HeatmapModule} from '../heatmap/heatmap_module'; 32 | import {SidebarModule} from '../sidebar/sidebar_module'; 33 | 34 | import {Dashboard} from './dashboard'; 35 | import {DashboardToolbar, DialogEditCollection} from './dashboard_toolbar'; 36 | 37 | @NgModule({ 38 | declarations: [Dashboard, DashboardToolbar, DialogEditCollection], 39 | exports: [Dashboard, DashboardToolbar], 40 | imports: [ 41 | BrowserModule, 42 | FormsModule, 43 | MatButtonModule, 44 | MatDialogModule, 45 | MatIconModule, 46 | MatInputModule, 47 | MatSnackBarModule, 48 | MatTooltipModule, 49 | HttpClientModule, 50 | HttpClientJsonpModule, 51 | HeatmapModule, 52 | MatProgressSpinnerModule, 53 | MatSidenavModule, 54 | SidebarModule, 55 | CommonModule, 56 | ], 57 | entryComponents: [DialogEditCollection] 58 | }) 59 | export class DashboardModule { 60 | } 61 | -------------------------------------------------------------------------------- /client/app/dashboard/dashboard_toolbar.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | align-items: flex-start; 4 | justify-content: space-between; 5 | width: 100%; 6 | height: 100%; 7 | } 8 | 9 | .dashboard-buttons { 10 | margin-top: -3px; 11 | } 12 | 13 | .collection-metadata { 14 | font-size: 16px; 15 | font-weight: normal; 16 | flex: 1 1 auto; 17 | overflow: hidden; 18 | text-align: center; 19 | } 20 | @media (max-width: 1230px) { 21 | .collection-metadata { 22 | font-size: 14px; 23 | } 24 | } 25 | @media (min-width: 1475px) { 26 | .collection-metadata { 27 | font-size: 18px; 28 | } 29 | } 30 | 31 | .emphasized { 32 | font-family: monospace; 33 | } 34 | 35 | .collections-button { 36 | font-size: 30px; 37 | padding: 0; 38 | height: 100%; 39 | margin-top: -3px; 40 | } 41 | -------------------------------------------------------------------------------- /client/app/dashboard/dashboard_toolbar.ng.html: -------------------------------------------------------------------------------- 1 | 7 | 12 |
13 | 19 | 25 | 31 |
32 | -------------------------------------------------------------------------------- /client/app/dialog_shortcuts.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {Component} from '@angular/core'; 18 | import {MatDialogRef} from '@angular/material/dialog'; 19 | 20 | import {Shortcut, ShortcutService} from './services/shortcut_service'; 21 | 22 | /** 23 | * Dialog containing a list of all available shortcuts. 24 | */ 25 | @Component({ 26 | selector: 'dialog-shortcuts', 27 | template: ` 28 |

29 | Keyboard shortcuts 30 |

31 | 32 |
33 | 34 | 35 | {{element.description}} 36 | 37 | 38 | {{element.friendlyKeyText}} 40 | 41 | 43 | 44 |
45 |
47 | 52 |
53 | `, 54 | styles: [` 55 | .shortcut-key { 56 | justify-content: flex-end; 57 | font-family: monospace; 58 | } 59 | `] 60 | }) 61 | export class DialogShortcuts { 62 | displayedColumns = ['description', 'keys']; 63 | shortcuts: Shortcut[]; 64 | constructor( 65 | public dialogRef: MatDialogRef, 66 | shortcutService: ShortcutService) { 67 | this.shortcuts = shortcutService.getShortcuts(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /client/app/heatmap/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm_bazel_typescript//:defs.bzl", "ts_library") 2 | load("@npm_angular_bazel//:index.bzl", "ng_module") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | licenses(["notice"]) # Apache License 2.0 7 | 8 | ng_module( 9 | name = "heatmap", 10 | srcs = [ 11 | "heatmap.ts", 12 | "heatmap_module.ts", 13 | "index.ts", 14 | "intervals_layer.ts", 15 | "preview_layer.ts", 16 | "timeline_zoom_brush.ts", 17 | "x_axis_layer.ts", 18 | ], 19 | assets = [ 20 | "heatmap.css", 21 | "heatmap.ng.html", 22 | ], 23 | deps = [ 24 | "//client/app/heatmap/cpu_axes", 25 | "//client/app/heatmap/metrics_overlay", 26 | "//client/app/models", 27 | "//client/app/models:service_models", 28 | "//client/app/services", 29 | "//client/app/util", 30 | "@npm//@angular/common", 31 | "@npm//@angular/core", 32 | "@npm//@angular/forms", 33 | "@npm//@angular/material", 34 | "@npm//@angular/platform-browser", 35 | "@npm//@types/node", 36 | "@npm//d3", 37 | "@npm//rxjs", 38 | ], 39 | ) 40 | 41 | ts_library( 42 | name = "heatmap_tests", 43 | testonly = True, 44 | srcs = [ 45 | "heatmap_test.ts", 46 | "preview_layer_test.ts", 47 | "timeline_zoom_brush_test.ts", 48 | ], 49 | deps = [ 50 | ":heatmap", 51 | "//client/app/models", 52 | "//client/app/models:service_models", 53 | "//client/app/services", 54 | "//client/app/services:services_tests", 55 | "//client/app/util", 56 | "@npm//@angular/core", 57 | "@npm//@angular/material", 58 | "@npm//@angular/platform-browser", 59 | "@npm//@angular/platform-browser-dynamic", 60 | "@npm//@types/jasmine", 61 | "@npm//d3", 62 | "@npm//rxjs", 63 | ], 64 | ) 65 | -------------------------------------------------------------------------------- /client/app/heatmap/cpu_axes/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm_bazel_typescript//:defs.bzl", "ts_library") 2 | load("@npm_angular_bazel//:index.bzl", "ng_module") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | licenses(["notice"]) # Apache License 2.0 7 | 8 | ng_module( 9 | name = "cpu_axes", 10 | srcs = [ 11 | "cpu_axes_module.ts", 12 | "cpu_axis_layer.ts", 13 | "index.ts", 14 | "topological_cpu_axis_layer.ts", 15 | ], 16 | assets = [ 17 | "cpu_axes.css", 18 | ], 19 | deps = [ 20 | "//client/app/util", 21 | "@npm//@angular/core", 22 | "@npm//@angular/platform-browser", 23 | "@npm//@types/node", 24 | "@npm//d3", 25 | "@npm//rxjs", 26 | ], 27 | ) 28 | 29 | ts_library( 30 | name = "cpu_axes_tests", 31 | testonly = True, 32 | srcs = [ 33 | "cpu_axes_test.ts", 34 | ], 35 | deps = [ 36 | ":cpu_axes", 37 | "//client/app/heatmap", 38 | "//client/app/models", 39 | "//client/app/services", 40 | "//client/app/util", 41 | "@npm//@angular/core", 42 | "@npm//@angular/platform-browser-dynamic", 43 | "@npm//@types/jasmine", 44 | "@npm//d3", 45 | "@npm//rxjs", 46 | ], 47 | ) 48 | -------------------------------------------------------------------------------- /client/app/heatmap/cpu_axes/cpu_axes.css: -------------------------------------------------------------------------------- 1 | .cpuLabel { 2 | cursor: pointer; 3 | fill: #eee; 4 | font-family: Roboto; 5 | font-size: 8px; 6 | } 7 | 8 | .cpuLabel:hover { 9 | fill: #ffca28; 10 | } 11 | -------------------------------------------------------------------------------- /client/app/heatmap/cpu_axes/cpu_axes_module.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {NgModule, NO_ERRORS_SCHEMA} from '@angular/core'; 18 | import {BrowserModule} from '@angular/platform-browser'; 19 | 20 | import {UtilModule} from '../../util'; 21 | 22 | import {CpuAxisLayer} from './cpu_axis_layer'; 23 | import {TopologicalCpuAxisLayer} from './topological_cpu_axis_layer'; 24 | 25 | @NgModule({ 26 | declarations: [ 27 | CpuAxisLayer, 28 | TopologicalCpuAxisLayer, 29 | ], 30 | imports: [ 31 | BrowserModule, 32 | UtilModule, 33 | ], 34 | exports: [ 35 | CpuAxisLayer, 36 | TopologicalCpuAxisLayer, 37 | ], 38 | schemas: [NO_ERRORS_SCHEMA], 39 | }) 40 | export class CpuAxesModule { 41 | } 42 | -------------------------------------------------------------------------------- /client/app/heatmap/cpu_axes/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | export * from './cpu_axes_module'; 18 | export * from './cpu_axis_layer'; 19 | export * from './topological_cpu_axis_layer'; 20 | -------------------------------------------------------------------------------- /client/app/heatmap/heatmap.css: -------------------------------------------------------------------------------- 1 | #scalingSvgContainer { 2 | position: relative; 3 | height: calc(100% - 43px); 4 | width: 100%; 5 | background: #000; 6 | } 7 | 8 | .scaling-svg { 9 | position: absolute; 10 | height: 100%; 11 | width: 100%; 12 | } 13 | 14 | .loader-hidden { 15 | visibility: hidden; 16 | } 17 | 18 | .loader-overlay { 19 | position: absolute; 20 | width:100%; 21 | top:0; 22 | left: 0; 23 | opacity: 1; 24 | z-index: 500000; 25 | } 26 | 27 | .tooltip { 28 | color: white; 29 | font-family: Roboto, sans-serif; 30 | position: absolute; 31 | z-index: 1000; 32 | background: #546e7a; 33 | border-radius: 10px; 34 | padding: 5px 10px; 35 | font-size: 14px; 36 | border: 2px solid #ffc107; 37 | opacity: 0; 38 | white-space: pre-wrap; 39 | pointer-events: none; 40 | } 41 | 42 | .heatmap-loading { 43 | position: absolute; 44 | top: 5px; 45 | z-index: 1; 46 | } 47 | -------------------------------------------------------------------------------- /client/app/heatmap/heatmap.ng.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 |
11 |
12 |
13 | {{prop.key}}: {{prop.value}}
14 |
15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 40 | 41 | 42 | 43 | 48 | 49 | 50 | 51 | 52 | 58 | 59 | 65 | 66 | 70 | 71 | 72 | 73 |
74 | -------------------------------------------------------------------------------- /client/app/heatmap/heatmap_module.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {HttpClientModule} from '@angular/common/http'; 18 | import {NgModule, NO_ERRORS_SCHEMA} from '@angular/core'; 19 | import {FormsModule} from '@angular/forms'; 20 | import {MatButtonModule} from '@angular/material/button'; 21 | import {MatDialogModule} from '@angular/material/dialog'; 22 | import {MatIconModule} from '@angular/material/icon'; 23 | import {MatProgressBarModule} from '@angular/material/progress-bar'; 24 | import {MatSelectModule} from '@angular/material/select'; 25 | import {MatSnackBarModule} from '@angular/material/snack-bar'; 26 | import {MatTooltipModule} from '@angular/material/tooltip'; 27 | import {BrowserModule} from '@angular/platform-browser'; 28 | 29 | import {UtilModule} from '../util'; 30 | 31 | import {CpuAxesModule} from './cpu_axes'; 32 | import {DialogChooseThreadLayer, Heatmap, IntervalsLayer, PreviewLayer, TimelineZoomBrush, XAxisLayer} from './index'; 33 | import {MetricsOverlayModule} from './metrics_overlay'; 34 | 35 | 36 | @NgModule({ 37 | declarations: [ 38 | Heatmap, 39 | PreviewLayer, 40 | XAxisLayer, 41 | IntervalsLayer, 42 | TimelineZoomBrush, 43 | DialogChooseThreadLayer, 44 | ], 45 | imports: [ 46 | BrowserModule, 47 | FormsModule, 48 | CpuAxesModule, 49 | HttpClientModule, 50 | MatButtonModule, 51 | MatSelectModule, 52 | MatDialogModule, 53 | MatIconModule, 54 | MatProgressBarModule, 55 | MatSnackBarModule, 56 | MatTooltipModule, 57 | UtilModule, 58 | MetricsOverlayModule, 59 | ], 60 | exports: [ 61 | Heatmap, 62 | ], 63 | schemas: [NO_ERRORS_SCHEMA], 64 | entryComponents: [ 65 | DialogChooseThreadLayer, 66 | ] 67 | }) 68 | export class HeatmapModule { 69 | } 70 | -------------------------------------------------------------------------------- /client/app/heatmap/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | export * from './intervals_layer'; 18 | export * from './heatmap'; 19 | export * from './preview_layer'; 20 | export * from './timeline_zoom_brush'; 21 | export * from './x_axis_layer'; 22 | export * from './metrics_overlay'; 23 | -------------------------------------------------------------------------------- /client/app/heatmap/metrics_overlay/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm_bazel_typescript//:defs.bzl", "ts_library") 2 | load("@npm_angular_bazel//:index.bzl", "ng_module") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | licenses(["notice"]) # Apache License 2.0 7 | 8 | ng_module( 9 | name = "metrics_overlay", 10 | srcs = [ 11 | "index.ts", 12 | "metrics_overlay.ts", 13 | "metrics_overlay_module.ts", 14 | ], 15 | assets = [ 16 | "metrics_overlay.css", 17 | "metrics_overlay.ng.html", 18 | "dialog_metrics_help.ng.html", 19 | ], 20 | deps = [ 21 | "//client/app/models", 22 | "//client/app/services", 23 | "//client/app/util", 24 | "@npm//@angular/common", 25 | "@npm//@angular/core", 26 | "@npm//@angular/material", 27 | "@npm//@angular/platform-browser", 28 | "@npm//@types/node", 29 | "@npm//d3", 30 | "@npm//rxjs", 31 | ], 32 | ) 33 | 34 | ts_library( 35 | name = "metrics_overlay_tests", 36 | testonly = True, 37 | srcs = [ 38 | "metrics_overlay_test.ts", 39 | ], 40 | deps = [ 41 | ":metrics_overlay", 42 | "//client/app/models", 43 | "//client/app/services", 44 | "//client/app/util", 45 | "@npm//@angular/core", 46 | "@npm//@angular/platform-browser-dynamic", 47 | "@npm//@types/jasmine", 48 | "@npm//d3", 49 | "@npm//rxjs", 50 | ], 51 | ) 52 | -------------------------------------------------------------------------------- /client/app/heatmap/metrics_overlay/dialog_metrics_help.ng.html: -------------------------------------------------------------------------------- 1 |

Idle-While-Overloaded Metrics

2 | 3 | The amount of time at least one CPU sat idle while runnable threads waited on 4 | another CPU. CPUs for which the trace contains no information are not included 5 | in these metrics, since it isn't possible to distinguish whether they were 6 | disabled, idle, or running a single thread during the entire trace. 7 |
    8 |
  • 9 | The Wall Time metric shows the accumulated time during which any 10 | CPU was idle while another CPU was overloaded. 11 |
  • 12 |
  • 13 | The Per-CPU Time metric shows the total time, aggregated across 14 | all idle CPUs, for which there was some other overloaded CPU. A given idle 15 | CPU is only matched with a single overloaded CPU. 16 |
  • 17 |
  • 18 | The Per-Thread Time metric shows the total amount of work, in 19 | CPU-seconds, that could have been accomplished if waiting threads had been 20 | moved to idle CPUs in the current view. A given idle CPU is only matched 21 | with a single waiting thread. Note that system policies such as CPU-set 22 | affinity may have precluded such moves. 23 |
  • 24 |
  • 25 | The CPU Utilization metric shows the proportion of not-idle 26 | CPU-time in the current view. 27 |
  • 28 |
29 | For example, if two CPUs were overloaded for one second, one with one waiting 30 | thread and the other with two waiting threads, and four other CPUs were idle 31 | for that same second, the Wall Time for that interval would be one 32 | second (At least one CPU was idle while another was overloaded for the entire 33 | second); the Per-CPU Time would be two seconds (two CPUs were 34 | overloaded while at least two others were idle); and the 35 | Per-Thread Time would be three seconds (three threads were waiting 36 | while at least three CPUs were idle.) If, however, only two CPUs were idle 37 | during that second, Per-CPU Time would remain the same while 38 | Per-Thread Time would only be two seconds, because while three threads 39 | were waiting over that second, only two CPUs were idle. 40 |
41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /client/app/heatmap/metrics_overlay/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | export * from './metrics_overlay'; 18 | export * from './metrics_overlay_module'; 19 | -------------------------------------------------------------------------------- /client/app/heatmap/metrics_overlay/metrics_overlay.css: -------------------------------------------------------------------------------- 1 | :host { 2 | background-color: #475d67; 3 | font-family: sans-serif; 4 | font-size: 13px; 5 | display: block; 6 | color: #fff; 7 | padding: 1px 10px 2px 10px; 8 | height: 40px; 9 | } 10 | 11 | .metric { 12 | display: inline-block; 13 | margin-right: 3px; 14 | } 15 | 16 | .metric:not(:last-child):after { 17 | content: '|'; 18 | margin-left: 3px; 19 | margin-right: 3px; 20 | } 21 | 22 | .metric-name { 23 | font-weight: bold; 24 | } 25 | 26 | .title > span { 27 | font-size: 14px; 28 | font-weight: bold; 29 | text-decoration: underline; 30 | margin-right: 5px; 31 | vertical-align: middle; 32 | } 33 | 34 | .help-button { 35 | width: 25px; 36 | height: 25px; 37 | line-height: 25px; 38 | } 39 | 40 | -------------------------------------------------------------------------------- /client/app/heatmap/metrics_overlay/metrics_overlay.ng.html: -------------------------------------------------------------------------------- 1 |
2 | Idle While Overloaded 3 | 10 |
11 |
12 |
13 | Wall Time: 14 | {{metrics.wallTime | formatTime}} 15 | 16 | {{metrics.wallTime/viewportDuration.value | percent:'1.3-3'}} 17 | 18 |
19 |
20 | Per CPU Time: 21 | {{metrics.perCpuTime | formatTime}} 22 | 23 | {{metrics.perCpuTime/viewportDuration.value | percent:'1.3-3'}} 24 | 25 |
26 |
27 | Per Thread Time: 28 | {{metrics.perThreadTime | formatTime}} 29 | 30 | {{metrics.perThreadTime/viewportDuration.value | percent:'1.3-3'}} 31 | 32 |
33 |
34 | CPU Utilization Fraction: 35 | 36 | {{metrics.cpuUtilizationFraction | percent:'1.2-2'}} 37 | 38 |
39 |
40 | -------------------------------------------------------------------------------- /client/app/heatmap/metrics_overlay/metrics_overlay_module.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {NgModule} from '@angular/core'; 18 | import {MatButtonModule} from '@angular/material/button'; 19 | import {MatDialogModule} from '@angular/material/dialog'; 20 | import {MatIconModule} from '@angular/material/icon'; 21 | import {MatSnackBarModule} from '@angular/material/snack-bar'; 22 | import {BrowserModule} from '@angular/platform-browser'; 23 | 24 | import {UtilModule} from '../../util'; 25 | 26 | import {DialogMetricsHelp, MetricsOverlay} from './metrics_overlay'; 27 | 28 | 29 | @NgModule({ 30 | declarations: [ 31 | MetricsOverlay, 32 | DialogMetricsHelp, 33 | ], 34 | imports: [ 35 | BrowserModule, 36 | MatButtonModule, 37 | MatDialogModule, 38 | MatIconModule, 39 | MatSnackBarModule, 40 | UtilModule, 41 | ], 42 | exports: [ 43 | MetricsOverlay, 44 | DialogMetricsHelp, 45 | ], 46 | entryComponents: [DialogMetricsHelp] 47 | }) 48 | export class MetricsOverlayModule { 49 | } 50 | -------------------------------------------------------------------------------- /client/app/heatmap/x_axis_layer.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {Component, ElementRef, Input, OnInit, ViewChild} from '@angular/core'; 18 | import * as d3 from 'd3'; 19 | import {BehaviorSubject} from 'rxjs'; 20 | 21 | import {CollectionParameters} from '../models'; 22 | import {Viewport} from '../util'; 23 | import * as Duration from '../util/duration'; 24 | 25 | /** 26 | * The xAxis component displays adaptable time ticks for the heatmap, based on 27 | * the current viewport. 28 | */ 29 | @Component({ 30 | selector: '[xAxis]', 31 | template: ` 32 | 33 | 39 | 40 | `, 41 | }) 42 | export class XAxisLayer implements OnInit { 43 | constructor() {} 44 | @ViewChild('xAxis', {static: true}) xAxis!: ElementRef; 45 | @Input() parameters!: BehaviorSubject; 46 | @Input() viewport = new BehaviorSubject(new Viewport()); 47 | 48 | ngOnInit() { 49 | // Check required inputs 50 | if (!this.parameters) { 51 | throw new Error('Missing required CollectionParameters'); 52 | } 53 | if (!this.viewport) { 54 | throw new Error('Missing Observable for viewport'); 55 | } 56 | // Redraw axis on viewport change. 57 | this.viewport.subscribe((viewport) => { 58 | this.drawAxis(viewport); 59 | }); 60 | } 61 | 62 | /** 63 | * Draws the x-axis ticks based on the current domain size. 64 | */ 65 | drawAxis(viewport: Viewport) { 66 | const parameters = this.parameters.value; 67 | if (!parameters) { 68 | return; 69 | } 70 | const domainSize = parameters.endTimeNs - parameters.startTimeNs; 71 | const xAxisDomain = 72 | [viewport.left * domainSize, viewport.right * domainSize]; 73 | const zoomedDomainSize = xAxisDomain[1] - xAxisDomain[0]; 74 | const unit = Duration.getMinDurationUnits(zoomedDomainSize); 75 | const chartWidthPx = viewport.chartWidthPx; 76 | const chartHeightPx = viewport.chartHeightPx; 77 | // Draw x-axis according to domain size. 78 | const x = d3.scaleLinear().domain(xAxisDomain).range([0, chartWidthPx]); 79 | const xAxisFunc = d3.axisBottom(x).tickFormat((d) => { 80 | return Duration.getHumanReadableDurationFromNs( 81 | d.valueOf(), unit.units[0]); 82 | }); 83 | // Style ticks. 84 | const xAxis = d3.select(this.xAxis.nativeElement); 85 | xAxis.select('.axisBase').attr('width', chartWidthPx + 200); 86 | xAxis.attr('transform', `translate(0, ${chartHeightPx})`); 87 | xAxis.call(xAxisFunc); 88 | xAxis.selectAll('text') 89 | .style('text-anchor', 'end') 90 | .attr('dx', '-.8em') 91 | .attr('dy', '.15em') 92 | .attr('transform', 'rotate(-45)') 93 | .attr('color', '#eee'); 94 | xAxis.selectAll('.tick line') 95 | .attr('y1', -1 * chartHeightPx) 96 | .attr('stroke', '#eee'); 97 | xAxis.selectAll('.domain').attr('stroke', '#eee'); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /client/app/models/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm_bazel_typescript//:defs.bzl", "ts_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | licenses(["notice"]) # Apache License 2.0 6 | 7 | ts_library( 8 | name = "models", 9 | srcs = [ 10 | "checkpoint.ts", 11 | "collection.ts", 12 | "cpu_intervals.ts", 13 | "cpu_layers.ts", 14 | "ftrace_interval.ts", 15 | "index.ts", 16 | "interval.ts", 17 | "layer.ts", 18 | "sched_event.ts", 19 | "thread.ts", 20 | "thread_intervals.ts", 21 | "utilization_metrics.ts", 22 | ], 23 | deps = [ 24 | ":collections_filter", 25 | ":service_models", 26 | "//client/app/util", 27 | "@npm//d3", 28 | ], 29 | ) 30 | 31 | ts_library( 32 | name = "service_models", 33 | srcs = [ 34 | "collection_data_services.ts", 35 | "events.ts", 36 | "metrics_services.ts", 37 | "render_data_services.ts", 38 | ], 39 | ) 40 | 41 | ts_library( 42 | name = "collections_filter", 43 | srcs = [ 44 | "collections_filter.ts", 45 | ], 46 | deps = ["@npm//rxjs"], 47 | ) 48 | 49 | ts_library( 50 | name = "interval_tests", 51 | testonly = True, 52 | srcs = ["ftrace_interval_test.ts"], 53 | deps = [ 54 | ":models", 55 | ":service_models", 56 | "@npm//@types/jasmine", 57 | ], 58 | ) 59 | -------------------------------------------------------------------------------- /client/app/models/checkpoint.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {Layer} from './layer'; 18 | import {Viewport} from '../util/viewport'; 19 | 20 | /** 21 | * The types that a value in the Checkpoint could be 22 | */ 23 | export type CheckpointValue = string|number|Viewport|boolean|Layer[]|undefined; 24 | 25 | /** 26 | * Checkpoint stores state related to display options 27 | */ 28 | export declare interface Checkpoint { 29 | displayOptions: { 30 | commandFilter: string, 31 | cpuFilter: string, 32 | maxIntervalCount: number, 33 | pidFilter: string, 34 | tab: string, 35 | showMigrations: boolean, 36 | showSleeping: boolean, 37 | threadListSortField: string, 38 | threadListSortOrder: number, 39 | viewport: Viewport, 40 | layers: Layer[], 41 | expandedThread: string, 42 | }; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /client/app/models/collection.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {CollectionParametersResponse} from './collection_data_services'; 18 | import {Metadata} from './events'; 19 | 20 | const NANOS_TO_MILLIS = 1e6; 21 | 22 | /** 23 | * Describes the high-level parameters of a collection, for view sizing, etc. 24 | */ 25 | export class CollectionParameters { 26 | readonly domainSizeNs: number; 27 | constructor( 28 | public name: string, 29 | public cpus: number[], 30 | public startTimeNs: number, 31 | public endTimeNs: number, 32 | public ftraceEventTypes: string[] = [], 33 | ) { 34 | this.domainSizeNs = this.endTimeNs - this.startTimeNs; 35 | } 36 | 37 | get size() { 38 | return this.cpus.length; 39 | } 40 | 41 | static fromJSON(json: CollectionParametersResponse): CollectionParameters { 42 | return new CollectionParameters( 43 | json.collectionName, 44 | json.cpus, 45 | json.startTimestampNs, 46 | json.endTimestampNs, 47 | json.ftraceEvents, 48 | ); 49 | } 50 | } 51 | 52 | /** 53 | * Describes high-level metadata of a collection. 54 | */ 55 | export class CollectionMetadata { 56 | constructor( 57 | public name: string, 58 | public creator: string, 59 | public owners: string[], 60 | public tags: string[], 61 | public description: string, 62 | public creationTime: Date|undefined, 63 | public eventNames: string[], 64 | public targetMachine: string, 65 | ) {} 66 | 67 | static fromJSON(json: Metadata): CollectionMetadata { 68 | return new CollectionMetadata( 69 | json.collectionUniqueName, 70 | json.creator, 71 | json.owners, 72 | json.tags, 73 | json.description, 74 | new Date(json.creationTime / NANOS_TO_MILLIS), 75 | json.ftraceEvents, 76 | json.targetMachine, 77 | ); 78 | } 79 | } 80 | 81 | /** 82 | * CollectionDuration contains metadata about a collection duration. 83 | */ 84 | export interface CollectionDuration { 85 | duration: number; 86 | name: string; 87 | description: string; 88 | } 89 | 90 | 91 | -------------------------------------------------------------------------------- /client/app/models/collections_filter.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {BehaviorSubject} from 'rxjs'; 18 | 19 | /** 20 | * The JSON representation of CollectionsFilter 21 | */ 22 | export interface CollectionsFilterJSON { 23 | creationTime?: string; 24 | description?: string; 25 | name?: string; 26 | tags?: string; 27 | targetMachine?: string; 28 | } 29 | 30 | /** 31 | * Helper Type to get all keys of an object that have a given type. 32 | */ 33 | type KeysOfType = K extends K ? 34 | TObj[K] extends TProp ? K : never : 35 | never; 36 | 37 | /** 38 | * CollectionFilterKeys is a type containing all the properties 39 | * (i.e. not functions) of CollectionsFilter. 40 | */ 41 | export type CollectionFilterKeys = KeysOfType; 42 | 43 | /** 44 | * Collections filter is a model class that holds the filter state 45 | * of the collections_table. 46 | */ 47 | export class CollectionsFilter { 48 | changes = new BehaviorSubject<{prop: string, newVal: string}|null>(null); 49 | constructor(filter: {[k in CollectionFilterKeys]?: string} = {}) { 50 | Object.assign(this, filter); 51 | } 52 | 53 | private targetMachineVal = ''; 54 | get targetMachine() { 55 | return this.targetMachineVal; 56 | } 57 | set targetMachine(newtargetMachine: string) { 58 | this.targetMachineVal = newtargetMachine; 59 | this.changes.next({prop: 'targetMachine', newVal: newtargetMachine}); 60 | } 61 | 62 | private creationTimeVal = ''; 63 | get creationTime() { 64 | return this.creationTimeVal; 65 | } 66 | set creationTime(newcreationTime: string) { 67 | this.creationTimeVal = newcreationTime; 68 | this.changes.next({prop: 'creationTime', newVal: newcreationTime}); 69 | } 70 | 71 | private tagsVal = ''; 72 | get tags() { 73 | return this.tagsVal; 74 | } 75 | set tags(newtags: string) { 76 | this.tagsVal = newtags; 77 | this.changes.next({prop: 'tags', newVal: newtags}); 78 | } 79 | 80 | private descriptionVal = ''; 81 | get description() { 82 | return this.descriptionVal; 83 | } 84 | set description(newdescription: string) { 85 | this.descriptionVal = newdescription; 86 | this.changes.next({prop: 'description', newVal: newdescription}); 87 | } 88 | 89 | private nameVal = ''; 90 | get name() { 91 | return this.nameVal; 92 | } 93 | set name(newname: string) { 94 | this.nameVal = newname; 95 | this.changes.next({prop: 'name', newVal: newname}); 96 | } 97 | 98 | toJSON(): CollectionsFilterJSON { 99 | const ret: CollectionsFilterJSON = {}; 100 | if (this.creationTime != null) { 101 | ret.creationTime = this.creationTime; 102 | } 103 | if (this.description != null) { 104 | ret.description = this.description; 105 | } 106 | if (this.name != null) { 107 | ret.name = this.name; 108 | } 109 | if (this.tags != null) { 110 | ret.tags = this.tags; 111 | } 112 | if (this.targetMachine != null) { 113 | ret.targetMachine = this.targetMachine; 114 | } 115 | return ret; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /client/app/models/events.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | /** 18 | * Metadata contains metadata about a collection 19 | */ 20 | export declare interface Metadata { 21 | // The unique name of the collection in PCC2. 22 | collectionUniqueName: string; 23 | // The creator tag provided at this collection's creation. 24 | creator: string; 25 | // This collection's owners. 26 | owners: string[]; 27 | // The collection's tags. 28 | tags: string[]; 29 | // The collection's description. 30 | description: string; 31 | // The time of this collection's creation. 32 | creationTime: number; 33 | // The events collected during the collection. 34 | ftraceEvents: string[]; 35 | // The target machine on which the collection was performed. 36 | targetMachine: string; 37 | // The schedviz.analysis.event_loaders.LoadersType used by default for this 38 | // collection. 39 | defaultEventLoader: number; 40 | } 41 | -------------------------------------------------------------------------------- /client/app/models/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | export * from './collection'; 18 | export * from './collections_filter'; 19 | export * from './checkpoint'; 20 | export * from './cpu_intervals'; 21 | export * from './cpu_layers'; 22 | export * from './interval'; 23 | export * from './layer'; 24 | export * from './sched_event'; 25 | export * from './thread'; 26 | export * from './ftrace_interval'; 27 | export * from './thread_intervals'; 28 | export * from './utilization_metrics'; 29 | 30 | import * as events from './events'; 31 | import * as collectionDataServices from './collection_data_services'; 32 | import * as metricsServices from './metrics_services'; 33 | import * as renderDataServices from './render_data_services'; 34 | 35 | /** 36 | * All service interfaces 37 | */ 38 | export const services = { 39 | events, 40 | ...collectionDataServices, 41 | ...metricsServices, 42 | ...renderDataServices, 43 | }; 44 | -------------------------------------------------------------------------------- /client/app/models/layer.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import * as d3 from 'd3'; 18 | 19 | import {Interval} from './interval'; 20 | 21 | // Dummy interface to prevent property renaming. 22 | declare interface LayerProps { 23 | name: string; 24 | dataType: string; 25 | ids: number[]; 26 | color: string; 27 | intervals: Interval[]; 28 | visible: boolean; 29 | interpolate: boolean; 30 | borderRadius: number; 31 | drawEdges: boolean; 32 | parent?: string; 33 | initialized: boolean; 34 | } 35 | 36 | /** 37 | * Client-side collection interval representation, for rendering. 38 | * TODO(tracked) Make templated and remove casts of intervals member 39 | */ 40 | export class Layer implements LayerProps { 41 | // Flag used to indicate when a Layer is new and has no initial render data 42 | initialized = false; 43 | 44 | constructor( 45 | public name: string, public dataType: string, public ids: number[] = [], 46 | public color = '#ffffff', public intervals: Interval[] = [], 47 | public visible = true, public interpolate = ids.length > 1, 48 | public borderRadius = 30, public drawEdges = true, 49 | public parent?: string) {} 50 | 51 | /** 52 | * @return the render color to use for the given layer datum. 53 | */ 54 | getIntervalColor(interval: Interval) { 55 | if (this.interpolate) { 56 | const idx = this.ids.indexOf(interval.id); 57 | const brighter = d3.hsl(this.color).brighter(1); 58 | const darker = d3.hsl(this.color).darker(2); 59 | const color = 60 | d3.interpolateHsl(brighter, darker)(idx / (this.ids.length - 1)); 61 | return color; 62 | } 63 | return this.color; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /client/app/models/sched_event.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {CollectionParameters} from './collection'; 18 | import {Interval} from './interval'; 19 | 20 | /** 21 | * Interval subclass representing an instantaneous event. 22 | */ 23 | export class SchedEvent extends Interval { 24 | constructor( 25 | public parameters: CollectionParameters, public uid: number, 26 | public name: string, public cpu: number, public timestampNs: number, 27 | public properties?: Map) { 28 | super(parameters, uid, cpu, timestampNs); 29 | this.opacity = 0.5; 30 | } 31 | 32 | get tooltipProps() { 33 | const tooltip : {[key: string]: string} = { 34 | 'Type': this.name, 35 | 'CPU': `${this.cpu}`, 36 | 'Timestamp': this.formatTime(this.startTimeNs), 37 | }; 38 | if (this.properties) { 39 | for (const property of this.properties.keys()) { 40 | tooltip[property] = `${this.properties.get(property)}`; 41 | } 42 | } 43 | return tooltip; 44 | } 45 | 46 | get dataType() { 47 | return 'SchedEvent'; 48 | } 49 | 50 | get label() { 51 | return `${this.name} : ${this.uid}`; 52 | } 53 | 54 | /** 55 | * @return render height, weighted by wait queue size 56 | */ 57 | height(sortedFilteredCpus: number[]) { 58 | return this.rowHeight(sortedFilteredCpus); 59 | } 60 | 61 | /** 62 | * Waiting intervals have straight edges 63 | */ 64 | rx(sortedFilteredCpus: number[]) { 65 | return 0; 66 | } 67 | 68 | /** 69 | * Waiting intervals have straight edges 70 | */ 71 | ry(sortedFilteredCpus: number[]) { 72 | return 0; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /client/app/models/thread.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {CollectionParameters} from './collection'; 18 | import {CpuInterval} from './cpu_intervals'; 19 | import {FtraceInterval} from './ftrace_interval'; 20 | import {Interval} from './interval'; 21 | import {ThreadState} from './render_data_services'; 22 | import {ThreadInterval} from './thread_intervals'; 23 | 24 | /** 25 | * Thread-specific datum with static thread metrics and a default CPU list 26 | * (represented as full collection-length intervals) for hover preview. 27 | */ 28 | export class Thread extends Interval { 29 | events: FtraceInterval[] = []; 30 | antagonists: ThreadInterval[] = []; 31 | intervals: ThreadInterval[] = []; 32 | constructor( 33 | public parameters: CollectionParameters, 34 | public pid: number, 35 | public cpus: number[], 36 | public command: string, 37 | public wakeups: number, 38 | public migrations: number, 39 | public runtime: number, 40 | public waittime: number, 41 | public sleeptime: number, 42 | public unknowntime: number, 43 | public eventsPending = false, 44 | public antagonistsPending = false, 45 | public intervalsPending = false, 46 | ) { 47 | super(parameters, pid); 48 | // Add a full-width interval for each CPU, for preview rendering. 49 | for (const cpu of cpus) { 50 | this.addChild(new CpuInterval( 51 | parameters, cpu, parameters.startTimeNs, parameters.endTimeNs, [{ 52 | thread: { 53 | pid: this.pid, 54 | command, 55 | priority: 0, 56 | }, 57 | duration: parameters.endTimeNs - parameters.startTimeNs, 58 | state: ThreadState.RUNNING_STATE, 59 | droppedEventIDs: [], 60 | includesSyntheticTransitions: false, 61 | }])); 62 | } 63 | } 64 | 65 | get tooltipProps() { 66 | return { 67 | 'Pid': `${this.pid}`, 68 | 'Command': this.command, 69 | 'CPUs': `${this.cpus}`, 70 | 'Start Time': this.formatTime(this.startTimeNs), 71 | 'End Time': this.formatTime(this.endTimeNs), 72 | 'Duration': this.formatTime(this.endTimeNs - this.startTimeNs), 73 | }; 74 | } 75 | 76 | get dataType() { 77 | return 'Thread'; // TODO(sainsley): store types in a util class or enum 78 | } 79 | 80 | get label() { 81 | return `${this.dataType}:${this.id}:${this.command}`; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /client/app/models/utilization_metrics.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {UtilizationMetricsResponse} from './metrics_services'; 18 | 19 | /** 20 | * UtilizationMetrics stores Idle While Overloaded Metrics. 21 | */ 22 | export class UtilizationMetrics { 23 | constructor( 24 | public wallTime: number, 25 | public perCpuTime: number, 26 | public perThreadTime: number, 27 | public cpuUtilizationFraction: number, 28 | ) {} 29 | 30 | static fromJSON(json: UtilizationMetricsResponse): UtilizationMetrics { 31 | const um = json.utilizationMetrics; 32 | return new UtilizationMetrics( 33 | um.wallTime, 34 | um.perCpuTime, 35 | um.perThreadTime, 36 | um.cpuUtilizationFraction, 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /client/app/services/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm_bazel_typescript//:defs.bzl", "ts_library") 2 | load("@npm_angular_bazel//:index.bzl", "ng_module") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | licenses(["notice"]) # Apache License 2.0 7 | 8 | ng_module( 9 | name = "services", 10 | srcs = [ 11 | "collection_data_service.ts", 12 | "color_service.ts", 13 | "index.ts", 14 | "metrics_service.ts", 15 | "render_data_service.ts", 16 | "shortcut_service.ts", 17 | ], 18 | deps = [ 19 | "//client/app/models", 20 | "//client/app/models:service_models", 21 | "//client/app/util", 22 | "//client/environments", 23 | "@npm//@angular/common", 24 | "@npm//@angular/core", 25 | "@npm//d3", 26 | "@npm//rxjs", 27 | ], 28 | ) 29 | 30 | ts_library( 31 | name = "services_tests", 32 | testonly = True, 33 | srcs = ["shortcut_service_test.ts"], 34 | deps = [ 35 | ":services", 36 | "@npm//@types/jasmine", 37 | ], 38 | ) 39 | -------------------------------------------------------------------------------- /client/app/services/color_service.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {Injectable} from '@angular/core'; 18 | import * as d3 from 'd3'; 19 | import {BehaviorSubject} from 'rxjs'; 20 | 21 | import {Layer} from '../models'; 22 | 23 | const GOOGLE_COLORS = [ 24 | '#4285f4', '#db4437', '#fbc02d', '#0f9d58', '#ab47bc', '#00acc1', '#ff7043', 25 | '#9e9d24', '#5c6bc0', '#f06292', '#00796b', '#c2185b', '#ff9800', '#8bc34a' 26 | ]; 27 | 28 | const MATERIAL_COLORS = [ 29 | // Red-Pink 30 | '#f44336', 31 | // Pink 32 | '#e91e63', 33 | // Purple 34 | '#9c27b0', 35 | // Indigo 36 | '#673ab7', 37 | // Grey blue 38 | '#3f51b5', 39 | // Blue 40 | '#2196f3', 41 | // Cyan 42 | '#03a9f4', 43 | // Turquoise 44 | '#00bcd4', 45 | // Teal 46 | '#009688', 47 | // Green 48 | '#4caf50', 49 | // Olive 50 | '#8bc34a', 51 | // Yellow 52 | '#cddc39', 53 | // Golden 54 | '#ffeb3b', 55 | // Orange 56 | '#ffc107', 57 | // Red-Orange 58 | '#ff9800', 59 | // Red 60 | '#ff5722', 61 | ]; 62 | 63 | 64 | /** 65 | * A service that provides root colors for new layers, attempting to maximize 66 | * the distance between colors in the heatmap. 67 | */ 68 | @Injectable({providedIn: 'root'}) 69 | export class ColorService { 70 | private lastColorIdx = -1; 71 | 72 | 73 | usedColors: {[name: string]: number|string} = {}; 74 | 75 | 76 | /** 77 | * @param name The key of the layer to get/generate a color for 78 | * @return The next available render color, given the current set 79 | * of layers. If the layer has already been assigned a color, the existing 80 | * color will be returned. 81 | */ 82 | getColorFor( 83 | layer: Layer|string, 84 | ): string { 85 | const name = layer instanceof Layer ? layer.name : layer; 86 | const existingColor = this.usedColors[name]; 87 | if (existingColor) { 88 | return (typeof existingColor === 'string') ? 89 | existingColor : 90 | MATERIAL_COLORS[existingColor]; 91 | } 92 | const nextColorIdx = (this.lastColorIdx + 1) % MATERIAL_COLORS.length; 93 | this.usedColors[name] = nextColorIdx; 94 | const nextColor = MATERIAL_COLORS[nextColorIdx]; 95 | this.lastColorIdx = nextColorIdx; 96 | return nextColor; 97 | } 98 | 99 | 100 | /** 101 | * Update the saved color for a layer 102 | * @param name The key of a layer to set the color for 103 | * @param color The color to save 104 | */ 105 | setColorFor(name: string, color: string) { 106 | this.usedColors[name] = color; 107 | } 108 | 109 | /** 110 | * Remove the saved color for a layer 111 | * @param name The key of the layer to remove the color for 112 | */ 113 | removeColorFor(name: string) { 114 | delete this.usedColors[name]; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /client/app/services/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | export * from './collection_data_service'; 18 | export * from './color_service'; 19 | export * from './metrics_service'; 20 | export * from './render_data_service'; 21 | -------------------------------------------------------------------------------- /client/app/sidebar/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm_bazel_typescript//:defs.bzl", "ts_library") 2 | load("@npm_angular_bazel//:index.bzl", "ng_module") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | licenses(["notice"]) # Apache License 2.0 7 | 8 | ng_module( 9 | name = "sidebar", 10 | srcs = [ 11 | "sidebar.ts", 12 | "sidebar_module.ts", 13 | ], 14 | assets = [ 15 | "sidebar.ng.html", 16 | "sidebar.css", 17 | ], 18 | strict_templates = False, 19 | deps = [ 20 | "//client/app/models", 21 | "//client/app/services", 22 | "//client/app/sidebar/settings_menu", 23 | "//client/app/sidebar/thread_table", 24 | "//client/app/util", 25 | "@npm//@angular/common", 26 | "@npm//@angular/core", 27 | "@npm//@angular/forms", 28 | "@npm//@angular/material", 29 | "@npm//@angular/platform-browser", 30 | "@npm//@types/node", 31 | "@npm//rxjs", 32 | ], 33 | ) 34 | 35 | ts_library( 36 | name = "sidebar_tests", 37 | testonly = True, 38 | srcs = ["sidebar_test.ts"], 39 | deps = [ 40 | ":sidebar", 41 | "//client/app/models", 42 | "//client/app/services", 43 | "//client/app/sidebar/thread_table:thread_table_tests", 44 | "//client/app/util", 45 | "@npm//@angular/common", 46 | "@npm//@angular/core", 47 | "@npm//@angular/forms", 48 | "@npm//@angular/material", 49 | "@npm//@angular/platform-browser-dynamic", 50 | "@npm//@types/jasmine", 51 | "@npm//rxjs", 52 | ], 53 | ) 54 | -------------------------------------------------------------------------------- /client/app/sidebar/settings_menu/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm_bazel_typescript//:defs.bzl", "ts_library") 2 | load("@npm_angular_bazel//:index.bzl", "ng_module") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | licenses(["notice"]) # Apache License 2.0 7 | 8 | ng_module( 9 | name = "settings_menu", 10 | srcs = [ 11 | "settings_menu.ts", 12 | "settings_menu_module.ts", 13 | ], 14 | assets = [ 15 | "settings_menu.ng.html", 16 | "settings_menu.css", 17 | ], 18 | strict_templates = False, 19 | deps = [ 20 | "//client/app/models", 21 | "//client/app/services", 22 | "//client/app/util", 23 | "@npm//@angular/cdk", 24 | "@npm//@angular/common", 25 | "@npm//@angular/core", 26 | "@npm//@angular/forms", 27 | "@npm//@angular/material", 28 | "@npm//@angular/platform-browser", 29 | "@npm//@types/node", 30 | "@npm//hammerjs", 31 | "@npm//rxjs", 32 | ], 33 | ) 34 | 35 | ts_library( 36 | name = "settings_menu_tests", 37 | testonly = True, 38 | srcs = ["settings_menu_test.ts"], 39 | deps = [ 40 | ":settings_menu", 41 | "//client/app/models", 42 | "//client/app/util", 43 | "@npm//@angular/cdk", 44 | "@npm//@angular/core", 45 | "@npm//@angular/forms", 46 | "@npm//@angular/material", 47 | "@npm//@angular/platform-browser-dynamic", 48 | "@npm//@types/jasmine", 49 | "@npm//d3", 50 | "@npm//rxjs", 51 | ], 52 | ) 53 | -------------------------------------------------------------------------------- /client/app/sidebar/settings_menu/settings_menu.css: -------------------------------------------------------------------------------- 1 | .settings-icon { 2 | margin-right: 10px; 3 | } 4 | 5 | .display-slider { 6 | width: calc(100% - 72px); 7 | margin-top: -12px; 8 | padding: 0px; 9 | height: 24px; 10 | vertical-align: top; 11 | } 12 | 13 | .settings-toggle { 14 | display: block; 15 | } 16 | 17 | .slider-container { 18 | margin: 10px; 19 | } 20 | 21 | .layer-list { 22 | width: 500px; 23 | margin-top: 10px; 24 | max-width: 100%; 25 | border: solid 1px #ccc; 26 | display: block; 27 | background: white; 28 | border-radius: 4px; 29 | overflow: hidden; 30 | } 31 | 32 | .layer-box { 33 | padding: 20px 10px; 34 | border-bottom: solid 1px #ccc; 35 | color: rgba(0, 0, 0, 0.87); 36 | display: flex; 37 | flex-direction: row; 38 | align-items: center; 39 | justify-content: space-between; 40 | box-sizing: border-box; 41 | background: white; 42 | font-size: 14px; 43 | max-height: 48px; 44 | } 45 | 46 | .cdk-drag-preview { 47 | box-sizing: border-box; 48 | border-radius: 4px; 49 | box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 50 | 0 8px 10px 1px rgba(0, 0, 0, 0.14), 51 | 0 3px 14px 2px rgba(0, 0, 0, 0.12); 52 | } 53 | 54 | .cdk-drag-placeholder { 55 | opacity: 0; 56 | } 57 | 58 | .cdk-drag-animating { 59 | transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); 60 | } 61 | 62 | .layer-box:last-child { 63 | border: none; 64 | } 65 | 66 | .layer-list.cdk-drop-list-dragging .layer-box:not(.cdk-drag-placeholder) { 67 | transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); 68 | } 69 | 70 | .layer-handle { 71 | color: #ccc; 72 | cursor: move; 73 | width: 24px; 74 | height: 24px; 75 | margin-right: 14px; 76 | vertical-align: middle; 77 | display: inline-block; 78 | } 79 | 80 | .layer-label { 81 | width: calc(100% - 100px); 82 | } 83 | 84 | .layer-fixed { 85 | text-align: center; 86 | width: 100%; 87 | } 88 | 89 | .color-input { 90 | position: relative; 91 | width: 20px; 92 | } 93 | 94 | .icon-right { 95 | position: relative; 96 | right: -300px; 97 | } 98 | 99 | .fixed { 100 | background: #99999f; 101 | color: #fff; 102 | } 103 | 104 | .hidden { 105 | background: lightgray; 106 | color: #99999f; 107 | } 108 | 109 | mat-icon { 110 | cursor: pointer; 111 | margin-left: 5px; 112 | } 113 | 114 | p { 115 | display: inline; 116 | } 117 | 118 | .viewport-form { 119 | margin: 10px; 120 | } 121 | -------------------------------------------------------------------------------- /client/app/sidebar/settings_menu/settings_menu_module.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | 18 | import {DragDropModule} from '@angular/cdk/drag-drop'; 19 | import {CommonModule} from '@angular/common'; 20 | import {NgModule} from '@angular/core'; 21 | import {FormsModule} from '@angular/forms'; 22 | import {MatButtonModule} from '@angular/material/button'; 23 | import {MAT_HAMMER_OPTIONS} from '@angular/material/core'; 24 | import {MatExpansionModule} from '@angular/material/expansion'; 25 | import {MatFormFieldModule} from '@angular/material/form-field'; 26 | import {MatIconModule} from '@angular/material/icon'; 27 | import {MatInputModule} from '@angular/material/input'; 28 | import {MatSlideToggleModule} from '@angular/material/slide-toggle'; 29 | import {MatSliderModule} from '@angular/material/slider'; 30 | import {MatTooltipModule} from '@angular/material/tooltip'; 31 | import {BrowserModule} from '@angular/platform-browser'; 32 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; 33 | 34 | import {UtilModule} from '../../util'; 35 | 36 | import {SettingsMenu} from './settings_menu'; 37 | 38 | @NgModule({ 39 | declarations: [ 40 | SettingsMenu, 41 | ], 42 | exports: [ 43 | SettingsMenu, 44 | ], 45 | imports: [ 46 | BrowserAnimationsModule, 47 | BrowserModule, 48 | CommonModule, 49 | DragDropModule, 50 | MatButtonModule, 51 | MatIconModule, 52 | MatInputModule, 53 | MatExpansionModule, 54 | MatFormFieldModule, 55 | MatSliderModule, 56 | MatSlideToggleModule, 57 | MatTooltipModule, 58 | FormsModule, 59 | UtilModule, 60 | ], 61 | providers: [ 62 | { 63 | provide: MAT_HAMMER_OPTIONS, 64 | // Allow for selection of items underneath tooltips 65 | useValue: {cssProps: {userSelect: true}}, 66 | }, 67 | ], 68 | }) 69 | export class SettingsMenuModule { 70 | } 71 | -------------------------------------------------------------------------------- /client/app/sidebar/sidebar.css: -------------------------------------------------------------------------------- 1 | /deep/.mat-tab-label, /deep/.mat-tab-label-active{ 2 | min-width: 120px!important; 3 | padding: 3px!important; 4 | margin: 3px!important; 5 | } 6 | 7 | .layers-badge-icon { 8 | margin-top: 5px; 9 | } 10 | 11 | .cpu-filter-form { 12 | width: calc(100% - 60px); 13 | margin: 10px; 14 | } 15 | -------------------------------------------------------------------------------- /client/app/sidebar/sidebar.ng.html: -------------------------------------------------------------------------------- 1 |
2 | 4 | 5 | 17 | 18 | 19 | 20 | 26 | 27 | 28 | 29 | 30 | 34 | layers 35 | 36 | 37 | 38 | 42 | 43 | 48 | 55 | 56 | 57 | 58 |
59 | -------------------------------------------------------------------------------- /client/app/sidebar/sidebar_module.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {HttpClientModule} from '@angular/common/http'; 18 | import {NgModule} from '@angular/core'; 19 | import {FormsModule} from '@angular/forms'; 20 | import {MatBadgeModule} from '@angular/material/badge'; 21 | import {MatButtonModule} from '@angular/material/button'; 22 | import {MatIconModule} from '@angular/material/icon'; 23 | import {MatInputModule} from '@angular/material/input'; 24 | import {MatSnackBarModule} from '@angular/material/snack-bar'; 25 | import {MatTabsModule} from '@angular/material/tabs'; 26 | import {MatTooltipModule} from '@angular/material/tooltip'; 27 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; 28 | import {SettingsMenuModule} from './settings_menu/settings_menu_module'; 29 | import {Sidebar} from './sidebar'; 30 | import {ThreadTableModule} from './thread_table/thread_table_module'; 31 | 32 | @NgModule({ 33 | declarations: [ 34 | Sidebar, 35 | ], 36 | exports: [ 37 | Sidebar, 38 | ], 39 | imports: [ 40 | HttpClientModule, 41 | FormsModule, 42 | BrowserAnimationsModule, 43 | SettingsMenuModule, 44 | ThreadTableModule, 45 | MatBadgeModule, 46 | MatButtonModule, 47 | MatIconModule, 48 | MatTabsModule, 49 | MatInputModule, 50 | MatSnackBarModule, 51 | MatTooltipModule, 52 | ], 53 | }) 54 | export class SidebarModule { 55 | } 56 | -------------------------------------------------------------------------------- /client/app/sidebar/thread_table/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm_bazel_typescript//:defs.bzl", "ts_library") 2 | load("@npm_angular_bazel//:index.bzl", "ng_module") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | licenses(["notice"]) # Apache License 2.0 7 | 8 | ng_module( 9 | name = "thread_table", 10 | srcs = [ 11 | "antagonist_table.ts", 12 | "event_table.ts", 13 | "interval_table.ts", 14 | "jump_to_time.ts", 15 | "layer_toggle.ts", 16 | "sched_events_table.ts", 17 | "selectable_table.ts", 18 | "thread_table.ts", 19 | "thread_table_module.ts", 20 | ], 21 | assets = [ 22 | "antagonist_table.ng.html", 23 | "event_table.ng.html", 24 | "interval_table.ng.html", 25 | "layer_toggle.css", 26 | "sched_events_table.ng.html", 27 | "thread_table.ng.html", 28 | "thread_table.css", 29 | ], 30 | strict_templates = False, 31 | deps = [ 32 | "//client/app/models", 33 | "//client/app/services", 34 | "//client/app/util", 35 | "@npm//@angular/animations", 36 | "@npm//@angular/cdk", 37 | "@npm//@angular/core", 38 | "@npm//@angular/forms", 39 | "@npm//@angular/material", 40 | "@npm//@angular/platform-browser", 41 | "@npm//@types/node", 42 | "@npm//rxjs", 43 | ], 44 | ) 45 | 46 | ts_library( 47 | name = "thread_table_tests", 48 | testonly = True, 49 | srcs = [ 50 | "antagonist_table_test.ts", 51 | "event_table_test.ts", 52 | "interval_table_test.ts", 53 | "jump_to_time_test.ts", 54 | "sched_events_table_test.ts", 55 | "table_helpers_test.ts", 56 | "thread_table_test.ts", 57 | ], 58 | deps = [ 59 | ":thread_table", 60 | "//client/app/models", 61 | "//client/app/models:service_models", 62 | "@npm//@angular/core", 63 | "@npm//@angular/forms", 64 | "@npm//@angular/material", 65 | "@npm//@angular/platform-browser", 66 | "@npm//@angular/platform-browser-dynamic", 67 | "@npm//@types/jasmine", 68 | "@npm//d3", 69 | "@npm//rxjs", 70 | ], 71 | ) 72 | -------------------------------------------------------------------------------- /client/app/sidebar/thread_table/antagonist_table.ng.html: -------------------------------------------------------------------------------- 1 |
2 | 9 | 10 | 11 | 15 | 16 | layers 17 | 18 | 19 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | Antagonist PID 31 | {{thread.pid}} 32 | 33 | 34 | 35 | 36 | Antagonist Command 37 | {{thread.command}} 38 | 39 | 40 | 41 | 42 | Start Time 43 | {{formatTime(thread.startTimeNs)}} 44 | 45 | 46 | 47 | 48 | End Time 49 | {{formatTime(thread.endTimeNs)}} 50 | 51 | 52 | 53 | 54 | Duration 55 | {{formatTime(thread.duration)}} 56 | 57 | 58 | 59 | 60 | 64 | 65 | 66 |
67 | 70 | 71 | 72 | No data to display 73 | 74 |
75 | 76 | 77 |
78 | -------------------------------------------------------------------------------- /client/app/sidebar/thread_table/antagonist_table.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core'; 18 | import {Sort} from '@angular/material/sort'; 19 | import {BehaviorSubject, ReplaySubject} from 'rxjs'; 20 | import {takeUntil} from 'rxjs/operators'; 21 | 22 | import {ThreadInterval} from '../../models'; 23 | import {ColorService} from '../../services/color_service'; 24 | 25 | import {jumpToTime} from './jump_to_time'; 26 | import {SelectableTable} from './selectable_table'; 27 | 28 | /** 29 | * A place holder for a table showing thread antagonists. 30 | */ 31 | @Component({ 32 | selector: 'antagonist-table', 33 | styleUrls: ['./thread_table.css'], 34 | templateUrl: 'antagonist_table.ng.html', 35 | }) 36 | export class AntagonistTable extends SelectableTable implements OnInit { 37 | @Input() jumpToTimeNs!: ReplaySubject; 38 | sort = new BehaviorSubject({active: '', direction: ''}); 39 | 40 | constructor( 41 | public colorService: ColorService, protected cdr: ChangeDetectorRef) { 42 | super(colorService, cdr); 43 | this.displayedColumns = this.displayedColumns.concat( 44 | ['pid', 'command', 'startTimeNs', 'endTimeNs', 'duration']); 45 | } 46 | 47 | ngOnInit() { 48 | super.ngOnInit(); 49 | 50 | if (!this.jumpToTimeNs) { 51 | throw new Error('Missing Observable for jump to time'); 52 | } 53 | 54 | this.dataSource.sortingDataAccessor = (data, sortHeaderId) => 55 | this.getSortingValue(data as ThreadInterval, sortHeaderId); 56 | 57 | this.jumpToTimeNs.pipe(takeUntil(this.unsub$)).subscribe((timeNs) => { 58 | jumpToTime(this.dataSource, timeNs); 59 | }); 60 | } 61 | 62 | getSortingValue(thread: ThreadInterval, sortHeaderId: string): string|number { 63 | switch (sortHeaderId) { 64 | case 'selected': 65 | return thread.selected ? 1 : 0; 66 | case 'pid': 67 | return thread.pid; 68 | case 'command': 69 | return thread.command; 70 | case 'startTimeNs': 71 | return thread.startTimeNs; 72 | case 'endTimeNs': 73 | return thread.endTimeNs; 74 | case 'duration': 75 | return thread.duration; 76 | default: 77 | this.outputErrorThrottled(`Unknown header: ${sortHeaderId}`); 78 | return ''; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /client/app/sidebar/thread_table/event_table.ng.html: -------------------------------------------------------------------------------- 1 |
2 | 9 | 10 | 11 | 25 | 26 | 27 | 28 | 29 | Timestamp 30 | 31 | 33 | {{formatTime(thread_event.startTimeNs)}} 34 | 35 | 36 | 37 | 38 | 39 | Event Detail 40 | 42 | {{thread_event.description}} 43 | 44 | 45 | 46 | 47 | 48 | 52 | 53 | 54 |
55 | 58 | 59 | 60 | No data to display 61 | 62 |
63 | 64 | 65 |
66 | -------------------------------------------------------------------------------- /client/app/sidebar/thread_table/event_table.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core'; 18 | import {Sort} from '@angular/material/sort'; 19 | import {BehaviorSubject, ReplaySubject} from 'rxjs'; 20 | import {takeUntil} from 'rxjs/operators'; 21 | 22 | import {FtraceInterval} from '../../models'; 23 | import {ColorService} from '../../services/color_service'; 24 | import * as Duration from '../../util/duration'; 25 | 26 | import {jumpToTime} from './jump_to_time'; 27 | import {SelectableTable} from './selectable_table'; 28 | 29 | /** 30 | * The EventTable displays thread events in an Angular 2 material table. 31 | */ 32 | @Component({ 33 | selector: 'event-table', 34 | styleUrls: ['thread_table.css'], 35 | templateUrl: 'event_table.ng.html', 36 | }) 37 | export class EventTable extends SelectableTable implements OnInit { 38 | @Input() jumpToTimeNs!: ReplaySubject; 39 | sort = new BehaviorSubject({active: '', direction: ''}); 40 | 41 | constructor( 42 | public colorService: ColorService, protected cdr: ChangeDetectorRef) { 43 | super(colorService, cdr); 44 | this.displayedColumns = ['startTimeNs', 'description']; 45 | } 46 | 47 | ngOnInit() { 48 | super.ngOnInit(); 49 | 50 | if (!this.jumpToTimeNs) { 51 | throw new Error('Missing Observable for jump to time'); 52 | } 53 | 54 | this.dataSource.sortingDataAccessor = (data, sortHeaderId) => 55 | this.getSortingValue(data as FtraceInterval, sortHeaderId); 56 | 57 | this.jumpToTimeNs.pipe(takeUntil(this.unsub$)).subscribe((timeNs) => { 58 | jumpToTime(this.dataSource, timeNs); 59 | }); 60 | } 61 | 62 | /** 63 | * Helper method to format durations as trimmed, human readable strings. 64 | * @param durationNs the duration value in nanoseconds 65 | */ 66 | formatTime(durationNs: number) { 67 | return Duration.getHumanReadableDurationFromNs(durationNs); 68 | } 69 | 70 | getSortingValue(interval: FtraceInterval, sortHeaderId: string): string 71 | |number { 72 | switch (sortHeaderId) { 73 | case 'startTimeNs': 74 | return interval.startTimeNs; 75 | default: 76 | this.outputErrorThrottled(`Unknown header: ${sortHeaderId}`); 77 | return ''; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /client/app/sidebar/thread_table/interval_table.ng.html: -------------------------------------------------------------------------------- 1 |
2 | 13 | 14 | 18 | 19 | 28 | 29 | 36 | 37 | 38 | CPU 39 | {{thread.cpu}} 40 | 41 | 42 | 43 | 44 | State 45 | {{thread.threadStatesToString()}} 46 | 47 | 48 | 49 | 50 | Start Time 51 | {{formatTime(thread.startTimeNs)}} 52 | 53 | 54 | 55 | 56 | End Time 57 | {{formatTime(thread.endTimeNs)}} 58 | 59 | 60 | 61 | 62 | Duration 63 | {{formatTime(thread.duration)}} 64 | 65 | 66 | 67 | 68 | 72 | 73 | 74 |
75 | 78 | 79 | 80 | No data to display 81 | 82 |
83 | 84 | 85 |
86 | -------------------------------------------------------------------------------- /client/app/sidebar/thread_table/jump_to_time.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {MatSort, Sort} from '@angular/material/sort'; 18 | import {MatTableDataSource} from '@angular/material/table'; 19 | 20 | import {Interval} from '../../models/interval'; 21 | 22 | /** 23 | * Jumps a given table of intervals to the first page which contains an interval 24 | * starting at or after a given time. If such an interval does not exist, the 25 | * table will be moved to the last page. 26 | * 27 | * @param table is a table of intervals to be jumped 28 | * @param timeNs is the time to jump to 29 | */ 30 | export function jumpToTime( 31 | table: MatTableDataSource, timeNs: number) { 32 | const paginator = table.paginator; 33 | if (!paginator) { 34 | console.warn('Unable to jump to time, table not paginated.'); 35 | return; 36 | } 37 | 38 | if (!table.sort) { 39 | console.warn('Unable to jump to time, table not sortable.'); 40 | return; 41 | } 42 | 43 | // In order to jump to a time the timestamps must be in ascending 44 | // order 45 | ensureSort(table.sort, {active: 'startTimeNs', direction: 'asc'}); 46 | 47 | const targetIntervalIndex = 48 | table.data.findIndex((interval) => interval.startTimeNs >= timeNs); 49 | 50 | if (targetIntervalIndex < 0) { 51 | // If a time was requested greater than any existing timestamp go to the 52 | // end 53 | paginator.lastPage(); 54 | return; 55 | } 56 | 57 | jumpToRow(table, targetIntervalIndex); 58 | } 59 | 60 | /** 61 | * Jumps a given table of intervals to the page which contains the given row 62 | * 63 | * @param table is a table of intervals to be jumped 64 | * @param rowIndex the index of the row to jump to 65 | */ 66 | export function jumpToRow(table: MatTableDataSource, rowIndex: number) { 67 | const paginator = table.paginator; 68 | if (!paginator) { 69 | console.warn('Unable to jump to row, table not paginated.'); 70 | return; 71 | } 72 | const targetPageIndex = Math.floor(rowIndex / paginator.pageSize); 73 | paginator.pageIndex = 74 | Math.max(Math.min(targetPageIndex, paginator.getNumberOfPages() - 1), 0); 75 | 76 | // Due to a bug (https://github.com/angular/components/issues/12620, to be 77 | // fixed by https://github.com/angular/components/pull/12586) the page 78 | // change event isn't fired automatically, so we have to do it manually 79 | paginator.page.emit({ 80 | previousPageIndex: paginator.pageIndex, 81 | pageIndex: targetPageIndex, 82 | pageSize: paginator.pageSize, 83 | length: paginator.length 84 | }); 85 | } 86 | 87 | function ensureSort(sortContainer: MatSort, sort: Sort) { 88 | if (!sortContainer.sortables.has(sort.active)) { 89 | console.warn('Unknown sort key', sort.active); 90 | return; 91 | } 92 | 93 | // due to a bug (https://github.com/angular/components/issues/10242), we 94 | // need to manually invoke the sort 95 | if (sortContainer.active !== sort.active || 96 | sortContainer.direction !== sort.direction) { 97 | sortContainer.sort( 98 | {id: sort.active, start: sortContainer.start, disableClear: true}); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /client/app/sidebar/thread_table/layer_toggle.css: -------------------------------------------------------------------------------- 1 | .toggle-root { 2 | user-select: none; 3 | cursor: pointer; 4 | height: 20px; 5 | width: 20px; 6 | margin-top: -5px; 7 | transition: rotate 0.5s ease-in-out; 8 | } 9 | 10 | .opened .vertical { 11 | transform: rotate(-90deg); 12 | opacity: 0; 13 | } 14 | 15 | .opened .horizontal { 16 | transform: rotate(-180deg); 17 | opacity: 1; 18 | } 19 | 20 | .closed .vertical { 21 | transform: rotate(90deg); 22 | opacity: 1; 23 | } 24 | 25 | .closed .horizontal { 26 | transform: rotate(180deg); 27 | opacity: 0; 28 | } 29 | 30 | .horizontal { 31 | position: relative; 32 | margin-top: -30px; 33 | top: -15px; 34 | } 35 | 36 | .vertical { 37 | position: relative; 38 | } 39 | -------------------------------------------------------------------------------- /client/app/sidebar/thread_table/layer_toggle.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {Component, EventEmitter, Input, Output} from '@angular/core'; 18 | 19 | import {Interval} from '../../models'; 20 | 21 | /** 22 | * A custom toggle for user-created layers. Layer toggles appear in Thread, 23 | * Event, Task, etc. rows in their respective tables, and create (or remove) 24 | * dedicated render layers for their associated data on click. 25 | */ 26 | @Component({ 27 | selector: 'layer-toggle', 28 | template: ` 29 |
34 | add_circle_outline 35 | remove_circle 37 |
38 | `, 39 | styleUrls: ['layer_toggle.css'], 40 | }) 41 | export class LayerToggle { 42 | readonly DEFAULT_OPEN_COLOR = '#ffca28'; 43 | readonly DEFAULT_CLOSE_COLOR = '#718792'; 44 | 45 | @Input() toggledOn = false; 46 | @Input() color?: string; 47 | 48 | /** Thread, event, etc. currently selected via mouseover */ 49 | previewDatum?: Interval; 50 | 51 | onToggle() { 52 | this.toggledOn = !this.toggledOn; 53 | this.toggledOnChange.emit(this.toggledOn); 54 | } 55 | 56 | @Output() toggledOnChange = new EventEmitter(); 57 | } 58 | -------------------------------------------------------------------------------- /client/app/sidebar/thread_table/sched_events_table.ng.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 | 7 | 8 | 9 | 10 | layers 11 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Event Type 25 | {{eventType}} 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 37 | 38 | 39 | No data to display 40 | 41 |
42 | 43 | 44 |
45 | -------------------------------------------------------------------------------- /client/app/sidebar/thread_table/sched_events_table.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit} from '@angular/core'; 18 | 19 | import {ColorService} from '../../services/color_service'; 20 | 21 | import {SelectableTable} from './selectable_table'; 22 | 23 | /** 24 | * The SchedEventsTable displays a list of available ktraced event types for 25 | * this collection and a button to create a dedicated layer for a given type. 26 | */ 27 | @Component({ 28 | selector: 'sched-events-table', 29 | styleUrls: ['thread_table.css'], 30 | templateUrl: 'sched_events_table.ng.html', 31 | host: {'(window:resize)': 'onResize()'}, 32 | changeDetection: ChangeDetectionStrategy.OnPush 33 | }) 34 | export class SchedEventsTable extends SelectableTable implements OnInit, 35 | AfterViewInit { 36 | rowHeightPx = 24; 37 | constructor( 38 | public colorService: ColorService, protected cdr: ChangeDetectorRef) { 39 | super(colorService, cdr); 40 | this.displayedColumns = this.displayedColumns.concat(['name']); 41 | } 42 | 43 | ngOnInit() { 44 | super.ngOnInit(); 45 | } 46 | 47 | ngAfterViewInit() { 48 | this.data.subscribe(() => { 49 | this.onResize(); 50 | }); 51 | } 52 | 53 | toggleEventType(row: string) { 54 | super.toggleLayer(row, 'SchedEvent'); 55 | } 56 | 57 | onResize() { 58 | const clientHeight = this.table.nativeElement.clientHeight; 59 | if (!clientHeight) { 60 | return; 61 | } 62 | // Update pageSize 63 | const headerFooterSize = 107; // 51px for header, 56px for footer 64 | const tableHeightPx = clientHeight - headerFooterSize; 65 | const pageSize = Math.floor(tableHeightPx / this.rowHeightPx) - 1; 66 | this.paginator._changePageSize(pageSize); 67 | // Detect changes again because when this is called from ngAfterViewInit(), 68 | // the change cycle has already occurred. 69 | this.cdr.detectChanges(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /client/app/sidebar/thread_table/sched_events_table_test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing'; 18 | import {FormsModule} from '@angular/forms'; 19 | import {MatFormFieldModule} from '@angular/material/form-field'; 20 | import {MatIconModule} from '@angular/material/icon'; 21 | import {MatInputModule} from '@angular/material/input'; 22 | import {Sort} from '@angular/material/sort'; 23 | import {MatTableModule} from '@angular/material/table'; 24 | import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing'; 25 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; 26 | import {BehaviorSubject} from 'rxjs'; 27 | 28 | import {Interval, Layer} from '../../models'; 29 | 30 | import {SchedEventsTable} from './sched_events_table'; 31 | import {mockThreads, verifyLayerToggle} from './table_helpers_test'; 32 | import {ThreadTableModule} from './thread_table_module'; 33 | 34 | try { 35 | TestBed.initTestEnvironment( 36 | BrowserDynamicTestingModule, platformBrowserDynamicTesting()); 37 | } catch { 38 | // Ignore exceptions when calling it multiple times. 39 | } 40 | 41 | /** 42 | * Initializes the properties for a given SchedEventsTable. 43 | * 44 | * @param component is the table component to initialize. 45 | */ 46 | export function setupSchedEventsTable(component: SchedEventsTable) { 47 | component.data = new BehaviorSubject([]); 48 | component.preview = new BehaviorSubject(undefined); 49 | component.layers = new BehaviorSubject>>([]); 50 | component.sort = new BehaviorSubject({active: '', direction: ''}); 51 | component.tab = new BehaviorSubject(0); 52 | } 53 | 54 | function createTableWithMockData(): ComponentFixture { 55 | const fixture = TestBed.createComponent(SchedEventsTable); 56 | const component = fixture.componentInstance; 57 | setupSchedEventsTable(component); 58 | component.data.next(mockThreads()); 59 | fixture.detectChanges(); 60 | 61 | return fixture; 62 | } 63 | 64 | describe('SchedEventsTable', () => { 65 | beforeEach(waitForAsync(() => { 66 | document.body.style.width = '500px'; 67 | document.body.style.height = '500px'; 68 | TestBed 69 | .configureTestingModule({ 70 | imports: [ 71 | BrowserAnimationsModule, FormsModule, MatFormFieldModule, 72 | MatInputModule, MatTableModule, MatIconModule, ThreadTableModule 73 | ], 74 | }) 75 | .compileComponents(); 76 | })); 77 | 78 | it('should create', () => { 79 | const fixture = createTableWithMockData(); 80 | expect(fixture.componentInstance).toBeTruthy(); 81 | }); 82 | 83 | it('should create layer on toggle click', () => { 84 | const fixture = createTableWithMockData(); 85 | verifyLayerToggle(fixture.nativeElement, fixture.componentInstance); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /client/app/sidebar/thread_table/thread_table_module.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {NgModule} from '@angular/core'; 18 | import {FormsModule} from '@angular/forms'; 19 | import {MatBadgeModule} from '@angular/material/badge'; 20 | import {MatButtonModule} from '@angular/material/button'; 21 | import {MatCardModule} from '@angular/material/card'; 22 | import {MatCheckboxModule} from '@angular/material/checkbox'; 23 | import {MAT_HAMMER_OPTIONS} from '@angular/material/core'; 24 | import {MatIconModule} from '@angular/material/icon'; 25 | import {MatInputModule} from '@angular/material/input'; 26 | import {MatPaginatorModule} from '@angular/material/paginator'; 27 | import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; 28 | import {MatSelectModule} from '@angular/material/select'; 29 | import {MatSlideToggleModule} from '@angular/material/slide-toggle'; 30 | import {MatSortModule} from '@angular/material/sort'; 31 | import {MatTableModule} from '@angular/material/table'; 32 | import {MatTabsModule} from '@angular/material/tabs'; 33 | import {MatTooltipModule} from '@angular/material/tooltip'; 34 | import {BrowserModule} from '@angular/platform-browser'; 35 | 36 | import {UtilModule} from '../../util'; 37 | 38 | import {AntagonistTable} from './antagonist_table'; 39 | import {EventTable} from './event_table'; 40 | import {IntervalTable} from './interval_table'; 41 | import {LayerToggle} from './layer_toggle'; 42 | import {SchedEventsTable} from './sched_events_table'; 43 | import {SelectableTable} from './selectable_table'; 44 | import {ThreadTable} from './thread_table'; 45 | 46 | @NgModule({ 47 | declarations: [ 48 | IntervalTable, SchedEventsTable, ThreadTable, EventTable, AntagonistTable, 49 | SelectableTable, LayerToggle 50 | ], 51 | exports: [ 52 | SchedEventsTable, 53 | ThreadTable, 54 | EventTable, 55 | IntervalTable, 56 | AntagonistTable, 57 | SelectableTable, 58 | LayerToggle, 59 | ], 60 | imports: [ 61 | BrowserModule, 62 | FormsModule, 63 | MatCheckboxModule, 64 | MatButtonModule, 65 | MatIconModule, 66 | MatSelectModule, 67 | MatSortModule, 68 | MatTableModule, 69 | MatInputModule, 70 | MatPaginatorModule, 71 | MatCardModule, 72 | MatProgressSpinnerModule, 73 | MatBadgeModule, 74 | MatSlideToggleModule, 75 | MatTooltipModule, 76 | MatTabsModule, 77 | UtilModule, 78 | ], 79 | providers: [ 80 | { 81 | provide: MAT_HAMMER_OPTIONS, 82 | // Allow for selection of items underneath tooltips 83 | useValue: {cssProps: {userSelect: true}}, 84 | }, 85 | ], 86 | }) 87 | export class ThreadTableModule { 88 | } 89 | -------------------------------------------------------------------------------- /client/app/util/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm_angular_bazel//:index.bzl", "ng_module") 2 | load("@npm_bazel_typescript//:defs.bzl", "ts_library") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | licenses(["notice"]) # Apache License 2.0 7 | 8 | ng_module( 9 | name = "util", 10 | srcs = [ 11 | "clipboard.ts", 12 | "complex_system_topology.ts", 13 | "duration.ts", 14 | "duration_validator.ts", 15 | "error_snackbar.ts", 16 | "hash_compressor.ts", 17 | "hash_keys.ts", 18 | "helpers.ts", 19 | "index.ts", 20 | "system_topology.ts", 21 | "util_module.ts", 22 | "viewport.ts", 23 | ], 24 | deps = [ 25 | "//client/app/models:collections_filter", 26 | "//client/app/models:service_models", 27 | "@npm//@angular/cdk", 28 | "@npm//@angular/common", 29 | "@npm//@angular/core", 30 | "@npm//@angular/forms", 31 | "@npm//@angular/material", 32 | "@npm//pako", 33 | "@npm//rxjs", 34 | ], 35 | ) 36 | 37 | ts_library( 38 | name = "util_tests", 39 | testonly = True, 40 | srcs = [ 41 | "duration_test.ts", 42 | "hash_compressor_test.ts", 43 | ], 44 | deps = [ 45 | ":util", 46 | "@npm//@types/jasmine", 47 | ], 48 | ) 49 | -------------------------------------------------------------------------------- /client/app/util/clipboard.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | /** 18 | * Copies text to the clipboard via a temporary DOM text element. 19 | * 20 | * @param text is the text to copy 21 | * @return a flag indicating whether the copy succeeded 22 | */ 23 | export function copyToClipboard(text: string): boolean { 24 | const tempElement = document.createElement('textarea'); 25 | document.body.appendChild(tempElement); 26 | tempElement.value = text; 27 | tempElement.select(); 28 | let success: boolean; 29 | try { 30 | success = document.execCommand('copy'); 31 | } catch { 32 | success = false; 33 | } 34 | 35 | document.body.removeChild(tempElement); 36 | return success; 37 | } 38 | -------------------------------------------------------------------------------- /client/app/util/duration_validator.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {Directive} from '@angular/core'; 18 | import {AbstractControl, NG_VALIDATORS, Validator} from '@angular/forms'; 19 | 20 | import {getDurationInNsFromHumanReadableString} from './duration'; 21 | 22 | /** 23 | * Validates that a given control contains a valid duration value. 24 | */ 25 | @Directive({ 26 | selector: '[isDuration]', 27 | providers: 28 | [{provide: NG_VALIDATORS, useExisting: DurationValidator, multi: true}] 29 | }) 30 | export class DurationValidator implements Validator { 31 | validate(control: AbstractControl): {[key: string]: string} | null { 32 | if (!control.value || isNaN( 33 | getDurationInNsFromHumanReadableString(control.value))) { 34 | return {'invalid': 'invalid input'}; 35 | } 36 | 37 | return null; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /client/app/util/hash_compressor_test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {compress, decompress, parseHashFragment, serializeHashFragment} from './hash_compressor'; 18 | 19 | describe('HashCompressor', () => { 20 | it('should compress and decompress an object', () => { 21 | interface DecompressType { 22 | a: number; 23 | b: number[]; 24 | } 25 | const inObj: DecompressType|undefined = { 26 | a: 1, 27 | b: [2, 3, 4], 28 | }; 29 | const compressStr = compress(inObj); 30 | expect(compressStr).toBe('eJyrVkpUsjLUUUpSsoo20jHWMYmtBQAuZQS%2B'); 31 | const outObj = decompress(compressStr); 32 | expect(outObj).toEqual(inObj); 33 | }); 34 | 35 | it('should parse and serialize hashes', () => { 36 | const hashes: Array<[string, {[k: string]: string}]> = [ 37 | ['', {}], 38 | ['#abc', {'abc': ''}], 39 | ['#abc&xyz=456', {'abc': '', 'xyz': '456'}], 40 | ['#abc=123', {'abc': '123'}], 41 | ['#abc=123&xyz=456', {'abc': '123', 'xyz': '456'}], 42 | [ 43 | '#abc=123%204&def=567&xyz=890', 44 | {'abc': '123 4', 'def': '567', 'xyz': '890'} 45 | ], 46 | ]; 47 | 48 | // These cases are only for parseHashFragment(). 49 | const parseHashes: Array<[string, {[k: string]: string}]> = [ 50 | ['#abc=123&xyz=456&xyz=789', {'abc': '123', 'xyz': '789'}], 51 | ]; 52 | for (const hash of parseHashes) { 53 | expect(parseHashFragment(hash[0])).toEqual(hash[1]); 54 | } 55 | 56 | for (const hash of hashes) { 57 | expect(parseHashFragment(hash[0])).toEqual(hash[1]); 58 | expect(serializeHashFragment(hash[1])).toEqual(hash[0]); 59 | } 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /client/app/util/hash_keys.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {CollectionFilterKeys} from '../models/collections_filter'; 18 | 19 | /** 20 | * Key for the owner property in the URL hash on the collections page 21 | */ 22 | export const OWNER_KEY: 'owner' = 'owner'; 23 | /** 24 | * Key for the creation time property in the URL hash on the collections page 25 | */ 26 | export const CREATION_TIME_KEY: 'creationTime' = 'creationTime'; 27 | /** 28 | * Key for the description property in the URL hash on the collections page 29 | */ 30 | export const DESCRIPTION_KEY: 'description' = 'description'; 31 | /** 32 | * Key for the name property in the URL hash on the collections page 33 | */ 34 | export const NAME_KEY: 'name' = 'name'; 35 | /** 36 | * Key for the tags property in the URL hash on the collections page 37 | */ 38 | export const TAGS_KEY: 'tags' = 'tags'; 39 | /** 40 | * Key for the target machine property in the URL hash on the collections page 41 | */ 42 | export const TARGET_MACHINE_KEY: 'targetMachine' = 'targetMachine'; 43 | 44 | /** 45 | * All the keys used for filtering on the collections page. 46 | */ 47 | export const COLLECTIONS_FILTER_KEYS: CollectionFilterKeys[] = [ 48 | CREATION_TIME_KEY, DESCRIPTION_KEY, NAME_KEY, TAGS_KEY, TARGET_MACHINE_KEY 49 | ]; 50 | /** 51 | * All the keys used on the collections page. 52 | */ 53 | export const COLLECTIONS_PAGE_KEYS = [OWNER_KEY, ...COLLECTIONS_FILTER_KEYS]; 54 | 55 | /** 56 | * Key for the collection name property in the URL hash on the dashboard page 57 | */ 58 | export const COLLECTION_NAME_KEY: 'collection' = 'collection'; 59 | /** 60 | * Key for the share property in the URL hash on the dashboard page 61 | */ 62 | export const SHARE_KEY: 'share' = 'share'; 63 | 64 | 65 | /** 66 | * All the keys used on the dashboard page. 67 | */ 68 | export const DASHBOARD_PAGE_KEYS = [ 69 | COLLECTION_NAME_KEY, SHARE_KEY, 70 | ]; 71 | -------------------------------------------------------------------------------- /client/app/util/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | export * from './duration'; 18 | export * from './helpers'; 19 | export * from './system_topology'; 20 | export * from './hash_compressor'; 21 | export * from './hash_keys'; 22 | export * from './viewport'; 23 | export * from './util_module'; 24 | export * from './complex_system_topology'; 25 | export * from './error_snackbar'; 26 | -------------------------------------------------------------------------------- /client/app/util/util_module.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import {NgModule} from '@angular/core'; 18 | import {CommonModule} from "@angular/common"; 19 | import {PortalModule} from '@angular/cdk/portal'; 20 | import {MatButtonModule} from '@angular/material/button'; 21 | import {MatSnackBarModule} from '@angular/material/snack-bar'; 22 | 23 | import {FormatTimePipe} from './duration'; 24 | import {DurationValidator} from './duration_validator'; 25 | import {ErrorWindowComponent, ErrorSnackBarComponent} from './error_snackbar'; 26 | 27 | @NgModule({ 28 | declarations: [FormatTimePipe, DurationValidator, ErrorWindowComponent, ErrorSnackBarComponent], 29 | exports: [FormatTimePipe, DurationValidator, ErrorWindowComponent, ErrorSnackBarComponent], 30 | imports: [ 31 | CommonModule, 32 | PortalModule, 33 | MatButtonModule, 34 | MatSnackBarModule, 35 | ], 36 | entryComponents: [ErrorWindowComponent, ErrorSnackBarComponent], 37 | }) 38 | export class UtilModule { 39 | } 40 | -------------------------------------------------------------------------------- /client/environments/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm_bazel_typescript//:defs.bzl", "ts_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | licenses(["notice"]) # Apache License 2.0 6 | 7 | genrule( 8 | name = "env_gen", 9 | srcs = [ 10 | ":environment.dev.ts", 11 | ":environment.prod.ts", 12 | ], 13 | outs = ["environment.ts"], 14 | cmd = select({ 15 | "//:environment": "cat $(location :environment.dev.ts) > $@", 16 | "//conditions:default": "cat $(location :environment.prod.ts) > $@", 17 | }), 18 | ) 19 | 20 | ts_library( 21 | name = "environments", 22 | srcs = [":environment.ts"], 23 | ) 24 | -------------------------------------------------------------------------------- /client/environments/environment.dev.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | /** 18 | * Is this a production build? 19 | */ 20 | export const production = false; 21 | -------------------------------------------------------------------------------- /client/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | /** 18 | * Is this a production build? 19 | */ 20 | export const production = true; 21 | -------------------------------------------------------------------------------- /client/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | // This file will be overwritten by either environment.dev.ts or 18 | // environment.prod.ts during the build. DO NOT EDIT 19 | 20 | /** 21 | * Is this a production build? 22 | */ 23 | export const production = undefined; 24 | -------------------------------------------------------------------------------- /client/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/client/favicon.ico -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchedViz 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /client/main.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | import 'zone.js/dist/zone.min.js'; 18 | import {enableProdMode} from '@angular/core'; 19 | import {platformBrowser} from '@angular/platform-browser'; 20 | import {AppRootModule} from './app/app_root_module'; 21 | 22 | platformBrowser().bootstrapModule(AppRootModule); 23 | -------------------------------------------------------------------------------- /client/material-theme.scss: -------------------------------------------------------------------------------- 1 | @import 'external/npm/node_modules/@angular/material/_theming.scss'; 2 | 3 | // Angular Material theme definition. 4 | 5 | // Include non-theme styles for core. 6 | @include mat-core(); 7 | 8 | $my-app-primary: mat-palette($mat-blue-grey, 600); 9 | $my-app-accent: mat-palette($mat-amber, 500); 10 | $my-app-warn: mat-palette($mat-deep-orange); 11 | $my-app-theme: mat-light-theme( 12 | $my-app-primary, 13 | $my-app-accent, 14 | $my-app-warn 15 | ); 16 | @include angular-material-theme($my-app-theme); 17 | 18 | // Workaround for sort arrows not showing up on initial load 19 | // Can remove when https://github.com/angular/material2/pull/13171 is merged 20 | .mat-header-cell 21 | .mat-sort-header-container.mat-sort-header-sorted 22 | .mat-sort-header-arrow { 23 | opacity: 1 !important; 24 | transform: translateY(0) !important; 25 | } 26 | 27 | .mat-table { 28 | .mat-header-cell { 29 | .mat-sort-header-arrow { 30 | .mat-sort-header-pointer-left { 31 | transform: rotate(-45deg); 32 | } 33 | .mat-sort-header-pointer-right { 34 | transform: rotate(45deg); 35 | } 36 | } 37 | } 38 | } 39 | 40 | // Stop cropping in Material select boxes 41 | .mat-select-panel { 42 | max-width: unset !important; 43 | } 44 | 45 | .mat-tab-group, 46 | .mat-tab-body-wrapper { 47 | height: 100%; 48 | } 49 | 50 | .mat-simple-snackbar { 51 | white-space: pre-wrap; 52 | word-break: break-word; 53 | height: 100%; 54 | } 55 | -------------------------------------------------------------------------------- /client/module-id.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | // Work-around for angular material issue with ts_devserver and 18 | // ts_web_test_suite. Material requires `module.id` to be valid. This symbol is 19 | // valid in the production bundle but not in ts_devserver and ts_web_test_suite. 20 | // See https://github.com/angular/material2/issues/13883. 21 | var module = {id: ''}; 22 | -------------------------------------------------------------------------------- /client/rxjs_shims.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | /** 18 | * @license 19 | * Copyright Google Inc. All Rights Reserved. 20 | * 21 | * Use of this source code is governed by an MIT-style license that can be 22 | * found in the LICENSE file at https://angular.io/license 23 | */ 24 | 25 | /** 26 | * @fileoverview these provide named UMD modules so that we can bundle 27 | * the application along with rxjs using the concatjs bundler. 28 | */ 29 | 30 | // rxjs/operators 31 | (function(factory) { 32 | if (typeof module === 'object' && typeof module.exports === 'object') { 33 | var v = factory(require, exports); 34 | if (v !== undefined) module.exports = v; 35 | } else if (typeof define === 'function' && define.amd) { 36 | define('rxjs/operators', ['exports', 'rxjs'], factory); 37 | } 38 | })(function(exports, rxjs) { 39 | 'use strict'; 40 | Object.keys(rxjs.operators).forEach(function(key) { 41 | exports[key] = rxjs.operators[key]; 42 | }); 43 | Object.defineProperty(exports, '__esModule', {value: true}); 44 | }); 45 | 46 | // rxjs/testing 47 | (function(factory) { 48 | if (typeof module === 'object' && typeof module.exports === 'object') { 49 | var v = factory(require, exports); 50 | if (v !== undefined) module.exports = v; 51 | } else if (typeof define === 'function' && define.amd) { 52 | define('rxjs/testing', ['exports', 'rxjs'], factory); 53 | } 54 | })(function(exports, rxjs) { 55 | 'use strict'; 56 | Object.keys(rxjs.testing).forEach(function(key) { 57 | exports[key] = rxjs.testing[key]; 58 | }); 59 | Object.defineProperty(exports, '__esModule', {value: true}); 60 | }); 61 | -------------------------------------------------------------------------------- /client/sched.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | SchedViz 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /client/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "lib": [ 11 | "es2018", 12 | "dom", 13 | ], 14 | }, 15 | "exclude": [ 16 | "test.ts", 17 | "**/*.test.ts", 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /doc/images/walkthrough/cib.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/cib.png -------------------------------------------------------------------------------- /doc/images/walkthrough/collections_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/collections_page.png -------------------------------------------------------------------------------- /doc/images/walkthrough/cpu_swimlane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/cpu_swimlane.png -------------------------------------------------------------------------------- /doc/images/walkthrough/events_tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/events_tab.png -------------------------------------------------------------------------------- /doc/images/walkthrough/expanded_thread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/expanded_thread.png -------------------------------------------------------------------------------- /doc/images/walkthrough/layers_tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/layers_tab.png -------------------------------------------------------------------------------- /doc/images/walkthrough/layers_tab_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/layers_tab_preview.png -------------------------------------------------------------------------------- /doc/images/walkthrough/manipulated_layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/manipulated_layers.png -------------------------------------------------------------------------------- /doc/images/walkthrough/metrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/metrics.png -------------------------------------------------------------------------------- /doc/images/walkthrough/metrics_pane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/metrics_pane.png -------------------------------------------------------------------------------- /doc/images/walkthrough/migrations_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/migrations_selected.png -------------------------------------------------------------------------------- /doc/images/walkthrough/round_robin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/round_robin.png -------------------------------------------------------------------------------- /doc/images/walkthrough/sleep_time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/sleep_time.png -------------------------------------------------------------------------------- /doc/images/walkthrough/trace_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/trace_view.png -------------------------------------------------------------------------------- /doc/images/walkthrough/unzoomed_heatmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/unzoomed_heatmap.png -------------------------------------------------------------------------------- /doc/images/walkthrough/unzoomed_heatmap_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/unzoomed_heatmap_preview.png -------------------------------------------------------------------------------- /doc/images/walkthrough/zoombrush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/zoombrush.png -------------------------------------------------------------------------------- /doc/images/walkthrough/zoomed_heatmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/zoomed_heatmap.png -------------------------------------------------------------------------------- /doc/images/walkthrough/zoomed_heatmap_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/doc/images/walkthrough/zoomed_heatmap_preview.png -------------------------------------------------------------------------------- /doc/sitemap.md: -------------------------------------------------------------------------------- 1 | * [Features and Usage Walkthrough](walkthrough.md) 2 | -------------------------------------------------------------------------------- /ebpf/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | licenses(["notice"]) # Apache License 2.0 6 | 7 | go_library( 8 | name = "schedbt", 9 | importpath = "github.com/google/schedviz/ebpf/schedbt", 10 | 11 | srcs = ["schedbt.go"], 12 | deps = [ 13 | "//analysis:event_loaders_go_proto", 14 | "//tracedata:eventsetbuilder", 15 | "//tracedata:schedviz_events_go_proto", 16 | "@com_github_golang_glog//:go_default_library", 17 | "@org_golang_google_grpc//codes:go_default_library", 18 | "@org_golang_google_grpc//status:go_default_library", 19 | ], 20 | ) 21 | 22 | go_test( 23 | name = "schedbt_test", 24 | size = "small", 25 | srcs = ["schedbt_test.go"], 26 | embed = [":schedbt"], 27 | deps = [ 28 | "//tracedata:schedviz_events_go_proto", 29 | "//tracedata:testeventsetbuilder", 30 | "@com_github_golang_protobuf//proto:go_default_library", 31 | "@com_github_google_go-cmp//cmp:go_default_library", 32 | ], 33 | ) 34 | -------------------------------------------------------------------------------- /ebpf/collect.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PROG=$0 4 | USAGE=$"Usage: ${PROG} -out 'Path to directory to save trace in' \ 5 | [-trace_lines 'Number of trace lines to record. Trace duration is only loosely 6 | correlated with recorded lines, and will vary based on system topology and 7 | workload. Default 1000000'] \ 8 | [-script 'Path to trace script']\n 9 | Must run as root." 10 | 11 | if [[ "$EUID" -ne 0 ]] 12 | then echo "Run ${0} as root."; exit 1 13 | fi 14 | 15 | # Parse arguments 16 | while [[ "$#" -gt 0 ]]; do case $1 in 17 | -o|-out) OUT="$2"; shift;; 18 | -l|-trace_lines) TRACELINES="$2"; shift;; 19 | -s|-script) LOCAL_PATH_TO_SCRIPT="$2"; shift;; 20 | -h|-help) echo "$USAGE"; exit 0;; 21 | *) echo "Unknown parameter passed: $1"; echo "$USAGE"; exit 1;; 22 | esac; shift; done 23 | 24 | if [[ -z "${OUT}" ]]; then 25 | echo "Missing required argument -out" 26 | echo 27 | echo "$USAGE" 28 | exit 1 29 | fi 30 | 31 | if [[ -z "${TRACELINES}" ]]; then 32 | TRACELINES=1000000 33 | fi 34 | 35 | if [[ -z "${LOCAL_PATH_TO_SCRIPT}" ]]; then 36 | LOCAL_PATH_TO_SCRIPT=`dirname "${PROG}"`/sched.bt 37 | fi 38 | 39 | TMP="${OUT}/tmp" 40 | 41 | kill_trap() { 42 | exit_status=$? 43 | rm -rf "${TMP}" 44 | exit "${exit_status}" 45 | } 46 | 47 | trap kill_trap SIGINT SIGTERM EXIT 48 | 49 | # Make the output directories if needed 50 | [[ ! -d "${OUT}" ]] && mkdir "${OUT}" 51 | mkdir "${TMP}" 52 | mkdir "${TMP}/topology" 53 | 54 | # Save the metadata file 55 | echo "trace_type: EBPF" > "${TMP}/metadata.textproto" 56 | 57 | # Save the topology 58 | find /sys/devices/system/node -regex "/sys/devices/system/node/node[0-9]+/cpu[0-9]+" -print0 | xargs -0 -I{} cp -RL -f --parents {}/topology . 59 | mv sys/devices/system/node/* "${TMP}/topology" 60 | rm -rf sys/ 61 | 62 | echo "Starting tracing..." 63 | bpftrace ${LOCAL_PATH_TO_SCRIPT} | 64 | head -${TRACELINES} | 65 | awk -F: '{val="0x" $2; print strtonum(val),$0 ;}' /dev/stdin | 66 | sort -n | 67 | sed 's/^[^ ]* //' > "${TMP}/ebpf_trace" 68 | 69 | echo "Creating tar file" 70 | rm -f "${OUT}/trace.tar.gz" 71 | chmod -R a+rwX "${TMP}" 72 | pushd ${OUT} > /dev/null 73 | ABS_OUT=`pwd` 74 | popd > /dev/null 75 | pushd "${TMP}" > /dev/null 76 | tar -zcf "${ABS_OUT}/trace.tar.gz" * 77 | popd > /dev/null 78 | rm -rf "${TMP}" 79 | chmod a+rw "${OUT}/trace.tar.gz" 80 | 81 | echo "Tracing done." 82 | -------------------------------------------------------------------------------- /ebpf/sched.bt: -------------------------------------------------------------------------------- 1 | BEGIN { 2 | // To keep rows shorter, log the trace start timestamp and compute subsequent 3 | // timestamps as offsets from that start. 4 | @start = nsecs; 5 | printf("ST:0:%x\n", @start); 6 | } 7 | 8 | // @cmd tracks which PIDs have already had their command and priority logged. 9 | // eBPF maps are of limited size, so infrequently-seen threads may be logged 10 | // multiple times. 11 | 12 | // On switch, log any threads seen for the first time, as well as the switch's 13 | // information. 14 | tracepoint:sched:sched_switch { 15 | if (@cmds[args->prev_pid] == 0) { 16 | printf("P:%x:%x:%x:%s\n", nsecs-@start, args->prev_pid, args->prev_prio, args->prev_comm); 17 | @cmds[args->prev_pid] = 1; 18 | } 19 | if (@cmds[args->next_pid] == 0) { 20 | printf("P:%x:%x:%x:%s\n", nsecs-@start, args->next_pid, args->next_prio, args->next_comm); 21 | @cmds[args->next_pid] = 1; 22 | } 23 | printf("S:%x:%x:%x:%x:%x\n", nsecs-@start, cpu, args->prev_pid, args->prev_state, args->next_pid); 24 | } 25 | 26 | // On a migrate, log the thread if seen for the first time, as well as the 27 | // migrate's information. 28 | tracepoint:sched:sched_migrate_task { 29 | if (@cmds[args->pid] == 0) { 30 | printf("P:%x:%x:%x:%s\n", nsecs-@start, args->pid, args->prio, args->comm); 31 | @cmds[args->pid] = 1; 32 | } 33 | printf("M:%x:%x:%x:%x:%x\n", nsecs-@start, cpu, args->orig_cpu, args->dest_cpu, args->pid); 34 | } 35 | 36 | // On a wakeup, log the thread if seen for the first time, as well as the 37 | // wakeup's information. 38 | tracepoint:sched:sched_wakeup { 39 | if (@cmds[args->pid] == 0) { 40 | printf("P:%x:%x:%x:%s\n", nsecs-@start, args->pid, args->prio, args->comm); 41 | @cmds[args->pid] = 1; 42 | } 43 | printf("W:%x:%x:%x:%x\n", nsecs-@start, cpu, args->target_cpu, args->pid); 44 | } 45 | 46 | // On a wakeup-new, log the thread as well as the wakeup-new's information. 47 | tracepoint:sched:sched_wakeup_new { 48 | // Force an update for any cached value for this PID. 49 | printf("P:%x:%x:%x:%s\n", nsecs-@start, args->pid, args->prio, args->comm); 50 | @cmds[args->pid] = 1; 51 | printf("W:%x:%x:%x:%x\n", nsecs-@start, cpu, args->target_cpu, args->pid); 52 | } 53 | 54 | END { 55 | clear(@cmds); 56 | } 57 | -------------------------------------------------------------------------------- /ebpf/schedbt_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | package schedbt 18 | 19 | import ( 20 | "bufio" 21 | "strings" 22 | "testing" 23 | 24 | "github.com/google/go-cmp/cmp" 25 | "github.com/golang/protobuf/proto" 26 | eventpb "github.com/google/schedviz/tracedata/schedviz_events_go_proto" 27 | "github.com/google/schedviz/tracedata/testeventsetbuilder" 28 | ) 29 | 30 | func TestParsing(t *testing.T) { 31 | tests := []struct { 32 | description string 33 | input string 34 | want *eventpb.EventSet 35 | }{{ 36 | "Valid trace", 37 | `Attaching 5 probes... 38 | ST:0:beef 39 | P:a:64:70:Thread 1 40 | P:a:c8:70:Thread 2 41 | S:a:0:64:0:c8 42 | M:14:0:0:1:64 43 | W:1e:0:1:64`, 44 | testeventsetbuilder.TestProtobuf(t, emptyEventSet(). 45 | WithEvent("sched_switch", 0, 0xbeef+10, false, 46 | 100, "Thread 1", 112, 0, 47 | 200, "Thread 2", 112). 48 | WithEvent("sched_migrate_task", 0, 0xbeef+20, false, 49 | 100, "Thread 1", 112, 50 | 0, 1). 51 | WithEvent("sched_wakeup", 0, 0xbeef+30, false, 52 | 100, "Thread 1", 112, 1)), 53 | }, { 54 | "Process name with colons", 55 | `Attaching 5 probes... 56 | ST:0:beef 57 | P:a:64:70:Thread:1 58 | M:a:0:0:1:64`, 59 | testeventsetbuilder.TestProtobuf(t, emptyEventSet(). 60 | WithEvent("sched_migrate_task", 0, 0xbeef+10, false, 61 | 100, "Thread:1", 112, 62 | 0, 1)), 63 | }} 64 | for _, test := range tests { 65 | t.Run(test.description, func(t *testing.T) { 66 | p := NewParser() 67 | r := strings.NewReader(test.input) 68 | bufr := bufio.NewReader(r) 69 | if err := p.Parse(bufr); err != nil { 70 | t.Fatalf("Parse() yielded unexpected error %v", err) 71 | } 72 | got, err := p.EventSet() 73 | if err != nil { 74 | t.Fatalf("EventSet() yielded unexpected error %v", err) 75 | } 76 | if d := cmp.Diff(test.want, got, cmp.Comparer(proto.Equal)); d != "" { 77 | t.Errorf("Parser produced %s, diff(want->got) %s", proto.MarshalTextString(got), d) 78 | } 79 | }) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "schedviz", 3 | "version": "0.0.1", 4 | "description": "Visualizer for kernel scheduler traces", 5 | "license": "Apache-2.0", 6 | "dependencies": { 7 | "@angular/animations": "8.1.0", 8 | "@angular/cdk": "8.0.2", 9 | "@angular/common": "8.1.0", 10 | "@angular/core": "8.1.0", 11 | "@angular/forms": "8.1.0", 12 | "@angular/material": "8.0.2", 13 | "@angular/platform-browser": "8.1.0", 14 | "@angular/platform-browser-dynamic": "8.1.0", 15 | "@angular/router": "8.1.0", 16 | "d3": "5.9.7", 17 | "hammerjs": "2.0.8", 18 | "pako": "1.0.10", 19 | "rxjs": "6.5.2", 20 | "systemjs": "0.21.6", 21 | "tslib": "1.10.0", 22 | "zone.js": "0.9.1" 23 | }, 24 | "devDependencies": { 25 | "@angular/bazel": "8.1.0", 26 | "@angular/cli": "8.1.0", 27 | "@angular/compiler": "8.1.0", 28 | "@angular/compiler-cli": "8.1.0", 29 | "@bazel/bazel": "1.0.0", 30 | "@bazel/benchmark-runner": "0.1.0", 31 | "@bazel/buildifier": "0.26.0", 32 | "@bazel/ibazel": "0.10.3", 33 | "@bazel/hide-bazel-files": "0.32.2", 34 | "@bazel/karma": "0.32.2", 35 | "@bazel/typescript": "0.32.2", 36 | "@types/hammerjs": "2.0.36", 37 | "@types/jasmine": "3.3.13", 38 | "@types/node": "12.0.10", 39 | "husky": "0.14.3", 40 | "jasmine": "3.4.0", 41 | "protractor": "6.0.0", 42 | "typescript": "3.4.5" 43 | }, 44 | "scripts": { 45 | "postinstall": "ngc -p angular-metadata.tsconfig.json" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /server/events.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | package models 18 | 19 | import elpb "github.com/google/schedviz/analysis/event_loaders_go_proto" 20 | 21 | // Metadata contains metadata about a collection 22 | type Metadata struct { 23 | // The unique name of the collection. 24 | CollectionUniqueName string `json:"collectionUniqueName"` 25 | // The creator tag provided at this collection's creation. 26 | Creator string `json:"creator"` 27 | // This collection's owners. 28 | Owners []string `json:"owners"` 29 | // The collection's tags. 30 | Tags []string `json:"tags"` 31 | // The collection's description. 32 | Description string `json:"description"` 33 | // The time of this collection's creation. 34 | CreationTime int64 `json:"creationTime"` 35 | // The events collected during the collection. 36 | FtraceEvents []string `json:"ftraceEvents"` 37 | // The target machine on which the collection was performed. 38 | TargetMachine string `json:"targetMachine"` 39 | // The elpb.LoadersType used by default for this collection. 40 | DefaultEventLoader elpb.LoadersType `json:"defaultEventLoader"` 41 | } 42 | -------------------------------------------------------------------------------- /server/testdata/ebpf_trace.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/server/testdata/ebpf_trace.tar.gz -------------------------------------------------------------------------------- /server/testdata/test.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/server/testdata/test.tar.gz -------------------------------------------------------------------------------- /server/testdata/test_no_metadata.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/server/testdata/test_no_metadata.tar.gz -------------------------------------------------------------------------------- /testhelpers/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | licenses(["notice"]) # Apache License 2.0 6 | 7 | go_library( 8 | name = "testhelpers", 9 | importpath = "github.com/google/schedviz/testhelpers/testhelpers", 10 | 11 | srcs = ["testhelpers.go"], 12 | deps = [ 13 | "@com_github_golang_protobuf//proto:go_default_library", 14 | "@com_github_google_go-cmp//cmp:go_default_library", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /testhelpers/testhelpers.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | // Package testhelpers contains helpers for tests 18 | package testhelpers 19 | 20 | import ( 21 | "os" 22 | "path" 23 | "testing" 24 | 25 | "github.com/google/go-cmp/cmp" 26 | "github.com/golang/protobuf/proto" 27 | ) 28 | 29 | // DiffProto compares the string representations of two protos 30 | func DiffProto(t *testing.T, a, b proto.Message) (diff string, equal bool) { 31 | t.Helper() 32 | equal = proto.Equal(a, b) 33 | if !equal { 34 | diff = cmp.Diff(a, b) 35 | } 36 | return 37 | } 38 | 39 | // GetRunFilesPath returns the path of the runfiles directory when running under Bazel 40 | func GetRunFilesPath() string { 41 | // Bazel stores test location in these environment variables 42 | runFiles := path.Join(os.Getenv("TEST_SRCDIR"), os.Getenv("TEST_WORKSPACE")) 43 | return runFiles 44 | } 45 | -------------------------------------------------------------------------------- /tracedata/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") 2 | load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") 3 | 4 | # A set of types and utilities for working with compacted trace data sets, such 5 | # as those produced by ../traceparser. 6 | licenses(["notice"]) # Apache License 2.0 7 | 8 | go_library( 9 | name = "trace", 10 | importpath = "github.com/google/schedviz/tracedata/trace", 11 | 12 | srcs = ["trace_event.go"], 13 | visibility = ["//visibility:public"], 14 | deps = [ 15 | ":schedviz_events_go_proto", 16 | "@org_golang_google_grpc//codes:go_default_library", 17 | "@org_golang_google_grpc//status:go_default_library", 18 | ], 19 | ) 20 | 21 | go_test( 22 | name = "trace_test", 23 | size = "small", 24 | srcs = ["trace_event_test.go"], 25 | embed = [":trace"], 26 | deps = [ 27 | ":eventsetbuilder", 28 | ":schedviz_events_go_proto", 29 | ":testeventsetbuilder", 30 | ], 31 | ) 32 | 33 | go_library( 34 | name = "eventsetbuilder", 35 | importpath = "github.com/google/schedviz/tracedata/eventsetbuilder", 36 | 37 | srcs = ["event_set_builder.go"], 38 | visibility = ["//visibility:public"], 39 | deps = [ 40 | ":schedviz_events_go_proto", 41 | "//analysis:event_loaders_go_proto", 42 | "//traceparser", 43 | ], 44 | ) 45 | 46 | go_test( 47 | name = "eventsetbuilder_test", 48 | size = "small", 49 | srcs = ["event_set_builder_test.go"], 50 | embed = [":eventsetbuilder"], 51 | deps = [ 52 | ":schedviz_events_go_proto", 53 | "//testhelpers", 54 | ], 55 | ) 56 | 57 | proto_library( 58 | name = "schedviz_events_proto", 59 | srcs = ["events.proto"], 60 | visibility = ["//visibility:public"], 61 | deps = [ 62 | "//analysis:event_loaders_proto", 63 | "@com_google_protobuf//:timestamp_proto", 64 | ], 65 | ) 66 | 67 | go_proto_library( 68 | name = "schedviz_events_go_proto", 69 | importpath = "github.com/google/schedviz/tracedata/schedviz_events_go_proto", 70 | 71 | proto = ":schedviz_events_proto", 72 | visibility = ["//visibility:public"], 73 | deps = ["//analysis:event_loaders_go_proto"], 74 | ) 75 | 76 | go_library( 77 | name = "testeventsetbuilder", 78 | importpath = "github.com/google/schedviz/tracedata/testeventsetbuilder", 79 | 80 | testonly = True, 81 | srcs = ["test_event_set_builder.go"], 82 | visibility = ["//visibility:public"], 83 | deps = [ 84 | ":eventsetbuilder", 85 | ":schedviz_events_go_proto", 86 | ], 87 | ) 88 | 89 | go_library( 90 | name = "schedevent", 91 | importpath = "github.com/google/schedviz/tracedata/schedevent", 92 | 93 | srcs = ["sched_event.go"], 94 | visibility = ["//visibility:public"], 95 | deps = [":trace"], 96 | ) 97 | 98 | go_library( 99 | name = "clipping", 100 | importpath = "github.com/google/schedviz/tracedata/clipping", 101 | 102 | srcs = ["clipping.go"], 103 | visibility = ["//visibility:public"], 104 | deps = [":schedviz_events_go_proto"], 105 | ) 106 | 107 | go_test( 108 | name = "clipping_test", 109 | size = "small", 110 | srcs = ["clipping_test.go"], 111 | embed = [":clipping"], 112 | deps = [ 113 | ":schedviz_events_go_proto", 114 | ], 115 | ) 116 | -------------------------------------------------------------------------------- /tracedata/clipping.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | // Package clipping provides utilities for deciding which events in eventpb.EventSets are clipped. 18 | package clipping 19 | 20 | import ( 21 | "errors" 22 | 23 | eventpb "github.com/google/schedviz/tracedata/schedviz_events_go_proto" 24 | ) 25 | 26 | func copyMap(overflowed map[int64]struct{}) map[int64]struct{} { 27 | attestedCPUs := make(map[int64]struct{}, len(overflowed)) 28 | for key, value := range overflowed { 29 | attestedCPUs[key] = value 30 | } 31 | return attestedCPUs 32 | } 33 | 34 | // ClipFromStartOfTrace marks clipped events. Given an input EventSet and a set of overflowedCPUs, 35 | // it determines the latest first timestamp among events generated by the overflowedCPUs, and marks 36 | // all events prior to that time on these CPUs as Clipped. Events are expected to be sorted by timestamp. 37 | func ClipFromStartOfTrace(es *eventpb.EventSet, overflowedCPUs map[int64]struct{}) error { 38 | if len(overflowedCPUs) == 0 { 39 | return nil 40 | } 41 | attested := copyMap(overflowedCPUs) 42 | for _, ev := range es.Event { 43 | if _, ok := attested[ev.GetCpu()]; ok { 44 | // First time we've seen an event from this CPU. Remove it from soughtCPUs. 45 | delete(attested, ev.GetCpu()) 46 | } 47 | if len(attested) == 0 { 48 | return nil 49 | } 50 | ev.Clipped = true 51 | } 52 | return errors.New("Not all overflowed CPUs emitted events") 53 | } 54 | 55 | // ClipFromEndOfTrace marks clipped events. Given an input EventSet and a set of overflowedCPUs, 56 | // it determines the earliest last timestamp among events generated by the overflowedCPUs, and marks 57 | // all events prior to that time on these CPUs as Clipped. Events are expected to be sorted by timestamp. 58 | func ClipFromEndOfTrace(es *eventpb.EventSet, overflowedCPUs map[int64]struct{}) error { 59 | if len(overflowedCPUs) == 0 { 60 | return nil 61 | } 62 | attested := copyMap(overflowedCPUs) 63 | for idx := len(es.Event) - 1; idx >= 0; idx-- { 64 | ev := es.Event[idx] 65 | if _, ok := attested[ev.GetCpu()]; ok { 66 | // First time we've seen an event from this CPU. Remove it from soughtCPUs. 67 | delete(attested, ev.GetCpu()) 68 | } 69 | if len(attested) == 0 { 70 | return nil 71 | } 72 | ev.Clipped = true 73 | } 74 | return errors.New("Not all overflowed CPUs emitted events") 75 | } 76 | -------------------------------------------------------------------------------- /tracedata/sched_event.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | // Package schedevent provides utilities for working with scheduling 18 | // trace.Events. 19 | package schedevent 20 | 21 | import ( 22 | "fmt" 23 | "strconv" 24 | 25 | "github.com/google/schedviz/tracedata/trace" 26 | ) 27 | 28 | // String returns a human-readable formatted sched event if the provided 29 | // trace.Event is a supported scheduling event, and the raw printed event 30 | // otherwise. 31 | func String(ev *trace.Event) string { 32 | prefix := fmt.Sprintf("[%3d] %-22s %-10s ", ev.CPU, strconv.Itoa(int(ev.Timestamp)), ev.Name) 33 | switch ev.Name { 34 | case "sched_switch": 35 | return fmt.Sprintf("%s PID %d ('%s', prio %d, task state %d) to PID %d ('%s', prio %d) on CPU %3d", 36 | prefix, 37 | ev.NumberProperties["prev_pid"], ev.TextProperties["prev_comm"], ev.NumberProperties["prev_prio"], ev.NumberProperties["prev_state"], 38 | ev.NumberProperties["next_pid"], ev.TextProperties["next_comm"], ev.NumberProperties["next_prio"], 39 | ev.CPU) 40 | case "sched_wakeup", "sched_wakeup_new": 41 | return fmt.Sprintf("%s PID %d ('%s', prio %d) on CPU %3d", 42 | prefix, 43 | ev.NumberProperties["pid"], ev.TextProperties["comm"], ev.NumberProperties["prio"], 44 | ev.NumberProperties["target_cpu"]) 45 | case "sched_migrate_task": 46 | return fmt.Sprintf("%s PID %d ('%s', prio %d) from CPU %3d to CPU %3d", 47 | prefix, 48 | ev.NumberProperties["pid"], ev.TextProperties["comm"], ev.NumberProperties["prio"], 49 | ev.NumberProperties["orig_cpu"], ev.NumberProperties["dest_cpu"]) 50 | case "sched_wait_task", "sched_process_wait": 51 | return fmt.Sprintf("%s PID %d ('%s', prio %d) on CPU %3d", 52 | prefix, 53 | ev.NumberProperties["pid"], ev.TextProperties["comm"], ev.NumberProperties["prio"], 54 | ev.CPU) 55 | default: 56 | return fmt.Sprintf("NON-SCHED %s", ev.String()) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tracedata/test_event_set_builder.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | // Package testeventsetbuilder provides helpers for streamlined use of 18 | // eventsetbuilder.Builders in tests. 19 | package testeventsetbuilder 20 | 21 | import ( 22 | "testing" 23 | 24 | "github.com/google/schedviz/tracedata/eventsetbuilder" 25 | eventpb "github.com/google/schedviz/tracedata/schedviz_events_go_proto" 26 | ) 27 | 28 | // TestProtobuf accepts a testing.T and apopulated Builder, and returns the 29 | // Builder's EventSet. If the Builder has errors, the test is failed with 30 | // an appropriate message. 31 | func TestProtobuf(t *testing.T, b *eventsetbuilder.Builder) *eventpb.EventSet { 32 | es, errs := b.EventSet() 33 | if len(errs) > 0 { 34 | t.Error("Errors building EventSet:") 35 | for err := range errs { 36 | t.Errorf(" %s", err) 37 | } 38 | t.Fatalf("Bailing...") 39 | } 40 | return es 41 | } 42 | -------------------------------------------------------------------------------- /traceparser/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | licenses(["notice"]) # Apache License 2.0 6 | 7 | go_library( 8 | name = "traceparser", 9 | importpath = "github.com/google/schedviz/traceparser/traceparser", 10 | 11 | srcs = [ 12 | "event_set_builder.go", 13 | "eventformat.go", 14 | "formatparser.go", 15 | "path.go", 16 | "ringbuffer.go", 17 | "trace_parser.go", 18 | "traceevent.go", 19 | ], 20 | deps = [ 21 | "//analysis:event_loaders_go_proto", 22 | "//tracedata:clipping", 23 | "//tracedata:schedviz_events_go_proto", 24 | "//util", 25 | "@com_github_golang_protobuf//proto:go_default_library", 26 | ], 27 | ) 28 | 29 | go_test( 30 | name = "formatparser_test", 31 | size = "small", 32 | srcs = ["formatparser_test.go"], 33 | embed = [":traceparser"], 34 | deps = [ 35 | "@com_github_google_go-cmp//cmp:go_default_library", 36 | "@com_github_google_go-cmp//cmp/cmpopts:go_default_library", 37 | ], 38 | ) 39 | 40 | go_test( 41 | name = "traceparser_test", 42 | size = "small", 43 | srcs = [ 44 | "event_set_builder_test.go", 45 | "trace_parser_test.go", 46 | ], 47 | data = [ 48 | "testdata/input/cpu0", 49 | "testdata/input/cpu0-32", 50 | "testdata/output/trace.gob", 51 | "testdata/output/trace-32.gob", 52 | ], 53 | embed = [":traceparser"], 54 | deps = [ 55 | "//testhelpers", 56 | "//tracedata:schedviz_events_go_proto", 57 | "@com_github_golang_protobuf//proto:go_default_library", 58 | "@com_github_google_go-cmp//cmp:go_default_library", 59 | "@com_github_google_go-cmp//cmp/cmpopts:go_default_library", 60 | ], 61 | ) 62 | -------------------------------------------------------------------------------- /traceparser/eventformat.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | package traceparser 18 | 19 | // EventFormat represents a TraceFS event's format 20 | type EventFormat struct { 21 | Name string 22 | ID uint16 23 | Format Format 24 | } 25 | 26 | // Format is a collection of fields contained within an event format 27 | type Format struct { 28 | // CommonFields are fields that are common to all Events 29 | CommonFields []*FormatField 30 | // Fields are fields that are unique to each Event type 31 | Fields []*FormatField 32 | } 33 | 34 | // FormatField describes a single field within a format 35 | type FormatField struct { 36 | // FieldType is the C declaration of the field 37 | FieldType string 38 | // Name is the name of the field. Extracted from the name in the C declaration in FieldType 39 | Name string 40 | // ProtoType is the "proto" type of the field. Extracted from FieldType. 41 | // If the type in FieldType is char or char[], ProtoType will be string. Otherwise, it'll be int64 42 | ProtoType string 43 | // The offset from the beginning of the event in bytes 44 | Offset uint64 45 | // Size is the size of this field in bytes 46 | Size uint64 47 | // NumElements is the number of elements in this type. Only relevant for array types. 48 | NumElements uint64 49 | // ElementSize is the size of each element in bytes. Only relevant for array types. 50 | ElementSize uint64 51 | // Signed states if this type is signed or unsigned. Only relevant for numeric types. 52 | Signed bool 53 | // IsDynamicArray is true if this field contains a struct describing a dynamic array 54 | // The struct is of the form: 55 | // struct { 56 | // uint16 offset 57 | // uint16 length 58 | // } 59 | IsDynamicArray bool 60 | } 61 | -------------------------------------------------------------------------------- /traceparser/example/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | licenses(["notice"]) # Apache License 2.0 6 | 7 | go_binary( 8 | name = "trace_to_proto_converter", 9 | srcs = ["trace_to_proto_converter.go"], 10 | deps = [ 11 | "//traceparser", 12 | "@com_github_golang_glog//:go_default_library", 13 | "@com_github_golang_protobuf//proto:go_default_library", 14 | ], 15 | ) 16 | -------------------------------------------------------------------------------- /traceparser/path.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | package traceparser 18 | 19 | import ( 20 | "bufio" 21 | "fmt" 22 | "os" 23 | "path" 24 | "path/filepath" 25 | "regexp" 26 | "strconv" 27 | ) 28 | 29 | var ( 30 | cpuRe = regexp.MustCompile(`cpu(\d+)$`) 31 | ) 32 | 33 | // WalkPerCPUDir walks the input directory looking for files of the format 34 | // cpu\d+. For each file it calls process with a bufio.Reader and the number 35 | // found. 36 | func WalkPerCPUDir(traceDir string, errorOnUnknown bool, process func(reader *bufio.Reader, cpu int64) error) error { 37 | err := filepath.Walk(traceDir, func(filePath string, info os.FileInfo, err error) error { 38 | if err != nil { 39 | return err 40 | } 41 | if info.IsDir() { 42 | if info.Name() != path.Base(traceDir) { 43 | return fmt.Errorf("expected trace directory to be flat, but found a subdirectory: %s", filePath) 44 | } 45 | return nil 46 | } 47 | if matches := cpuRe.FindStringSubmatch(info.Name()); matches != nil { 48 | cpu, err := strconv.ParseInt(matches[1], 10, 64) 49 | if err != nil { 50 | return fmt.Errorf("error extracting CPU number from filename (filePath: %s): %s", filePath, err) 51 | } 52 | file, err := os.Open(filePath) 53 | if err != nil { 54 | return fmt.Errorf("error opening %s for reading: %s", filePath, err) 55 | } 56 | reader := bufio.NewReader(file) 57 | if err := process(reader, cpu); err != nil { 58 | return err 59 | } 60 | } else if errorOnUnknown { 61 | return fmt.Errorf("unknown file in trace directory: %s", filePath) 62 | } 63 | return nil 64 | }) 65 | return err 66 | } 67 | -------------------------------------------------------------------------------- /traceparser/testdata/input/cpu0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/traceparser/testdata/input/cpu0 -------------------------------------------------------------------------------- /traceparser/testdata/input/cpu0-32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/traceparser/testdata/input/cpu0-32 -------------------------------------------------------------------------------- /traceparser/testdata/output/trace-32.gob: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/traceparser/testdata/output/trace-32.gob -------------------------------------------------------------------------------- /traceparser/testdata/output/trace.gob: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/schedviz/72ec4f339e15209b32611277dfe0ab258c7ef91c/traceparser/testdata/output/trace.gob -------------------------------------------------------------------------------- /traceparser/traceevent.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | package traceparser 18 | 19 | import ( 20 | "encoding/binary" 21 | "errors" 22 | "fmt" 23 | "strings" 24 | ) 25 | 26 | // TraceEvent holds a single trace event as unmarshalled from the raw binary trace output 27 | type TraceEvent struct { 28 | // The timestamp in the trace of this event. 29 | Timestamp uint64 30 | // The CPU that this event was recorded on 31 | CPU int64 32 | // A mapping of text property names to property values, normally strings. 33 | // If there is a dynamic array field, it will be stored as a binary blob in TextProperties. 34 | TextProperties map[string]string 35 | // A mapping of numeric properties to property values. 36 | NumberProperties map[string]int64 37 | // The Format ID of this event. Should be an event ID defined in a loaded format file. 38 | FormatID uint16 39 | // True if this Event fell outside of the known-valid range of a trace which 40 | // experienced buffer overruns. 41 | Clipped bool 42 | // An error, if one occurred while creating this TraceEvent 43 | Err error 44 | } 45 | 46 | // NewTraceEvent creates a new TraceEvent 47 | func NewTraceEvent(cpu int64) *TraceEvent { 48 | return &TraceEvent{ 49 | CPU: cpu, 50 | TextProperties: make(map[string]string), 51 | NumberProperties: make(map[string]int64), 52 | } 53 | } 54 | 55 | // SaveFieldValue saves a byte array into a TraceEvent's field. 56 | // The byte array will be converted to the type specified in the field definition. 57 | func (t *TraceEvent) SaveFieldValue(field *FormatField, buf []byte, endianness binary.ByteOrder) error { 58 | if field.IsDynamicArray { 59 | // Store binary blob as a string, which is allowed in Go. 60 | t.TextProperties[field.Name] = string(buf) 61 | return nil 62 | } 63 | 64 | if field.ProtoType == "string" { 65 | // Convert to string and remove extra trailing null bytes 66 | t.TextProperties[field.Name] = strings.Split(string(buf), "\x00")[0] 67 | } else if field.ProtoType == "int64" { 68 | // Pad to 8 bytes 69 | if len(buf) < 8 { 70 | padding := [8]byte{} 71 | switch endianness { 72 | case binary.LittleEndian: 73 | copy(padding[:(8-len(buf))], buf) 74 | case binary.BigEndian: 75 | return errors.New("big endian is not supported") 76 | default: 77 | return errors.New("unknown endianness") 78 | } 79 | buf = padding[:] 80 | } 81 | t.NumberProperties[field.Name] = int64(endianness.Uint64(buf)) 82 | } else { 83 | return fmt.Errorf("unknown field type %s. only string and int64 are supported", field.ProtoType) 84 | } 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /util/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_binary") 2 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | licenses(["notice"]) # Apache License 2.0 7 | 8 | cc_binary( 9 | name = "trace", 10 | srcs = [ 11 | "status.h", 12 | "trace.cc", 13 | "trace.h", 14 | ], 15 | copts = ["-std=c++17"], 16 | deps = [ 17 | "@com_google_absl//absl/base:core_headers", 18 | "@com_google_absl//absl/flags:flag", 19 | "@com_google_absl//absl/flags:parse", 20 | "@com_google_absl//absl/strings", 21 | "@com_google_absl//absl/time", 22 | "@com_googlesource_code_re2//:re2", 23 | ], 24 | ) 25 | 26 | go_library( 27 | name = "util", 28 | importpath = "github.com/google/schedviz/util/util", 29 | 30 | srcs = ["log.go"], 31 | deps = [ 32 | "@com_github_golang_glog//:go_default_library", 33 | "@com_github_golang_time//rate:go_default_library", 34 | ], 35 | ) 36 | -------------------------------------------------------------------------------- /util/gcloud_trace.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Copyright 2019 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS-IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # 18 | 19 | 20 | PROG=$0 21 | USAGE="Usage: ${PROG} -instance 'GCE Instance Name' \ 22 | -trace_args 'Arguments to forward to trace script' \ 23 | [-project 'GCP Project Name'] \ 24 | [-zone 'GCP Project Zone'] \ 25 | [-script 'Path to trace script']" 26 | 27 | # Parse arguments 28 | while [[ "$#" -gt 0 ]]; do case $1 in 29 | -i|-instance) GCE_INSTANCE="$2"; shift;; 30 | -p|-project) GCP_PROJECT_NAME="$2"; shift;; 31 | -z|-zone) GCP_ZONE="$2"; shift;; 32 | -s|-script) LOCAL_PATH_TO_SCRIPT="$2"; shift;; 33 | -ta|-trace_args) TRACE_ARGS="$2"; shift;; 34 | -h|-help) echo "$USAGE"; exit 0;; 35 | *) echo "Unknown parameter passed: $1"; echo ${USAGE}; exit 1;; 36 | esac; shift; done 37 | 38 | if [[ -z "${GCE_INSTANCE}" ]]; then 39 | echo "GCE Instance Name is required." 40 | echo ${USAGE} 41 | exit 1 42 | fi 43 | 44 | if [[ -z "${TRACE_ARGS}" ]]; then 45 | echo "Trace Arguments are required." 46 | echo ${USAGE} 47 | exit 1 48 | fi 49 | 50 | if [[ -z "${LOCAL_PATH_TO_SCRIPT}" ]]; then 51 | LOCAL_PATH_TO_SCRIPT=`dirname "${PROG}"`/trace.sh 52 | fi 53 | 54 | if [[ ! -z "${GCP_PROJECT_NAME}" ]]; then 55 | OLD_PROJECT=$(gcloud config get-value project 2>/dev/null) 56 | gcloud config set project ${GCP_PROJECT_NAME} 57 | fi 58 | 59 | reset_project () { 60 | if [[ ! -z "${OLD_PROJECT+x}" ]]; then 61 | # If empty string, unset the config setting 62 | if [[ -z "${OLD_PROJECT}" ]]; then 63 | gcloud config unset project 2>/dev/null 64 | else 65 | gcloud config set project ${OLD_PROJECT} 2>/dev/null 66 | fi 67 | fi 68 | } 69 | 70 | kill_trap() { 71 | exit_status=$? 72 | reset_project 73 | exit "${exit_status}" 74 | } 75 | 76 | trap kill_trap SIGINT SIGTERM EXIT 77 | 78 | if [[ ! -z "${GCP_ZONE}" ]]; then 79 | GCP_ZONE_CMD="--zone ${GCP_ZONE}" 80 | fi 81 | 82 | # Copy the script to the remote machine 83 | echo "Uploading trace script to ${GCE_INSTANCE}" 84 | gcloud compute scp ${GCP_ZONE_CMD} ${LOCAL_PATH_TO_SCRIPT} ${GCE_INSTANCE}:/tmp/trace.sh 85 | # Make the script executable 86 | gcloud compute ssh ${GCP_ZONE_CMD} ${GCE_INSTANCE} --command="chmod +x /tmp/trace.sh" 87 | # Collect a trace on the remote machine 88 | echo "Collecting trace on ${GCE_INSTANCE}" 89 | gcloud compute ssh ${GCP_ZONE_CMD} ${GCE_INSTANCE} --command="sudo /tmp/trace.sh ${TRACE_ARGS} -out /tmp/trace/" 90 | # Download recorded trace to the current directory 91 | echo "Downloading recorded trace from ${GCE_INSTANCE}" 92 | gcloud compute scp ${GCP_ZONE_CMD} ${GCE_INSTANCE}:/tmp/trace/trace.tar.gz trace.tar.gz 93 | # Cleanup 94 | echo "Cleaning up trace files on ${GCE_INSTANCE}" 95 | gcloud compute ssh ${GCP_ZONE_CMD} ${GCE_INSTANCE} --command="sudo rm -rf /tmp/trace.sh /tmp/trace/" 96 | 97 | reset_project 98 | -------------------------------------------------------------------------------- /util/log.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019 Google Inc. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS-IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // 17 | // Package util contains various helper functions 18 | package util 19 | 20 | import ( 21 | "time" 22 | 23 | log "github.com/golang/glog" 24 | "golang.org/x/time/rate" 25 | ) 26 | 27 | var rateLimiters = map[time.Duration]*rate.Limiter{} 28 | 29 | func allowedToLog(interval time.Duration) bool { 30 | lim, ok := rateLimiters[interval] 31 | if !ok { 32 | lim = rate.NewLimiter(rate.Every(interval), 1) 33 | rateLimiters[interval] = lim 34 | } 35 | return lim.Allow() 36 | } 37 | 38 | // LogErrEveryNTime is a throttled logging function. 39 | // At most 1 error can be logged every interval. 40 | func LogErrEveryNTime(interval time.Duration, message ...interface{}) { 41 | if allowedToLog(interval) { 42 | log.Error(message...) 43 | } 44 | } 45 | 46 | // LogWarnEveryNTime is a throttled logging function. 47 | // At most 1 warning can be logged every interval. 48 | func LogWarnEveryNTime(interval time.Duration, message ...interface{}) { 49 | if allowedToLog(interval) { 50 | log.Warning(message...) 51 | } 52 | } 53 | 54 | // LogErrfEveryNTime is a throttled formatted logging function. 55 | // At most 1 error can be logged every interval. 56 | func LogErrfEveryNTime(interval time.Duration, format string, args ...interface{}) { 57 | if allowedToLog(interval) { 58 | log.Errorf(format, args...) 59 | } 60 | } 61 | 62 | // LogWarnfEveryNTime is a throttled formatted logging function. 63 | // At most 1 warning can be logged every interval. 64 | func LogWarnfEveryNTime(interval time.Duration, format string, args ...interface{}) { 65 | if allowedToLog(interval) { 66 | log.Warningf(format, args...) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /util/status.h: -------------------------------------------------------------------------------- 1 | #ifndef SCHEDVIZ_UTIL_STATUS_H_ 2 | #define SCHEDVIZ_UTIL_STATUS_H_ 3 | 4 | #include 5 | 6 | #include "absl/base/attributes.h" 7 | #include "absl/strings/string_view.h" 8 | 9 | enum class StatusCode : int { 10 | kOk = 0, 11 | kInternal = 13, 12 | }; 13 | 14 | class ABSL_MUST_USE_RESULT Status final { 15 | StatusCode code_; 16 | std::string message_; 17 | 18 | Status(StatusCode code, absl::string_view message) 19 | : code_(code), message_(message) {} 20 | 21 | public: 22 | Status() : code_(StatusCode::kOk), message_("") {} 23 | 24 | inline static Status InternalError(absl::string_view msg) { 25 | return Status(StatusCode::kInternal, msg); 26 | } 27 | 28 | inline static Status OkStatus() { return Status(); } 29 | 30 | StatusCode code() const { return code_; } 31 | 32 | ABSL_MUST_USE_RESULT bool ok() const { return code_ == StatusCode::kOk; } 33 | 34 | absl::string_view message() const { return message_; } 35 | }; 36 | 37 | #endif // SCHEDVIZ_UTIL_STATUS_H_ 38 | --------------------------------------------------------------------------------