<!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="/experimental/mappers/reduce.html">
<link rel="import" href="/tracing/extras/ads/domain_category.html">
<link rel="import" href="/tracing/extras/chrome/slice_title_fixer.html">
<link rel="import" href="/tracing/model/source_info/js_source_info.html">

<script>
'use strict';

tr.exportTo('pi.m', function() {
  var JSSourceState = tr.model.source_info.JSSourceState;

  function SliceCostInfo() {
    this.threadGroup = undefined;
    this.railTypeName = undefined;
    this.title = undefined;
    this.domainCategory = undefined;
    this.domain = undefined;
    this.userFriendlyCategory = undefined;

    this.selfTime = 0;
    this.cpuSelfTime = 0;

    this.jsTime = 0;
    this.jsTimeByState = {};
    for (var state in JSSourceState) {
      this.jsTimeByState[JSSourceState[state]] = 0;
    }
    this.data = {};
  }

  SliceCostInfo.asReduceTarget = function(key, firstValue) {
    var sliceCostInfo = new SliceCostInfo();
    sliceCostInfo.threadGroup = firstValue.threadGroup;
    sliceCostInfo.railTypeName = firstValue.railTypeName;
    sliceCostInfo.title = firstValue.title;
    sliceCostInfo.domainCategory = firstValue.domainCategory;
    sliceCostInfo.domain = firstValue.domain;
    sliceCostInfo.userFriendlyCategory = firstValue.userFriendlyCategory;
    sliceCostInfo.data = firstValue.data;
    return sliceCostInfo;
  };

  SliceCostInfo.fromDict = function(d) {
    var sliceCostInfo = new SliceCostInfo();
    sliceCostInfo.threadGroup = d.threadGroup;
    sliceCostInfo.railTypeName = d.railTypeName;
    sliceCostInfo.title = d.title;
    sliceCostInfo.domainCategory = d.domainCategory;
    sliceCostInfo.domain = d.domain;
    sliceCostInfo.userFriendlyCategory = d.userFriendlyCategory;
    sliceCostInfo.selfTime = d.selfTime;
    sliceCostInfo.cpuSelfTime = d.cpuSelfTime;
    sliceCostInfo.jsTime = d.jsTime || 0;
    for (var state in JSSourceState) {
      if (d.jsTimeByState === undefined) {
        sliceCostInfo.jsTimeByState[state] = 0;
      } else {
        sliceCostInfo.jsTimeByState[JSSourceState[state]] =
            d.jsTimeByState[JSSourceState[state]] || 0;
      }
    }
    sliceCostInfo.data = d.data;
    return sliceCostInfo;
  };

  SliceCostInfo.prototype = {
    push: function(sliceCostKey, threadSlice) {
      if (threadSlice.selfTime !== undefined)
        this.selfTime += threadSlice.selfTime;
      if (threadSlice.cpuSelfTime !== undefined)
        this.cpuSelfTime += threadSlice.cpuSelfTime;
      if (threadSlice.jsTime !== undefined)
        this.jsTime += threadSlice.jsTime;
      if (threadSlice.jsTimeByState !== undefined) {
        for (var state in JSSourceState) {
          this.jsTimeByState[JSSourceState[state]] +=
              threadSlice.jsTimeByState[JSSourceState[state]];
        }
      }
    },

    finalizeAndGetResult: function() {
      return this;
    }
  };


  function getSliceCostReport(model, threadGrouping, railTypeNameByGUID,
      filterFunction, dataCB) {
    var reduce = new pi.m.StreamingReducer(SliceCostInfo.asReduceTarget);

    function generateDomainCosts(slice) {
      // V8.Execute events may generate several sliceCostInfo, based on the
      // origin of the JS being executed.
      var range = new tr.b.Range();
      slice.addBoundsToRange(range);
      var filtered = range.filterArray(
          slice.parentContainer.samples,
          function(sample) {return sample.start;});
      filtered.forEach(function(sample) {
        var sliceCostInfo = new SliceCostInfo();
        sliceCostInfo.threadGroup = threadGrouping.getGroupNameForEvent(slice);
        sliceCostInfo.railTypeName = railTypeNameByGUID[slice.guid];

        var ufc = model.getUserFriendlyCategoryFromEvent(slice);
        sliceCostInfo.userFriendlyCategory = ufc || 'other';
        sliceCostInfo.title = tr.e.chrome.SliceTitleFixer.fromEvent(slice);
        sliceCostInfo.domain = sample.leafStackFrame.domain;
        sliceCostInfo.domainCategory =
            tr.e.ads.DomainCategory.fromDomain(sliceCostInfo.domain);
        sliceCostInfo.selfTime = sample.weight;
        sliceCostInfo.cpuSelfTime = sample.weight;
        if (dataCB !== undefined)
          sliceCostInfo.data = dataCB(slice);
        // Let's use the state of the leaf frame. TODO(chiniforooshan):
        // understand what it means if frames of a sample stack are in different
        // states (BUG #1542).
        var sourceInfo = sample.leafStackFrame.sourceInfo;
        if (sourceInfo === undefined ||
            !(sourceInfo instanceof tr.model.source_info.JSSourceInfo)) {
          sliceCostInfo.jsTime = sample.weight;
          sliceCostInfo.jsTimeByState[JSSourceState.UNKNOWN] = sample.weight;
        } else {
          sliceCostInfo.jsTimeByState[sourceInfo.state] = sample.weight;
        }
        var key = sliceCostInfo.threadGroup + '/' +
                  sliceCostInfo.railTypeName + '/' +
                  sliceCostInfo.title + '/' +
                  sliceCostInfo.domain;
        reduce.push(key, sliceCostInfo);
      });
    }

    for (var event of model.descendentEvents()) {
      if (!(event instanceof tr.model.ThreadSlice))
        continue;
      if (filterFunction && !filterFunction(event))
        continue;

      var threadSlice = event;
      if (threadSlice.title === 'V8.Execute') {
        generateDomainCosts(threadSlice);
        continue;
      }

      var sliceCostInfo = new SliceCostInfo();
      sliceCostInfo.threadGroup = threadGrouping.getGroupNameForEvent(
          threadSlice);
      sliceCostInfo.railTypeName = railTypeNameByGUID[threadSlice.guid];
      var ufc = model.getUserFriendlyCategoryFromEvent(threadSlice);
      sliceCostInfo.userFriendlyCategory = ufc || 'other';
      sliceCostInfo.title = tr.e.chrome.SliceTitleFixer.fromEvent(threadSlice);
      // For all other events, just generate one sliceCostInfo.
      sliceCostInfo.selfTime = threadSlice.selfTime;
      sliceCostInfo.cpuSelfTime = threadSlice.cpuSelfTime;
      if (dataCB !== undefined)
        sliceCostInfo.data = dataCB(event);

      var key = sliceCostInfo.threadGroup + '/' +
                sliceCostInfo.railTypeName + '/' +
                sliceCostInfo.title;
      reduce.push(key, sliceCostInfo);
    }

    var sliceCostInfos = [];
    reduce.finalizeAndIterResults(function(key, sliceCostInfo) {
      sliceCostInfos.push(sliceCostInfo);
    });
    return sliceCostInfos;
  }

  tr.mre.FunctionRegistry.register(getSliceCostReport);

  return {
    SliceCostInfo: SliceCostInfo,

    getSliceCostReport: getSliceCostReport
  };
});
</script>