diff options
Diffstat (limited to 'chromium/v8/tools/zone-stats/details-selection.js')
| -rw-r--r-- | chromium/v8/tools/zone-stats/details-selection.js | 365 |
1 files changed, 365 insertions, 0 deletions
diff --git a/chromium/v8/tools/zone-stats/details-selection.js b/chromium/v8/tools/zone-stats/details-selection.js new file mode 100644 index 00000000000..b25a11337a3 --- /dev/null +++ b/chromium/v8/tools/zone-stats/details-selection.js @@ -0,0 +1,365 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +import {CATEGORIES, CATEGORY_NAMES, categoryByZoneName} from './categories.js'; + +export const VIEW_TOTALS = 'by-totals'; +export const VIEW_BY_ZONE_NAME = 'by-zone-name'; +export const VIEW_BY_ZONE_CATEGORY = 'by-zone-category'; + +export const KIND_ALLOCATED_MEMORY = 'kind-detailed-allocated'; +export const KIND_USED_MEMORY = 'kind-detailed-used'; + +defineCustomElement('details-selection', (templateText) => + class DetailsSelection extends HTMLElement { + constructor() { + super(); + const shadowRoot = this.attachShadow({mode: 'open'}); + shadowRoot.innerHTML = templateText; + this.isolateSelect.addEventListener( + 'change', e => this.handleIsolateChange(e)); + this.dataViewSelect.addEventListener( + 'change', e => this.notifySelectionChanged(e)); + this.dataKindSelect.addEventListener( + 'change', e => this.notifySelectionChanged(e)); + this.showTotalsSelect.addEventListener( + 'change', e => this.notifySelectionChanged(e)); + this.memoryUsageSampleSelect.addEventListener( + 'change', e => this.notifySelectionChanged(e)); + this.timeStartSelect.addEventListener( + 'change', e => this.notifySelectionChanged(e)); + this.timeEndSelect.addEventListener( + 'change', e => this.notifySelectionChanged(e)); + } + + connectedCallback() { + for (let category of CATEGORIES.keys()) { + this.$('#categories').appendChild(this.buildCategory(category)); + } + } + + set data(value) { + this._data = value; + this.dataChanged(); + } + + get data() { + return this._data; + } + + get selectedIsolate() { + return this._data[this.selection.isolate]; + } + + get selectedData() { + console.assert(this.data, 'invalid data'); + console.assert(this.selection, 'invalid selection'); + const time = this.selection.time; + return this.selectedIsolate.samples.get(time); + } + + $(id) { + return this.shadowRoot.querySelector(id); + } + + querySelectorAll(query) { + return this.shadowRoot.querySelectorAll(query); + } + + get dataViewSelect() { + return this.$('#data-view-select'); + } + + get dataKindSelect() { + return this.$('#data-kind-select'); + } + + get isolateSelect() { + return this.$('#isolate-select'); + } + + get memoryUsageSampleSelect() { + return this.$('#memory-usage-sample-select'); + } + + get showTotalsSelect() { + return this.$('#show-totals-select'); + } + + get timeStartSelect() { + return this.$('#time-start-select'); + } + + get timeEndSelect() { + return this.$('#time-end-select'); + } + + buildCategory(name) { + const div = document.createElement('div'); + div.id = name; + div.classList.add('box'); + const ul = document.createElement('ul'); + div.appendChild(ul); + const name_li = document.createElement('li'); + ul.appendChild(name_li); + name_li.innerHTML = CATEGORY_NAMES.get(name); + const percent_li = document.createElement('li'); + ul.appendChild(percent_li); + percent_li.innerHTML = '0%'; + percent_li.id = name + 'PercentContent'; + const all_li = document.createElement('li'); + ul.appendChild(all_li); + const all_button = document.createElement('button'); + all_li.appendChild(all_button); + all_button.innerHTML = 'All'; + all_button.addEventListener('click', e => this.selectCategory(name)); + const none_li = document.createElement('li'); + ul.appendChild(none_li); + const none_button = document.createElement('button'); + none_li.appendChild(none_button); + none_button.innerHTML = 'None'; + none_button.addEventListener('click', e => this.unselectCategory(name)); + const innerDiv = document.createElement('div'); + div.appendChild(innerDiv); + innerDiv.id = name + 'Content'; + const percentDiv = document.createElement('div'); + div.appendChild(percentDiv); + percentDiv.className = 'percentBackground'; + percentDiv.id = name + 'PercentBackground'; + return div; + } + + dataChanged() { + this.selection = {categories: {}, zones: new Map()}; + this.resetUI(true); + this.populateIsolateSelect(); + this.handleIsolateChange(); + this.$('#dataSelectionSection').style.display = 'block'; + } + + populateIsolateSelect() { + let isolates = Object.entries(this.data); + // Sort by peak heap memory consumption. + isolates.sort((a, b) => b[1].peakAllocatedMemory - a[1].peakAllocatedMemory); + this.populateSelect( + '#isolate-select', isolates, (key, isolate) => isolate.getLabel()); + } + + resetUI(resetIsolateSelect) { + if (resetIsolateSelect) removeAllChildren(this.isolateSelect); + + removeAllChildren(this.dataViewSelect); + removeAllChildren(this.dataKindSelect); + removeAllChildren(this.memoryUsageSampleSelect); + this.clearCategories(); + } + + handleIsolateChange(e) { + this.selection.isolate = this.isolateSelect.value; + if (this.selection.isolate.length === 0) { + this.selection.isolate = null; + return; + } + this.resetUI(false); + this.populateSelect( + '#data-view-select', [ + [VIEW_TOTALS, 'Total memory usage'], + [VIEW_BY_ZONE_NAME, 'Selected zones types'], + [VIEW_BY_ZONE_CATEGORY, 'Selected zone categories'], + ], + (key, label) => label, VIEW_TOTALS); + this.populateSelect( + '#data-kind-select', [ + [KIND_ALLOCATED_MEMORY, 'Allocated memory per zone'], + [KIND_USED_MEMORY, 'Used memory per zone'], + ], + (key, label) => label, KIND_ALLOCATED_MEMORY); + + this.populateSelect( + '#memory-usage-sample-select', + [...this.selectedIsolate.samples.entries()].filter(([time, sample]) => { + // Remove samples that does not have detailed per-zone data. + return sample.zones !== undefined; + }), + (time, sample, index) => { + return ((index + ': ').padStart(6, '\u00A0') + + formatSeconds(time).padStart(8, '\u00A0') + ' ' + + formatBytes(sample.allocated).padStart(12, '\u00A0')); + }, + this.selectedIsolate.peakUsageTime); + + this.timeStartSelect.value = this.selectedIsolate.start; + this.timeEndSelect.value = this.selectedIsolate.end; + + this.populateCategories(); + this.notifySelectionChanged(); + } + + notifySelectionChanged(e) { + if (!this.selection.isolate) return; + + this.selection.data_view = this.dataViewSelect.value; + this.selection.data_kind = this.dataKindSelect.value; + this.selection.categories = Object.create(null); + this.selection.zones = new Map(); + this.$('#categories').style.display = 'none'; + for (let category of CATEGORIES.keys()) { + const selected = this.selectedInCategory(category); + if (selected.length > 0) this.selection.categories[category] = selected; + for (const zone_name of selected) { + this.selection.zones.set(zone_name, category); + } + } + this.$('#categories').style.display = 'block'; + this.selection.category_names = CATEGORY_NAMES; + this.selection.show_totals = this.showTotalsSelect.checked; + this.selection.time = Number(this.memoryUsageSampleSelect.value); + this.selection.timeStart = Number(this.timeStartSelect.value); + this.selection.timeEnd = Number(this.timeEndSelect.value); + this.updatePercentagesInCategory(); + this.updatePercentagesInZones(); + this.dispatchEvent(new CustomEvent( + 'change', {bubbles: true, composed: true, detail: this.selection})); + } + + updatePercentagesInCategory() { + const overalls = Object.create(null); + let overall = 0; + // Reset all categories. + this.selection.category_names.forEach((_, category) => { + overalls[category] = 0; + }); + // Only update categories that have selections. + Object.entries(this.selection.categories).forEach(([category, value]) => { + overalls[category] = + Object.values(value).reduce( + (accu, current) => { + const zone_data = this.selectedData.zones.get(current); + return zone_data === undefined ? accu + : accu + zone_data.allocated; + }, 0) / + KB; + overall += overalls[category]; + }); + Object.entries(overalls).forEach(([category, category_overall]) => { + let percents = category_overall / overall * 100; + this.$(`#${category}PercentContent`).innerHTML = + `${percents.toFixed(1)}%`; + this.$('#' + category + 'PercentBackground').style.left = percents + '%'; + }); + } + + updatePercentagesInZones() { + const selected_data = this.selectedData; + const zones_data = selected_data.zones; + const total_allocated = selected_data.allocated; + this.querySelectorAll('.zonesSelectBox input').forEach(checkbox => { + const zone_name = checkbox.value; + const zone_data = zones_data.get(zone_name); + const zone_allocated = zone_data === undefined ? 0 : zone_data.allocated; + if (zone_allocated == 0) { + checkbox.parentNode.style.display = 'none'; + } else { + const percents = zone_allocated / total_allocated; + const percent_div = checkbox.parentNode.querySelector('.percentBackground'); + percent_div.style.left = (percents * 100) + '%'; + checkbox.parentNode.style.display = 'block'; + } + }); + } + + selectedInCategory(category) { + let tmp = []; + this.querySelectorAll('input[name=' + category + 'Checkbox]:checked') + .forEach(checkbox => tmp.push(checkbox.value)); + return tmp; + } + + createOption(value, text) { + const option = document.createElement('option'); + option.value = value; + option.text = text; + return option; + } + + populateSelect(id, iterable, labelFn = null, autoselect = null) { + if (labelFn == null) labelFn = e => e; + let index = 0; + for (let [key, value] of iterable) { + index++; + const label = labelFn(key, value, index); + const option = this.createOption(key, label); + if (autoselect === key) { + option.selected = 'selected'; + } + this.$(id).appendChild(option); + } + } + + clearCategories() { + for (const category of CATEGORIES.keys()) { + let f = this.$('#' + category + 'Content'); + while (f.firstChild) { + f.removeChild(f.firstChild); + } + } + } + + populateCategories() { + this.clearCategories(); + const categories = Object.create(null); + for (let cat of CATEGORIES.keys()) { + categories[cat] = []; + } + + for (const [zone_name, zone_stats] of this.selectedIsolate.zones) { + const category = categoryByZoneName(zone_name); + categories[category].push(zone_name); + } + for (let category of Object.keys(categories)) { + categories[category].sort(); + for (let zone_name of categories[category]) { + this.$('#' + category + 'Content') + .appendChild(this.createCheckBox(zone_name, category)); + } + } + } + + unselectCategory(category) { + this.querySelectorAll('input[name=' + category + 'Checkbox]') + .forEach(checkbox => checkbox.checked = false); + this.notifySelectionChanged(); + } + + selectCategory(category) { + this.querySelectorAll('input[name=' + category + 'Checkbox]') + .forEach(checkbox => checkbox.checked = true); + this.notifySelectionChanged(); + } + + createCheckBox(instance_type, category) { + const div = document.createElement('div'); + div.classList.add('zonesSelectBox'); + div.style.width = "200px"; + const input = document.createElement('input'); + div.appendChild(input); + input.type = 'checkbox'; + input.name = category + 'Checkbox'; + input.checked = 'checked'; + input.id = instance_type + 'Checkbox'; + input.instance_type = instance_type; + input.value = instance_type; + input.addEventListener('change', e => this.notifySelectionChanged(e)); + const label = document.createElement('label'); + div.appendChild(label); + label.innerText = instance_type; + label.htmlFor = instance_type + 'Checkbox'; + const percentDiv = document.createElement('div'); + percentDiv.className = 'percentBackground'; + div.appendChild(percentDiv); + return div; + } +}); |
