| 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>
|
| +
|
|
|