blob: 0822a6140b309cfc3c059bb78e58dd8800f61f07 [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 2013 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/guid.html">
<link rel="import" href="/tracing/base/math/range.html">
<link rel="import" href="/tracing/model/async_slice_group.html">
<link rel="import" href="/tracing/model/event_container.html">
<link rel="import" href="/tracing/model/slice_group.html">
<link rel="import" href="/tracing/model/thread_slice.html">
<script>
'use strict';
/**
* @fileoverview Provides the Thread class.
*/
tr.exportTo('tr.model', function() {
const AsyncSlice = tr.model.AsyncSlice;
const AsyncSliceGroup = tr.model.AsyncSliceGroup;
const SliceGroup = tr.model.SliceGroup;
const ThreadSlice = tr.model.ThreadSlice;
const ThreadTimeSlice = tr.model.ThreadTimeSlice;
/**
* A Thread stores all the trace events collected for a particular
* thread. We organize the synchronous slices on a thread by "subrows," where
* subrow 0 has all the root slices, subrow 1 those nested 1 deep, and so on.
* The asynchronous slices are stored in an AsyncSliceGroup object.
*
* The slices stored on a Thread should be instances of
* ThreadSlice.
*
* @constructor
* @extends {tr.model.EventContainer}
*/
function Thread(parent, tid) {
if (!parent) {
throw new Error('Parent must be provided.');
}
tr.model.EventContainer.call(this);
this.parent = parent;
this.sortIndex = 0;
this.tid = tid;
this.name = undefined;
this.samples_ = undefined; // Set during createSubSlices
this.sliceGroup = new SliceGroup(this, ThreadSlice, 'slices');
this.timeSlices = undefined;
this.kernelSliceGroup = new SliceGroup(
this, ThreadSlice, 'kernel-slices');
this.asyncSliceGroup = new AsyncSliceGroup(this, 'async-slices');
}
Thread.prototype = {
__proto__: tr.model.EventContainer.prototype,
get model() {
return this.parent.model;
},
get stableId() {
return this.parent.stableId + '.' + this.tid;
},
compareTo(that) {
return Thread.compare(this, that);
},
* childEventContainers() {
if (this.sliceGroup.length) {
yield this.sliceGroup;
}
if (this.kernelSliceGroup.length) {
yield this.kernelSliceGroup;
}
if (this.asyncSliceGroup.length) {
yield this.asyncSliceGroup;
}
},
* childEvents() {
if (this.timeSlices) {
yield* this.timeSlices;
}
},
iterateAllPersistableObjects(cb) {
cb(this);
if (this.sliceGroup.length) {
cb(this.sliceGroup);
}
this.asyncSliceGroup.viewSubGroups.forEach(cb);
},
/**
* Shifts all the timestamps inside this thread forward by the amount
* specified.
*/
shiftTimestampsForward(amount) {
this.sliceGroup.shiftTimestampsForward(amount);
if (this.timeSlices) {
for (let i = 0; i < this.timeSlices.length; i++) {
const slice = this.timeSlices[i];
slice.start += amount;
}
}
this.kernelSliceGroup.shiftTimestampsForward(amount);
this.asyncSliceGroup.shiftTimestampsForward(amount);
},
/**
* Determines whether this thread is empty. If true, it usually implies
* that it should be pruned from the model.
*/
get isEmpty() {
if (this.sliceGroup.length) return false;
if (this.sliceGroup.openSliceCount) return false;
if (this.timeSlices && this.timeSlices.length) return false;
if (this.kernelSliceGroup.length) return false;
if (this.asyncSliceGroup.length) return false;
if (this.samples_.length) return false;
return true;
},
/**
* Updates the bounds based on the
* current objects associated with the thread.
*/
updateBounds() {
this.bounds.reset();
this.sliceGroup.updateBounds();
this.bounds.addRange(this.sliceGroup.bounds);
this.kernelSliceGroup.updateBounds();
this.bounds.addRange(this.kernelSliceGroup.bounds);
this.asyncSliceGroup.updateBounds();
this.bounds.addRange(this.asyncSliceGroup.bounds);
if (this.timeSlices && this.timeSlices.length) {
this.bounds.addValue(this.timeSlices[0].start);
this.bounds.addValue(
this.timeSlices[this.timeSlices.length - 1].end);
}
if (this.samples_ && this.samples_.length) {
this.bounds.addValue(this.samples_[0].start);
this.bounds.addValue(
this.samples_[this.samples_.length - 1].end);
}
},
addCategoriesToDict(categoriesDict) {
for (let i = 0; i < this.sliceGroup.length; i++) {
categoriesDict[this.sliceGroup.slices[i].category] = true;
}
for (let i = 0; i < this.kernelSliceGroup.length; i++) {
categoriesDict[this.kernelSliceGroup.slices[i].category] = true;
}
for (let i = 0; i < this.asyncSliceGroup.length; i++) {
categoriesDict[this.asyncSliceGroup.slices[i].category] = true;
}
if (this.samples_) {
for (let i = 0; i < this.samples_.length; i++) {
categoriesDict[this.samples_[i].category] = true;
}
}
},
autoCloseOpenSlices() {
this.sliceGroup.autoCloseOpenSlices();
this.asyncSliceGroup.autoCloseOpenSlices();
this.kernelSliceGroup.autoCloseOpenSlices();
},
mergeKernelWithUserland() {
if (this.kernelSliceGroup.length > 0) {
const newSlices = SliceGroup.merge(
this.sliceGroup, this.kernelSliceGroup);
this.sliceGroup.slices = newSlices.slices;
this.kernelSliceGroup = new SliceGroup(this);
this.updateBounds();
}
},
createSubSlices() {
this.sliceGroup.createSubSlices();
this.samples_ = this.parent.model.samples.filter(sample =>
sample.thread === this);
},
/**
* @return {String} A user-friendly name for this thread.
*/
get userFriendlyName() {
return this.name || this.tid;
},
/**
* @return {String} User friendly details about this thread.
*/
get userFriendlyDetails() {
return 'tid: ' + this.tid +
(this.name ? ', name: ' + this.name : '');
},
getSettingsKey() {
if (!this.name) return undefined;
const parentKey = this.parent.getSettingsKey();
if (!parentKey) return undefined;
return parentKey + '.' + this.name;
},
getProcess() {
return this.parent;
},
/*
* Returns the index of the slice in the timeSlices array, or undefined.
*/
indexOfTimeSlice(timeSlice) {
const i = tr.b.findLowIndexInSortedArray(
this.timeSlices,
function(slice) { return slice.start; },
timeSlice.start);
if (this.timeSlices[i] !== timeSlice) return undefined;
return i;
},
sumOverToplevelSlicesInRange(range, func) {
let sum = 0;
tr.b.iterateOverIntersectingIntervals(
this.sliceGroup.topLevelSlices,
slice => slice.start, slice => slice.end,
range.min, range.max,
slice => {
let fractionOfSliceInsideRangeOfInterest = 1;
if (slice.duration > 0) {
const intersection = range.findIntersection(slice.range);
fractionOfSliceInsideRangeOfInterest =
intersection.duration / slice.duration;
}
// We assume that if a slice doesn't lie entirely inside the range
// of interest, then |func| is evenly distributed inside of the
// slice.
sum += func(slice) * fractionOfSliceInsideRangeOfInterest;
});
return sum;
},
/**
* Returns the total cpu time consumed within |range| by this thread.
*/
getCpuTimeForRange(range) {
return this.sumOverToplevelSlicesInRange(
range, slice => slice.cpuDuration || 0);
},
/**
* Returns the total number of top-level slices within |range| in this
* thread. If a slice overlaps with |range| and is not completely inside it,
* then we attribute the portion that is inside the range only. For example,
* |getNumToplevelSlicesForRange| will return 1 + 1/3 when we have:
*
* 01 02 03 04 05 06 07 08 09 10
* <---------- range ---------->
* <- slice #1 -> <- slice #2 ->
*/
getNumToplevelSlicesForRange(range) {
return this.sumOverToplevelSlicesInRange(range, slice => 1);
},
getWallTimeForRange(range) {
return this.sumOverToplevelSlicesInRange(
range, slice => slice.duration || 0);
},
getSchedulingStatsForRange(start, end) {
const stats = {};
if (!this.timeSlices) return stats;
function addStatsForSlice(threadTimeSlice) {
const overlapStart = Math.max(threadTimeSlice.start, start);
const overlapEnd = Math.min(threadTimeSlice.end, end);
const schedulingState = threadTimeSlice.schedulingState;
if (!(schedulingState in stats)) stats[schedulingState] = 0;
stats[schedulingState] += overlapEnd - overlapStart;
}
tr.b.iterateOverIntersectingIntervals(this.timeSlices,
function(x) { return x.start; },
function(x) { return x.end; },
start,
end,
addStatsForSlice);
return stats;
},
get samples() {
return this.samples_;
},
/**
* Returns substring of this.name from beginning to the first numeric
* character or the character '/'.
*
* Example:
* ThreadName12 -> ThreadName
* ThreadName/34123 -> ThreadName
* ThreadName1/34123 -> ThreadName
*/
get type() {
const re = /^[^0-9|\/]+/;
const matches = re.exec(this.name);
if (matches && matches[0]) return matches[0];
// If a thread is named 42GPU, let's not try to find its type.
// We should fix the thread name.
throw new Error('Could not determine thread type for thread name ' +
this.name);
}
};
/**
* Comparison between threads that orders first by parent.compareTo,
* then by names, then by tid.
*/
Thread.compare = function(x, y) {
let tmp = x.parent.compareTo(y.parent);
if (tmp) return tmp;
tmp = x.sortIndex - y.sortIndex;
if (tmp) return tmp;
if (x.name !== undefined) {
if (y.name !== undefined) {
tmp = x.name.localeCompare(y.name);
} else {
tmp = -1;
}
} else if (y.name !== undefined) {
tmp = 1;
}
if (tmp) return tmp;
return x.tid - y.tid;
};
return {
Thread,
};
});
</script>