Support multiple trace files.
This commit allows to view trace for both SurfaceFlinger and WindowManager at the same time using one timeline. Test: yarn run dev Bug: Change-Id: I11abcf8b6423a03ca9af63e56a7b992958d21175
This commit is contained in:
1
tools/winscope/.gitignore
vendored
Normal file
1
tools/winscope/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules/
|
||||||
@@ -16,169 +16,63 @@
|
|||||||
<div id="app">
|
<div id="app">
|
||||||
<md-whiteframe md-tag="md-toolbar">
|
<md-whiteframe md-tag="md-toolbar">
|
||||||
<h1 class="md-title" style="flex: 1">{{title}}</h1>
|
<h1 class="md-title" style="flex: 1">{{title}}</h1>
|
||||||
|
<a class="md-button md-accent md-raised md-theme-default" @click="clear()" v-if="dataLoaded">Clear</a>
|
||||||
<div>
|
|
||||||
<md-checkbox v-model="store.displayDefaults">Show default properties
|
|
||||||
<md-tooltip md-direction="bottom">
|
|
||||||
If checked, shows the value of all properties.
|
|
||||||
Otherwise, hides all properties whose value is the default for its data type.
|
|
||||||
</md-tooltip>
|
|
||||||
</md-checkbox>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="file" @change="onLoadFile" id="upload-file" v-show="false"/>
|
|
||||||
<label class="md-button md-accent md-raised md-theme-default" for="upload-file">Open File</label>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<md-select v-model="fileType" id="file-type" placeholder="File type">
|
|
||||||
<md-option value="auto">Detect type</md-option>
|
|
||||||
<md-option :value="k" v-for="(v,k) in FILE_TYPES">{{v.name}}</md-option>
|
|
||||||
</md-select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</md-whiteframe>
|
</md-whiteframe>
|
||||||
|
<div class="main-content">
|
||||||
<div class="main-content" v-if="timeline.length">
|
<datainput v-if="!dataLoaded" ref="input" :store="store" @dataReady="onDataReady" @statusChange="setStatus" />
|
||||||
<md-card class="timeline-card">
|
<md-card v-if="dataLoaded">
|
||||||
<md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense"><h2 class="md-title">Timeline</h2></md-whiteframe>
|
<md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense">
|
||||||
<timeline :items="timeline" :selected="tree" @item-selected="onTimelineItemSelected" class="timeline" />
|
<h2 class="md-title">Timeline</h2>
|
||||||
|
</md-whiteframe>
|
||||||
|
<md-list>
|
||||||
|
<md-list-item v-for="(file, idx) in files" :key="file.filename">
|
||||||
|
<md-icon>{{file.type.icon}}</md-icon>
|
||||||
|
<timeline :items="file.timeline" :selected-index="file.selectedIndex" :scale="scale" @item-selected="onTimelineItemSelected($event, idx)" class="timeline" />
|
||||||
|
</md-list-item>
|
||||||
|
</md-list>
|
||||||
</md-card>
|
</md-card>
|
||||||
|
<dataview v-for="file in files" :key="file.filename" :ref="file.filename" :store="store" :file="file" @focus="onDataViewFocus(file.filename)" />
|
||||||
<div class="container">
|
|
||||||
<md-card class="rects">
|
|
||||||
<md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense"><h2 class="md-title">Screen</h2></md-whiteframe>
|
|
||||||
<md-whiteframe md-elevation="8">
|
|
||||||
<rects :bounds="bounds" :rects="rects" :highlight="highlight" @rect-click="onRectClick" />
|
|
||||||
</md-whiteframe>
|
|
||||||
</md-card>
|
|
||||||
|
|
||||||
<md-card class="hierarchy">
|
|
||||||
<md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense">
|
|
||||||
<h2 class="md-title" style="flex: 1;">Hierarchy</h2>
|
|
||||||
<md-checkbox v-model="store.onlyVisible">Only visible</md-checkbox>
|
|
||||||
<md-checkbox v-model="store.flattened">Flat</md-checkbox>
|
|
||||||
</md-whiteframe>
|
|
||||||
<tree-view :item="tree" @item-selected="itemSelected" :selected="hierarchySelected" :filter="hierarchyFilter" :flattened="store.flattened" ref="hierarchy" />
|
|
||||||
</md-card>
|
|
||||||
|
|
||||||
<md-card class="properties">
|
|
||||||
<md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense">
|
|
||||||
<h2 class="md-title" style="flex: 1">Properties</h2>
|
|
||||||
<div class="filter">
|
|
||||||
<input id="filter" type="search" placeholder="Filter..." v-model="propertyFilterString" />
|
|
||||||
</div>
|
|
||||||
</md-whiteframe>
|
|
||||||
<tree-view :item="selectedTree" :filter="propertyFilter" />
|
|
||||||
</md-card>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import jsonProtoDefs from 'frameworks/base/core/proto/android/server/windowmanagertrace.proto'
|
|
||||||
import jsonProtoDefsSF from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto'
|
|
||||||
import protobuf from 'protobufjs'
|
|
||||||
|
|
||||||
import TreeView from './TreeView.vue'
|
import TreeView from './TreeView.vue'
|
||||||
import Timeline from './Timeline.vue'
|
import Timeline from './Timeline.vue'
|
||||||
import Rects from './Rects.vue'
|
import Rects from './Rects.vue'
|
||||||
|
import DataView from './DataView.vue'
|
||||||
import detectFile from './detectfile.js'
|
import DataInput from './DataInput.vue'
|
||||||
import LocalStore from './localstore.js'
|
import LocalStore from './localstore.js'
|
||||||
|
|
||||||
import {transform_json} from './transform.js'
|
const APP_NAME = "Winscope"
|
||||||
import {transform_layers, transform_layers_trace} from './transform_sf.js'
|
|
||||||
import {transform_window_service, transform_window_trace} from './transform_wm.js'
|
|
||||||
import {fill_transform_data, format_transform_type, is_simple_transform} from './matrix_utils.js'
|
|
||||||
|
|
||||||
|
// Find the index of the last element matching the predicate in a sorted array
|
||||||
var protoDefs = protobuf.Root.fromJSON(jsonProtoDefs)
|
function findLastMatchingSorted(array, predicate) {
|
||||||
.addJSON(jsonProtoDefsSF.nested);
|
var a = 0;
|
||||||
|
var b = array.length - 1;
|
||||||
var TraceMessage = protoDefs.lookupType(
|
while (b - a > 1) {
|
||||||
"com.android.server.wm.WindowManagerTraceFileProto");
|
var m = Math.floor((a + b) / 2);
|
||||||
var ServiceMessage = protoDefs.lookupType(
|
if (predicate(array, m)) {
|
||||||
"com.android.server.wm.WindowManagerServiceDumpProto");
|
a = m;
|
||||||
var LayersMessage = protoDefs.lookupType("android.surfaceflinger.LayersProto");
|
} else {
|
||||||
var LayersTraceMessage = protoDefs.lookupType("android.surfaceflinger.LayersTraceFileProto");
|
b = m - 1;
|
||||||
|
|
||||||
function formatProto(obj) {
|
|
||||||
if (!obj || !obj.$type) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (obj.$type.fullName === '.android.surfaceflinger.RectProto' ||
|
|
||||||
obj.$type.fullName === '.android.graphics.RectProto') {
|
|
||||||
return `(${obj.left}, ${obj.top}) - (${obj.right}, ${obj.bottom})`;
|
|
||||||
} else if (obj.$type.fullName === '.android.surfaceflinger.FloatRectProto') {
|
|
||||||
return `(${obj.left.toFixed(3)}, ${obj.top.toFixed(3)}) - (${obj.right.toFixed(3)}, ${obj.bottom.toFixed(3)})`;
|
|
||||||
}
|
|
||||||
else if (obj.$type.fullName === '.android.surfaceflinger.PositionProto') {
|
|
||||||
return `(${obj.x.toFixed(3)}, ${obj.y.toFixed(3)})`;
|
|
||||||
} else if (obj.$type.fullName === '.android.surfaceflinger.SizeProto') {
|
|
||||||
return `${obj.w} x ${obj.h}`;
|
|
||||||
} else if (obj.$type.fullName === '.android.surfaceflinger.ColorProto') {
|
|
||||||
return `r:${obj.r} g:${obj.g} \n b:${obj.b} a:${obj.a}`;
|
|
||||||
} else if (obj.$type.fullName === '.android.surfaceflinger.TransformProto') {
|
|
||||||
var transform_type = format_transform_type(obj);
|
|
||||||
if (is_simple_transform(obj)) {
|
|
||||||
return `${transform_type}`;
|
|
||||||
}
|
}
|
||||||
return `${transform_type} dsdx:${obj.dsdx.toFixed(3)} dtdx:${obj.dtdx.toFixed(3)} dsdy:${obj.dsdy.toFixed(3)} dtdy:${obj.dtdy.toFixed(3)}`;
|
|
||||||
}
|
}
|
||||||
|
return predicate(array, b) ? b : a;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FILE_TYPES = {
|
|
||||||
'window_dump': {
|
|
||||||
protoType: ServiceMessage,
|
|
||||||
transform: transform_window_service,
|
|
||||||
name: "WindowManager dump",
|
|
||||||
timeline: false,
|
|
||||||
},
|
|
||||||
'window_trace': {
|
|
||||||
protoType: TraceMessage,
|
|
||||||
transform: transform_window_trace,
|
|
||||||
name: "WindowManager trace",
|
|
||||||
timeline: true,
|
|
||||||
},
|
|
||||||
'layers_dump': {
|
|
||||||
protoType: LayersMessage,
|
|
||||||
transform: transform_layers,
|
|
||||||
name: "SurfaceFlinger dump",
|
|
||||||
timeline: false,
|
|
||||||
},
|
|
||||||
'layers_trace': {
|
|
||||||
protoType: LayersTraceMessage,
|
|
||||||
transform: transform_layers_trace,
|
|
||||||
name: "SurfaceFlinger trace",
|
|
||||||
timeline: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedTree: {},
|
files: [],
|
||||||
hierarchySelected: null,
|
title: APP_NAME,
|
||||||
tree: {},
|
currentTimestamp: 0,
|
||||||
timeline: [],
|
activeDataView: null,
|
||||||
bounds: {},
|
|
||||||
rects: [],
|
|
||||||
highlight: null,
|
|
||||||
timelineIndex: 0,
|
|
||||||
title: "The Tool",
|
|
||||||
filename: "",
|
|
||||||
lastSelectedStableId: null,
|
|
||||||
propertyFilterString: "",
|
|
||||||
store: LocalStore('app', {
|
store: LocalStore('app', {
|
||||||
flattened: false,
|
flattened: false,
|
||||||
onlyVisible: false,
|
onlyVisible: false,
|
||||||
displayDefaults: true
|
displayDefaults: true,
|
||||||
}),
|
}),
|
||||||
FILE_TYPES,
|
|
||||||
fileType: "auto",
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@@ -186,191 +80,85 @@ export default {
|
|||||||
document.title = this.title;
|
document.title = this.title;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onLoadFile(e) {
|
clear() {
|
||||||
return this.onLoadProtoFile(e, this.fileType);
|
this.files = [];
|
||||||
},
|
},
|
||||||
onLoadProtoFile(event, type) {
|
onTimelineItemSelected(index, timelineIndex) {
|
||||||
var files = event.target.files || event.dataTransfer.files;
|
this.files[timelineIndex].selectedIndex = index;
|
||||||
var file = files[0];
|
var t = parseInt(this.files[timelineIndex].timeline[index].timestamp);
|
||||||
if (!file) {
|
for (var i = 0; i < this.files.length; i++) {
|
||||||
// No file selected.
|
if (i != timelineIndex) {
|
||||||
return;
|
this.files[i].selectedIndex = findLastMatchingSorted(this.files[i].timeline, function(array, idx) {
|
||||||
|
return parseInt(array[idx].timestamp) <= t;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.filename = file.name;
|
this.currentTimestamp = t;
|
||||||
this.title = this.filename + " (loading)";
|
},
|
||||||
|
advanceTimeline(direction) {
|
||||||
var reader = new FileReader();
|
var closestTimeline = -1;
|
||||||
reader.onload = (e) => {
|
var timeDiff = Infinity;
|
||||||
var buffer = new Uint8Array(e.target.result);
|
for (var idx = 0; idx < this.files.length; idx++) {
|
||||||
try {
|
var file = this.files[idx];
|
||||||
if (FILE_TYPES[type]) {
|
var cur = file.selectedIndex;
|
||||||
var filetype = FILE_TYPES[type];
|
if (cur + direction < 0 || cur + direction >= this.files[idx].timeline.length) {
|
||||||
var decoded = filetype.protoType.decode(buffer);
|
continue;
|
||||||
modifyProtoFields(decoded, this.store.displayDefaults);
|
|
||||||
var transformed = filetype.transform(decoded);
|
|
||||||
} else {
|
|
||||||
var [filetype, decoded] = detectFile(buffer);
|
|
||||||
modifyProtoFields(decoded, this.store.displayDefaults);
|
|
||||||
var transformed = filetype.transform(decoded);
|
|
||||||
}} catch (ex) {
|
|
||||||
this.title = this.filename + ': ' + ex;
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
event.target.value =''
|
|
||||||
}
|
}
|
||||||
this.title = this.filename + " (loading " + filetype.name + ")";
|
var d = Math.abs(parseInt(file.timeline[cur + direction].timestamp) - this.currentTimestamp);
|
||||||
// Replace enum values with string representation and
|
if (timeDiff > d) {
|
||||||
// add default values to the proto objects. This function also handles
|
timeDiff = d;
|
||||||
// a special case with TransformProtos where the matrix may be derived
|
closestTimeline = idx;
|
||||||
// from the transform type.
|
|
||||||
function modifyProtoFields(protoObj, displayDefaults) {
|
|
||||||
if (!protoObj || protoObj !== Object(protoObj) || !protoObj.$type) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (var fieldName in protoObj.$type.fields) {
|
|
||||||
var fieldProperties = protoObj.$type.fields[fieldName];
|
|
||||||
var field = protoObj[fieldName];
|
|
||||||
|
|
||||||
if (Array.isArray(field)) {
|
|
||||||
field.forEach((item, _) => {
|
|
||||||
modifyProtoFields(item, displayDefaults);
|
|
||||||
})
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (displayDefaults && !(field)) {
|
|
||||||
protoObj[fieldName] = fieldProperties.defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fieldProperties.type === 'TransformProto'){
|
|
||||||
fill_transform_data(protoObj[fieldName]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fieldProperties.resolvedType && fieldProperties.resolvedType.valuesById) {
|
|
||||||
protoObj[fieldName] = fieldProperties.resolvedType.valuesById[protoObj[fieldProperties.name]];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
modifyProtoFields(protoObj[fieldName], displayDefaults);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (filetype.timeline) {
|
|
||||||
this.timeline = transformed.children;
|
|
||||||
} else {
|
|
||||||
this.timeline = [transformed];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.title = this.filename + " (" + filetype.name + ")";
|
|
||||||
|
|
||||||
this.lastSelectedStableId = null;
|
|
||||||
this.onTimelineItemSelected(this.timeline[0], 0);
|
|
||||||
}
|
}
|
||||||
reader.readAsArrayBuffer(files[0]);
|
if (closestTimeline >= 0) {
|
||||||
},
|
this.files[closestTimeline].selectedIndex += direction;
|
||||||
itemSelected(item) {
|
this.currentTimestamp = parseInt(this.files[closestTimeline].timeline[this.files[closestTimeline].selectedIndex].timestamp);
|
||||||
this.hierarchySelected = item;
|
|
||||||
this.selectedTree = transform_json(item.obj, item.name, {
|
|
||||||
skip: item.skip,
|
|
||||||
formatter: formatProto});
|
|
||||||
this.highlight = item.highlight;
|
|
||||||
this.lastSelectedStableId = item.stableId;
|
|
||||||
},
|
|
||||||
onRectClick(item) {
|
|
||||||
if (item) {
|
|
||||||
this.itemSelected(item);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onTimelineItemSelected(item, index) {
|
onDataViewFocus(view) {
|
||||||
this.timelineIndex = index;
|
this.activeDataView = view;
|
||||||
this.tree = item;
|
|
||||||
this.rects = [...item.rects].reverse();
|
|
||||||
this.bounds = item.bounds;
|
|
||||||
|
|
||||||
this.hierarchySelected = null;
|
|
||||||
this.selectedTree = {};
|
|
||||||
this.highlight = null;
|
|
||||||
|
|
||||||
function find_item(item, stableId) {
|
|
||||||
if (item.stableId === stableId) {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
if (Array.isArray(item.children)) {
|
|
||||||
for (var child of item.children) {
|
|
||||||
var found = find_item(child, stableId);
|
|
||||||
if (found) {
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.lastSelectedStableId) {
|
|
||||||
var found = find_item(item, this.lastSelectedStableId);
|
|
||||||
if (found) {
|
|
||||||
this.itemSelected(found);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onKeyDown(event) {
|
onKeyDown(event) {
|
||||||
event = event || window.event;
|
event = event || window.event;
|
||||||
if (event.keyCode == 37 /* left */) {
|
if (event.keyCode == 37 /* left */ ) {
|
||||||
this.advanceTimeline(-1);
|
this.advanceTimeline(-1);
|
||||||
} else if (event.keyCode == 39 /* right */) {
|
} else if (event.keyCode == 39 /* right */ ) {
|
||||||
this.advanceTimeline(1);
|
this.advanceTimeline(1);
|
||||||
} else if (event.keyCode == 38 /* up */) {
|
} else if (event.keyCode == 38 /* up */ ) {
|
||||||
this.$refs.hierarchy.selectPrev();
|
this.$refs[this.activeView][0].arrowUp();
|
||||||
} else if (event.keyCode == 40 /* down */) {
|
} else if (event.keyCode == 40 /* down */ ) {
|
||||||
this.$refs.hierarchy.selectNext();
|
this.$refs[this.activeView][0].arrowDown();
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
advanceTimeline(frames) {
|
onDataReady(files) {
|
||||||
if (!Array.isArray(this.timeline) || this.timeline.length == 0) {
|
this.files = files;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var nextIndex = this.timelineIndex + frames;
|
|
||||||
if (nextIndex < 0) {
|
|
||||||
nextIndex = 0;
|
|
||||||
}
|
|
||||||
if (nextIndex >= this.timeline.length) {
|
|
||||||
nextIndex = this.timeline.length - 1;
|
|
||||||
}
|
|
||||||
this.onTimelineItemSelected(this.timeline[nextIndex], nextIndex);
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
setStatus(status) {
|
||||||
|
if (status) {
|
||||||
|
this.title = status;
|
||||||
|
} else {
|
||||||
|
this.title = APP_NAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
prettyDump: function() { return JSON.stringify(this.dump, null, 2); },
|
prettyDump: function() { return JSON.stringify(this.dump, null, 2); },
|
||||||
hierarchyFilter() {
|
dataLoaded: function() { return this.files.length > 0 },
|
||||||
return this.store.onlyVisible ? (c, flattened) => {
|
scale() {
|
||||||
return c.visible || c.childrenVisible && !flattened;
|
var mx = Math.max(...(this.files.map(f => Math.max(...f.timeline.map(t => t.timestamp)))));
|
||||||
} : null;
|
var mi = Math.min(...(this.files.map(f => Math.min(...f.timeline.map(t => t.timestamp)))));
|
||||||
},
|
return [mi, mx];
|
||||||
propertyFilter() {
|
|
||||||
var filterStrings = this.propertyFilterString.split(",");
|
|
||||||
var positive = [];
|
|
||||||
var negative = [];
|
|
||||||
filterStrings.forEach((f) => {
|
|
||||||
if (f.startsWith("!")) {
|
|
||||||
var str = f.substring(1);
|
|
||||||
negative.push((s) => s.indexOf(str) === -1);
|
|
||||||
} else {
|
|
||||||
var str = f;
|
|
||||||
positive.push((s) => s.indexOf(str) !== -1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
var filter = (item) => {
|
|
||||||
var apply = (f) => f(item.name);
|
|
||||||
return (positive.length === 0 || positive.some(apply))
|
|
||||||
&& (negative.length === 0 || negative.every(apply));
|
|
||||||
};
|
|
||||||
filter.includeChildren = true;
|
|
||||||
return filter;
|
|
||||||
},
|
},
|
||||||
|
activeView: function() {
|
||||||
|
if (!this.activeDataView) {
|
||||||
|
this.activeDataView = this.files[0].filename;
|
||||||
|
}
|
||||||
|
return this.activeDataView;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
title() {
|
title() {
|
||||||
@@ -378,58 +166,33 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
'tree-view': TreeView,
|
|
||||||
'timeline': Timeline,
|
'timeline': Timeline,
|
||||||
'rects': Rects,
|
'dataview': DataView,
|
||||||
}
|
'datainput': DataInput,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#app {
|
.main-content>* {
|
||||||
}
|
margin: 1em;
|
||||||
|
|
||||||
.main-content {
|
|
||||||
padding: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-toolbar {
|
.card-toolbar {
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, .12);
|
border-bottom: 1px solid rgba(0, 0, 0, .12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-card {
|
|
||||||
margin: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline {
|
.timeline {
|
||||||
margin: 16px;
|
margin: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.screen {
|
|
||||||
border: 1px solid black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rects {
|
h1,
|
||||||
flex: none;
|
h2 {
|
||||||
margin: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hierarchy, .properties {
|
|
||||||
flex: 1;
|
|
||||||
margin: 8px;
|
|
||||||
min-width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hierarchy > .tree-view, .properties > .tree-view {
|
|
||||||
margin: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2 {
|
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,4 +209,5 @@ li {
|
|||||||
a {
|
a {
|
||||||
color: #42b983;
|
color: #42b983;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
178
tools/winscope/src/DataInput.vue
Normal file
178
tools/winscope/src/DataInput.vue
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
<!-- Copyright (C) 2019 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<md-layout class="md-alignment-top-center">
|
||||||
|
<md-card style="min-width: 50em">
|
||||||
|
<!-- v-if="!timeline.length" -->
|
||||||
|
<md-card-header>
|
||||||
|
<div class="md-title">Open files</div>
|
||||||
|
</md-card-header>
|
||||||
|
<md-card-content>
|
||||||
|
<md-list>
|
||||||
|
<md-list-item v-for="file in dataFiles" v-bind:key="file.filename">
|
||||||
|
<md-icon>{{file.type.icon}}</md-icon>
|
||||||
|
<span class="md-list-item-text">{{file.filename}} ({{file.type.name}})</span>
|
||||||
|
<md-button class="md-icon-button md-accent" @click="onRemoveFile(file.type.name)">
|
||||||
|
<md-icon>close</md-icon>
|
||||||
|
</md-button>
|
||||||
|
</md-list-item>
|
||||||
|
</md-list>
|
||||||
|
<div>
|
||||||
|
<md-checkbox v-model="store.displayDefaults">Show default properties
|
||||||
|
<md-tooltip md-direction="bottom">
|
||||||
|
If checked, shows the value of all properties.
|
||||||
|
Otherwise, hides all properties whose value is the default for its data type.
|
||||||
|
</md-tooltip>
|
||||||
|
</md-checkbox>
|
||||||
|
</div>
|
||||||
|
<div class="md-layout">
|
||||||
|
<div class="md-layout-item md-small-size-100">
|
||||||
|
<md-select v-model="fileType" id="file-type" placeholder="File type">
|
||||||
|
<md-option value="auto">Detect type</md-option>
|
||||||
|
<md-option :value="k" v-for="(v,k) in FILE_TYPES" v-bind:key="v.name">{{v.name}}</md-option>
|
||||||
|
</md-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="md-layout md-gutter">
|
||||||
|
<input type="file" @change="onLoadFile" id="upload-file" v-show="false" />
|
||||||
|
<label class="md-button md-accent md-raised md-theme-default" for="upload-file">Add File</label>
|
||||||
|
<md-button v-if="dataReady" @click="onSubmit" class="md-button md-primary md-raised md-theme-default">Submit</md-button>
|
||||||
|
</div>
|
||||||
|
</md-card-content>
|
||||||
|
</md-card>
|
||||||
|
</md-layout>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import jsonProtoDefs from 'frameworks/base/core/proto/android/server/windowmanagertrace.proto'
|
||||||
|
import jsonProtoDefsSF from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto'
|
||||||
|
import protobuf from 'protobufjs'
|
||||||
|
|
||||||
|
import { detectFile, dataFile, FILE_TYPES, DATA_TYPES } from './detectfile.js'
|
||||||
|
|
||||||
|
import { fill_transform_data } from './matrix_utils.js'
|
||||||
|
|
||||||
|
var protoDefs = protobuf.Root.fromJSON(jsonProtoDefs)
|
||||||
|
.addJSON(jsonProtoDefsSF.nested);
|
||||||
|
|
||||||
|
var TraceMessage = protoDefs.lookupType(
|
||||||
|
"com.android.server.wm.WindowManagerTraceFileProto");
|
||||||
|
var ServiceMessage = protoDefs.lookupType(
|
||||||
|
"com.android.server.wm.WindowManagerServiceDumpProto");
|
||||||
|
var LayersMessage = protoDefs.lookupType("android.surfaceflinger.LayersProto");
|
||||||
|
var LayersTraceMessage = protoDefs.lookupType("android.surfaceflinger.LayersTraceFileProto");
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'datainput',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
FILE_TYPES,
|
||||||
|
fileType: "auto",
|
||||||
|
dataFiles: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: ['store'],
|
||||||
|
methods: {
|
||||||
|
onLoadFile(e) {
|
||||||
|
var type = this.fileType;
|
||||||
|
var files = event.target.files || event.dataTransfer.files;
|
||||||
|
var file = files[0];
|
||||||
|
if (!file) {
|
||||||
|
// No file selected.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$emit('statusChange', this.filename + " (loading)");
|
||||||
|
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
var buffer = new Uint8Array(e.target.result);
|
||||||
|
try {
|
||||||
|
if (FILE_TYPES[type]) {
|
||||||
|
var filetype = FILE_TYPES[type];
|
||||||
|
var decoded = filetype.protoType.decode(buffer);
|
||||||
|
modifyProtoFields(decoded, this.store.displayDefaults);
|
||||||
|
var transformed = filetype.transform(decoded);
|
||||||
|
} else {
|
||||||
|
var [filetype, decoded] = detectFile(buffer);
|
||||||
|
modifyProtoFields(decoded, this.store.displayDefaults);
|
||||||
|
var transformed = filetype.transform(decoded);
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
this.$emit('statusChange', this.filename + ': ' + ex);
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
event.target.value = ''
|
||||||
|
}
|
||||||
|
this.$emit('statusChange', this.filename + " (loading " + filetype.name + ")");
|
||||||
|
|
||||||
|
// Replace enum values with string representation and
|
||||||
|
// add default values to the proto objects. This function also handles
|
||||||
|
// a special case with TransformProtos where the matrix may be derived
|
||||||
|
// from the transform type.
|
||||||
|
function modifyProtoFields(protoObj, displayDefaults) {
|
||||||
|
if (!protoObj || protoObj !== Object(protoObj) || !protoObj.$type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (var fieldName in protoObj.$type.fields) {
|
||||||
|
var fieldProperties = protoObj.$type.fields[fieldName];
|
||||||
|
var field = protoObj[fieldName];
|
||||||
|
|
||||||
|
if (Array.isArray(field)) {
|
||||||
|
field.forEach((item, _) => {
|
||||||
|
modifyProtoFields(item, displayDefaults);
|
||||||
|
})
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (displayDefaults && !(field)) {
|
||||||
|
protoObj[fieldName] = fieldProperties.defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldProperties.type === 'TransformProto') {
|
||||||
|
fill_transform_data(protoObj[fieldName]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldProperties.resolvedType && fieldProperties.resolvedType.valuesById) {
|
||||||
|
protoObj[fieldName] = fieldProperties.resolvedType.valuesById[protoObj[fieldProperties.name]];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
modifyProtoFields(protoObj[fieldName], displayDefaults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var timeline;
|
||||||
|
if (filetype.timeline) {
|
||||||
|
timeline = transformed.children;
|
||||||
|
} else {
|
||||||
|
timeline = [transformed];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$set(this.dataFiles, filetype.dataType.name, dataFile(file.name, timeline, filetype.dataType));
|
||||||
|
this.$emit('statusChange', null);
|
||||||
|
}
|
||||||
|
reader.readAsArrayBuffer(files[0]);
|
||||||
|
},
|
||||||
|
onRemoveFile(typeName) {
|
||||||
|
this.$delete(this.dataFiles, typeName);
|
||||||
|
},
|
||||||
|
onSubmit() {
|
||||||
|
this.$emit('dataReady', Object.keys(this.dataFiles).map(key => this.dataFiles[key]));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dataReady: function() { return Object.keys(this.dataFiles).length > 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
215
tools/winscope/src/DataView.vue
Normal file
215
tools/winscope/src/DataView.vue
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
<!-- Copyright (C) 2019 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<md-card v-if="tree">
|
||||||
|
<md-card-header>
|
||||||
|
<div class="md-title">
|
||||||
|
<md-icon>{{file.type.icon}}</md-icon> {{file.filename}}
|
||||||
|
</div>
|
||||||
|
</md-card-header>
|
||||||
|
<md-card-content class="container">
|
||||||
|
<md-card class="rects">
|
||||||
|
<md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense">
|
||||||
|
<h2 class="md-title">Screen</h2>
|
||||||
|
</md-whiteframe>
|
||||||
|
<md-whiteframe md-elevation="8">
|
||||||
|
<rects :bounds="bounds" :rects="rects" :highlight="highlight" @rect-click="onRectClick" />
|
||||||
|
</md-whiteframe>
|
||||||
|
</md-card>
|
||||||
|
<md-card class="hierarchy">
|
||||||
|
<md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense">
|
||||||
|
<h2 class="md-title" style="flex: 1;">Hierarchy</h2>
|
||||||
|
<md-checkbox v-model="store.onlyVisible">Only visible</md-checkbox>
|
||||||
|
<md-checkbox v-model="store.flattened">Flat</md-checkbox>
|
||||||
|
</md-whiteframe>
|
||||||
|
<tree-view :item="tree" @item-selected="itemSelected" :selected="hierarchySelected" :filter="hierarchyFilter" :flattened="store.flattened" ref="hierarchy" />
|
||||||
|
</md-card>
|
||||||
|
<md-card class="properties">
|
||||||
|
<md-whiteframe md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense">
|
||||||
|
<h2 class="md-title" style="flex: 1">Properties</h2>
|
||||||
|
<div class="filter">
|
||||||
|
<input id="filter" type="search" placeholder="Filter..." v-model="propertyFilterString" />
|
||||||
|
</div>
|
||||||
|
</md-whiteframe>
|
||||||
|
<tree-view :item="selectedTree" :filter="propertyFilter" />
|
||||||
|
</md-card>
|
||||||
|
</md-card-content>
|
||||||
|
</md-card>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import TreeView from './TreeView.vue'
|
||||||
|
import Timeline from './Timeline.vue'
|
||||||
|
import Rects from './Rects.vue'
|
||||||
|
|
||||||
|
import { transform_json } from './transform.js'
|
||||||
|
import { format_transform_type, is_simple_transform } from './matrix_utils.js'
|
||||||
|
|
||||||
|
function formatProto(obj) {
|
||||||
|
if (!obj || !obj.$type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (obj.$type.fullName === '.android.surfaceflinger.RectProto' ||
|
||||||
|
obj.$type.fullName === '.android.graphics.RectProto') {
|
||||||
|
return `(${obj.left}, ${obj.top}) - (${obj.right}, ${obj.bottom})`;
|
||||||
|
} else if (obj.$type.fullName === '.android.surfaceflinger.FloatRectProto') {
|
||||||
|
return `(${obj.left.toFixed(3)}, ${obj.top.toFixed(3)}) - (${obj.right.toFixed(3)}, ${obj.bottom.toFixed(3)})`;
|
||||||
|
} else if (obj.$type.fullName === '.android.surfaceflinger.PositionProto') {
|
||||||
|
return `(${obj.x.toFixed(3)}, ${obj.y.toFixed(3)})`;
|
||||||
|
} else if (obj.$type.fullName === '.android.surfaceflinger.SizeProto') {
|
||||||
|
return `${obj.w} x ${obj.h}`;
|
||||||
|
} else if (obj.$type.fullName === '.android.surfaceflinger.ColorProto') {
|
||||||
|
return `r:${obj.r} g:${obj.g} \n b:${obj.b} a:${obj.a}`;
|
||||||
|
} else if (obj.$type.fullName === '.android.surfaceflinger.TransformProto') {
|
||||||
|
var transform_type = format_transform_type(obj);
|
||||||
|
if (is_simple_transform(obj)) {
|
||||||
|
return `${transform_type}`;
|
||||||
|
}
|
||||||
|
return `${transform_type} dsdx:${obj.dsdx.toFixed(3)} dtdx:${obj.dtdx.toFixed(3)} dsdy:${obj.dsdy.toFixed(3)} dtdy:${obj.dtdy.toFixed(3)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'dataview',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
propertyFilterString: "",
|
||||||
|
selectedTree: {},
|
||||||
|
hierarchySelected: null,
|
||||||
|
lastSelectedStableId: null,
|
||||||
|
bounds: {},
|
||||||
|
rects: [],
|
||||||
|
tree: null,
|
||||||
|
highlight: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
itemSelected(item) {
|
||||||
|
this.hierarchySelected = item;
|
||||||
|
this.selectedTree = transform_json(item.obj, item.name, {
|
||||||
|
skip: item.skip,
|
||||||
|
formatter: formatProto
|
||||||
|
});
|
||||||
|
this.highlight = item.highlight;
|
||||||
|
this.lastSelectedStableId = item.stableId;
|
||||||
|
this.$emit('focus');
|
||||||
|
},
|
||||||
|
onRectClick(item) {
|
||||||
|
if (item) {
|
||||||
|
this.itemSelected(item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setData(item) {
|
||||||
|
this.tree = item;
|
||||||
|
this.rects = [...item.rects].reverse();
|
||||||
|
this.bounds = item.bounds;
|
||||||
|
|
||||||
|
this.hierarchySelected = null;
|
||||||
|
this.selectedTree = {};
|
||||||
|
this.highlight = null;
|
||||||
|
|
||||||
|
function find_item(item, stableId) {
|
||||||
|
if (item.stableId === stableId) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
if (Array.isArray(item.children)) {
|
||||||
|
for (var child of item.children) {
|
||||||
|
var found = find_item(child, stableId);
|
||||||
|
if (found) {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.lastSelectedStableId) {
|
||||||
|
var found = find_item(item, this.lastSelectedStableId);
|
||||||
|
if (found) {
|
||||||
|
this.itemSelected(found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
arrowUp() {
|
||||||
|
return this.$refs.hierarchy.selectPrev();
|
||||||
|
},
|
||||||
|
arrowDown() {
|
||||||
|
return this.$refs.hierarchy.selectNext();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.setData(this.file.timeline[this.file.selectedIndex]);
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selectedIndex() {
|
||||||
|
this.setData(this.file.timeline[this.file.selectedIndex]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: ['store', 'file'],
|
||||||
|
computed: {
|
||||||
|
selectedIndex() {
|
||||||
|
return this.file.selectedIndex;
|
||||||
|
},
|
||||||
|
hierarchyFilter() {
|
||||||
|
return this.store.onlyVisible ? (c, flattened) => {
|
||||||
|
return c.visible || c.childrenVisible && !flattened;
|
||||||
|
} : null;
|
||||||
|
},
|
||||||
|
propertyFilter() {
|
||||||
|
var filterStrings = this.propertyFilterString.split(",");
|
||||||
|
var positive = [];
|
||||||
|
var negative = [];
|
||||||
|
filterStrings.forEach((f) => {
|
||||||
|
if (f.startsWith("!")) {
|
||||||
|
var str = f.substring(1);
|
||||||
|
negative.push((s) => s.indexOf(str) === -1);
|
||||||
|
} else {
|
||||||
|
var str = f;
|
||||||
|
positive.push((s) => s.indexOf(str) !== -1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var filter = (item) => {
|
||||||
|
var apply = (f) => f(item.name);
|
||||||
|
return (positive.length === 0 || positive.some(apply)) &&
|
||||||
|
(negative.length === 0 || negative.every(apply));
|
||||||
|
};
|
||||||
|
filter.includeChildren = true;
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
'tree-view': TreeView,
|
||||||
|
'rects': Rects,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.rects {
|
||||||
|
flex: none;
|
||||||
|
margin: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hierarchy,
|
||||||
|
.properties {
|
||||||
|
flex: 1;
|
||||||
|
margin: 8px;
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hierarchy>.tree-view,
|
||||||
|
.properties>.tree-view {
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -14,19 +14,15 @@
|
|||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<svg width="2000" height="20" viewBox="-5,0,2010,20">
|
<svg width="2000" height="20" viewBox="-5,0,2010,20">
|
||||||
<circle :cx="translate(c.timestamp)" cy="10" r="5" v-for="(c,i) in items"
|
<circle :cx="translate(c.timestamp)" cy="10" r="5" v-for="(c,i) in items" @click="onItemClick(c, i)" :class="itemClass(i)" />
|
||||||
@click="onItemClick(c, i)" :class="itemClass(c)" />
|
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'timeline',
|
name: 'timeline',
|
||||||
props: ['items', 'selected'],
|
props: ['items', 'selectedIndex', 'scale'],
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {};
|
||||||
};
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
translate(cx) {
|
translate(cx) {
|
||||||
@@ -37,16 +33,13 @@ export default {
|
|||||||
return (cx - scale[0]) / (scale[1] - scale[0]) * 2000;
|
return (cx - scale[0]) / (scale[1] - scale[0]) * 2000;
|
||||||
},
|
},
|
||||||
onItemClick(item, index) {
|
onItemClick(item, index) {
|
||||||
this.$emit('item-selected', item, index);
|
this.$emit('item-selected', index);
|
||||||
|
},
|
||||||
|
itemClass(index) {
|
||||||
|
return (this.selectedIndex == index) ? 'selected' : 'not-selected'
|
||||||
},
|
},
|
||||||
itemClass(item) {
|
|
||||||
return (this.selected == item) ? 'selected' : 'not-selected'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
scale() {
|
|
||||||
return [Math.min(...this.timestamps), Math.max(...this.timestamps)];
|
|
||||||
},
|
|
||||||
timestamps() {
|
timestamps() {
|
||||||
if (this.items.length == 1) {
|
if (this.items.length == 1) {
|
||||||
return [0];
|
return [0];
|
||||||
@@ -55,10 +48,11 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
|
|
||||||
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.selected {
|
.selected {
|
||||||
fill: red;
|
fill: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -21,14 +21,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="children" v-if="children">
|
<div class="children" v-if="children">
|
||||||
<tree-view v-for="(c,i) in children" :item="c" @item-selected="childItemSelected" :selected="selected" :v-key="i" :chip-class='chipClass' :filter="childFilter(c)" :flattened="flattened"
|
<tree-view v-for="(c,i) in children" :item="c" @item-selected="childItemSelected" :selected="selected" :key="i" :chip-class='chipClass' :filter="childFilter(c)" :flattened="flattened" :force-flattened="applyingFlattened" v-show="filterMatches(c)" ref='children' />
|
||||||
:force-flattened="applyingFlattened" v-show="filterMatches(c)" ref='children' />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import jsonProtoDefs from 'frameworks/base/core/proto/android/server/windowmanagertrace.proto'
|
import jsonProtoDefs from 'frameworks/base/core/proto/android/server/windowmanagertrace.proto'
|
||||||
import protobuf from 'protobufjs'
|
import protobuf from 'protobufjs'
|
||||||
|
|
||||||
@@ -41,9 +38,8 @@ var ServiceMessage = protoDefs.lookupType(
|
|||||||
export default {
|
export default {
|
||||||
name: 'tree-view',
|
name: 'tree-view',
|
||||||
props: ['item', 'selected', 'chipClass', 'filter', 'flattened', 'force-flattened'],
|
props: ['item', 'selected', 'chipClass', 'filter', 'flattened', 'force-flattened'],
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {};
|
||||||
};
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
selectNext(found, parent) {
|
selectNext(found, parent) {
|
||||||
@@ -84,7 +80,8 @@ export default {
|
|||||||
},
|
},
|
||||||
chipClassForChip(c) {
|
chipClassForChip(c) {
|
||||||
return ['tree-view-internal-chip', this.chipClassOrDefault,
|
return ['tree-view-internal-chip', this.chipClassOrDefault,
|
||||||
this.chipClassOrDefault + '-' + (c.class || 'default')];
|
this.chipClassOrDefault + '-' + (c.class || 'default')
|
||||||
|
];
|
||||||
},
|
},
|
||||||
filterMatches(c) {
|
filterMatches(c) {
|
||||||
if (this.filter) {
|
if (this.filter) {
|
||||||
@@ -117,22 +114,26 @@ export default {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
|
|
||||||
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.children {
|
.children {
|
||||||
margin-left: 24px;
|
margin-left: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kind {
|
.kind {
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
background-color: #3f51b5;
|
background-color: #3f51b5;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
.selected .kind{
|
|
||||||
|
.selected .kind {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-view-internal-chip {
|
.tree-view-internal-chip {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,11 @@
|
|||||||
import jsonProtoDefs from 'frameworks/base/core/proto/android/server/windowmanagertrace.proto'
|
import jsonProtoDefs from 'frameworks/base/core/proto/android/server/windowmanagertrace.proto'
|
||||||
import jsonProtoDefsSF from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto'
|
import jsonProtoDefsSF from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto'
|
||||||
import protobuf from 'protobufjs'
|
import protobuf from 'protobufjs'
|
||||||
import {transform_layers, transform_layers_trace} from './transform_sf.js'
|
import { transform_layers, transform_layers_trace } from './transform_sf.js'
|
||||||
import {transform_window_service, transform_window_trace} from './transform_wm.js'
|
import { transform_window_service, transform_window_trace } from './transform_wm.js'
|
||||||
|
|
||||||
var protoDefs = protobuf.Root.fromJSON(jsonProtoDefs)
|
var protoDefs = protobuf.Root.fromJSON(jsonProtoDefs)
|
||||||
.addJSON(jsonProtoDefsSF.nested);
|
.addJSON(jsonProtoDefsSF.nested);
|
||||||
|
|
||||||
var WindowTraceMessage = protoDefs.lookupType(
|
var WindowTraceMessage = protoDefs.lookupType(
|
||||||
"com.android.server.wm.WindowManagerTraceFileProto");
|
"com.android.server.wm.WindowManagerTraceFileProto");
|
||||||
@@ -34,33 +34,58 @@ var LayersTraceMessage = protoDefs.lookupType("android.surfaceflinger.LayersTrac
|
|||||||
|
|
||||||
const LAYER_TRACE_MAGIC_NUMBER = [0x09, 0x4c, 0x59, 0x52, 0x54, 0x52, 0x41, 0x43, 0x45] // .LYRTRACE
|
const LAYER_TRACE_MAGIC_NUMBER = [0x09, 0x4c, 0x59, 0x52, 0x54, 0x52, 0x41, 0x43, 0x45] // .LYRTRACE
|
||||||
const WINDOW_TRACE_MAGIC_NUMBER = [0x09, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45] // .WINTRACE
|
const WINDOW_TRACE_MAGIC_NUMBER = [0x09, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45] // .WINTRACE
|
||||||
|
|
||||||
|
const DATA_TYPES = {
|
||||||
|
WINDOW_MANAGER: {
|
||||||
|
name: "WindowManager",
|
||||||
|
icon: "view_compact",
|
||||||
|
},
|
||||||
|
SURFACE_FLINGER: {
|
||||||
|
name: "SurfaceFlinger",
|
||||||
|
icon: "filter_none",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
const FILE_TYPES = {
|
const FILE_TYPES = {
|
||||||
'window_trace': {
|
'window_trace': {
|
||||||
protoType: WindowTraceMessage,
|
protoType: WindowTraceMessage,
|
||||||
transform: transform_window_trace,
|
transform: transform_window_trace,
|
||||||
name: "WindowManager trace",
|
name: "WindowManager trace",
|
||||||
timeline: true,
|
timeline: true,
|
||||||
|
dataType: DATA_TYPES.WINDOW_MANAGER,
|
||||||
},
|
},
|
||||||
'layers_trace': {
|
'layers_trace': {
|
||||||
protoType: LayersTraceMessage,
|
protoType: LayersTraceMessage,
|
||||||
transform: transform_layers_trace,
|
transform: transform_layers_trace,
|
||||||
name: "SurfaceFlinger trace",
|
name: "SurfaceFlinger trace",
|
||||||
timeline: true,
|
timeline: true,
|
||||||
|
dataType: DATA_TYPES.SURFACE_FLINGER,
|
||||||
},
|
},
|
||||||
'layers_dump': {
|
'layers_dump': {
|
||||||
protoType: LayersMessage,
|
protoType: LayersMessage,
|
||||||
transform: transform_layers,
|
transform: transform_layers,
|
||||||
name: "SurfaceFlinger dump",
|
name: "SurfaceFlinger dump",
|
||||||
timeline: false,
|
timeline: false,
|
||||||
|
dataType: DATA_TYPES.SURFACE_FLINGER,
|
||||||
},
|
},
|
||||||
'window_dump': {
|
'window_dump': {
|
||||||
protoType: WindowMessage,
|
protoType: WindowMessage,
|
||||||
transform: transform_window_service,
|
transform: transform_window_service,
|
||||||
name: "WindowManager dump",
|
name: "WindowManager dump",
|
||||||
timeline: false,
|
timeline: false,
|
||||||
|
dataType: DATA_TYPES.WINDOW_MANAGER,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function dataFile(filename, timeline, type) {
|
||||||
|
return {
|
||||||
|
filename: filename,
|
||||||
|
timeline: timeline,
|
||||||
|
type: type,
|
||||||
|
selectedIndex: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function arrayEquals(a, b) {
|
function arrayEquals(a, b) {
|
||||||
if (a.length !== b.length) {
|
if (a.length !== b.length) {
|
||||||
return false;
|
return false;
|
||||||
@@ -82,7 +107,7 @@ function decodedFile(filename, buffer) {
|
|||||||
return [FILE_TYPES[filename], decoded];
|
return [FILE_TYPES[filename], decoded];
|
||||||
}
|
}
|
||||||
|
|
||||||
function detect(buffer) {
|
function detectFile(buffer) {
|
||||||
if (arrayStartsWith(buffer, LAYER_TRACE_MAGIC_NUMBER)) {
|
if (arrayStartsWith(buffer, LAYER_TRACE_MAGIC_NUMBER)) {
|
||||||
return decodedFile('layers_trace', buffer);
|
return decodedFile('layers_trace', buffer);
|
||||||
}
|
}
|
||||||
@@ -91,7 +116,7 @@ function detect(buffer) {
|
|||||||
}
|
}
|
||||||
for (var filename of ['layers_dump', 'window_dump']) {
|
for (var filename of ['layers_dump', 'window_dump']) {
|
||||||
try {
|
try {
|
||||||
var [filetype,decoded] = decodedFile(filename, buffer);
|
var [filetype, decoded] = decodedFile(filename, buffer);
|
||||||
var transformed = filetype.transform(decoded);
|
var transformed = filetype.transform(decoded);
|
||||||
return [FILE_TYPES[filename], decoded];
|
return [FILE_TYPES[filename], decoded];
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
@@ -100,4 +125,5 @@ function detect(buffer) {
|
|||||||
}
|
}
|
||||||
throw new Error('Unable to detect file');
|
throw new Error('Unable to detect file');
|
||||||
}
|
}
|
||||||
export default detect;
|
|
||||||
|
export { detectFile, dataFile, DATA_TYPES, FILE_TYPES };
|
||||||
|
|||||||
Reference in New Issue
Block a user