| <!DOCTYPE html> |
| <!-- |
| Copyright (c) 2015 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/extras/chrome/chrome_processes.html"> |
| <link rel="import" href="/tracing/model/user_model/user_expectation.html"> |
| |
| <script> |
| 'use strict'; |
| |
| tr.exportTo('tr.metrics.sh', function() { |
| // Returns a weight for this score. |
| // score should be a number between 0 and 1 inclusive. |
| // This function is expected to be passed to tr.b.math.Statistics.weightedMean |
| // as its weightCallback. |
| function perceptualBlend(ir, index, score) { |
| // Lower scores are exponentially more important than higher scores |
| // due to the Peak-end rule. |
| // Other than that general rule, there is no specific reasoning behind this |
| // specific formula -- it is fairly arbitrary. |
| return Math.exp(1 - score); |
| } |
| |
| function filterExpectationsByRange(irs, opt_range) { |
| const filteredExpectations = []; |
| irs.forEach(function(ir) { |
| if (!(ir instanceof tr.model.um.UserExpectation)) return; |
| |
| if (!opt_range || |
| opt_range.intersectsExplicitRangeInclusive(ir.start, ir.end)) { |
| filteredExpectations.push(ir); |
| } |
| }); |
| return filteredExpectations; |
| } |
| |
| /** |
| * Splits the global memory dumps in |model| by browser name. |
| * |
| * @param {!tr.Model} model The trace model from which the global dumps |
| * should be extracted. |
| * @param {!tr.b.math.Range=} opt_rangeOfInterest If provided, global memory |
| * dumps that do not inclusively intersect the range will be skipped. |
| * @return {!Map<string, !Array<!tr.model.GlobalMemoryDump>} A map from |
| * browser names to the associated global memory dumps. |
| */ |
| function splitGlobalDumpsByBrowserName(model, opt_rangeOfInterest) { |
| const chromeModelHelper = |
| model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper); |
| const browserNameToGlobalDumps = new Map(); |
| const globalDumpToBrowserHelper = new WeakMap(); |
| |
| // 1. For each browser process in the model, add its global memory dumps to |
| // |browserNameToGlobalDumps|. |chromeModelHelper| can be undefined if |
| // it fails to find any browser, renderer or GPU process (see |
| // tr.model.helpers.ChromeModelHelper.supportsModel). |
| |
| if (chromeModelHelper) { |
| chromeModelHelper.browserHelpers.forEach(function(helper) { |
| // Retrieve the associated global memory dumps and check that they |
| // haven't been classified as belonging to another browser process. |
| const globalDumps = skipDumpsThatDoNotIntersectRange( |
| helper.process.memoryDumps.map(d => d.globalMemoryDump), |
| opt_rangeOfInterest); |
| globalDumps.forEach(function(globalDump) { |
| const existingHelper = globalDumpToBrowserHelper.get(globalDump); |
| if (existingHelper !== undefined) { |
| throw new Error('Memory dump ID clash across multiple browsers ' + |
| 'with PIDs: ' + existingHelper.pid + ' and ' + helper.pid); |
| } |
| globalDumpToBrowserHelper.set(globalDump, helper); |
| }); |
| |
| makeKeyUniqueAndSet(browserNameToGlobalDumps, |
| tr.e.chrome.chrome_processes.canonicalizeName(helper.browserName), |
| globalDumps); |
| }); |
| } |
| |
| // 2. If any global memory dump does not have any associated browser |
| // process for some reason, associate it with an 'unknown_browser' browser |
| // so that we don't lose the data. |
| |
| const unclassifiedGlobalDumps = skipDumpsThatDoNotIntersectRange( |
| model.globalMemoryDumps.filter(g => !globalDumpToBrowserHelper.has(g)), |
| opt_rangeOfInterest); |
| if (unclassifiedGlobalDumps.length > 0) { |
| makeKeyUniqueAndSet( |
| browserNameToGlobalDumps, 'unknown_browser', unclassifiedGlobalDumps); |
| } |
| |
| return browserNameToGlobalDumps; |
| } |
| |
| /** |
| * Function for adding entries with duplicate keys to a map without |
| * overriding existing entries. |
| * |
| * This is achieved by appending numeric indices (2, 3, 4, ...) to duplicate |
| * keys. Example: |
| * |
| * const map = new Map(); |
| * // map = Map {}. |
| * |
| * makeKeyUniqueAndSet(map, 'key', 'a'); |
| * // map = Map {"key" => "a"}. |
| * |
| * makeKeyUniqueAndSet(map, 'key', 'b'); |
| * // map = Map {"key" => "a", "key2" => "b"}. |
| * ^^^^ |
| * makeKeyUniqueAndSet(map, 'key', 'c'); |
| * // map = Map {"key" => "a", "key2" => "b", "key3" => "c"}. |
| * ^^^^ ^^^^ |
| */ |
| function makeKeyUniqueAndSet(map, key, value) { |
| let uniqueKey = key; |
| let nextIndex = 2; |
| while (map.has(uniqueKey)) { |
| uniqueKey = key + nextIndex; |
| nextIndex++; |
| } |
| map.set(uniqueKey, value); |
| } |
| |
| function skipDumpsThatDoNotIntersectRange(dumps, opt_range) { |
| if (!opt_range) return dumps; |
| return dumps.filter(d => opt_range.intersectsExplicitRangeInclusive( |
| d.start, d.end)); |
| } |
| |
| /** |
| * Returns true if |category| is one of the categories of |event|, and |event| |
| * has title |title|. |
| * |
| * TODO(dproy): Make this a method on a suitable parent class of the |
| * event/slice classes that are used with this function. |
| */ |
| function hasCategoryAndName(event, category, title) { |
| return event.title === title && event.category && |
| tr.b.getCategoryParts(event.category).includes(category); |
| } |
| |
| return { |
| hasCategoryAndName, |
| filterExpectationsByRange, |
| perceptualBlend, |
| splitGlobalDumpsByBrowserName |
| }; |
| }); |
| </script> |