1// Copyright 2018 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5'use strict'; 6 7import { 8 VIEW_BY_INSTANCE_TYPE, 9 VIEW_BY_INSTANCE_CATEGORY, 10 VIEW_BY_FIELD_TYPE 11} from './details-selection.js'; 12 13defineCustomElement('global-timeline', (templateText) => 14 class GlobalTimeline extends HTMLElement { 15 constructor() { 16 super(); 17 const shadowRoot = this.attachShadow({mode: 'open'}); 18 shadowRoot.innerHTML = templateText; 19 } 20 21 $(id) { 22 return this.shadowRoot.querySelector(id); 23 } 24 25 set data(value) { 26 this._data = value; 27 this.stateChanged(); 28 } 29 30 get data() { 31 return this._data; 32 } 33 34 set selection(value) { 35 this._selection = value; 36 this.stateChanged(); 37 } 38 39 get selection() { 40 return this._selection; 41 } 42 43 isValid() { 44 return this.data && this.selection; 45 } 46 47 hide() { 48 this.$('#container').style.display = 'none'; 49 } 50 51 show() { 52 this.$('#container').style.display = 'block'; 53 } 54 55 stateChanged() { 56 if (this.isValid()) { 57 this.drawChart(); 58 } else { 59 this.hide(); 60 } 61 } 62 63 getFieldData() { 64 const labels = [ 65 {type: 'number', label: 'Time'}, 66 {type: 'number', label: 'Ptr compression benefit'}, 67 {type: 'string', role: 'tooltip'}, 68 {type: 'number', label: 'Embedder fields'}, 69 {type: 'number', label: 'Tagged fields (excl. in-object Smis)'}, 70 {type: 'number', label: 'In-object Smi-only fields'}, 71 {type: 'number', label: 'Other raw fields'}, 72 {type: 'number', label: 'Unboxed doubles'}, 73 {type: 'number', label: 'Boxed doubles'}, 74 {type: 'number', label: 'String data'} 75 ]; 76 const chart_data = [labels]; 77 const isolate_data = this.data[this.selection.isolate]; 78 let sum_total = 0; 79 let sum_ptr_compr_benefit_perc = 0; 80 let count = 0; 81 Object.keys(isolate_data.gcs).forEach(gc_key => { 82 const gc_data = isolate_data.gcs[gc_key]; 83 const data_set = gc_data[this.selection.data_set].field_data; 84 const data = []; 85 data.push(gc_data.time * kMillis2Seconds); 86 const total = data_set.tagged_fields + 87 data_set.inobject_smi_fields + 88 data_set.embedder_fields + 89 data_set.other_raw_fields + 90 data_set.unboxed_double_fields + 91 data_set.boxed_double_fields + 92 data_set.string_data; 93 const ptr_compr_benefit = 94 (data_set.inobject_smi_fields + data_set.tagged_fields) / 2; 95 const ptr_compr_benefit_perc = ptr_compr_benefit / total * 100; 96 sum_total += total; 97 sum_ptr_compr_benefit_perc += ptr_compr_benefit_perc; 98 count++; 99 const tooltip = "Ptr compression benefit: " + 100 (ptr_compr_benefit / KB).toFixed(2) + "KB " + 101 " (" + ptr_compr_benefit_perc.toFixed(2) + "%)"; 102 data.push(ptr_compr_benefit / KB); 103 data.push(tooltip); 104 data.push(data_set.embedder_fields / KB); 105 data.push(data_set.tagged_fields / KB); 106 data.push(data_set.inobject_smi_fields / KB); 107 data.push(data_set.other_raw_fields / KB); 108 data.push(data_set.unboxed_double_fields / KB); 109 data.push(data_set.boxed_double_fields / KB); 110 data.push(data_set.string_data / KB); 111 chart_data.push(data); 112 }); 113 const avg_ptr_compr_benefit_perc = 114 count ? sum_ptr_compr_benefit_perc / count : 0; 115 console.log("=================================================="); 116 console.log("= Average ptr compression benefit is " + 117 avg_ptr_compr_benefit_perc.toFixed(2) + "%"); 118 console.log("= Average V8 heap size " + 119 (sum_total / count / KB).toFixed(2) + " KB"); 120 console.log("=================================================="); 121 return chart_data; 122 } 123 124 getCategoryData() { 125 const categories = Object.keys(this.selection.categories) 126 .map(k => this.selection.category_names.get(k)); 127 const labels = ['Time', ...categories]; 128 const chart_data = [labels]; 129 const isolate_data = this.data[this.selection.isolate]; 130 Object.keys(isolate_data.gcs).forEach(gc_key => { 131 const gc_data = isolate_data.gcs[gc_key]; 132 const data_set = gc_data[this.selection.data_set].instance_type_data; 133 const data = []; 134 data.push(gc_data.time * kMillis2Seconds); 135 Object.values(this.selection.categories).forEach(instance_types => { 136 data.push( 137 instance_types 138 .map(instance_type => { 139 return data_set[instance_type].overall; 140 }) 141 .reduce((accu, current) => accu + current, 0) / 142 KB); 143 }); 144 chart_data.push(data); 145 }); 146 return chart_data; 147 } 148 149 getInstanceTypeData() { 150 const instance_types = 151 Object.values(this.selection.categories) 152 .reduce((accu, current) => accu.concat(current), []); 153 const labels = ['Time', ...instance_types]; 154 const chart_data = [labels]; 155 const isolate_data = this.data[this.selection.isolate]; 156 Object.keys(isolate_data.gcs).forEach(gc_key => { 157 const gc_data = isolate_data.gcs[gc_key]; 158 const data_set = gc_data[this.selection.data_set].instance_type_data; 159 const data = []; 160 data.push(gc_data.time * kMillis2Seconds); 161 instance_types.forEach(instance_type => { 162 data.push(data_set[instance_type].overall / KB); 163 }); 164 chart_data.push(data); 165 }); 166 return chart_data; 167 } 168 169 getChartData() { 170 switch (this.selection.data_view) { 171 case VIEW_BY_FIELD_TYPE: 172 return this.getFieldData(); 173 case VIEW_BY_INSTANCE_CATEGORY: 174 return this.getCategoryData(); 175 case VIEW_BY_INSTANCE_TYPE: 176 default: 177 return this.getInstanceTypeData(); 178 } 179 } 180 181 getChartOptions() { 182 const options = { 183 isStacked: true, 184 hAxis: { 185 format: '###.##s', 186 title: 'Time [s]', 187 }, 188 vAxis: { 189 format: '#,###KB', 190 title: 'Memory consumption [KBytes]' 191 }, 192 chartArea: {left:100, width: '85%', height: '70%'}, 193 legend: {position: 'top', maxLines: '1'}, 194 pointsVisible: true, 195 pointSize: 5, 196 explorer: {}, 197 }; 198 switch (this.selection.data_view) { 199 case VIEW_BY_FIELD_TYPE: 200 // Overlay pointer compression benefit on top of the graph 201 return Object.assign(options, { 202 series: {0: {type: 'line', lineDashStyle: [13, 13]}}, 203 }); 204 case VIEW_BY_INSTANCE_CATEGORY: 205 case VIEW_BY_INSTANCE_TYPE: 206 default: 207 return options; 208 } 209 } 210 211 drawChart() { 212 setTimeout(() => this._drawChart(), 10); 213 } 214 215 _drawChart() { 216 console.assert(this.data, 'invalid data'); 217 console.assert(this.selection, 'invalid selection'); 218 219 const chart_data = this.getChartData(); 220 221 const data = google.visualization.arrayToDataTable(chart_data); 222 const options = this.getChartOptions(); 223 const chart = new google.visualization.AreaChart(this.$('#chart')); 224 this.show(); 225 chart.draw(data, google.charts.Line.convertOptions(options)); 226 } 227}); 228