histogram-viewer.js 5.26 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('histogram-viewer', (templateText) =>
 class HistogramViewer extends HTMLElement {
15 16 17
  constructor() {
    super();
    const shadowRoot = this.attachShadow({mode: 'open'});
18
    shadowRoot.innerHTML = templateText;
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
  }

  $(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() {
44 45 46 47
    return this.data && this.selection &&
           (this.selection.data_view === VIEW_BY_INSTANCE_CATEGORY ||
            this.selection.data_view === VIEW_BY_INSTANCE_TYPE);
    ;
48 49 50 51 52 53 54 55 56 57
  }

  hide() {
    this.$('#container').style.display = 'none';
  }

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

58 59 60 61 62 63 64 65 66 67 68 69
  getOverallValue() {
    switch (this.selection.data_view) {
      case VIEW_BY_FIELD_TYPE:
        return NaN;
      case VIEW_BY_INSTANCE_CATEGORY:
        return this.getPropertyForCategory('overall');
      case VIEW_BY_INSTANCE_TYPE:
      default:
        return this.getPropertyForInstanceTypes('overall');
    }
  }

70 71
  stateChanged() {
    if (this.isValid()) {
72
      const overall_bytes = this.getOverallValue();
73
      this.$('#overall').innerHTML = `Overall: ${overall_bytes / KB} KB`;
74 75 76 77 78 79 80 81 82 83 84 85 86
      this.drawChart();
    } else {
      this.hide();
    }
  }

  get selectedData() {
    console.assert(this.data, 'invalid data');
    console.assert(this.selection, 'invalid selection');
    return this.data[this.selection.isolate]
        .gcs[this.selection.gc][this.selection.data_set];
  }

87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
  get selectedInstanceTypes() {
    console.assert(this.selection, 'invalid selection');
    return Object.values(this.selection.categories)
        .reduce((accu, current) => accu.concat(current), []);
  }

  getPropertyForCategory(property) {
    return Object.values(this.selection.categories)
        .reduce(
            (outer_accu, instance_types) => outer_accu +
                instance_types.reduce(
                    (inner_accu, instance_type) => inner_accu +
                        this.selectedData
                            .instance_type_data[instance_type][property],
                    0),
            0);
  }

  getPropertyForInstanceTypes(property) {
    return this.selectedInstanceTypes.reduce(
        (accu, instance_type) => accu +
            this.selectedData.instance_type_data[instance_type][property],
        0);
  }

112 113 114 115 116 117 118 119 120 121 122
  formatBytes(bytes) {
    const units = ['B', 'KiB', 'MiB'];
    const divisor = 1024;
    let index = 0;
    while (index < units.length && bytes >= divisor) {
      index++;
      bytes /= divisor;
    }
    return bytes + units[index];
  }

123 124 125 126 127 128 129 130
  getCategoryData() {
    const labels = [
      'Bucket',
      ...Object.keys(this.selection.categories)
          .map(k => this.selection.category_names.get(k))
    ];
    const data = this.selectedData.bucket_sizes.map(
        (bucket_size, index) =>
131
            [`<${this.formatBytes(bucket_size)}`,
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
             ...Object.values(this.selection.categories)
                 .map(
                     instance_types =>
                         instance_types
                             .map(
                                 instance_type =>
                                     this.selectedData
                                         .instance_type_data[instance_type]
                                         .histogram[index])
                             .reduce((accu, current) => accu + current, 0))]);
    // Adjust last histogram bucket label.
    data[data.length - 1][0] = 'rest';
    return [labels, ...data];
  }

  getInstanceTypeData() {
148
    const instance_types = this.selectedInstanceTypes;
149 150 151 152 153 154 155 156 157 158 159 160 161
    const labels = ['Bucket', ...instance_types];
    const data = this.selectedData.bucket_sizes.map(
        (bucket_size, index) =>
            [`<${bucket_size}`,
             ...instance_types.map(
                 instance_type =>
                     this.selectedData.instance_type_data[instance_type]
                         .histogram[index])]);
    // Adjust last histogram bucket label.
    data[data.length - 1][0] = 'rest';
    return [labels, ...data];
  }

162 163 164 165 166 167 168 169 170 171 172 173
  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();
    }
  }

174
  drawChart() {
175
    const chart_data = this.getChartData();
176 177 178 179 180
    const data = google.visualization.arrayToDataTable(chart_data);
    const options = {
      legend: {position: 'top', maxLines: '1'},
      chartArea: {width: '85%', height: '85%'},
      bar: {groupWidth: '80%'},
181 182 183 184
      hAxis: {
        title: 'Count',
        minValue: 0
      },
185
      explorer: {},
186 187 188 189 190
    };
    const chart = new google.visualization.BarChart(this.$('#chart'));
    this.show();
    chart.draw(data, options);
  }
191
});