2018-01-15 15:24:18 +00:00
|
|
|
// Copyright 2018 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';
|
|
|
|
|
|
|
|
const details_selection_template =
|
|
|
|
document.currentScript.ownerDocument.querySelector(
|
|
|
|
'#details-selection-template');
|
|
|
|
|
2018-05-14 13:06:24 +00:00
|
|
|
const VIEW_BY_INSTANCE_TYPE = 'by-instance-type';
|
|
|
|
const VIEW_BY_INSTANCE_CATEGORY = 'by-instance-category';
|
|
|
|
const VIEW_BY_FIELD_TYPE = 'by-field-type';
|
|
|
|
|
2018-01-15 15:24:18 +00:00
|
|
|
class DetailsSelection extends HTMLElement {
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
const shadowRoot = this.attachShadow({mode: 'open'});
|
|
|
|
shadowRoot.appendChild(details_selection_template.content.cloneNode(true));
|
|
|
|
this.isolateSelect.addEventListener(
|
|
|
|
'change', e => this.handleIsolateChange(e));
|
2018-05-14 13:06:24 +00:00
|
|
|
this.dataViewSelect.addEventListener(
|
|
|
|
'change', e => this.notifySelectionChanged(e));
|
2018-01-15 15:24:18 +00:00
|
|
|
this.datasetSelect.addEventListener(
|
|
|
|
'change', e => this.notifySelectionChanged(e));
|
2018-01-17 15:37:29 +00:00
|
|
|
this.gcSelect.addEventListener(
|
2018-05-14 13:06:24 +00:00
|
|
|
'change', e => this.notifySelectionChanged(e));
|
2018-01-26 16:30:08 +00:00
|
|
|
this.$('#csv-export-btn')
|
2018-01-17 15:37:29 +00:00
|
|
|
.addEventListener('click', e => this.exportCurrentSelection(e));
|
2018-01-26 16:30:08 +00:00
|
|
|
this.$('#category-filter-btn')
|
2018-03-05 16:55:18 +00:00
|
|
|
.addEventListener('click', e => this.filterCurrentSelection(e));
|
2018-03-01 14:08:59 +00:00
|
|
|
this.$('#category-auto-filter-btn')
|
|
|
|
.addEventListener('click', e => this.filterTop20Categories(e));
|
2018-01-15 15:24:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-03-01 14:08:59 +00:00
|
|
|
get selectedIsolate() {
|
|
|
|
return this._data[this.selection.isolate];
|
|
|
|
}
|
|
|
|
|
2018-01-22 15:50:48 +00:00
|
|
|
get selectedData() {
|
|
|
|
console.assert(this.data, 'invalid data');
|
|
|
|
console.assert(this.selection, 'invalid selection');
|
2018-03-01 14:08:59 +00:00
|
|
|
return this.selectedIsolate.gcs[this.selection.gc][this.selection.data_set];
|
2018-01-22 15:50:48 +00:00
|
|
|
}
|
|
|
|
|
2018-03-05 16:55:18 +00:00
|
|
|
$(id) {
|
|
|
|
return this.shadowRoot.querySelector(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
querySelectorAll(query) {
|
|
|
|
return this.shadowRoot.querySelectorAll(query);
|
|
|
|
}
|
|
|
|
|
2018-05-14 13:06:24 +00:00
|
|
|
get dataViewSelect() {
|
|
|
|
return this.$('#data-view-select');
|
|
|
|
}
|
|
|
|
|
2018-03-05 16:55:18 +00:00
|
|
|
get datasetSelect() {
|
|
|
|
return this.$('#dataset-select');
|
|
|
|
}
|
|
|
|
|
|
|
|
get isolateSelect() {
|
|
|
|
return this.$('#isolate-select');
|
|
|
|
}
|
|
|
|
|
|
|
|
get gcSelect() {
|
|
|
|
return this.$('#gc-select');
|
|
|
|
}
|
|
|
|
|
2018-01-15 15:24:18 +00:00
|
|
|
buildCategory(name) {
|
|
|
|
const div = document.createElement('div');
|
|
|
|
div.id = name;
|
|
|
|
div.classList.add('box');
|
2018-01-22 15:50:48 +00:00
|
|
|
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);
|
2018-01-23 15:25:24 +00:00
|
|
|
percent_li.innerHTML = '0%';
|
2018-01-22 15:50:48 +00:00
|
|
|
percent_li.id = name + 'PercentContent';
|
|
|
|
const all_li = document.createElement('li');
|
|
|
|
ul.appendChild(all_li);
|
2018-01-15 15:24:18 +00:00
|
|
|
const all_button = document.createElement('button');
|
2018-01-22 15:50:48 +00:00
|
|
|
all_li.appendChild(all_button);
|
2018-01-15 15:24:18 +00:00
|
|
|
all_button.innerHTML = 'All';
|
|
|
|
all_button.addEventListener('click', e => this.selectCategory(name));
|
2018-01-22 15:50:48 +00:00
|
|
|
const none_li = document.createElement('li');
|
|
|
|
ul.appendChild(none_li);
|
2018-01-15 15:24:18 +00:00
|
|
|
const none_button = document.createElement('button');
|
2018-01-22 15:50:48 +00:00
|
|
|
none_li.appendChild(none_button);
|
2018-01-15 15:24:18 +00:00
|
|
|
none_button.innerHTML = 'None';
|
|
|
|
none_button.addEventListener('click', e => this.unselectCategory(name));
|
|
|
|
const innerDiv = document.createElement('div');
|
|
|
|
div.appendChild(innerDiv);
|
|
|
|
innerDiv.id = name + 'Content';
|
2018-03-05 16:55:18 +00:00
|
|
|
const percentDiv = document.createElement('div');
|
|
|
|
div.appendChild(percentDiv);
|
|
|
|
percentDiv.className = 'percentBackground';
|
|
|
|
percentDiv.id = name + 'PercentBackground';
|
2018-01-15 15:24:18 +00:00
|
|
|
return div;
|
|
|
|
}
|
|
|
|
|
|
|
|
dataChanged() {
|
2018-01-24 09:02:55 +00:00
|
|
|
this.selection = {categories: {}};
|
|
|
|
this.resetUI(true);
|
2018-02-13 14:03:38 +00:00
|
|
|
this.populateIsolateSelect();
|
2018-01-15 15:24:18 +00:00
|
|
|
this.handleIsolateChange();
|
2018-02-14 08:26:42 +00:00
|
|
|
this.$('#dataSelectionSection').style.display = 'block';
|
2018-01-15 15:24:18 +00:00
|
|
|
}
|
|
|
|
|
2018-02-13 14:03:38 +00:00
|
|
|
populateIsolateSelect() {
|
2018-03-01 14:08:59 +00:00
|
|
|
let isolates = Object.entries(this.data);
|
2018-02-13 14:03:38 +00:00
|
|
|
// Sorty by peak heap memory consumption.
|
2018-03-01 14:08:59 +00:00
|
|
|
isolates.sort((a, b) => b[1].peakMemory - a[1].peakMemory);
|
2018-02-13 14:03:38 +00:00
|
|
|
this.populateSelect(
|
2018-03-01 14:08:59 +00:00
|
|
|
'#isolate-select', isolates, (key, isolate) => isolate.getLabel());
|
2018-02-13 14:03:38 +00:00
|
|
|
}
|
|
|
|
|
2018-01-24 09:02:55 +00:00
|
|
|
resetUI(resetIsolateSelect) {
|
|
|
|
if (resetIsolateSelect) removeAllChildren(this.isolateSelect);
|
|
|
|
|
2018-05-14 13:06:24 +00:00
|
|
|
removeAllChildren(this.dataViewSelect);
|
2018-01-15 15:24:18 +00:00
|
|
|
removeAllChildren(this.datasetSelect);
|
2018-01-17 15:37:29 +00:00
|
|
|
removeAllChildren(this.gcSelect);
|
2018-01-15 15:24:18 +00:00
|
|
|
this.clearCategories();
|
2018-03-01 14:08:59 +00:00
|
|
|
this.setButtonState('disabled');
|
|
|
|
}
|
|
|
|
|
|
|
|
setButtonState(disabled) {
|
|
|
|
this.$('#csv-export-btn').disabled = disabled;
|
|
|
|
this.$('#category-filter').disabled = disabled;
|
|
|
|
this.$('#category-filter-btn').disabled = disabled;
|
|
|
|
this.$('#category-auto-filter-btn').disabled = disabled;
|
2018-01-15 15:24:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
handleIsolateChange(e) {
|
|
|
|
this.selection.isolate = this.isolateSelect.value;
|
2018-01-15 20:27:40 +00:00
|
|
|
if (this.selection.isolate.length === 0) {
|
|
|
|
this.selection.isolate = null;
|
|
|
|
return;
|
|
|
|
}
|
2018-01-24 09:02:55 +00:00
|
|
|
this.resetUI(false);
|
2018-05-14 13:06:24 +00:00
|
|
|
this.populateSelect(
|
|
|
|
'#data-view-select', [
|
|
|
|
[VIEW_BY_INSTANCE_TYPE, 'Selected instance types'],
|
|
|
|
[VIEW_BY_INSTANCE_CATEGORY, 'Selected type categories'],
|
|
|
|
[VIEW_BY_FIELD_TYPE, 'Field type statistics']
|
|
|
|
],
|
|
|
|
(key, label) => label, VIEW_BY_INSTANCE_TYPE);
|
2018-01-15 15:24:18 +00:00
|
|
|
this.populateSelect(
|
2018-03-01 14:08:59 +00:00
|
|
|
'#dataset-select', this.selectedIsolate.data_sets.entries(), null,
|
|
|
|
'live');
|
2018-01-17 15:37:29 +00:00
|
|
|
this.populateSelect(
|
|
|
|
'#gc-select',
|
2018-03-01 14:08:59 +00:00
|
|
|
Object.keys(this.selectedIsolate.gcs)
|
2018-03-02 10:51:03 +00:00
|
|
|
.map(id => [id, this.selectedIsolate.gcs[id].time]),
|
2018-03-05 16:55:18 +00:00
|
|
|
(key, time, index) => {
|
|
|
|
return (index + ': ').padStart(4, '0') +
|
|
|
|
formatSeconds(time).padStart(6, '0') + ' ' +
|
|
|
|
formatBytes(this.selectedIsolate.gcs[key].live.overall)
|
|
|
|
.padStart(9, '0');
|
|
|
|
});
|
2018-01-15 15:24:18 +00:00
|
|
|
this.populateCategories();
|
|
|
|
this.notifySelectionChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
notifySelectionChanged(e) {
|
|
|
|
if (!this.selection.isolate) return;
|
2018-01-15 17:58:02 +00:00
|
|
|
|
2018-05-14 13:06:24 +00:00
|
|
|
this.selection.data_view = this.dataViewSelect.value;
|
2018-01-15 17:58:02 +00:00
|
|
|
this.selection.categories = {};
|
2018-05-14 13:06:24 +00:00
|
|
|
if (this.selection.data_view === VIEW_BY_FIELD_TYPE) {
|
|
|
|
this.$('#categories').style.display = 'none';
|
|
|
|
} else {
|
|
|
|
for (let category of CATEGORIES.keys()) {
|
|
|
|
const selected = this.selectedInCategory(category);
|
|
|
|
if (selected.length > 0) this.selection.categories[category] = selected;
|
|
|
|
}
|
|
|
|
this.$('#categories').style.display = 'block';
|
2018-01-15 15:24:18 +00:00
|
|
|
}
|
|
|
|
this.selection.category_names = CATEGORY_NAMES;
|
|
|
|
this.selection.data_set = this.datasetSelect.value;
|
2018-01-17 15:37:29 +00:00
|
|
|
this.selection.gc = this.gcSelect.value;
|
2018-03-01 14:08:59 +00:00
|
|
|
this.setButtonState(false);
|
2018-01-23 15:25:24 +00:00
|
|
|
this.updatePercentagesInCategory();
|
2018-03-05 16:55:18 +00:00
|
|
|
this.updatePercentagesInInstanceTypes();
|
2018-01-15 15:24:18 +00:00
|
|
|
this.dispatchEvent(new CustomEvent(
|
|
|
|
'change', {bubbles: true, composed: true, detail: this.selection}));
|
2018-01-23 15:25:24 +00:00
|
|
|
}
|
2018-01-22 15:50:48 +00:00
|
|
|
|
2018-03-05 16:55:18 +00:00
|
|
|
filterCurrentSelection(e) {
|
2018-03-01 14:08:59 +00:00
|
|
|
const minSize = this.$('#category-filter').value * KB;
|
|
|
|
this.filterCurrentSelectionWithThresold(minSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
filterTop20Categories(e) {
|
|
|
|
// Limit to show top 20 categories only.
|
|
|
|
let minSize = 0;
|
|
|
|
let count = 0;
|
|
|
|
let sizes = this.selectedIsolate.instanceTypePeakMemory;
|
|
|
|
for (let key in sizes) {
|
|
|
|
if (count == 20) break;
|
|
|
|
minSize = sizes[key];
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
this.filterCurrentSelectionWithThresold(minSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
filterCurrentSelectionWithThresold(minSize) {
|
|
|
|
if (minSize === 0) return;
|
2018-01-26 16:30:08 +00:00
|
|
|
|
|
|
|
this.selection.category_names.forEach((_, category) => {
|
2018-03-05 16:55:18 +00:00
|
|
|
for (let checkbox of this.querySelectorAll(
|
2018-01-26 16:30:08 +00:00
|
|
|
'input[name=' + category + 'Checkbox]')) {
|
|
|
|
checkbox.checked =
|
|
|
|
this.selectedData.instance_type_data[checkbox.instance_type]
|
2018-03-01 14:08:59 +00:00
|
|
|
.overall > minSize;
|
2018-03-05 16:55:18 +00:00
|
|
|
console.log(
|
|
|
|
checkbox.instance_type, checkbox.checked,
|
|
|
|
this.selectedData.instance_type_data[checkbox.instance_type]
|
|
|
|
.overall);
|
2018-01-26 16:30:08 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
this.notifySelectionChanged();
|
|
|
|
}
|
|
|
|
|
2018-01-23 15:25:24 +00:00
|
|
|
updatePercentagesInCategory() {
|
2018-01-22 15:50:48 +00:00
|
|
|
const overalls = {};
|
|
|
|
let overall = 0;
|
2018-01-23 15:25:24 +00:00
|
|
|
// Reset all categories.
|
|
|
|
this.selection.category_names.forEach((_, category) => {
|
2018-03-05 16:55:18 +00:00
|
|
|
overalls[category] = 0;
|
2018-01-23 15:25:24 +00:00
|
|
|
});
|
|
|
|
// Only update categories that have selections.
|
|
|
|
Object.entries(this.selection.categories).forEach(([category, value]) => {
|
|
|
|
overalls[category] =
|
2018-01-22 15:50:48 +00:00
|
|
|
Object.values(value).reduce(
|
|
|
|
(accu, current) =>
|
|
|
|
accu + this.selectedData.instance_type_data[current].overall,
|
|
|
|
0) /
|
|
|
|
KB;
|
2018-01-23 15:25:24 +00:00
|
|
|
overall += overalls[category];
|
2018-01-22 15:50:48 +00:00
|
|
|
});
|
2018-01-23 15:25:24 +00:00
|
|
|
Object.entries(overalls).forEach(([category, category_overall]) => {
|
2018-03-05 16:55:18 +00:00
|
|
|
let percents = category_overall / overall * 100;
|
2018-01-23 15:25:24 +00:00
|
|
|
this.$(`#${category}PercentContent`).innerHTML =
|
2018-03-05 16:55:18 +00:00
|
|
|
`${percents.toFixed(1)}%`;
|
|
|
|
this.$('#' + category + 'PercentBackground').style.left = percents + '%';
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
updatePercentagesInInstanceTypes() {
|
|
|
|
const instanceTypeData = this.selectedData.instance_type_data;
|
|
|
|
const maxInstanceType = this.selectedData.singleInstancePeakMemory;
|
|
|
|
this.querySelectorAll('.instanceTypeSelectBox input').forEach(checkbox => {
|
|
|
|
let instanceType = checkbox.value;
|
|
|
|
let instanceTypeSize = instanceTypeData[instanceType].overall;
|
|
|
|
let percents = instanceTypeSize / maxInstanceType;
|
|
|
|
let percentDiv = checkbox.parentNode.querySelector('.percentBackground');
|
|
|
|
percentDiv.style.left = (percents * 100) + '%';
|
|
|
|
|
2018-01-22 15:50:48 +00:00
|
|
|
});
|
2018-01-15 15:24:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
selectedInCategory(category) {
|
2018-03-05 16:55:18 +00:00
|
|
|
let tmp = [];
|
|
|
|
this.querySelectorAll('input[name=' + category + 'Checkbox]:checked')
|
|
|
|
.forEach(checkbox => tmp.push(checkbox.value));
|
2018-01-15 15:24:18 +00:00
|
|
|
return tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
categoryForType(instance_type) {
|
|
|
|
for (let [key, value] of CATEGORIES.entries()) {
|
|
|
|
if (value.has(instance_type)) return key;
|
|
|
|
}
|
|
|
|
return 'unclassified';
|
|
|
|
}
|
|
|
|
|
2018-01-17 15:37:29 +00:00
|
|
|
createOption(value, text) {
|
2018-01-15 15:24:18 +00:00
|
|
|
const option = document.createElement('option');
|
2018-01-17 15:37:29 +00:00
|
|
|
option.value = value;
|
2018-01-15 15:24:18 +00:00
|
|
|
option.text = text;
|
|
|
|
return option;
|
|
|
|
}
|
|
|
|
|
2018-02-13 14:03:38 +00:00
|
|
|
populateSelect(id, iterable, labelFn = null, autoselect = null) {
|
|
|
|
if (labelFn == null) labelFn = e => e;
|
2018-03-02 10:51:03 +00:00
|
|
|
let index = 0;
|
2018-02-13 14:03:38 +00:00
|
|
|
for (let [key, value] of iterable) {
|
2018-03-02 10:51:03 +00:00
|
|
|
index++;
|
|
|
|
const label = labelFn(key, value, index);
|
2018-02-13 14:03:38 +00:00
|
|
|
const option = this.createOption(key, label);
|
|
|
|
if (autoselect === key) {
|
2018-01-15 15:24:18 +00:00
|
|
|
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 = {};
|
|
|
|
for (let cat of CATEGORIES.keys()) {
|
|
|
|
categories[cat] = [];
|
|
|
|
}
|
|
|
|
|
2018-03-01 14:08:59 +00:00
|
|
|
for (let instance_type of this.selectedIsolate.non_empty_instance_types) {
|
2018-01-15 15:24:18 +00:00
|
|
|
const category = this.categoryForType(instance_type);
|
|
|
|
categories[category].push(instance_type);
|
|
|
|
}
|
|
|
|
for (let category of Object.keys(categories)) {
|
|
|
|
categories[category].sort();
|
|
|
|
for (let instance_type of categories[category]) {
|
|
|
|
this.$('#' + category + 'Content')
|
|
|
|
.appendChild(this.createCheckBox(instance_type, category));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unselectCategory(category) {
|
2018-03-05 16:55:18 +00:00
|
|
|
this.querySelectorAll('input[name=' + category + 'Checkbox]')
|
|
|
|
.forEach(checkbox => checkbox.checked = false);
|
2018-01-15 15:24:18 +00:00
|
|
|
this.notifySelectionChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
selectCategory(category) {
|
2018-03-05 16:55:18 +00:00
|
|
|
this.querySelectorAll('input[name=' + category + 'Checkbox]')
|
|
|
|
.forEach(checkbox => checkbox.checked = true);
|
2018-01-15 15:24:18 +00:00
|
|
|
this.notifySelectionChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
createCheckBox(instance_type, category) {
|
|
|
|
const div = document.createElement('div');
|
2018-03-05 16:55:18 +00:00
|
|
|
div.classList.add('instanceTypeSelectBox');
|
2018-01-15 15:24:18 +00:00
|
|
|
const input = document.createElement('input');
|
|
|
|
div.appendChild(input);
|
|
|
|
input.type = 'checkbox';
|
|
|
|
input.name = category + 'Checkbox';
|
|
|
|
input.checked = 'checked';
|
|
|
|
input.id = instance_type + 'Checkbox';
|
2018-01-26 16:30:08 +00:00
|
|
|
input.instance_type = instance_type;
|
2018-01-15 15:24:18 +00:00
|
|
|
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';
|
2018-03-05 16:55:18 +00:00
|
|
|
const percentDiv = document.createElement('div');
|
|
|
|
percentDiv.className = 'percentBackground';
|
|
|
|
div.appendChild(percentDiv);
|
2018-01-15 15:24:18 +00:00
|
|
|
return div;
|
|
|
|
}
|
2018-01-17 15:37:29 +00:00
|
|
|
|
|
|
|
exportCurrentSelection(e) {
|
|
|
|
const data = [];
|
2018-03-01 14:08:59 +00:00
|
|
|
const selected_data =
|
|
|
|
this.selectedIsolate.gcs[this.selection.gc][this.selection.data_set]
|
|
|
|
.instance_type_data;
|
2018-01-17 15:37:29 +00:00
|
|
|
Object.values(this.selection.categories).forEach(instance_types => {
|
|
|
|
instance_types.forEach(instance_type => {
|
|
|
|
data.push([instance_type, selected_data[instance_type].overall / KB]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
const createInlineContent = arrayOfRows => {
|
|
|
|
const content = arrayOfRows.reduce(
|
|
|
|
(accu, rowAsArray) => {return accu + `${rowAsArray.join(',')}\n`},
|
|
|
|
'');
|
|
|
|
return `data:text/csv;charset=utf-8,${content}`;
|
|
|
|
};
|
|
|
|
const encodedUri = encodeURI(createInlineContent(data));
|
|
|
|
const link = document.createElement('a');
|
|
|
|
link.setAttribute('href', encodedUri);
|
|
|
|
link.setAttribute(
|
|
|
|
'download',
|
|
|
|
`heap_objects_data_${this.selection.isolate}_${this.selection.gc}.csv`);
|
|
|
|
this.shadowRoot.appendChild(link);
|
|
|
|
link.click();
|
|
|
|
this.shadowRoot.removeChild(link);
|
|
|
|
}
|
2018-01-15 15:24:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
customElements.define('details-selection', DetailsSelection);
|