Implement diff transformation for properties

Test: N/A
Change-Id: Ib231e617db3d87fe1abe20e95137d9af17572d19
This commit is contained in:
Pablo Gamito
2020-05-20 16:50:15 +01:00
parent 5651a6aa57
commit aa2c1335ac
4 changed files with 258 additions and 46 deletions

View File

@@ -45,6 +45,7 @@
<md-content md-tag="md-toolbar" md-elevation="0" class="card-toolbar md-transparent md-dense"> <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> <h2 class="md-title" style="flex: 1">Properties</h2>
<div class="filter"> <div class="filter">
<md-checkbox v-model="showPropertiesDiff">Show Diff</md-checkbox>
<input id="filter" type="search" placeholder="Filter..." v-model="propertyFilterString" /> <input id="filter" type="search" placeholder="Filter..." v-model="propertyFilterString" />
</div> </div>
</md-content> </md-content>
@@ -63,7 +64,7 @@ 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 { transform_json } from './transform.js' import { ObjectTransformer } from './transform.js'
import { format_transform_type, is_simple_transform } from './matrix_utils.js' import { format_transform_type, is_simple_transform } from './matrix_utils.js'
import { DATA_TYPES } from './decode.js' import { DATA_TYPES } from './decode.js'
import { stableIdCompatibilityFixup } from './utils/utils.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 { export default {
name: 'traceview', name: 'traceview',
data() { data() {
@@ -118,24 +138,34 @@ export default {
rects: [], rects: [],
tree: null, tree: null,
highlight: null, highlight: null,
showPropertiesDiff: false,
} }
}, },
methods: { methods: {
itemSelected(item) { itemSelected(item) {
this.hierarchySelected = item; this.hierarchySelected = item;
this.selectedTree = transform_json( this.selectedTree = this.getTransformedProperties(item);
item.obj,
item.name,
stableIdCompatibilityFixup(item),
{
skip: item.skip,
formatter: formatProto
},
);
this.highlight = item.highlight; this.highlight = item.highlight;
this.lastSelectedStableId = item.stableId; this.lastSelectedStableId = item.stableId;
this.$emit('focus'); 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) { onRectClick(item) {
if (item) { if (item) {
this.itemSelected(item); this.itemSelected(item);
@@ -178,6 +208,39 @@ export default {
arrowDown() { arrowDown() {
return this.$refs.hierarchy.selectNext(); 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() { created() {
this.setData(this.file.data[this.file.selectedIndex]); this.setData(this.file.data[this.file.selectedIndex]);
@@ -185,6 +248,11 @@ export default {
watch: { watch: {
selectedIndex() { selectedIndex() {
this.setData(this.file.data[this.file.selectedIndex]); this.setData(this.file.data[this.file.selectedIndex]);
},
showPropertiesDiff() {
if (this.hierarchySelected) {
this.selectedTree = this.getTransformedProperties(this.hierarchySelected);
}
} }
}, },
props: ['store', 'file'], props: ['store', 'file'],

View File

@@ -15,7 +15,7 @@
<template> <template>
<div class="tree-view" v-if="item"> <div class="tree-view" v-if="item">
<div class="node" <div class="node"
:class="{ leaf: isLeaf, selected: isSelected, clickable: isClickable }" :class="{ leaf: isLeaf, selected: isSelected, clickable: isClickable, diffClass }"
:style="nodeOffsetStyle" :style="nodeOffsetStyle"
@click="clicked" @click="clicked"
ref="node" ref="node"
@@ -76,6 +76,8 @@
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";
import { DiffType } from "./utils/diff.js";
var protoDefs = protobuf.Root.fromJSON(jsonProtoDefs); var protoDefs = protobuf.Root.fromJSON(jsonProtoDefs);
var TraceMessage = protoDefs.lookupType( var TraceMessage = protoDefs.lookupType(
"com.android.server.wm.WindowManagerTraceFileProto" "com.android.server.wm.WindowManagerTraceFileProto"
@@ -116,6 +118,12 @@ export default {
clickTimeout: null, clickTimeout: null,
isCollapsedByDefault, isCollapsedByDefault,
localCollapsedState: isCollapsedByDefault, localCollapsedState: isCollapsedByDefault,
diffSymbol: {
[DiffType.NONE]: "",
[DiffType.ADDED]: "+",
[DiffType.DELETED]: "-",
[DiffType.MODIFIED]: ".",
},
}; };
}, },
methods: { methods: {
@@ -271,6 +279,9 @@ export default {
return false; return false;
}, },
diffClass() {
return this.item.diff ? this.item.diff.type : ''
},
chipClassOrDefault() { chipClassOrDefault() {
return this.chipClass || "tree-view-chip"; return this.chipClass || "tree-view-chip";
}, },
@@ -335,6 +346,18 @@ export default {
background: #f1f1f1; 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 { .children {
/* Aligns border with collapse arrows */ /* Aligns border with collapse arrows */
margin-left: 12px; margin-left: 12px;

View File

@@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { DiffType } from './utils/diff.js';
// kind - a type used for categorization of different levels // kind - a type used for categorization of different levels
// name - name of the node // name - name of the node
// children - list of child entries. Each child entry is pair list [raw object, nested transform function]. // 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); 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) { // Represents termination of the object traversal,
let { skip, formatter } = options; // differentiated with a null value in the object.
class Terminal { }
var children = []; function isTerminal(obj) {
var formatted = undefined; 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)) { if (skip && skip.includes(obj)) {
// skip // skip
} else if ((formatted = formatter(obj))) { } 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)) { } else if (Array.isArray(obj)) {
obj.forEach((e, i) => { obj.forEach((e, i) => {
children.push(transform_json(e, "" + i, `${stableId}[${i}]`, options)); transformedObj["" + i] = e;
}) });
} else if (typeof obj == 'string') { } 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') { } 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') { } else if (obj && typeof obj == 'object') {
Object.keys(obj).forEach((key) => { 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 transformedObj;
return Object.freeze({
kind: "",
name: name + ": " + children[0].name,
stableId: stableId,
children: children[0].children,
combined: true
});
} }
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: "", kind: "",
name: name, name: name + ": " + child.name,
stableId: stableId, 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) { function nanos_to_string(elapsedRealtimeNanos) {
@@ -163,4 +274,4 @@ function get_visible_chip() {
return { short: 'V', long: "visible", class: 'default' }; 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 };

View 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 };