// 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 global_timeline_template =
document.currentScript.ownerDocument.querySelector(
'#global-timeline-template');
class GlobalTimeline extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(global_timeline_template.content.cloneNode(true));
}
$(id) {
return this.shadowRoot.querySelector(id);
}
set data(value) {
this._data = value;
this.stateChanged();
}
get data() {
return this._data;
}
set selection(value) {
this._selection = value;
this.stateChanged();
}
get selection() {
return this._selection;
}
isValid() {
return this.data && this.selection;
}
hide() {
this.$('#container').style.display = 'none';
}
show() {
this.$('#container').style.display = 'block';
}
stateChanged() {
if (this.isValid()) {
this.drawChart();
} else {
this.hide();
}
}
getFieldData() {
const labels = [
{type: 'number', label: 'Time'},
{type: 'number', label: 'Ptr compression benefit'},
{type: 'string', role: 'tooltip'},
{type: 'number', label: 'Embedder fields'},
{type: 'number', label: 'Tagged fields'},
{type: 'number', label: 'Other raw fields'},
{type: 'number', label: 'Unboxed doubles'}
];
const chart_data = [labels];
const isolate_data = this.data[this.selection.isolate];
let sum_total = 0;
let sum_ptr_compr_benefit_perc = 0;
let count = 0;
Object.keys(isolate_data.gcs).forEach(gc_key => {
const gc_data = isolate_data.gcs[gc_key];
const data_set = gc_data[this.selection.data_set].field_data;
const data = [];
data.push(gc_data.time * kMillis2Seconds);
const total = data_set.tagged_fields +
data_set.embedder_fields +
data_set.other_raw_fields +
data_set.unboxed_double_fields;
const ptr_compr_benefit = data_set.tagged_fields / 2;
const ptr_compr_benefit_perc = ptr_compr_benefit / total * 100;
sum_total += total;
sum_ptr_compr_benefit_perc += ptr_compr_benefit_perc;
count++;
const tooltip = "Ptr compression benefit: " +
(ptr_compr_benefit / KB).toFixed(2) + "KB " +
" (" + ptr_compr_benefit_perc.toFixed(2) + "%)";
data.push(ptr_compr_benefit / KB);
data.push(tooltip);
data.push(data_set.embedder_fields / KB);
data.push(data_set.tagged_fields / KB);
data.push(data_set.other_raw_fields / KB);
data.push(data_set.unboxed_double_fields / KB);
chart_data.push(data);
});
const avg_ptr_compr_benefit_perc =
count ? sum_ptr_compr_benefit_perc / count : 0;
console.log("==================================================");
console.log("= Average ptr compression benefit is " +
avg_ptr_compr_benefit_perc.toFixed(2) + "%");
console.log("= Average V8 heap size " +
(sum_total / count / KB).toFixed(2) + " KB");
console.log("==================================================");
return chart_data;
}
getCategoryData() {
const categories = Object.keys(this.selection.categories)
.map(k => this.selection.category_names.get(k));
const labels = ['Time', ...categories];
const chart_data = [labels];
const isolate_data = this.data[this.selection.isolate];
Object.keys(isolate_data.gcs).forEach(gc_key => {
const gc_data = isolate_data.gcs[gc_key];
const data_set = gc_data[this.selection.data_set].instance_type_data;
const data = [];
data.push(gc_data.time * kMillis2Seconds);
Object.values(this.selection.categories).forEach(instance_types => {
data.push(
instance_types
.map(instance_type => {
return data_set[instance_type].overall;
})
.reduce((accu, current) => accu + current, 0) /
KB);
});
chart_data.push(data);
});
return chart_data;
}
getInstanceTypeData() {
const instance_types =
Object.values(this.selection.categories)
.reduce((accu, current) => accu.concat(current), []);
const labels = ['Time', ...instance_types];
const chart_data = [labels];
const isolate_data = this.data[this.selection.isolate];
Object.keys(isolate_data.gcs).forEach(gc_key => {
const gc_data = isolate_data.gcs[gc_key];
const data_set = gc_data[this.selection.data_set].instance_type_data;
const data = [];
data.push(gc_data.time * kMillis2Seconds);
instance_types.forEach(instance_type => {
data.push(data_set[instance_type].overall / KB);
});
chart_data.push(data);
});
return chart_data;
}
getChartData() {
switch (this.selection.data_view) {
case VIEW_BY_FIELD_TYPE:
return this.getFieldData();
case VIEW_BY_INSTANCE_CATEGORY:
return this.getCategoryData();
case VIEW_BY_INSTANCE_TYPE:
default:
return this.getInstanceTypeData();
}
}
getChartOptions() {
const options = {
isStacked: true,
hAxis: {
format: '###.##s',
title: 'Time [s]',
},
vAxis: {
format: '#,###KB',
title: 'Memory consumption [KBytes]'
},
chartArea: {left:100, width: '85%', height: '70%'},
legend: {position: 'top', maxLines: '1'},
pointsVisible: true,
pointSize: 5,
explorer: {},
};
switch (this.selection.data_view) {
case VIEW_BY_FIELD_TYPE:
// Overlay pointer compression benefit on top of the graph
return Object.assign(options, {
series: {0: {type: 'line', lineDashStyle: [13, 13]}},
});
case VIEW_BY_INSTANCE_CATEGORY:
case VIEW_BY_INSTANCE_TYPE:
default:
return options;
}
}
drawChart() {
console.assert(this.data, 'invalid data');
console.assert(this.selection, 'invalid selection');
const chart_data = this.getChartData();
const data = google.visualization.arrayToDataTable(chart_data);
const options = this.getChartOptions();
const chart = new google.visualization.AreaChart(this.$('#chart'));
this.show();
chart.draw(data, google.charts.Line.convertOptions(options));
}
}
customElements.define('global-timeline', GlobalTimeline);