Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(283)

Unified Diff: tracing/tracing/extras/importer/profiling_dictionary_reader.html

Issue 2635023002: [tracing] Support new heap dump format (Closed)
Patch Set: fix bug''fix bug Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tracing/tracing/extras/importer/profiling_dictionary_reader.html
diff --git a/tracing/tracing/extras/importer/profiling_dictionary_reader.html b/tracing/tracing/extras/importer/profiling_dictionary_reader.html
new file mode 100644
index 0000000000000000000000000000000000000000..ae8c51334e8fd6ed57f321425b6b35056b8f8e60
--- /dev/null
+++ b/tracing/tracing/extras/importer/profiling_dictionary_reader.html
@@ -0,0 +1,315 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/base/base.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.e.importer', function() {
+ const STRING_ID_SUFFIX = '_sid';
+ const PLURAL_STRING_ID_SUFFIX = '_sids';
+
+ function isStringReference(s) {
+ return s.endsWith(STRING_ID_SUFFIX) || s.endsWith(PLURAL_STRING_ID_SUFFIX);
+ }
+
+ function getStringReferenceName(name) {
+ if (name.endsWith(PLURAL_STRING_ID_SUFFIX)) {
+ return name.slice(0, -PLURAL_STRING_ID_SUFFIX.length);
+ }
+ return name.slice(0, -STRING_ID_SUFFIX.length);
+ }
+
+ function deferenceStrings(idToString, o) {
+ const clone = Object.assign({}, o);
+ for (const [key, value] of Object.entries(clone)) {
+ if (isStringReference(key)) {
+ const name = getStringReferenceName(key);
+ clone[name] = idToString(value);
+ }
+ }
+ return clone;
+ }
+
+ function singularize(word) {
+ if (word.endsWith('s')) {
+ return word.slice(0, -1);
+ }
+ return word;
+ }
+
+ function getMetadataPairs(dataJson) {
+ const isMetadata = v => typeof v !== 'object' || Array.isArray(v);
+ const pairs = Object.entries(dataJson);
+ const metadataPairs = pairs.filter(([_, v]) => isMetadata(v));
+ return metadataPairs;
+ }
+
+ function getGroupPairs(dataJson) {
+ const pairs = Object.entries(dataJson);
+ const nonMapPairs = pairs.filter(([k, _]) => k !== 'maps');
+ const groupPairs = nonMapPairs.filter(([_, v]) => typeof v === 'object');
+ return groupPairs;
+ }
+
+ function createMap(mapJson) {
+ const map = new Map();
+ for (const entry of mapJson) {
+ if (entry.id === undefined) {
+ throw new Error('Missing required key "id" in streaming event.');
+ }
+ map.set(entry.id, entry);
+ }
+ return map;
+ }
+
+ function createMaps(mapsJson) {
+ const maps = new Map();
+ for (const [name, mapJson] of Object.entries(mapsJson)) {
+ maps.set(name, createMap(mapJson));
+ }
+ return maps;
+ }
+
+ function createGroup(groupJson, opt_startTime) {
+ const entries = [];
+ const n = Object.values(groupJson)[0].length;
+
+ for (let i = 0; i < n; i++) {
+ const entry = {};
+ for (const name in groupJson) {
+ entry[name] = groupJson[name][i];
+ }
+ entries.push(entry);
+ }
+
+ const timeDelta = groupJson.timeDelta;
+ if (opt_startTime === undefined && timeDelta !== undefined) {
+ throw new Error('Missing required key "startTime" in streaming event.');
+ }
+
+ if (opt_startTime) {
+ let delta = 0;
+ for (const entry of entries) {
+ delta += entry.timeDelta ? entry.timeDelta : 0;
+ entry.time = opt_startTime + delta;
+ }
+ }
+
+ return entries;
+ }
+
+ function createGroups(groupsJson, opt_startTime) {
+ const groups = new Map();
+ for (const [name, groupJson] of Object.entries(groupsJson)) {
+ groups.set(name, createGroup(groupJson, opt_startTime));
+ }
+
+ return groups;
+ }
+
+ function createMetadata(metadataPairs) {
+ const metadata = new Map();
+ for (const [name, value] of metadataPairs) {
+ metadata.set(name, value);
+ }
+ if (metadata.get('version') === undefined) {
+ throw new Error('Missing required key "version" in streaming event.');
+ }
+ return metadata;
+ }
+
+ /**
+ * Extracts data from a profiling dictionary. See goo.gl/R0Ae4f.
+ *
+ * A profiling dictionary is a compressed format that is good for recording
+ * sampling data. ProfilingDictionaryReader unpacks that data. To use the
+ * ProfilingDictionaryReader first create an 'empty' reader using .empty()
+ * then call #expandData(data) on your dictionary or the helper:
+ * #expandEvent(event) on a tracing event containing the profiling dictionary.
+ * ProfilingDictionaryReader is an immutable data structure so these methods
+ * don't modify the ProfilingDictionaryReader instead they return new
+ * ProfilingDictionaryReaders which wrap the data you passed. To access the
+ * unpacked data use the #inflated property and the #getMapValue() method.
+ *
+ * Usage example, given input like:
+ * $ let input = {
+ * version: 1,
+ * allocators: {
+ * books: {
+ * authors: [1, 1, 2],
+ * title_sid: [10, 11, 12],
+ * },
+ * },
+ * maps: {
+ * authors: [
+ * { id: 1, name_sid: 1 },
+ * { id: 2, name_sid: 2 },
+ * ],
+ * strings: [
+ * { id: 1, string: 'DFW' },
+ * { id: 2, string: 'C. Stross' },
+ * { id: 10, string: 'Book A' },
+ * { id: 11, string: 'Book B' },
+ * { id: 12, string: 'Book C' },
+ * ],
+ * }
+ * };
+ * We can create an empty reader:
+ * $ let reader = ProfilingDictionaryReader.empty();
+ * Then read in the input:
+ * $ reader = reader.expandData(input);
+ * Then view the expanded data:
+ * $ console.log(reader.inflated);
+ * {
+ * books: [
+ * { author: { id: 1, name: 'DFW' }, title: "Book A", },
+ * { author: { id: 2, name: 'C. Stross' }, title: "Book B", },
+ * { author: { id: 2, name: 'C. Stross' }, title: "Book C", },
+ * ],
+ * }
+ *
+ */
+ class ProfilingDictionaryReader {
+ constructor(opt_metadata, opt_maps, opt_groups, opt_parent) {
+ this.metadata = opt_metadata || new Map();
+ this.maps = opt_maps || new Map();
+ this.groups = opt_groups || new Map();
+ this.parent_ = opt_parent || undefined;
+ this.inflated_ = undefined;
+ this.raw_ = undefined;
+ this.boundGetString_ = this.getString.bind(this);
+ this.deferenceStrings_ = o => deferenceStrings(this.boundGetString_, o);
+ }
+
+ /**
+ * Creates an empty ProfilingDictionaryReader.
+ */
+ static empty() {
+ return new ProfilingDictionaryReader();
+ }
+
+ /**
+ * Returns the parent or null if this is the root ProfilingDictionaryReader.
+ */
+ get parent() {
+ return this.parent_;
+ }
+
+ get raw() {
+ if (this.raw_) return this.raw_;
+ this.raw_ = {};
+ for (const [name, group] of this.groups.entries()) {
+ this.raw_[name] = group;
+ }
+ return this.raw_;
+ }
+
+ get inflated() {
+ if (this.inflated_) return this.inflated_;
+ this.inflated_ = {};
+ for (const [name, group] of this.groups.entries()) {
+ this.inflated_[name] = this.inflateGroup(group);
+ }
+ return this.inflated_;
+ }
+
+ /**
+ * Get a map from the newest event by name.
+ * If no map with that name was present returns an empty Map.
+ */
+ getNewMap(name) {
+ return this.maps.get(name) || new Map();
+ }
+
+ /**
+ * Get a record with the id |id| from the map with name |mapName|.
+ * This method searches through the expanded events in reverse order of
+ * expansion until it finds a matching value. If no value matches returns
+ * undefined.
+ */
+ getMapValue(mapName, id) {
+ let value = this.getNewMap(mapName).get(id);
+ if (value === undefined && this.parent) {
+ value = this.parent.getMapValue(mapName, id);
+ }
+ return value;
+ }
+
+ /**
+ * Get the string with the id |id|.
+ * This method searches through the expanded events in reverse order of
+ * expansion until it finds a string with the matching id. If there is no
+ * matching string with returns undefined.
+ */
+ getString(id) {
+ const value = this.getMapValue('strings', id);
+ if (value === undefined) return undefined;
+ return value.string;
+ }
+
+ /**
+ * True iff this or any parent has a map with name |name|.
+ */
+ hasMap(name) {
+ if (this.maps.has(name)) return true;
+ if (this.parent === undefined) return false;
+ return this.parent.hasMap(name);
+ }
+
+ inflateGroup(group) {
+ return group.map(this.inflateEntry.bind(this));
+ }
+
+ inflateEntry(entry) {
+ const inflatedEntry = {};
+ for (const [name, value] of Object.entries(entry)) {
+ let inflatedValue;
+ if (this.hasMap(name)) {
+ const id = value;
+ inflatedValue = this.deferenceStrings_(this.getMapValue(name, id));
+ } else {
+ inflatedValue = value;
+ }
+ inflatedEntry[singularize(name)] = inflatedValue;
+ }
+ return this.deferenceStrings_(inflatedEntry);
+ }
+
+ /**
+ * Returns a new ProfilingDictionaryReader with this
+ * ProfilingDictionaryReader as its parent and the fields 'maps', 'groups'
+ * and 'metadata' filled in based on |data|.
+ */
+ expandData(data) {
+ const mapsJson = data.maps || {};
+ const groupsJson = data.allocators || {};
+ const metadataPairs = getMetadataPairs(data);
+ const metadata = createMetadata(metadataPairs);
+ const opt_startTime = metadata.get('startTime');
+ const maps = createMaps(mapsJson);
+ const groups = createGroups(groupsJson, opt_startTime);
+ return new ProfilingDictionaryReader(metadata, maps, groups, this);
+ }
+
+ /**
+ * Convenience method for this.expandData(event.args.data).
+ */
+ expandEvent(event) {
+ return this.expandData(event.args.data);
+ }
+ }
+
+ return {
+ ProfilingDictionaryReader,
+ singularize,
+ deferenceStringsForTest: deferenceStrings,
+ };
+});
+</script>
+

Powered by Google App Engine
This is Rietveld 408576698