// 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);