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