global-timeline.js 7 KB
Newer Older
1 2 3 4 5 6
// 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';

7 8 9 10
import {
  VIEW_BY_INSTANCE_TYPE,
  VIEW_BY_INSTANCE_CATEGORY,
  VIEW_BY_FIELD_TYPE
11
} from './details-selection.js';
12 13 14

defineCustomElement('global-timeline', (templateText) =>
 class GlobalTimeline extends HTMLElement {
15 16 17
  constructor() {
    super();
    const shadowRoot = this.attachShadow({mode: 'open'});
18
    shadowRoot.innerHTML = templateText;
19 20
  }

21 22 23 24
  $(id) {
    return this.shadowRoot.querySelector(id);
  }

25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
  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;
  }

47 48 49 50 51 52 53 54
  hide() {
    this.$('#container').style.display = 'none';
  }

  show() {
    this.$('#container').style.display = 'block';
  }

55
  stateChanged() {
56 57 58 59 60
    if (this.isValid()) {
      this.drawChart();
    } else {
      this.hide();
    }
61 62
  }

63
  getFieldData() {
64 65 66 67 68
    const labels = [
      {type: 'number', label: 'Time'},
      {type: 'number', label: 'Ptr compression benefit'},
      {type: 'string', role: 'tooltip'},
      {type: 'number', label: 'Embedder fields'},
69 70
      {type: 'number', label: 'Tagged fields (excl. in-object Smis)'},
      {type: 'number', label: 'In-object Smi-only fields'},
71
      {type: 'number', label: 'Other raw fields'},
72 73 74
      {type: 'number', label: 'Unboxed doubles'},
      {type: 'number', label: 'Boxed doubles'},
      {type: 'number', label: 'String data'}
75
    ];
76 77
    const chart_data = [labels];
    const isolate_data = this.data[this.selection.isolate];
78 79 80
    let sum_total = 0;
    let sum_ptr_compr_benefit_perc = 0;
    let count = 0;
81 82 83 84 85
    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);
86
      const total = data_set.tagged_fields +
87
                    data_set.inobject_smi_fields +
88 89
                    data_set.embedder_fields +
                    data_set.other_raw_fields +
90 91 92 93
                    data_set.unboxed_double_fields +
                    data_set.boxed_double_fields +
                    data_set.string_data;
      const ptr_compr_benefit =
94
          (data_set.inobject_smi_fields + data_set.tagged_fields) / 2;
95 96 97 98 99 100 101 102 103
      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);
104 105
      data.push(data_set.embedder_fields / KB);
      data.push(data_set.tagged_fields / KB);
106
      data.push(data_set.inobject_smi_fields / KB);
107 108
      data.push(data_set.other_raw_fields / KB);
      data.push(data_set.unboxed_double_fields / KB);
109 110
      data.push(data_set.boxed_double_fields / KB);
      data.push(data_set.string_data / KB);
111 112
      chart_data.push(data);
    });
113 114 115 116 117 118 119 120
    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("==================================================");
121 122 123
    return chart_data;
  }

124
  getCategoryData() {
125 126 127 128 129
    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];
130 131
    Object.keys(isolate_data.gcs).forEach(gc_key => {
      const gc_data = isolate_data.gcs[gc_key];
132 133
      const data_set = gc_data[this.selection.data_set].instance_type_data;
      const data = [];
134
      data.push(gc_data.time * kMillis2Seconds);
135 136 137 138 139 140 141 142 143
      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);
      });
144
      chart_data.push(data);
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
    });
    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 = [];
160
      data.push(gc_data.time * kMillis2Seconds);
161 162 163 164 165 166 167 168
      instance_types.forEach(instance_type => {
        data.push(data_set[instance_type].overall / KB);
      });
      chart_data.push(data);
    });
    return chart_data;
  }

169 170 171 172 173 174 175 176 177 178 179
  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();
    }
  }
180

181
  getChartOptions() {
182 183 184
    const options = {
      isStacked: true,
      hAxis: {
185 186 187 188 189 190
        format: '###.##s',
        title: 'Time [s]',
      },
      vAxis: {
        format: '#,###KB',
        title: 'Memory consumption [KBytes]'
191
      },
192
      chartArea: {left:100, width: '85%', height: '70%'},
193 194 195
      legend: {position: 'top', maxLines: '1'},
      pointsVisible: true,
      pointSize: 5,
196
      explorer: {},
197
    };
198 199 200 201 202 203 204 205 206 207 208 209 210 211
    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() {
212 213 214 215
    setTimeout(() => this._drawChart(), 10);
  }

  _drawChart() {
216 217 218 219 220 221 222
    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();
223
    const chart = new google.visualization.AreaChart(this.$('#chart'));
224
    this.show();
225 226
    chart.draw(data, google.charts.Line.convertOptions(options));
  }
227
});