Chromium Code Reviews| Index: tracing/tracing/extras/importer/streaming_event_expander.html |
| diff --git a/tracing/tracing/extras/importer/streaming_event_expander.html b/tracing/tracing/extras/importer/streaming_event_expander.html |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..d8149e3d1bad1ec7a03527ea9d3ebe55d09c778d |
| --- /dev/null |
| +++ b/tracing/tracing/extras/importer/streaming_event_expander.html |
| @@ -0,0 +1,267 @@ |
| +<!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() { |
| + 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 (let 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 (let [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 (let 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 (let 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 (let [name, groupJson] of Object.entries(groupsJson)) { |
| + groups.set(name, createGroup(groupJson, opt_startTime)); |
| + } |
| + |
| + return groups; |
| + } |
| + |
| + function createMetadata(metadataPairs) { |
| + const metadata = new Map(); |
| + for (let [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 'streaming p event'. |
|
Primiano Tucci (use gerrit)
2017/03/22 15:53:52
IMHO would be great if this description could be a
fmeawad
2017/03/23 20:43:57
+1
Can this class be used to import non memory rel
|
| + * See goo.gl/l6KIe3. |
| + * The format of these events have two features: |
| + * * It encodes samples as a struct of arrays. |
| + * * Common values are stored in id keyed maps. These maps build up over time. |
| + * Consequently this class has two matching tasks: |
| + * * It transforms the input from a struct of arrays to an array of structs. |
| + * * It remembers the maps of all events it has seen and can deference and id |
| + * |
| + * Concretely StreamingEventExpander is a immutable data structure you build |
| + * up from tracing events which carry streaming p event data. It provides |
| + * access to the sample data in an array of structs format (via the 'raw' |
| + * property). It provides access to the "maps" of all parsed events via the |
| + * 'getMapValue' method. Finally it can automatically deference keys in the |
| + * structs which refer to maps. This is done via the 'inflated' property. |
| + * |
| + * To use first create an empty StreamingEventExpander with |
| + * StreamingEventExpander.empty then append new data using |
| + * #expandData and access that data using #raw, #inflated and #getMapValue. |
| + */ |
| + class StreamingEventExpander { |
| + 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; |
| + } |
| + |
| + /** |
| + * Creates an empty StreamingEventExpander. |
| + */ |
| + static empty() { |
| + return new StreamingEventExpander(); |
| + } |
| + |
| + /** |
| + * Returns the parent or null if this is the root StreamingEventExpander. |
| + */ |
| + get parent() { |
| + return this.parent_; |
| + } |
| + |
| + get raw() { |
| + if (this.raw_) return this.raw_; |
| + this.raw_ = {}; |
| + for (let [name, group] of this.groups.entries()) { |
| + this.raw_[name] = group; |
| + } |
| + return this.raw_; |
| + } |
| + |
| + get inflated() { |
| + if (this.inflated_) return this.inflated_; |
| + this.inflated_ = {}; |
| + for (let [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)); |
| + } |
| + |
| + deferenceStrings(o) { |
| + let clone = Object.assign({}, o); |
| + for (let [key, value] of Object.entries(clone)) { |
| + if (key.endsWith('_sid')) { |
| + clone[key.slice(0, -4)] = this.getString(value); |
| + } |
| + } |
| + return clone; |
| + } |
| + |
| + inflateEntry(entry) { |
| + let inflatedEntry = {}; |
| + for (let [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 inflatedEntry; |
| + } |
| + |
| + /** |
| + * Returns a new StreamingEventExpander with this StreamingEventExpander 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 StreamingEventExpander(metadata, maps, groups, this); |
| + } |
| + |
| + /** |
| + * Convenience method for this.expandData(event.args.data). |
| + */ |
| + expandEvent(event) { |
| + return this.expandData(event.args.data); |
| + } |
| + } |
| + |
| + return { |
| + StreamingEventExpander, |
| + singularize, |
| + }; |
| +}); |
| +</script> |
| + |