Implement diff transformation for properties
Test: N/A Change-Id: Ib231e617db3d87fe1abe20e95137d9af17572d19
This commit is contained in:
@@ -45,6 +45,7 @@
|
||||
<md-content 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">
|
||||
<md-checkbox v-model="showPropertiesDiff">Show Diff</md-checkbox>
|
||||
<input id="filter" type="search" placeholder="Filter..." v-model="propertyFilterString" />
|
||||
</div>
|
||||
</md-content>
|
||||
@@ -63,7 +64,7 @@ import TreeView from './TreeView.vue'
|
||||
import Timeline from './Timeline.vue'
|
||||
import Rects from './Rects.vue'
|
||||
|
||||
import { transform_json } from './transform.js'
|
||||
import { ObjectTransformer } from './transform.js'
|
||||
import { format_transform_type, is_simple_transform } from './matrix_utils.js'
|
||||
import { DATA_TYPES } from './decode.js'
|
||||
import { stableIdCompatibilityFixup } from './utils/utils.js'
|
||||
@@ -105,6 +106,25 @@ function formatProto(obj) {
|
||||
}
|
||||
}
|
||||
|
||||
function findEntryInTree(tree, id) {
|
||||
if (tree.stableId === id) {
|
||||
return tree;
|
||||
}
|
||||
|
||||
if (!tree.children) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const child of tree.children) {
|
||||
const foundEntry = findEntryInTree(child, id);
|
||||
if (foundEntry) {
|
||||
return foundEntry;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'traceview',
|
||||
data() {
|
||||
@@ -118,24 +138,34 @@ export default {
|
||||
rects: [],
|
||||
tree: null,
|
||||
highlight: null,
|
||||
showPropertiesDiff: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
itemSelected(item) {
|
||||
this.hierarchySelected = item;
|
||||
this.selectedTree = transform_json(
|
||||
item.obj,
|
||||
item.name,
|
||||
stableIdCompatibilityFixup(item),
|
||||
{
|
||||
skip: item.skip,
|
||||
formatter: formatProto
|
||||
},
|
||||
);
|
||||
this.selectedTree = this.getTransformedProperties(item);
|
||||
this.highlight = item.highlight;
|
||||
this.lastSelectedStableId = item.stableId;
|
||||
this.$emit('focus');
|
||||
},
|
||||
getTransformedProperties(item) {
|
||||
const transformer = new ObjectTransformer(
|
||||
item.obj,
|
||||
item.name,
|
||||
stableIdCompatibilityFixup(item)
|
||||
).setOptions({
|
||||
skip: item.skip,
|
||||
formatter: formatProto,
|
||||
});
|
||||
|
||||
if (this.showPropertiesDiff) {
|
||||
const prevItem = this.getItemFromPrevTree(item);
|
||||
transformer.withDiff(prevItem?.obj);
|
||||
}
|
||||
|
||||
return transformer.transform();
|
||||
},
|
||||
onRectClick(item) {
|
||||
if (item) {
|
||||
this.itemSelected(item);
|
||||
@@ -178,6 +208,39 @@ export default {
|
||||
arrowDown() {
|
||||
return this.$refs.hierarchy.selectNext();
|
||||
},
|
||||
getDataWithOffset(offset) {
|
||||
const index = this.file.selectedIndex + offset;
|
||||
|
||||
if (index < 0 || index >= this.file.data.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.file.data[index];
|
||||
},
|
||||
getItemFromPrevTree(entry) {
|
||||
if (!this.showPropertiesDiff || !this.hierarchySelected) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const id = entry.stableId;
|
||||
if (!id) {
|
||||
throw new Error("Entry has no stableId...");
|
||||
}
|
||||
|
||||
const prevTree = this.getDataWithOffset(-1);
|
||||
if (!prevTree) {
|
||||
console.warn("No previous entry");
|
||||
return null;
|
||||
}
|
||||
|
||||
const prevEntry = findEntryInTree(prevTree, id);
|
||||
if (!prevEntry) {
|
||||
console.warn("Didn't exist in last entry");
|
||||
// TODO: Maybe handle this in some way.
|
||||
}
|
||||
|
||||
return prevEntry;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.setData(this.file.data[this.file.selectedIndex]);
|
||||
@@ -185,6 +248,11 @@ export default {
|
||||
watch: {
|
||||
selectedIndex() {
|
||||
this.setData(this.file.data[this.file.selectedIndex]);
|
||||
},
|
||||
showPropertiesDiff() {
|
||||
if (this.hierarchySelected) {
|
||||
this.selectedTree = this.getTransformedProperties(this.hierarchySelected);
|
||||
}
|
||||
}
|
||||
},
|
||||
props: ['store', 'file'],
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<template>
|
||||
<div class="tree-view" v-if="item">
|
||||
<div class="node"
|
||||
:class="{ leaf: isLeaf, selected: isSelected, clickable: isClickable }"
|
||||
:class="{ leaf: isLeaf, selected: isSelected, clickable: isClickable, diffClass }"
|
||||
:style="nodeOffsetStyle"
|
||||
@click="clicked"
|
||||
ref="node"
|
||||
@@ -76,6 +76,8 @@
|
||||
import jsonProtoDefs from "frameworks/base/core/proto/android/server/windowmanagertrace.proto";
|
||||
import protobuf from "protobufjs";
|
||||
|
||||
import { DiffType } from "./utils/diff.js";
|
||||
|
||||
var protoDefs = protobuf.Root.fromJSON(jsonProtoDefs);
|
||||
var TraceMessage = protoDefs.lookupType(
|
||||
"com.android.server.wm.WindowManagerTraceFileProto"
|
||||
@@ -116,6 +118,12 @@ export default {
|
||||
clickTimeout: null,
|
||||
isCollapsedByDefault,
|
||||
localCollapsedState: isCollapsedByDefault,
|
||||
diffSymbol: {
|
||||
[DiffType.NONE]: "",
|
||||
[DiffType.ADDED]: "+",
|
||||
[DiffType.DELETED]: "-",
|
||||
[DiffType.MODIFIED]: ".",
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@@ -271,6 +279,9 @@ export default {
|
||||
|
||||
return false;
|
||||
},
|
||||
diffClass() {
|
||||
return this.item.diff ? this.item.diff.type : ''
|
||||
},
|
||||
chipClassOrDefault() {
|
||||
return this.chipClass || "tree-view-chip";
|
||||
},
|
||||
@@ -335,6 +346,18 @@ export default {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
.tree-view .node:not(.selected).added {
|
||||
background: chartreuse;
|
||||
}
|
||||
|
||||
.tree-view .node:not(.selected).removed {
|
||||
background: coral;
|
||||
}
|
||||
|
||||
.tree-view .node:not(.selected).modified {
|
||||
background: cyan;
|
||||
}
|
||||
|
||||
.children {
|
||||
/* Aligns border with collapse arrows */
|
||||
margin-left: 12px;
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DiffType } from './utils/diff.js';
|
||||
|
||||
// kind - a type used for categorization of different levels
|
||||
// name - name of the node
|
||||
// children - list of child entries. Each child entry is pair list [raw object, nested transform function].
|
||||
@@ -92,47 +94,156 @@ function transform({ obj, kind, name, children, timestamp, rect, bounds, highlig
|
||||
return Object.freeze(result);
|
||||
}
|
||||
|
||||
function getDiff(val, compareVal) {
|
||||
if (val && isTerminal(compareVal)) {
|
||||
return { type: DiffType.ADDED };
|
||||
} else if (isTerminal(val) && compareVal) {
|
||||
return { type: DiffType.DELETED };
|
||||
} else if (compareVal != val) {
|
||||
return { type: DiffType.MODIFIED };
|
||||
} else {
|
||||
return { type: DiffType.NONE };
|
||||
}
|
||||
}
|
||||
|
||||
function transform_json(obj, name, stableId, options) {
|
||||
let { skip, formatter } = options;
|
||||
// Represents termination of the object traversal,
|
||||
// differentiated with a null value in the object.
|
||||
class Terminal { }
|
||||
|
||||
var children = [];
|
||||
var formatted = undefined;
|
||||
function isTerminal(obj) {
|
||||
return obj instanceof Terminal;
|
||||
}
|
||||
|
||||
class ObjectTransformer {
|
||||
constructor(obj, rootName, stableId) {
|
||||
this.obj = obj;
|
||||
this.rootName = rootName;
|
||||
this.stableId = stableId;
|
||||
this.diff = false;
|
||||
}
|
||||
|
||||
setOptions(options) {
|
||||
this.options = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
withDiff(obj) {
|
||||
this.diff = true;
|
||||
this.compareWithObj = obj ?? new Terminal();
|
||||
return this;
|
||||
}
|
||||
|
||||
transform() {
|
||||
const { formatter } = this.options;
|
||||
if (!formatter) {
|
||||
throw new Error("Missing formatter, please set with setOptions()");
|
||||
}
|
||||
|
||||
return this._transform(this.obj, this.rootName, this.compareWithObj, this.rootName, this.stableId);
|
||||
}
|
||||
|
||||
_transformKeys(obj) {
|
||||
const { skip, formatter } = this.options;
|
||||
const transformedObj = {};
|
||||
let formatted = undefined;
|
||||
|
||||
if (skip && skip.includes(obj)) {
|
||||
// skip
|
||||
} else if ((formatted = formatter(obj))) {
|
||||
children.push(transform_json(null, formatted, `${stableId}.${formatted}`, options));
|
||||
// Obj has been formatted into a terminal node — has no children.
|
||||
transformedObj[formatted] = new Terminal();
|
||||
} else if (Array.isArray(obj)) {
|
||||
obj.forEach((e, i) => {
|
||||
children.push(transform_json(e, "" + i, `${stableId}[${i}]`, options));
|
||||
})
|
||||
transformedObj["" + i] = e;
|
||||
});
|
||||
} else if (typeof obj == 'string') {
|
||||
children.push(transform_json(null, obj, `${stableId}.${obj}`, options));
|
||||
// Object is a primitive type — has no children. Set to terminal
|
||||
// to differentiate between null object and Terminal element.
|
||||
transformedObj[obj] = new Terminal();
|
||||
} else if (typeof obj == 'number' || typeof obj == 'boolean') {
|
||||
children.push(transform_json(null, "" + obj, `${stableId}.${obj}`, options));
|
||||
// Similar to above — primitive type node has no children.
|
||||
transformedObj["" + obj] = new Terminal();
|
||||
} else if (obj && typeof obj == 'object') {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
children.push(transform_json(obj[key], key, `${stableId}.${key}`, options));
|
||||
transformedObj[key] = obj[key];
|
||||
});
|
||||
} else if (obj === null) {
|
||||
// Null object is a has no children — set to be terminal node.
|
||||
transformedObj.null = new Terminal();
|
||||
}
|
||||
|
||||
if (children.length == 1 && !children[0].combined) {
|
||||
return Object.freeze({
|
||||
kind: "",
|
||||
name: name + ": " + children[0].name,
|
||||
stableId: stableId,
|
||||
children: children[0].children,
|
||||
combined: true
|
||||
});
|
||||
return transformedObj;
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
_transform(obj, name, compareWithObj, compareWithName, stableId) {
|
||||
const children = [];
|
||||
|
||||
if (!isTerminal(obj)) {
|
||||
obj = this._transformKeys(obj);
|
||||
}
|
||||
if (!isTerminal(compareWithObj)) {
|
||||
compareWithObj = this._transformKeys(compareWithObj);
|
||||
}
|
||||
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
let compareWithChild = new Terminal();
|
||||
let compareWithName = new Terminal();
|
||||
if (compareWithObj.hasOwnProperty(key)) {
|
||||
compareWithChild = compareWithObj[key];
|
||||
compareWithName = key;
|
||||
}
|
||||
children.push(this._transform(obj[key], key, compareWithChild, compareWithName, `${stableId}.${key}`));
|
||||
}
|
||||
}
|
||||
|
||||
// Takes care of adding deleted items to final tree
|
||||
for (const key in compareWithObj) {
|
||||
if (!obj.hasOwnProperty(key) && compareWithObj.hasOwnProperty(key)) {
|
||||
children.push(this._transform(new Terminal(), new Terminal(), compareWithObj[key], key));
|
||||
}
|
||||
}
|
||||
|
||||
let transformedObj;
|
||||
if (
|
||||
children.length == 1 &&
|
||||
children[0].children.length == 0 &&
|
||||
!children[0].combined
|
||||
) {
|
||||
// Merge leaf key value pairs.
|
||||
const child = children[0];
|
||||
|
||||
transformedObj = {
|
||||
kind: "",
|
||||
name: name,
|
||||
name: name + ": " + child.name,
|
||||
stableId: stableId,
|
||||
children: children,
|
||||
});
|
||||
children: child.children,
|
||||
combined: true,
|
||||
}
|
||||
|
||||
if (this.diff) {
|
||||
transformedObj.diff = child.diff;
|
||||
}
|
||||
} else {
|
||||
transformedObj = {
|
||||
kind: "",
|
||||
name,
|
||||
stableId: stableId,
|
||||
children,
|
||||
};
|
||||
|
||||
if (this.diff) {
|
||||
const diff = getDiff(name, compareWithName);
|
||||
transformedObj.diff = diff;
|
||||
|
||||
if (diff.type == DiffType.DELETED) {
|
||||
transformedObj.name = compareWithName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Object.freeze(transformedObj);
|
||||
}
|
||||
}
|
||||
|
||||
function nanos_to_string(elapsedRealtimeNanos) {
|
||||
@@ -163,4 +274,4 @@ function get_visible_chip() {
|
||||
return { short: 'V', long: "visible", class: 'default' };
|
||||
}
|
||||
|
||||
export { transform, transform_json, nanos_to_string, get_visible_chip };
|
||||
export { transform, ObjectTransformer, nanos_to_string, get_visible_chip };
|
||||
|
||||
10
tools/winscope/src/utils/diff.js
Normal file
10
tools/winscope/src/utils/diff.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const DiffType = Object.freeze({
|
||||
NONE: 'none',
|
||||
ADDED: 'added',
|
||||
DELETED: 'deleted',
|
||||
ADDED_MOVE: 'addedMove',
|
||||
DELETED_MOVE: 'deletedMove',
|
||||
MODIFIED: 'modified',
|
||||
});
|
||||
|
||||
export { DiffType };
|
||||
Reference in New Issue
Block a user