OLD | NEW |
(Empty) | |
| 1 <!DOCTYPE html> |
| 2 <!-- |
| 3 Copyright 2017 The Chromium Authors. All rights reserved. |
| 4 Use of this source code is governed by a BSD-style license that can be |
| 5 found in the LICENSE file. |
| 6 --> |
| 7 |
| 8 <link rel="import" href="/tracing/base/base.html"> |
| 9 |
| 10 <script> |
| 11 'use strict'; |
| 12 |
| 13 tr.exportTo('tr.e.importer', function() { |
| 14 const STRING_ID_SUFFIX = '_sid'; |
| 15 const PLURAL_STRING_ID_SUFFIX = '_sids'; |
| 16 |
| 17 function isStringReference(s) { |
| 18 return s.endsWith(STRING_ID_SUFFIX) || s.endsWith(PLURAL_STRING_ID_SUFFIX); |
| 19 } |
| 20 |
| 21 function getStringReferenceName(name) { |
| 22 if (name.endsWith(PLURAL_STRING_ID_SUFFIX)) { |
| 23 return name.slice(0, -PLURAL_STRING_ID_SUFFIX.length); |
| 24 } |
| 25 return name.slice(0, -STRING_ID_SUFFIX.length); |
| 26 } |
| 27 |
| 28 function deferenceStrings(idToString, o) { |
| 29 const clone = Object.assign({}, o); |
| 30 for (const [key, value] of Object.entries(clone)) { |
| 31 if (isStringReference(key)) { |
| 32 const name = getStringReferenceName(key); |
| 33 clone[name] = idToString(value); |
| 34 } |
| 35 } |
| 36 return clone; |
| 37 } |
| 38 |
| 39 function singularize(word) { |
| 40 if (word.endsWith('s')) { |
| 41 return word.slice(0, -1); |
| 42 } |
| 43 return word; |
| 44 } |
| 45 |
| 46 function getMetadataPairs(dataJson) { |
| 47 const isMetadata = v => typeof v !== 'object' || Array.isArray(v); |
| 48 const pairs = Object.entries(dataJson); |
| 49 const metadataPairs = pairs.filter(([_, v]) => isMetadata(v)); |
| 50 return metadataPairs; |
| 51 } |
| 52 |
| 53 function getGroupPairs(dataJson) { |
| 54 const pairs = Object.entries(dataJson); |
| 55 const nonMapPairs = pairs.filter(([k, _]) => k !== 'maps'); |
| 56 const groupPairs = nonMapPairs.filter(([_, v]) => typeof v === 'object'); |
| 57 return groupPairs; |
| 58 } |
| 59 |
| 60 function createMap(mapJson) { |
| 61 const map = new Map(); |
| 62 for (const entry of mapJson) { |
| 63 if (entry.id === undefined) { |
| 64 throw new Error('Missing required key "id" in streaming event.'); |
| 65 } |
| 66 map.set(entry.id, entry); |
| 67 } |
| 68 return map; |
| 69 } |
| 70 |
| 71 function createMaps(mapsJson) { |
| 72 const maps = new Map(); |
| 73 for (const [name, mapJson] of Object.entries(mapsJson)) { |
| 74 maps.set(name, createMap(mapJson)); |
| 75 } |
| 76 return maps; |
| 77 } |
| 78 |
| 79 function createGroup(groupJson, opt_startTime) { |
| 80 const entries = []; |
| 81 const n = Object.values(groupJson)[0].length; |
| 82 |
| 83 for (let i = 0; i < n; i++) { |
| 84 const entry = {}; |
| 85 for (const name in groupJson) { |
| 86 entry[name] = groupJson[name][i]; |
| 87 } |
| 88 entries.push(entry); |
| 89 } |
| 90 |
| 91 const timeDelta = groupJson.timeDelta; |
| 92 if (opt_startTime === undefined && timeDelta !== undefined) { |
| 93 throw new Error('Missing required key "startTime" in streaming event.'); |
| 94 } |
| 95 |
| 96 if (opt_startTime) { |
| 97 let delta = 0; |
| 98 for (const entry of entries) { |
| 99 delta += entry.timeDelta ? entry.timeDelta : 0; |
| 100 entry.time = opt_startTime + delta; |
| 101 } |
| 102 } |
| 103 |
| 104 return entries; |
| 105 } |
| 106 |
| 107 function createGroups(groupsJson, opt_startTime) { |
| 108 const groups = new Map(); |
| 109 for (const [name, groupJson] of Object.entries(groupsJson)) { |
| 110 groups.set(name, createGroup(groupJson, opt_startTime)); |
| 111 } |
| 112 |
| 113 return groups; |
| 114 } |
| 115 |
| 116 function createMetadata(metadataPairs) { |
| 117 const metadata = new Map(); |
| 118 for (const [name, value] of metadataPairs) { |
| 119 metadata.set(name, value); |
| 120 } |
| 121 if (metadata.get('version') === undefined) { |
| 122 throw new Error('Missing required key "version" in streaming event.'); |
| 123 } |
| 124 return metadata; |
| 125 } |
| 126 |
| 127 /** |
| 128 * Extracts data from a profiling dictionary. See goo.gl/R0Ae4f. |
| 129 * |
| 130 * A profiling dictionary is a compressed format that is good for recording |
| 131 * sampling data. ProfilingDictionaryReader unpacks that data. To use the |
| 132 * ProfilingDictionaryReader first create an 'empty' reader using .empty() |
| 133 * then call #expandData(data) on your dictionary or the helper: |
| 134 * #expandEvent(event) on a tracing event containing the profiling dictionary. |
| 135 * ProfilingDictionaryReader is an immutable data structure so these methods |
| 136 * don't modify the ProfilingDictionaryReader instead they return new |
| 137 * ProfilingDictionaryReaders which wrap the data you passed. To access the |
| 138 * unpacked data use the #inflated property and the #getMapValue() method. |
| 139 * |
| 140 * Usage example, given input like: |
| 141 * $ let input = { |
| 142 * version: 1, |
| 143 * allocators: { |
| 144 * books: { |
| 145 * authors: [1, 1, 2], |
| 146 * title_sid: [10, 11, 12], |
| 147 * }, |
| 148 * }, |
| 149 * maps: { |
| 150 * authors: [ |
| 151 * { id: 1, name_sid: 1 }, |
| 152 * { id: 2, name_sid: 2 }, |
| 153 * ], |
| 154 * strings: [ |
| 155 * { id: 1, string: 'DFW' }, |
| 156 * { id: 2, string: 'C. Stross' }, |
| 157 * { id: 10, string: 'Book A' }, |
| 158 * { id: 11, string: 'Book B' }, |
| 159 * { id: 12, string: 'Book C' }, |
| 160 * ], |
| 161 * } |
| 162 * }; |
| 163 * We can create an empty reader: |
| 164 * $ let reader = ProfilingDictionaryReader.empty(); |
| 165 * Then read in the input: |
| 166 * $ reader = reader.expandData(input); |
| 167 * Then view the expanded data: |
| 168 * $ console.log(reader.inflated); |
| 169 * { |
| 170 * books: [ |
| 171 * { author: { id: 1, name: 'DFW' }, title: "Book A", }, |
| 172 * { author: { id: 2, name: 'C. Stross' }, title: "Book B", }, |
| 173 * { author: { id: 2, name: 'C. Stross' }, title: "Book C", }, |
| 174 * ], |
| 175 * } |
| 176 * |
| 177 */ |
| 178 class ProfilingDictionaryReader { |
| 179 constructor(opt_metadata, opt_maps, opt_groups, opt_parent) { |
| 180 this.metadata = opt_metadata || new Map(); |
| 181 this.maps = opt_maps || new Map(); |
| 182 this.groups = opt_groups || new Map(); |
| 183 this.parent_ = opt_parent || undefined; |
| 184 this.inflated_ = undefined; |
| 185 this.raw_ = undefined; |
| 186 this.boundGetString_ = this.getString.bind(this); |
| 187 this.deferenceStrings_ = o => deferenceStrings(this.boundGetString_, o); |
| 188 } |
| 189 |
| 190 /** |
| 191 * Creates an empty ProfilingDictionaryReader. |
| 192 */ |
| 193 static empty() { |
| 194 return new ProfilingDictionaryReader(); |
| 195 } |
| 196 |
| 197 /** |
| 198 * Returns the parent or null if this is the root ProfilingDictionaryReader. |
| 199 */ |
| 200 get parent() { |
| 201 return this.parent_; |
| 202 } |
| 203 |
| 204 get raw() { |
| 205 if (this.raw_) return this.raw_; |
| 206 this.raw_ = {}; |
| 207 for (const [name, group] of this.groups.entries()) { |
| 208 this.raw_[name] = group; |
| 209 } |
| 210 return this.raw_; |
| 211 } |
| 212 |
| 213 get inflated() { |
| 214 if (this.inflated_) return this.inflated_; |
| 215 this.inflated_ = {}; |
| 216 for (const [name, group] of this.groups.entries()) { |
| 217 this.inflated_[name] = this.inflateGroup(group); |
| 218 } |
| 219 return this.inflated_; |
| 220 } |
| 221 |
| 222 /** |
| 223 * Get a map from the newest event by name. |
| 224 * If no map with that name was present returns an empty Map. |
| 225 */ |
| 226 getNewMap(name) { |
| 227 return this.maps.get(name) || new Map(); |
| 228 } |
| 229 |
| 230 /** |
| 231 * Get a record with the id |id| from the map with name |mapName|. |
| 232 * This method searches through the expanded events in reverse order of |
| 233 * expansion until it finds a matching value. If no value matches returns |
| 234 * undefined. |
| 235 */ |
| 236 getMapValue(mapName, id) { |
| 237 let value = this.getNewMap(mapName).get(id); |
| 238 if (value === undefined && this.parent) { |
| 239 value = this.parent.getMapValue(mapName, id); |
| 240 } |
| 241 return value; |
| 242 } |
| 243 |
| 244 /** |
| 245 * Get the string with the id |id|. |
| 246 * This method searches through the expanded events in reverse order of |
| 247 * expansion until it finds a string with the matching id. If there is no |
| 248 * matching string with returns undefined. |
| 249 */ |
| 250 getString(id) { |
| 251 const value = this.getMapValue('strings', id); |
| 252 if (value === undefined) return undefined; |
| 253 return value.string; |
| 254 } |
| 255 |
| 256 /** |
| 257 * True iff this or any parent has a map with name |name|. |
| 258 */ |
| 259 hasMap(name) { |
| 260 if (this.maps.has(name)) return true; |
| 261 if (this.parent === undefined) return false; |
| 262 return this.parent.hasMap(name); |
| 263 } |
| 264 |
| 265 inflateGroup(group) { |
| 266 return group.map(this.inflateEntry.bind(this)); |
| 267 } |
| 268 |
| 269 inflateEntry(entry) { |
| 270 const inflatedEntry = {}; |
| 271 for (const [name, value] of Object.entries(entry)) { |
| 272 let inflatedValue; |
| 273 if (this.hasMap(name)) { |
| 274 const id = value; |
| 275 inflatedValue = this.deferenceStrings_(this.getMapValue(name, id)); |
| 276 } else { |
| 277 inflatedValue = value; |
| 278 } |
| 279 inflatedEntry[singularize(name)] = inflatedValue; |
| 280 } |
| 281 return this.deferenceStrings_(inflatedEntry); |
| 282 } |
| 283 |
| 284 /** |
| 285 * Returns a new ProfilingDictionaryReader with this |
| 286 * ProfilingDictionaryReader as its parent and the fields 'maps', 'groups' |
| 287 * and 'metadata' filled in based on |data|. |
| 288 */ |
| 289 expandData(data) { |
| 290 const mapsJson = data.maps || {}; |
| 291 const groupsJson = data.allocators || {}; |
| 292 const metadataPairs = getMetadataPairs(data); |
| 293 const metadata = createMetadata(metadataPairs); |
| 294 const opt_startTime = metadata.get('startTime'); |
| 295 const maps = createMaps(mapsJson); |
| 296 const groups = createGroups(groupsJson, opt_startTime); |
| 297 return new ProfilingDictionaryReader(metadata, maps, groups, this); |
| 298 } |
| 299 |
| 300 /** |
| 301 * Convenience method for this.expandData(event.args.data). |
| 302 */ |
| 303 expandEvent(event) { |
| 304 return this.expandData(event.args.data); |
| 305 } |
| 306 } |
| 307 |
| 308 return { |
| 309 ProfilingDictionaryReader, |
| 310 singularize, |
| 311 deferenceStringsForTest: deferenceStrings, |
| 312 }; |
| 313 }); |
| 314 </script> |
| 315 |
OLD | NEW |