[object-stats] CSV export and simple fixes

Allow exporting the current selection as CSV.

No-try: true
Bug: v8:7266
Change-Id: Idd275e749506d2a195a132efa5ec08ebb21ca72f
Reviewed-on: https://chromium-review.googlesource.com/870781
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50668}
This commit is contained in:
Michael Lippautz 2018-01-17 16:37:29 +01:00 committed by Commit Bot
parent db129b6525
commit b68cdf2594
4 changed files with 89 additions and 14 deletions

View File

@ -63,9 +63,19 @@ span {
Merge categories
</label>
</li>
<li>
<label for="gc-select">
Garbage collection (at a specific time in ms)
</label>
<select id="gc-select">
<option>No data</option>
</select>
</li>
<li>
<button id="csv-export" disabled="disabled">Export selection as CSV</button>
</li>
</ul>
<div id="categories"></div>
</template>
<script type="text/javascript" src="categories.js"></script>

View File

@ -17,6 +17,10 @@ class DetailsSelection extends HTMLElement {
'change', e => this.handleIsolateChange(e));
this.datasetSelect.addEventListener(
'change', e => this.notifySelectionChanged(e));
this.gcSelect.addEventListener(
'change', e => this.notifySelectionChanged(e));
this.$('#csv-export')
.addEventListener('click', e => this.exportCurrentSelection(e));
this.$('#merge-categories')
.addEventListener('change', e => this.notifySelectionChanged(e));
}
@ -69,9 +73,14 @@ class DetailsSelection extends HTMLElement {
return this.$('#isolate-select');
}
get gcSelect() {
return this.$('#gc-select');
}
dataChanged() {
this.clearUI();
this.populateSelect('#isolate-select', Object.keys(this.data));
this.populateSelect(
'#isolate-select', Object.keys(this.data).map(v => [v, v]));
this.handleIsolateChange();
}
@ -79,7 +88,9 @@ class DetailsSelection extends HTMLElement {
this.selection = {categories: {}};
removeAllChildren(this.isolateSelect);
removeAllChildren(this.datasetSelect);
removeAllChildren(this.gcSelect);
this.clearCategories();
this.$('#csv-export').disabled = 'disabled';
}
handleIsolateChange(e) {
@ -90,7 +101,12 @@ class DetailsSelection extends HTMLElement {
}
this.populateSelect(
'#dataset-select', this.data[this.selection.isolate].data_sets, 'live');
'#dataset-select',
this.data[this.selection.isolate].data_sets.entries(), 'live');
this.populateSelect(
'#gc-select',
Object.keys(this.data[this.selection.isolate].gcs)
.map(v => [v, this.data[this.selection.isolate].gcs[v].time]));
this.populateCategories();
this.notifySelectionChanged();
}
@ -106,6 +122,8 @@ class DetailsSelection extends HTMLElement {
this.selection.category_names = CATEGORY_NAMES;
this.selection.data_set = this.datasetSelect.value;
this.selection.merge_categories = this.$('#merge-categories').checked;
this.selection.gc = this.gcSelect.value;
this.$('#csv-export').disabled = false;
this.dispatchEvent(new CustomEvent(
'change', {bubbles: true, composed: true, detail: this.selection}));
}
@ -125,17 +143,17 @@ class DetailsSelection extends HTMLElement {
return 'unclassified';
}
createOption(text) {
createOption(value, text) {
const option = document.createElement('option');
option.value = text;
option.value = value;
option.text = text;
return option;
}
populateSelect(id, iterable, autoselect = null) {
for (let option_value of iterable) {
const option = this.createOption(option_value);
if (autoselect === option_value) {
for (let [value, text] of iterable) {
const option = this.createOption(value, text);
if (autoselect === value) {
option.selected = 'selected';
}
this.$(id).appendChild(option);
@ -206,6 +224,33 @@ class DetailsSelection extends HTMLElement {
label.htmlFor = instance_type + 'Checkbox';
return div;
}
exportCurrentSelection(e) {
const data = [];
const selected_data = this.data[this.selection.isolate]
.gcs[this.selection.gc][this.selection.data_set]
.instance_type_data;
Object.values(this.selection.categories).forEach(instance_types => {
instance_types.forEach(instance_type => {
data.push([instance_type, selected_data[instance_type].overall / KB]);
});
});
const createInlineContent = arrayOfRows => {
const content = arrayOfRows.reduce(
(accu, rowAsArray) => {return accu + `${rowAsArray.join(',')}\n`},
'');
return `data:text/csv;charset=utf-8,${content}`;
};
const encodedUri = encodeURI(createInlineContent(data));
const link = document.createElement('a');
link.setAttribute('href', encodedUri);
link.setAttribute(
'download',
`heap_objects_data_${this.selection.isolate}_${this.selection.gc}.csv`);
this.shadowRoot.appendChild(link);
link.click();
this.shadowRoot.removeChild(link);
}
}
customElements.define('details-selection', DetailsSelection);

View File

@ -66,7 +66,7 @@ function globalSelectionChangedA(e) {
<h1>V8 Heap Statistics</h1>
<p>Visualize object statistics that have been gathered using</p>
<ul>
<li><code>--trace-gc-object-stats on V8</code></li>
<li><code>--trace-gc-object-stats</code> on V8</li>
<li>
<a
href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chrome's

View File

@ -151,14 +151,24 @@ class TraceFileReader extends HTMLElement {
Array(
data_set.instance_type_data.FIXED_ARRAY_TYPE.histogram.length)
.fill(0);
let known_over_allocated = 0;
let known_over_allocated_histogram =
Array(data_set.instance_type_data.FIXED_ARRAY_TYPE
.over_allocated_histogram.length)
.fill(0);
for (const instance_type in data_set.instance_type_data) {
if (!instance_type.startsWith('*FIXED_ARRAY')) continue;
const subtype = data_set.instance_type_data[instance_type];
known_count += subtype.count;
known_overall += subtype.count;
known_over_allocated += subtype.over_allocated;
for (let i = 0; i < subtype.histogram.length; i++) {
known_histogram[i] += subtype.histogram[i];
}
for (let i = 0; i < subtype.over_allocated_histogram.length; i++) {
known_over_allocated_histogram[i] +=
subtype.over_allocated_histogram[i];
}
}
const fixed_array_data = data_set.instance_type_data.FIXED_ARRAY_TYPE;
@ -166,20 +176,30 @@ class TraceFileReader extends HTMLElement {
count: fixed_array_data.count - known_count,
overall: fixed_array_data.overall - known_overall,
histogram: fixed_array_data.histogram.map(
(value, index) => value - known_histogram[index])
(value, index) => value - known_histogram[index]),
over_allocated:
fixed_array_data.over_allocated - known_over_allocated,
over_allocated_histogram:
fixed_array_data.over_allocated_histogram.map(
(value, index) =>
value - known_over_allocated_histogram[index])
};
// Check for non-negative values.
checkNonNegativeProperty(unknown_entry, 'count');
checkNonNegativeProperty(unknown_entry, 'overall');
checkNonNegativeProperty(unknown_entry, 'over_allocated');
for (let i = 0; i < unknown_entry.histogram.length; i++) {
checkNonNegativeProperty(unknown_entry.histogram, i);
}
for (let i = 0; i < unknown_entry.over_allocated_histogram.length;
i++) {
checkNonNegativeProperty(unknown_entry.over_allocated_histogram, i);
}
data_set.instance_type_data['*FIXED_ARRAY_UNKNOWN_SUB_TYPE'] =
unknown_entry;
data_set.non_empty_instance_types.add(
'*FIXED_ARRAY_UNKNOWN_SUB_TYPE');
this.addInstanceTypeData(
data, keys, isolate, gc, data_set_key,
'*FIXED_ARRAY_UNKNOWN_SUB_TYPE', unknown_entry);
}
}
}