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