[DO NOT MERGE] Sync flicker from master to sc-v2

Flicker on master diverged form sc-v2, to make it easier to debug
flicker issues on sc-v2, push the current version of flicker into sc-v2

Test: atest FlickerTests WMShellFlickerTests
Bug: 188792659
Change-Id: Iaacfaa75102f93351a6ccd0252ecd739784f94ff
This commit is contained in:
Nataniel Borges
2021-08-31 15:08:30 +00:00
parent ba5a679a01
commit 325b0476f5
47 changed files with 2315 additions and 312 deletions

View File

@@ -55,6 +55,14 @@ WINSCOPE_TOKEN_HEADER = "Winscope-Token"
# Location to save the proxy security token # Location to save the proxy security token
WINSCOPE_TOKEN_LOCATION = os.path.expanduser('~/.config/winscope/.token') WINSCOPE_TOKEN_LOCATION = os.path.expanduser('~/.config/winscope/.token')
# Winscope traces extensions
WINSCOPE_EXT = ".winscope"
WINSCOPE_EXT_LEGACY = ".pb"
WINSCOPE_EXTS = [WINSCOPE_EXT, WINSCOPE_EXT_LEGACY]
# Winscope traces directory
WINSCOPE_DIR = "/data/misc/wmtrace/"
# Max interval between the client keep-alive requests in seconds # Max interval between the client keep-alive requests in seconds
KEEP_ALIVE_INTERVAL_S = 5 KEEP_ALIVE_INTERVAL_S = 5
@@ -85,12 +93,29 @@ class FileMatcher:
matchingFiles = call_adb( matchingFiles = call_adb(
f"shell su root find {self.path} -name {self.matcher}", device_id) f"shell su root find {self.path} -name {self.matcher}", device_id)
log.debug("Found file %s", matchingFiles.split('\n')[:-1])
return matchingFiles.split('\n')[:-1] return matchingFiles.split('\n')[:-1]
def get_filetype(self): def get_filetype(self):
return self.type return self.type
class WinscopeFileMatcher(FileMatcher):
def __init__(self, path, matcher, filetype) -> None:
self.path = path
self.internal_matchers = list(map(lambda ext: FileMatcher(path, f'{matcher}{ext}', filetype),
WINSCOPE_EXTS))
self.type = filetype
def get_filepaths(self, device_id):
for matcher in self.internal_matchers:
files = matcher.get_filepaths(device_id)
if len(files) > 0:
return files
log.debug("No files found")
return []
class TraceTarget: class TraceTarget:
"""Defines a single parameter to trace. """Defines a single parameter to trace.
@@ -111,51 +136,50 @@ class TraceTarget:
# Order of files matters as they will be expected in that order and decoded in that order # Order of files matters as they will be expected in that order and decoded in that order
TRACE_TARGETS = { TRACE_TARGETS = {
"window_trace": TraceTarget( "window_trace": TraceTarget(
File("/data/misc/wmtrace/wm_trace.pb", "window_trace"), WinscopeFileMatcher(WINSCOPE_DIR, "wm_trace", "window_trace"),
'su root cmd window tracing start\necho "WM trace started."', 'su root cmd window tracing start\necho "WM trace started."',
'su root cmd window tracing stop >/dev/null 2>&1' 'su root cmd window tracing stop >/dev/null 2>&1'
), ),
"accessibility_trace": TraceTarget( "accessibility_trace": TraceTarget(
File("/data/misc/a11ytrace/a11y_trace.pb", "accessibility_trace"), WinscopeFileMatcher("/data/misc/a11ytrace", "a11y_trace", "accessibility_trace"),
'su root cmd accessibility start-trace\necho "Accessibility trace started."', 'su root cmd accessibility start-trace\necho "Accessibility trace started."',
'su root cmd accessibility stop-trace >/dev/null 2>&1' 'su root cmd accessibility stop-trace >/dev/null 2>&1'
), ),
"layers_trace": TraceTarget( "layers_trace": TraceTarget(
File("/data/misc/wmtrace/layers_trace.pb", "layers_trace"), WinscopeFileMatcher(WINSCOPE_DIR, "layers_trace", "layers_trace"),
'su root service call SurfaceFlinger 1025 i32 1\necho "SF trace started."', 'su root service call SurfaceFlinger 1025 i32 1\necho "SF trace started."',
'su root service call SurfaceFlinger 1025 i32 0 >/dev/null 2>&1' 'su root service call SurfaceFlinger 1025 i32 0 >/dev/null 2>&1'
), ),
"screen_recording": TraceTarget( "screen_recording": TraceTarget(
File("/data/local/tmp/screen.winscope.mp4", "screen_recording"), File(f'/data/local/tmp/screen.mp4', "screen_recording"),
'screenrecord --bit-rate 8M /data/local/tmp/screen.winscope.mp4 >/dev/null 2>&1 &\necho "ScreenRecorder started."', f'screenrecord --bit-rate 8M /data/local/tmp/screen.mp4 >/dev/null 2>&1 &\necho "ScreenRecorder started."',
'pkill -l SIGINT screenrecord >/dev/null 2>&1' 'pkill -l SIGINT screenrecord >/dev/null 2>&1'
), ),
"transaction": TraceTarget( "transaction": TraceTarget(
[ [
File("/data/misc/wmtrace/transaction_trace.pb", "transactions"), WinscopeFileMatcher(WINSCOPE_DIR, "transaction_trace", "transactions"),
FileMatcher("/data/misc/wmtrace/", "transaction_merges_*.pb", FileMatcher(WINSCOPE_DIR, f'transaction_merges_*', "transaction_merges"),
"transaction_merges"),
], ],
'su root service call SurfaceFlinger 1020 i32 1\necho "SF transactions recording started."', 'su root service call SurfaceFlinger 1020 i32 1\necho "SF transactions recording started."',
'su root service call SurfaceFlinger 1020 i32 0 >/dev/null 2>&1' 'su root service call SurfaceFlinger 1020 i32 0 >/dev/null 2>&1'
), ),
"proto_log": TraceTarget( "proto_log": TraceTarget(
File("/data/misc/wmtrace/wm_log.pb", "proto_log"), WinscopeFileMatcher(WINSCOPE_DIR, "wm_log", "proto_log"),
'su root cmd window logging start\necho "WM logging started."', 'su root cmd window logging start\necho "WM logging started."',
'su root cmd window logging stop >/dev/null 2>&1' 'su root cmd window logging stop >/dev/null 2>&1'
), ),
"ime_trace_clients": TraceTarget( "ime_trace_clients": TraceTarget(
File("/data/misc/wmtrace/ime_trace_clients.pb", "ime_trace_clients"), WinscopeFileMatcher(WINSCOPE_DIR, "ime_trace_clients", "ime_trace_clients"),
'su root ime tracing start\necho "Clients IME trace started."', 'su root ime tracing start\necho "Clients IME trace started."',
'su root ime tracing stop >/dev/null 2>&1' 'su root ime tracing stop >/dev/null 2>&1'
), ),
"ime_trace_service": TraceTarget( "ime_trace_service": TraceTarget(
File("/data/misc/wmtrace/ime_trace_service.pb", "ime_trace_service"), WinscopeFileMatcher(WINSCOPE_DIR, "ime_trace_service", "ime_trace_service"),
'su root ime tracing start\necho "Service IME trace started."', 'su root ime tracing start\necho "Service IME trace started."',
'su root ime tracing stop >/dev/null 2>&1' 'su root ime tracing stop >/dev/null 2>&1'
), ),
"ime_trace_managerservice": TraceTarget( "ime_trace_managerservice": TraceTarget(
File("/data/misc/wmtrace/ime_trace_managerservice.pb", "ime_trace_managerservice"), WinscopeFileMatcher(WINSCOPE_DIR, "ime_trace_managerservice", "ime_trace_managerservice"),
'su root ime tracing start\necho "ManagerService IME trace started."', 'su root ime tracing start\necho "ManagerService IME trace started."',
'su root ime tracing stop >/dev/null 2>&1' 'su root ime tracing stop >/dev/null 2>&1'
), ),
@@ -204,12 +228,12 @@ class DumpTarget:
DUMP_TARGETS = { DUMP_TARGETS = {
"window_dump": DumpTarget( "window_dump": DumpTarget(
File("/data/local/tmp/wm_dump.pb", "window_dump"), File(f'/data/local/tmp/wm_dump{WINSCOPE_EXT}', "window_dump"),
'su root dumpsys window --proto > /data/local/tmp/wm_dump.pb' f'su root dumpsys window --proto > /data/local/tmp/wm_dump{WINSCOPE_EXT}'
), ),
"layers_dump": DumpTarget( "layers_dump": DumpTarget(
File("/data/local/tmp/sf_dump.pb", "layers_dump"), File(f'/data/local/tmp/sf_dump{WINSCOPE_EXT}', "layers_dump"),
'su root dumpsys SurfaceFlinger --proto > /data/local/tmp/sf_dump.pb' f'su root dumpsys SurfaceFlinger --proto > /data/local/tmp/sf_dump{WINSCOPE_EXT}'
) )
} }
@@ -425,6 +449,9 @@ class FetchFilesEndpoint(DeviceRequestEndpoint):
buf = base64.encodebytes(tmp.read()).decode("utf-8") buf = base64.encodebytes(tmp.read()).decode("utf-8")
file_buffers[file_type].append(buf) file_buffers[file_type].append(buf)
if (len(file_buffers) == 0):
log.error("Proxy didn't find any file to fetch")
# server.send_header('X-Content-Type-Options', 'nosniff') # server.send_header('X-Content-Type-Options', 'nosniff')
# add_standard_headers(server) # add_standard_headers(server)
j = json.dumps(file_buffers) j = json.dumps(file_buffers)

View File

@@ -1,10 +1,24 @@
import { DiffGenerator, DiffType } from "../src/utils/diff.js"; import { DiffGenerator, DiffType } from "../src/utils/diff.js";
import { Node, DiffNode, toPlainObject } from "./utils/tree.js"; import { Node, DiffNode, toPlainObject } from "./utils/tree.js";
const treeOne = new Node({ id: 1 }, [
new Node({ id: 2 }, []),
new Node({ id: 3 }, []),
new Node({ id: 4 }, []),
]);
const treeTwo = new Node({ id: 1 }, [
new Node({ id: 2 }, []),
new Node({ id: 3 }, [
new Node({ id: 5 }, []),
]),
new Node({ id: 4 }, []),
]);
function checkDiffTreeWithNoModifiedCheck(oldTree, newTree, expectedDiffTree) { function checkDiffTreeWithNoModifiedCheck(oldTree, newTree, expectedDiffTree) {
const diffTree = new DiffGenerator(newTree) const diffTree = new DiffGenerator(newTree)
.compareWith(oldTree) .compareWith(oldTree)
.withUniqueNodeId(node => node.id) .withUniqueNodeId(node => node.id)
.withModifiedCheck(() => false)
.generateDiffTree(); .generateDiffTree();
expect(diffTree).toEqual(expectedDiffTree); expect(diffTree).toEqual(expectedDiffTree);
@@ -12,25 +26,8 @@ function checkDiffTreeWithNoModifiedCheck(oldTree, newTree, expectedDiffTree) {
describe("DiffGenerator", () => { describe("DiffGenerator", () => {
it("can generate a simple add diff", () => { it("can generate a simple add diff", () => {
const oldTree = new Node({ id: 1 }, [ const oldTree = treeOne;
new Node({ id: 2 }, []), const newTree = treeTwo;
new Node({ id: 3 }, []),
new Node({ id: 4 }, []),
]);
const newTree = new Node({ id: 1 }, [
new Node({ id: 2 }, []),
new Node({ id: 3 }, [
new Node({ id: 5 }, []),
]),
new Node({ id: 4 }, []),
]);
const diffTree = new DiffGenerator(newTree)
.compareWith(oldTree)
.withUniqueNodeId(node => node.id)
.withModifiedCheck(() => false)
.generateDiffTree();
const expectedDiffTree = toPlainObject( const expectedDiffTree = toPlainObject(
new DiffNode({ id: 1 }, DiffType.NONE, [ new DiffNode({ id: 1 }, DiffType.NONE, [
@@ -46,19 +43,8 @@ describe("DiffGenerator", () => {
}); });
it("can generate a simple delete diff", () => { it("can generate a simple delete diff", () => {
const oldTree = new Node({ id: 1 }, [ const oldTree = treeTwo;
new Node({ id: 2 }, []), const newTree = treeOne;
new Node({ id: 3 }, [
new Node({ id: 5 }, []),
]),
new Node({ id: 4 }, []),
]);
const newTree = new Node({ id: 1 }, [
new Node({ id: 2 }, []),
new Node({ id: 3 }, []),
new Node({ id: 4 }, []),
]);
const expectedDiffTree = toPlainObject( const expectedDiffTree = toPlainObject(
new DiffNode({ id: 1 }, DiffType.NONE, [ new DiffNode({ id: 1 }, DiffType.NONE, [
@@ -74,13 +60,7 @@ describe("DiffGenerator", () => {
}); });
it("can generate a simple move diff", () => { it("can generate a simple move diff", () => {
const oldTree = new Node({ id: 1 }, [ const oldTree = treeTwo;
new Node({ id: 2 }, []),
new Node({ id: 3 }, [
new Node({ id: 5 }, []),
]),
new Node({ id: 4 }, []),
]);
const newTree = new Node({ id: 1 }, [ const newTree = new Node({ id: 1 }, [
new Node({ id: 2 }, []), new Node({ id: 2 }, []),

View File

@@ -1,32 +1,6 @@
import { DiffType } from "../src/utils/diff.js"; import { DiffType } from "../src/utils/diff.js";
import { ObjectTransformer } from "../src/transform.js"; import { ObjectTransformer } from "../src/transform.js";
import { Node, DiffNode, toPlainObject } from "./utils/tree.js"; import { ObjNode, ObjDiffNode, toPlainObject } from "./utils/tree.js";
class ObjNode extends Node {
constructor(name, children, combined) {
const nodeDef = {
kind: '',
name: name,
};
if (combined) {
nodeDef.combined = true;
}
super(nodeDef, children);
}
}
class ObjDiffNode extends DiffNode {
constructor(name, diffType, children, combined) {
const nodeDef = {
kind: '',
name: name,
};
if (combined) {
nodeDef.combined = true;
}
super(nodeDef, diffType, children);
}
}
describe("ObjectTransformer", () => { describe("ObjectTransformer", () => {
it("can transform a simple object", () => { it("can transform a simple object", () => {
@@ -46,19 +20,19 @@ describe("ObjectTransformer", () => {
const expectedTransformedObj = toPlainObject( const expectedTransformedObj = toPlainObject(
new ObjNode('root', [ new ObjNode('root', [
new ObjNode('obj', [ new ObjNode('obj', [
new ObjNode('string: string', [], true), new ObjNode('string: string', [], true, 'root.obj.string'),
new ObjNode('number: 3', [], true), new ObjNode('number: 3', [], true, 'root.obj.number'),
]), ], undefined, 'root.obj'),
new ObjNode('array', [ new ObjNode('array', [
new ObjNode('0', [ new ObjNode('0', [
new ObjNode('nested: item', [], true), new ObjNode('nested: item', [], true, 'root.array.0.nested'),
]), ], undefined, 'root.array.0'),
new ObjNode("1: two", [], true), new ObjNode("1: two", [], true, 'root.array.1'),
]), ], undefined, 'root.array'),
]) ], undefined, 'root')
); );
const transformedObj = new ObjectTransformer(obj, 'root') const transformedObj = new ObjectTransformer(obj, 'root', 'root')
.setOptions({ formatter: () => { } }) .setOptions({ formatter: () => { } })
.transform(); .transform();
@@ -75,12 +49,12 @@ describe("ObjectTransformer", () => {
const expectedTransformedObj = toPlainObject( const expectedTransformedObj = toPlainObject(
new ObjNode('root', [ new ObjNode('root', [
new ObjNode('obj', [ new ObjNode('obj', [
new ObjNode('null: null', [], true), new ObjNode('null: null', [], true, 'root.obj.null'),
]), ], undefined, 'root.obj'),
]) ], undefined, 'root')
); );
const transformedObj = new ObjectTransformer(obj, 'root') const transformedObj = new ObjectTransformer(obj, 'root', 'root')
.setOptions({ formatter: () => { } }) .setOptions({ formatter: () => { } })
.transform(); .transform();
@@ -106,14 +80,14 @@ describe("ObjectTransformer", () => {
const expectedTransformedObj = toPlainObject( const expectedTransformedObj = toPlainObject(
new ObjDiffNode('root', DiffType.NONE, [ new ObjDiffNode('root', DiffType.NONE, [
new ObjDiffNode('a', DiffType.NONE, [ new ObjDiffNode('a', DiffType.NONE, [
new ObjDiffNode('b: 1', DiffType.NONE, [], true), new ObjDiffNode('b: 1', DiffType.NONE, [], true, 'root.a.b'),
new ObjDiffNode('d: 3', DiffType.ADDED, [], true), new ObjDiffNode('d: 3', DiffType.ADDED, [], true, 'root.a.d'),
]), ], false, 'root.a'),
new ObjDiffNode('c: 2', DiffType.NONE, [], true), new ObjDiffNode('c: 2', DiffType.NONE, [], true, 'root.c'),
]) ], false, 'root')
); );
const transformedObj = new ObjectTransformer(newObj, 'root') const transformedObj = new ObjectTransformer(newObj, 'root', 'root')
.setOptions({ formatter: () => { } }) .setOptions({ formatter: () => { } })
.withDiff(oldObj) .withDiff(oldObj)
.transform(); .transform();
@@ -133,13 +107,13 @@ describe("ObjectTransformer", () => {
const expectedTransformedObj = toPlainObject( const expectedTransformedObj = toPlainObject(
new ObjDiffNode('root', DiffType.NONE, [ new ObjDiffNode('root', DiffType.NONE, [
new ObjDiffNode('a', DiffType.NONE, [ new ObjDiffNode('a', DiffType.NONE, [
new ObjDiffNode('1', DiffType.ADDED, []), new ObjDiffNode('1', DiffType.ADDED, [], false, 'root.a.1'),
new ObjDiffNode('null', DiffType.DELETED, []), new ObjDiffNode('null', DiffType.DELETED, [], false, 'root.a.null'),
]), ], false, 'root.a'),
]) ], false, 'root')
); );
const transformedObj = new ObjectTransformer(newObj, 'root') const transformedObj = new ObjectTransformer(newObj, 'root', 'root')
.setOptions({ formatter: () => { } }) .setOptions({ formatter: () => { } })
.withDiff(oldObj) .withDiff(oldObj)
.transform(); .transform();
@@ -166,15 +140,15 @@ describe("ObjectTransformer", () => {
new ObjDiffNode('root', DiffType.NONE, [ new ObjDiffNode('root', DiffType.NONE, [
new ObjDiffNode('a', DiffType.NONE, [ new ObjDiffNode('a', DiffType.NONE, [
new ObjDiffNode('b', DiffType.NONE, [ new ObjDiffNode('b', DiffType.NONE, [
new ObjDiffNode('1', DiffType.ADDED, []), new ObjDiffNode('1', DiffType.ADDED, [], false, 'root.a.b.1'),
new ObjDiffNode('null', DiffType.DELETED, []), new ObjDiffNode('null', DiffType.DELETED, [], false, 'root.a.b.null'),
]), ], false, 'root.a.b'),
]), ], false, 'root.a'),
new ObjDiffNode('c: 2', DiffType.NONE, [], true), new ObjDiffNode('c: 2', DiffType.NONE, [], true, 'root.c'),
]) ], false, 'root')
); );
const transformedObj = new ObjectTransformer(newObj, 'root') const transformedObj = new ObjectTransformer(newObj, 'root', 'root')
.setOptions({ formatter: () => { } }) .setOptions({ formatter: () => { } })
.withDiff(oldObj) .withDiff(oldObj)
.transform(); .transform();

View File

@@ -1,25 +1,28 @@
import { detectAndDecode, decodeAndTransformProto, FILE_TYPES } from '../src/decode'; import { decodeAndTransformProto, FILE_TYPES, FILE_DECODERS } from '../src/decode';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { expectedEntries, expectedLayers, layers_traces } from './traces/ExpectedTraces';
const layers_traces = [
require('./traces/layers_trace/layers_trace_emptyregion.pb'),
require('./traces/layers_trace/layers_trace_invalid_layer_visibility.pb'),
require('./traces/layers_trace/layers_trace_orphanlayers.pb'),
require('./traces/layers_trace/layers_trace_root.pb'),
require('./traces/layers_trace/layers_trace_root_aosp.pb'),
];
describe("Proto Transformations", () => { describe("Proto Transformations", () => {
it("can transform surface flinger traces", () => { it("can transform surface flinger traces", () => {
for (const trace of layers_traces) { for (var i = 0; i < layers_traces.length; i++) {
fs.readFileSync(path.resolve(__dirname, trace)); const trace = layers_traces[i];
const traceBuffer = fs.readFileSync(path.resolve(__dirname, trace)); const buffer = new Uint8Array(fs.readFileSync(path.resolve(__dirname, trace)));
const data = decodeAndTransformProto(buffer, FILE_DECODERS[FILE_TYPES.SURFACE_FLINGER_TRACE].decoderParams, true);
const buffer = new Uint8Array(traceBuffer); // use final entry as this determines if there was any error in previous entry parsing
const data = decodeAndTransformProto(buffer, FILE_TYPES.layers_trace, true); const transformedEntry = data.entries[data.entries.length-1];
const expectedEntry = expectedEntries[i];
for (const property in expectedEntry) {
expect(transformedEntry[property]).toEqual(expectedEntry[property]);
}
expect(true).toBe(true); // check final flattened layer
const transformedLayer = transformedEntry.flattenedLayers[transformedEntry.flattenedLayers.length-1];
const expectedLayer = expectedLayers[i];
for (const property in expectedLayer) {
expect(transformedLayer[property]).toEqual(expectedLayer[property]);
}
} }
}); });
}); });

View File

@@ -0,0 +1,50 @@
import { decodeAndTransformProto, FILE_TYPES, FILE_DECODERS } from '../src/decode';
import Tag from '../src/flickerlib/tags/Tag';
import Error from '../src/flickerlib/errors/Error';
import fs from 'fs';
import path from 'path';
const tagTrace = '../spec/traces/tag_trace.winscope';
const errorTrace = '../spec/traces/error_trace.winscope';
describe("Tag Transformation", () => {
it("can transform tag traces", () => {
const buffer = new Uint8Array(fs.readFileSync(path.resolve(__dirname, tagTrace)));
const data = decodeAndTransformProto(buffer, FILE_DECODERS[FILE_TYPES.TAG_TRACE].decoderParams, true);
expect(data.entries[0].timestamp.toString()).toEqual('159979677861');
expect(data.entries[1].timestamp.toString()).toEqual('161268519083');
expect(data.entries[2].timestamp.toString()).toEqual('161126718496');
expect(data.entries[3].timestamp.toString()).toEqual('161613497398');
expect(data.entries[4].timestamp.toString()).toEqual('161227062777');
expect(data.entries[5].timestamp.toString()).toEqual('161268519083');
expect(data.entries[6].timestamp.toString()).toEqual('161825945076');
expect(data.entries[7].timestamp.toString()).toEqual('162261072567');
expect(data.entries[0].tags).toEqual([new Tag(12345,"PIP_ENTER",true,1,"",0)]);
expect(data.entries[1].tags).toEqual([new Tag(12345,"PIP_ENTER",false,2,"",2)]);
expect(data.entries[2].tags).toEqual([new Tag(67890,"ROTATION",true,3,"",3)]);
expect(data.entries[3].tags).toEqual([new Tag(67890,"ROTATION",false,4,"",4)]);
expect(data.entries[4].tags).toEqual([new Tag(9876,"PIP_EXIT",true,5,"",5)]);
expect(data.entries[5].tags).toEqual([new Tag(9876,"PIP_EXIT",false,6,"",6)]);
expect(data.entries[6].tags).toEqual([new Tag(54321,"IME_APPEAR",true,7,"",7)]);
expect(data.entries[7].tags).toEqual([new Tag(54321,"IME_APPEAR",false,8,"",8)]);
})
});
describe("Error Transformation", () => {
it("can transform error traces", () => {
const buffer = new Uint8Array(fs.readFileSync(path.resolve(__dirname, errorTrace)));
const data = decodeAndTransformProto(buffer, FILE_DECODERS[FILE_TYPES.ERROR_TRACE].decoderParams, true);
expect(data.entries[0].timestamp.toString()).toEqual('161401263106');
expect(data.entries[1].timestamp.toString()).toEqual('161126718496');
expect(data.entries[2].timestamp.toString()).toEqual('162261072567');
expect(data.entries[0].errors).toEqual([new Error("","",33,"",33)]);
expect(data.entries[1].errors).toEqual([new Error("","",66,"",66)]);
expect(data.entries[2].errors).toEqual([new Error("","",99,"",99)]);
})
});

View File

@@ -0,0 +1,492 @@
import { Buffer, RectF, Transform, Matrix, Color, Rect, Region } from '../../src/flickerlib/common.js';
import { VISIBLE_CHIP } from '../../src/flickerlib/treeview/Chips';
const standardTransform = new Transform(0, new Matrix(1, 0, 0, 0, 1, 0));
const standardRect = new Rect(0, 0, 0, 0);
const standardColor = new Color(0, 0, 0, 1);
const standardCrop = new Rect(0, 0, -1, -1);
const expectedEmptyRegionLayer = {
backgroundBlurRadius: 0,
chips: [],
cornerRadius: 0,
effectiveScalingMode: 0,
hwcCompositionType: "INVALID",
id: 580,
isOpaque: false,
isRelativeOf: false,
kind: "580",
name: "SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0",
shadowRadius: 0,
shortName: "SurfaceView - com.android.(...).Main#0",
type: "BufferLayer",
z: -1,
zOrderRelativeOf: null,
parentId: 579,
activeBuffer: new Buffer(1440, 2614, 1472, 1),
bufferTransform: standardTransform,
color: new Color(0, 0, 0, 0.0069580078125),
crop: standardCrop,
hwcFrame: standardRect,
screenBounds: new RectF(37, 43, 146, 152),
transform: new Transform(0, new Matrix(1, 0, 37.37078094482422, 0, 1, -3.5995326042175293)),
visibleRegion: new Region([new Rect(37, 43, 146, 152)]),
};
const emptyRegionProto = {
2: "\nparent=0\ntype=BufferLayer\nname=Display Root#0",
3: "\nparent=0\ntype=BufferLayer\nname=Display Overlays#0",
4: "\nparent=2\ntype=BufferLayer\nname=mBelowAppWindowsContainers#0",
5: "\nparent=2\ntype=BufferLayer\nname=com.android.server.wm.DisplayContent$TaskStackContainers@193aa46#0",
6: "\nparent=5\ntype=BufferLayer\nname=animationLayer#0",
7: "\nparent=5\ntype=BufferLayer\nname=splitScreenDividerAnchor#0",
8: "\nparent=2\ntype=BufferLayer\nname=mAboveAppWindowsContainers#0",
9: "\nparent=2\ntype=BufferLayer\nname=mImeWindowsContainers#0",
10: "\nparent=5\ntype=BufferLayer\nname=Stack=0#0",
11: "\nparent=10\ntype=ColorLayer\nname=animation background stackId=0#0",
12: "\nparent=9\ntype=BufferLayer\nname=WindowToken{f81e7fc android.os.Binder@7c880ef}#0",
13: "\nparent=4\ntype=BufferLayer\nname=WallpaperWindowToken{3756850 token=android.os.Binder@25b3e13}#0",
18: "\nparent=13\ntype=BufferLayer\nname=fd46a8e com.breel.wallpapers.dioramas.lagos.LagosWallpaperService#0",
19: "\nparent=18\ntype=BufferLayer\nname=com.breel.wallpapers.dioramas.lagos.LagosWallpaperService#0",
20: "\nparent=8\ntype=BufferLayer\nname=WindowToken{fc1aa98 android.os.BinderProxy@3517c7b}#0",
21: "\nparent=20\ntype=BufferLayer\nname=10022f1 DockedStackDivider#0",
22: "\nparent=8\ntype=BufferLayer\nname=WindowToken{49a6772 android.os.BinderProxy@7ba1c7d}#0",
23: "\nparent=22\ntype=BufferLayer\nname=56ef7c3 AssistPreviewPanel#0",
24: "\nparent=8\ntype=BufferLayer\nname=WindowToken{35f7d5c android.os.BinderProxy@8b38fcf}#0",
25: "\nparent=24\ntype=BufferLayer\nname=9029865 NavigationBar#0",
26: "\nparent=8\ntype=BufferLayer\nname=WindowToken{a9a69ab android.os.BinderProxy@f64ffa}#0",
27: "\nparent=26\ntype=BufferLayer\nname=5334808 StatusBar#0",
28: "\nparent=8\ntype=BufferLayer\nname=WindowToken{a63ca37 android.os.BinderProxy@435eb36}#0",
29: "\nparent=28\ntype=BufferLayer\nname=1a40ba4 ScreenDecorOverlay#0",
30: "\nparent=8\ntype=BufferLayer\nname=WindowToken{4ed84c2 android.os.BinderProxy@33d1d0d}#0",
31: "\nparent=30\ntype=BufferLayer\nname=7a0d2d3 ScreenDecorOverlayBottom#0",
32: "\nparent=25\ntype=BufferLayer\nname=NavigationBar#0",
33: "\nparent=27\ntype=BufferLayer\nname=StatusBar#0",
34: "\nparent=29\ntype=BufferLayer\nname=ScreenDecorOverlay#0",
35: "\nparent=31\ntype=BufferLayer\nname=ScreenDecorOverlayBottom#0",
36: "\nparent=10\ntype=BufferLayer\nname=Task=239#0",
37: "\nparent=632\ntype=BufferLayer\nname=AppWindowToken{188ce21 token=Token{824488 ActivityRecord{b0d882b u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t239}}}#0",
38: "\nparent=37\ntype=BufferLayer\nname=9f6e33d com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
44: "\nparent=37\ntype=BufferLayer\nname=81a00fc com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
88: "\nparent=5\ntype=BufferLayer\nname=Stack=2#0",
89: "\nparent=88\ntype=ColorLayer\nname=animation background stackId=2#0",
90: "\nparent=88\ntype=BufferLayer\nname=Task=241#0",
91: "\nparent=633\ntype=BufferLayer\nname=AppWindowToken{a9f5144 token=Token{f102257 ActivityRecord{3a0fd6 u0 com.android.chrome/com.google.android.apps.chrome.Main t241}}}#0",
96: "\nparent=91\ntype=BufferLayer\nname=87e310e com.android.chrome/com.google.android.apps.chrome.Main#0",
574: "\nparent=8\ntype=BufferLayer\nname=WindowToken{37eed7d android.os.Binder@6e217d4}#0",
579: "\nparent=96\ntype=BufferLayer\nname=com.android.chrome/com.google.android.apps.chrome.Main#0",
580: "\nparent=579\ntype=BufferLayer\nname=SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0",
581: "\nparent=579\ntype=ColorLayer\nname=Background for -SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0",
583: "\nparent=44\ntype=BufferLayer\nname=com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
629: "\nparent=38\ntype=BufferLayer\nname=com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#2",
632: "\nparent=6\ntype=BufferLayer\nname=Surface(name=AppWindowToken{188ce21 token=Token{824488 ActivityRecord{b0d882b u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t239}}})/@0x90c9c46 - animation-leash#1",
633: "\nparent=6\ntype=BufferLayer\nname=Surface(name=AppWindowToken{a9f5144 token=Token{f102257 ActivityRecord{3a0fd6 u0 com.android.chrome/com.google.android.apps.chrome.Main t241}}})/@0xd9b9374 - animation-leash#1"
};
const expectedEmptyRegion = {
chips: [],
proto: emptyRegionProto,
hwcBlob: "",
isVisible: true,
kind: "entry",
rects: [],
shortName: "0d0h38m28s521ms",
timestampMs: "2308521813510",
where: "",
name: "0d0h38m28s521ms",
stableId: "LayerTraceEntry",
visibleLayers: [],
};
const expectedInvalidLayerVisibilityLayer = {
backgroundBlurRadius: 0,
chips: [],
cornerRadius: 0,
effectiveScalingMode: 0,
hwcCompositionType: "INVALID",
id: 1536,
isOpaque: false,
isRelativeOf: false,
kind: "1536",
name: "com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#2",
shadowRadius: 0,
shortName: "com.google.(...).NexusLauncherActivity#2",
type: "BufferLayer",
z: 0,
zOrderRelativeOf: null,
parentId: 1535,
stableId: "BufferLayer 1536 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#2",
activeBuffer: new Buffer(1440, 2880, 1472, 1),
bufferTransform: standardTransform,
color: new Color(-1, -1, -1, 0),
hwcFrame: standardRect,
transform: standardTransform,
visibleRegion: new Region([standardRect]),
};
const invalidLayerVisibilityProto = {
2: "\nparent=0\ntype=BufferLayer\nname=Display Root#0",
3: "\nparent=0\ntype=BufferLayer\nname=Display Overlays#0",
4: "\nparent=2\ntype=BufferLayer\nname=mBelowAppWindowsContainers#0",
5: "\nparent=2\ntype=BufferLayer\nname=com.android.server.wm.DisplayContent$TaskStackContainers@4270eb4#0",
6: "\nparent=5\ntype=BufferLayer\nname=animationLayer#0",
7: "\nparent=5\ntype=BufferLayer\nname=boostedAnimationLayer#0",
8: "\nparent=5\ntype=BufferLayer\nname=homeAnimationLayer#0",
9: "\nparent=5\ntype=BufferLayer\nname=splitScreenDividerAnchor#0",
10: "\nparent=2\ntype=BufferLayer\nname=mAboveAppWindowsContainers#0",
11: "\nparent=2\ntype=BufferLayer\nname=mImeWindowsContainers#0",
12: "\nparent=5\ntype=BufferLayer\nname=Stack=0#0",
13: "\nparent=12\ntype=ColorLayer\nname=animation background stackId=0#0",
14: "\nparent=11\ntype=BufferLayer\nname=WindowToken{268fcff android.os.Binder@6688c1e}#0",
15: "\nparent=4\ntype=BufferLayer\nname=WallpaperWindowToken{6572e20 token=android.os.Binder@9543923}#0",
20: "\nparent=15\ntype=BufferLayer\nname=5e2e96f com.breel.wallpapers.dioramas.lagos.LagosWallpaperService#0",
21: "\nparent=20\ntype=BufferLayer\nname=com.breel.wallpapers.dioramas.lagos.LagosWallpaperService#0",
26: "\nparent=10\ntype=BufferLayer\nname=WindowToken{68e3f31 android.os.BinderProxy@f018fd8}#0",
27: "\nparent=26\ntype=BufferLayer\nname=2b80616 NavigationBar#0",
28: "\nparent=10\ntype=BufferLayer\nname=WindowToken{4e20cae android.os.BinderProxy@9086129}#0",
29: "\nparent=28\ntype=BufferLayer\nname=b09a4f StatusBar#0",
30: "\nparent=3\ntype=BufferLayer\nname=WindowToken{501e3b8 android.os.BinderProxy@238661b}#0",
31: "\nparent=30\ntype=BufferLayer\nname=d803591 ScreenDecorOverlay#0",
32: "\nparent=3\ntype=BufferLayer\nname=WindowToken{56d48f7 android.os.BinderProxy@f0f2cf6}#0",
33: "\nparent=32\ntype=BufferLayer\nname=1cd8364 ScreenDecorOverlayBottom#0",
35: "\nparent=29\ntype=BufferLayer\nname=StatusBar#0",
36: "\nparent=31\ntype=BufferLayer\nname=ScreenDecorOverlay#0",
37: "\nparent=33\ntype=BufferLayer\nname=ScreenDecorOverlayBottom#0",
38: "\nparent=12\ntype=BufferLayer\nname=Task=2#0",
39: "\nparent=38\ntype=BufferLayer\nname=AppWindowToken{215b919 token=Token{104a060 ActivityRecord{7e30c63 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t2}}}#0",
821: "\nparent=14\ntype=BufferLayer\nname=5c937c8 InputMethod#0",
1078: "\nparent=10\ntype=BufferLayer\nname=WindowToken{7dc6283 android.os.BinderProxy@f83c532}#0",
1079: "\nparent=1078\ntype=BufferLayer\nname=32c0c00 AssistPreviewPanel#0",
1080: "\nparent=10\ntype=BufferLayer\nname=WindowToken{9f8a3df android.os.BinderProxy@825027e}#0",
1081: "\nparent=1080\ntype=BufferLayer\nname=26d9efb DockedStackDivider#0",
1403: "\nparent=10\ntype=BufferLayer\nname=WindowToken{dedcfff android.os.Binder@a80cb1e}#0",
1447: "\nparent=39\ntype=BufferLayer\nname=39ca531 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
1454: "\nparent=27\ntype=BufferLayer\nname=NavigationBar#0",
1502: "\nparent=10\ntype=BufferLayer\nname=WindowToken{3ea357b android.os.Binder@6d9a90a}#0",
1505: "\nparent=1518\ntype=BufferLayer\nname=Task=623#0",
1506: "\nparent=1505\ntype=BufferLayer\nname=AppWindowToken{6deed44 token=Token{45cae57 ActivityRecord{7f14bd6 u0 com.android.server.wm.flicker.testapp/.SimpleActivity t623}}}#0",
1518: "\nparent=5\ntype=BufferLayer\nname=Stack=51#0",
1519: "\nparent=1518\ntype=ColorLayer\nname=animation background stackId=51#0",
1521: "\nparent=1506\ntype=BufferLayer\nname=496d52e com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp.SimpleActivity#0",
1534: "\nparent=1447\ntype=BufferLayer\nname=com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
1535: "\nparent=39\ntype=BufferLayer\nname=e280197 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
1536: "\nparent=1535\ntype=BufferLayer\nname=com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#2",
};
const expectedInvalidLayerVisibility = {
chips: [],
proto: invalidLayerVisibilityProto,
hwcBlob: "",
isVisible: true,
kind: "entry",
rects: [],
shortName: "2d22h13m17s233ms",
timestampMs: "252797233543024",
where: "",
name: "2d22h13m17s233ms",
stableId: "LayerTraceEntry",
visibleLayers: [],
};
const expectedOrphanLayersLayer = {
backgroundBlurRadius: 0,
chips: [],
cornerRadius: 0,
effectiveScalingMode: 0,
hwcCompositionType: "INVALID",
id: 1012,
isOpaque: true,
isRelativeOf: false,
kind: "1012",
name: "SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0",
shadowRadius: 0,
shortName: "SurfaceView - com.android.(...).Main#0",
type: "BufferLayer",
z: -1,
zOrderRelativeOf: null,
parentId: 1011,
stableId: "BufferLayer 1012 SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0",
activeBuffer: new Buffer(1440, 2614, 1472, 1),
bufferTransform: standardTransform,
color: standardColor,
crop: standardCrop,
hwcFrame: standardRect,
screenBounds: new RectF(0, 98, 1440, 2712),
transform: new Transform(0, new Matrix(1, 0, 0, 0, 1, 98)),
visibleRegion: new Region([new Rect(0, 98, 1440, 2712)]),
};
const expectedOrphanLayersProto = {
2: "\nparent=0\ntype=BufferLayer\nname=Display Root#0",
3: "\nparent=0\ntype=BufferLayer\nname=Display Overlays#0",
4: "\nparent=2\ntype=BufferLayer\nname=mBelowAppWindowsContainers#0",
5: "\nparent=2\ntype=BufferLayer\nname=com.android.server.wm.DisplayContent$TaskStackContainers@e7dd520#0",
6: "\nparent=5\ntype=BufferLayer\nname=animationLayer#0",
7: "\nparent=5\ntype=BufferLayer\nname=splitScreenDividerAnchor#0",
8: "\nparent=2\ntype=BufferLayer\nname=mAboveAppWindowsContainers#0",
9: "\nparent=2\ntype=BufferLayer\nname=mImeWindowsContainers#0",
10: "\nparent=5\ntype=BufferLayer\nname=Stack=0#0",
11: "\nparent=10\ntype=ColorLayer\nname=animation background stackId=0#0",
12: "\nparent=9\ntype=BufferLayer\nname=WindowToken{1350b6f android.os.Binder@d1b0e4e}#0",
13: "\nparent=4\ntype=BufferLayer\nname=WallpaperWindowToken{4537182 token=android.os.Binder@d87c4cd}#0",
18: "\nparent=13\ntype=BufferLayer\nname=8d26107 com.breel.wallpapers.dioramas.lagos.LagosWallpaperService#0",
19: "\nparent=18\ntype=BufferLayer\nname=com.breel.wallpapers.dioramas.lagos.LagosWallpaperService#0",
20: "\nparent=8\ntype=BufferLayer\nname=WindowToken{fba948d android.os.BinderProxy@756b124}#0",
21: "\nparent=20\ntype=BufferLayer\nname=dc26642 DockedStackDivider#0",
22: "\nparent=8\ntype=BufferLayer\nname=WindowToken{45663b4 android.os.BinderProxy@5273887}#0",
23: "\nparent=22\ntype=BufferLayer\nname=c617bdd AssistPreviewPanel#0",
24: "\nparent=8\ntype=BufferLayer\nname=WindowToken{ef90888 android.os.BinderProxy@9d4dc2b}#0",
25: "\nparent=24\ntype=BufferLayer\nname=1d24221 NavigationBar#0",
26: "\nparent=8\ntype=BufferLayer\nname=WindowToken{6b1dca9 android.os.BinderProxy@a53d830}#0",
27: "\nparent=26\ntype=BufferLayer\nname=eaca22e StatusBar#0",
28: "\nparent=8\ntype=BufferLayer\nname=WindowToken{72e584c android.os.BinderProxy@3ba407f}#0",
29: "\nparent=28\ntype=BufferLayer\nname=46af095 ScreenDecorOverlay#0",
30: "\nparent=8\ntype=BufferLayer\nname=WindowToken{bc659b android.os.BinderProxy@f1405aa}#0",
31: "\nparent=30\ntype=BufferLayer\nname=80ead38 ScreenDecorOverlayBottom#0",
33: "\nparent=27\ntype=BufferLayer\nname=StatusBar#0",
34: "\nparent=29\ntype=BufferLayer\nname=ScreenDecorOverlay#0",
35: "\nparent=31\ntype=BufferLayer\nname=ScreenDecorOverlayBottom#0",
36: "\nparent=10\ntype=BufferLayer\nname=Task=2#0",
37: "\nparent=36\ntype=BufferLayer\nname=AppWindowToken{5162f77 token=Token{ac99d76 ActivityRecord{7749795 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t2}}}#0",
38: "\nparent=37\ntype=BufferLayer\nname=2c19e73 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
41: "\nparent=25\ntype=BufferLayer\nname=NavigationBar#1",
43: "\nparent=37\ntype=BufferLayer\nname=2f0c80b com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
46: "\nparent=5\ntype=BufferLayer\nname=Stack=1#0",
47: "\nparent=46\ntype=ColorLayer\nname=animation background stackId=1#0",
48: "\nparent=46\ntype=BufferLayer\nname=Task=89#0",
49: "\nparent=48\ntype=BufferLayer\nname=AppWindowToken{1d514da token=Token{d36fe85 ActivityRecord{e0ec0ef u0 com.android.chrome/com.google.android.apps.chrome.Main t89}}}#0",
54: "\nparent=49\ntype=BufferLayer\nname=8ae6e06 com.android.chrome/com.google.android.apps.chrome.Main#0",
607: "\nparent=5\ntype=BufferLayer\nname=Stack=9#0",
608: "\nparent=607\ntype=ColorLayer\nname=animation background stackId=9#0",
609: "\nparent=607\ntype=BufferLayer\nname=Task=97#0",
615: "\nparent=609\ntype=BufferLayer\nname=AppWindowToken{28730c9 token=Token{4d768d0 ActivityRecord{faf093 u0 com.google.android.gm/.welcome.WelcomeTourActivity t97}}}#0",
616: "\nparent=615\ntype=BufferLayer\nname=44e6e5c com.google.android.gm/com.google.android.gm.welcome.WelcomeTourActivity#0",
679: "\nparent=12\ntype=BufferLayer\nname=2d0b1e4 InputMethod#0",
993: "\nparent=8\ntype=BufferLayer\nname=WindowToken{e425e58 android.os.Binder@6d9a73b}#0",
1011: "\nparent=54\ntype=BufferLayer\nname=com.android.chrome/com.google.android.apps.chrome.Main#0",
1012: "\nparent=1011\ntype=BufferLayer\nname=SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0",
1013: "\nparent=1011\ntype=ColorLayer\nname=Background for -SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0",
};
const expectedOrphanLayers = {
chips: [],
proto: expectedOrphanLayersProto,
hwcBlob: "",
isVisible: true,
kind: "entry",
rects: [],
shortName: "3d23h30m9s820ms",
timestampMs: "343809820196384",
where: "",
name: "3d23h30m9s820ms",
stableId: "LayerTraceEntry",
visibleLayers: [],
};
const expectedRootLayer = {
backgroundBlurRadius: 0,
cornerRadius: 0,
effectiveScalingMode: 0,
hwcCompositionType: "INVALID",
id: 12545,
isOpaque: true,
isRelativeOf: false,
kind: "12545",
name: "com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp.SimpleActivity#0",
shadowRadius: 0,
shortName: "com.android.(...).SimpleActivity#0",
type: "BufferQueueLayer",
z: 0,
zOrderRelativeOf: null,
parentId: 12541,
stableId: "BufferQueueLayer 12545 com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp.SimpleActivity#0",
activeBuffer: new Buffer(1440, 2960, 1472, 1),
chips: [VISIBLE_CHIP],
bufferTransform: standardTransform,
color: standardColor,
crop: new Rect(0, 0, 1440, 2960),
hwcFrame: standardRect,
screenBounds: new RectF(0, 0, 1440, 2960),
sourceBounds: new RectF(0, 0, 1440, 2960),
transform: standardTransform,
visibleRegion: new Region([new Rect(0, 0, 1440, 2960)]),
};
const expectedRootProto = {
2: "\nparent=-1\ntype=ContainerLayer\nname=Root#0",
3: "\nparent=2\ntype=ContainerLayer\nname=mWindowContainers#0",
4: "\nparent=2\ntype=ContainerLayer\nname=mOverlayContainers#0",
5: "\nparent=3\ntype=ContainerLayer\nname=mBelowAppWindowsContainers#0",
6: "\nparent=3\ntype=ContainerLayer\nname=com.android.server.wm.DisplayContent$TaskContainers@708b672#0",
7: "\nparent=6\ntype=ContainerLayer\nname=animationLayer#0",
8: "\nparent=6\ntype=ContainerLayer\nname=boostedAnimationLayer#0",
9: "\nparent=6\ntype=ContainerLayer\nname=homeAnimationLayer#0",
10: "\nparent=6\ntype=ContainerLayer\nname=splitScreenDividerAnchor#0",
11: "\nparent=3\ntype=ContainerLayer\nname=mAboveAppWindowsContainers#0",
12: "\nparent=3\ntype=ContainerLayer\nname=ImeContainer#0",
13: "\nparent=6\ntype=ContainerLayer\nname=Task=1#0",
18: "\nparent=5\ntype=ContainerLayer\nname=WallpaperWindowToken{4c3f8ef token=android.os.Binder@a0341ce}#0",
19: "\nparent=18\ntype=ContainerLayer\nname=aa9ba7e com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2#0",
20: "\nparent=19\ntype=BufferQueueLayer\nname=com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2#0",
23: "\nparent=11\ntype=ContainerLayer\nname=WindowToken{2e98b86 android.os.BinderProxy@6e5dbc8}#0",
24: "\nparent=23\ntype=ContainerLayer\nname=5976c47 NavigationBar0#0",
25: "\nparent=11\ntype=ContainerLayer\nname=WindowToken{525aa4 android.os.BinderProxy@df1e236}#0",
26: "\nparent=25\ntype=ContainerLayer\nname=986c00d NotificationShade#0",
27: "\nparent=11\ntype=ContainerLayer\nname=WindowToken{7ec5009 android.os.BinderProxy@de2add3}#0",
28: "\nparent=27\ntype=ContainerLayer\nname=3a0542f StatusBar#0",
31: "\nparent=-1\ntype=ContainerLayer\nname=WindowToken{eef604c android.os.BinderProxy@d3a687f}#0",
32: "\nparent=31\ntype=ContainerLayer\nname=20b5895 ScreenDecorOverlay#0",
33: "\nparent=-1\ntype=ContainerLayer\nname=WindowToken{4846f6f android.os.BinderProxy@39824e}#0",
34: "\nparent=33\ntype=ContainerLayer\nname=1d714 ScreenDecorOverlayBottom#0",
36: "\nparent=32\ntype=BufferQueueLayer\nname=ScreenDecorOverlay#0",
38: "\nparent=34\ntype=BufferQueueLayer\nname=ScreenDecorOverlayBottom#0",
40: "\nparent=28\ntype=BufferQueueLayer\nname=StatusBar#0",
43: "\nparent=12\ntype=ContainerLayer\nname=WindowToken{fa12db9 android.os.Binder@4b88380}#0",
46: "\nparent=13\ntype=ContainerLayer\nname=Task=4#0",
47: "\nparent=46\ntype=ContainerLayer\nname=ActivityRecord{99bbfb0 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity#0",
54: "\nparent=24\ntype=BufferQueueLayer\nname=NavigationBar0#0",
71: "\nparent=43\ntype=ContainerLayer\nname=e8f94d2 InputMethod#0",
11499: "\nparent=47\ntype=ContainerLayer\nname=6737b79 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
11501: "\nparent=-1\ntype=ContainerLayer\nname=Input Consumer recents_animation_input_consumer#2",
11759: "\nparent=6\ntype=ContainerLayer\nname=Task=873#0",
11760: "\nparent=11759\ntype=ContainerLayer\nname=Task=874#0",
11761: "\nparent=11760\ntype=ContainerLayer\nname=ActivityRecord{7398002 u0 com.android.server.wm.flicker.testapp/.ImeActivityAutoFocus#0",
11785: "\nparent=11761\ntype=ColorLayer\nname=Letterbox - right#0",
12131: "\nparent=11\ntype=ContainerLayer\nname=WindowToken{bbffcfd android.os.Binder@547b554}#0",
12379: "\nparent=47\ntype=ContainerLayer\nname=3f8f098 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0",
12412: "\nparent=11761\ntype=ContainerLayer\nname=edca7c6 com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp.ImeActivityAutoFocus#0",
12448: "\nparent=2147483645\ntype=ContainerLayer\nname=Surface(name=ActivityRecord{99bbfb0 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity)/@0x2c3972c - animation-leash#0",
12449: "\nparent=2147483645\ntype=ContainerLayer\nname=Surface(name=ActivityRecord{fc16c94 u0 com.android.server.wm.flicker.testapp/.ImeActivity)/@0x7049863 - animation-leash#0",
12485: "\nparent=6\ntype=ContainerLayer\nname=Task=908#0",
12486: "\nparent=12485\ntype=ContainerLayer\nname=Task=909#0",
12487: "\nparent=12486\ntype=ContainerLayer\nname=ActivityRecord{4b3c5cb u0 com.android.server.wm.flicker.testapp/.ImeActivity#0",
12500: "\nparent=2147483645\ntype=ContainerLayer\nname=Surface(name=ActivityRecord{99bbfb0 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity)/@0x2c3972c - animation-leash#0",
12501: "\nparent=2147483645\ntype=ContainerLayer\nname=Surface(name=ActivityRecord{4b3c5cb u0 com.android.server.wm.flicker.testapp/.ImeActivity)/@0x4ad47a1 - animation-leash#0",
12502: "\nparent=2147483645\ntype=ContainerLayer\nname=Surface(name=WallpaperWindowToken{4c3f8ef token=android.os.Binder@a0341ce})/@0xcde5e65 - animation-leash#0",
12511: "\nparent=12487\ntype=ColorLayer\nname=Letterbox - right#1",
12514: "\nparent=12487\ntype=ContainerLayer\nname=debe1ed com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp.ImeActivity#0",
12526: "\nparent=11\ntype=ContainerLayer\nname=WindowToken{6b7d663 android.os.BinderProxy@391f21d}#0",
12527: "\nparent=12526\ntype=ContainerLayer\nname=32aa260 AssistPreviewPanel#0",
12529: "\nparent=11\ntype=ContainerLayer\nname=WindowToken{31f7489 android.os.BinderProxy@67b1e53}#0",
12530: "\nparent=12529\ntype=ContainerLayer\nname=cbb28bc DockedStackDivider#0",
12536: "\nparent=6\ntype=ContainerLayer\nname=Task=910#0",
12537: "\nparent=12536\ntype=ContainerLayer\nname=Task=911#0",
12538: "\nparent=12537\ntype=ContainerLayer\nname=ActivityRecord{d3b8a44 u0 com.android.server.wm.flicker.testapp/.SimpleActivity#0",
12541: "\nparent=12538\ntype=ContainerLayer\nname=a3583c5 com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp.SimpleActivity#0",
12545: "\nparent=12541\ntype=BufferQueueLayer\nname=com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp.SimpleActivity#0",
2147483645: "\nparent=-1\ntype=\nname=Offscreen Root",
};
const expectedRoot = {
chips: [],
proto: expectedRootProto,
hwcBlob: "",
isVisible: true,
kind: "entry",
shortName: "0d1h46m19s146ms",
timestampMs: "6379146308030",
where: "",
name: "0d1h46m19s146ms",
stableId: "LayerTraceEntry",
};
const expectedRootAospLayer = {
backgroundBlurRadius: 0,
cornerRadius: 0,
effectiveScalingMode: 0,
hwcCompositionType: "INVALID",
id: 876,
isOpaque: false,
isRelativeOf: false,
kind: "876",
name: "com.android.launcher3/com.android.launcher3.Launcher#0",
shadowRadius: 0,
shortName: "com.android.(...).Launcher#0",
type: "BufferLayer",
z: 0,
zOrderRelativeOf: null,
parentId: 41,
activeBuffer: new Buffer(1440, 2880, 1472, 1),
bufferTransform: standardTransform,
chips: [VISIBLE_CHIP],
color: standardColor,
crop: new Rect(0, 0, 1440, 2880),
hwcFrame: standardRect,
screenBounds: new RectF(0, 0, 1440, 2880),
sourceBounds: new RectF(0, 0, 1440, 2880),
transform: standardTransform,
visibleRegion: new Region([new Rect(0, 0, 1440, 2880)]),
};
const expectedRootAospProto = {
2: "\nparent=-1\ntype=ContainerLayer\nname=Display Root#0",
3: "\nparent=-1\ntype=ContainerLayer\nname=Display Overlays#0",
4: "\nparent=2\ntype=ContainerLayer\nname=mBelowAppWindowsContainers#0",
5: "\nparent=2\ntype=ContainerLayer\nname=com.android.server.wm.DisplayContent$TaskStackContainers@d8077b3#0",
6: "\nparent=5\ntype=ContainerLayer\nname=animationLayer#0",
7: "\nparent=5\ntype=ContainerLayer\nname=boostedAnimationLayer#0",
8: "\nparent=5\ntype=ContainerLayer\nname=homeAnimationLayer#0",
9: "\nparent=5\ntype=ContainerLayer\nname=splitScreenDividerAnchor#0",
10: "\nparent=2\ntype=ContainerLayer\nname=mAboveAppWindowsContainers#0",
11: "\nparent=2\ntype=ContainerLayer\nname=mImeWindowsContainers#0",
12: "\nparent=5\ntype=ContainerLayer\nname=Stack=0#0",
13: "\nparent=12\ntype=ColorLayer\nname=animation background stackId=0#0",
18: "\nparent=4\ntype=ContainerLayer\nname=WallpaperWindowToken{5a7eaca token=android.os.Binder@438b635}#0",
23: "\nparent=10\ntype=ContainerLayer\nname=WindowToken{d19e48 android.os.BinderProxy@560ac3a}#0",
24: "\nparent=23\ntype=ContainerLayer\nname=b2a84e1 NavigationBar0#0",
25: "\nparent=10\ntype=ContainerLayer\nname=WindowToken{74d6851 android.os.BinderProxy@8b22adb}#0",
26: "\nparent=25\ntype=ContainerLayer\nname=16448b6 StatusBar#0",
27: "\nparent=-1\ntype=ContainerLayer\nname=WindowToken{624863c android.os.BinderProxy@975b02f}#0",
28: "\nparent=27\ntype=ContainerLayer\nname=cdb9fc5 ScreenDecorOverlay#0",
29: "\nparent=-1\ntype=ContainerLayer\nname=WindowToken{cb7204b android.os.BinderProxy@b8f3d1a}#0",
30: "\nparent=29\ntype=ContainerLayer\nname=ad1ca28 ScreenDecorOverlayBottom#0",
31: "\nparent=28\ntype=BufferLayer\nname=ScreenDecorOverlay#0",
32: "\nparent=30\ntype=BufferLayer\nname=ScreenDecorOverlayBottom#0",
33: "\nparent=18\ntype=ContainerLayer\nname=4f4b23b com.android.systemui.ImageWallpaper#0",
34: "\nparent=33\ntype=BufferLayer\nname=com.android.systemui.ImageWallpaper#0",
36: "\nparent=26\ntype=BufferLayer\nname=StatusBar#0",
37: "\nparent=12\ntype=ContainerLayer\nname=Task=144#0",
38: "\nparent=37\ntype=ContainerLayer\nname=AppWindowToken{54e2de0 token=Token{f4c5fe3 ActivityRecord{6a9dc12 u0 com.android.launcher3/.Launcher t144}}}#0",
40: "\nparent=-1\ntype=ContainerLayer\nname=Input Consumer recents_animation_input_consumer#1",
41: "\nparent=38\ntype=ContainerLayer\nname=418b5c0 com.android.launcher3/com.android.launcher3.Launcher#0",
45: "\nparent=11\ntype=ContainerLayer\nname=WindowToken{9158878 android.os.Binder@4f4a5db}#0",
46: "\nparent=24\ntype=BufferLayer\nname=NavigationBar0#0",
731: "\nparent=10\ntype=ContainerLayer\nname=WindowToken{c0ebbde android.os.BinderProxy@1af0e60}#0",
732: "\nparent=731\ntype=ContainerLayer\nname=b37d1bf AssistPreviewPanel#0",
733: "\nparent=10\ntype=ContainerLayer\nname=WindowToken{dc6b7ea android.os.BinderProxy@166b08c}#0",
734: "\nparent=733\ntype=ContainerLayer\nname=2a1cadb DockedStackDivider#0",
862: "\nparent=10\ntype=ContainerLayer\nname=WindowToken{f63efe6 android.os.Binder@d536e41}#0",
865: "\nparent=887\ntype=ContainerLayer\nname=Task=170#0",
866: "\nparent=865\ntype=ContainerLayer\nname=AppWindowToken{c829d40 token=Token{59970c3 ActivityRecord{36f2472 u0 com.android.server.wm.flicker.testapp/.PipActivity t170}}}#0",
871: "\nparent=866\ntype=ContainerLayer\nname=8153ff7 com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp.PipActivity#0",
876: "\nparent=41\ntype=BufferLayer\nname=com.android.launcher3/com.android.launcher3.Launcher#0",
887: "\nparent=5\ntype=ContainerLayer\nname=Stack=78#0",
888: "\nparent=887\ntype=ColorLayer\nname=animation background stackId=78#0",
};
const expectedRootAosp = {
chips: [],
proto: expectedRootAospProto,
hwcBlob: "",
isVisible: true,
kind: "entry",
shortName: "0d1h3m1s911ms",
timestampMs: "3781911657318",
where: "",
name: "0d1h3m1s911ms",
stableId: "LayerTraceEntry",
};
const expectedEntries = [
expectedEmptyRegion,
expectedInvalidLayerVisibility,
expectedOrphanLayers,
expectedRoot,
expectedRootAosp
];
const expectedLayers = [
expectedEmptyRegionLayer,
expectedInvalidLayerVisibilityLayer,
expectedOrphanLayersLayer,
expectedRootLayer,
expectedRootAospLayer
];
const layers_traces = [
'../spec/traces/layers_trace/layers_trace_emptyregion.pb',
'../spec/traces/layers_trace/layers_trace_invalid_layer_visibility.pb',
'../spec/traces/layers_trace/layers_trace_orphanlayers.pb',
'../spec/traces/layers_trace/layers_trace_root.pb',
'../spec/traces/layers_trace/layers_trace_root_aosp.pb',
];
export { expectedEntries, expectedLayers, layers_traces };

Binary file not shown.

Binary file not shown.

View File

@@ -9,6 +9,31 @@ class DiffNode extends Node {
constructor(nodeDef, diffType, children) { constructor(nodeDef, diffType, children) {
super(nodeDef, children); super(nodeDef, children);
this.diff = { type: diffType }; this.diff = { type: diffType };
this.name = undefined;
this.stableId = undefined;
this.kind = undefined;
this.shortName = undefined;
}
}
class ObjNode extends Node {
constructor(name, children, combined, stableId) {
const nodeDef = {
kind: '',
name: name,
stableId: stableId,
};
if (combined) {
nodeDef.combined = true;
}
super(nodeDef, children);
}
}
class ObjDiffNode extends ObjNode {
constructor(name, diffType, children, combined, stableId) {
super(name, children, combined, stableId);
this.diff = { type: diffType };
} }
} }
@@ -30,4 +55,4 @@ function toPlainObject(theClass) {
} }
} }
export { Node, DiffNode, toPlainObject }; export { Node, DiffNode, ObjNode, ObjDiffNode, toPlainObject };

View File

@@ -14,7 +14,13 @@
--> -->
<template> <template>
<TraceView :store="store" :file="file" :summarizer="summarizer" /> <TraceView
:store="store"
:file="file"
:summarizer="summarizer"
:presentTags="[]"
:presentErrors="[]"
/>
</template> </template>
<script> <script>

View File

@@ -52,13 +52,20 @@
:ref="file.type" :ref="file.type"
:store="store" :store="store"
:file="file" :file="file"
:presentTags="Object.freeze(presentTags)"
:presentErrors="Object.freeze(presentErrors)"
:dataViewFiles="dataViewFiles"
@click="onDataViewFocus(file)" @click="onDataViewFocus(file)"
/> />
</div> </div>
<overlay <overlay
:presentTags="Object.freeze(presentTags)"
:presentErrors="Object.freeze(presentErrors)"
:tagAndErrorTraces="tagAndErrorTraces"
:store="store" :store="store"
:ref="overlayRef" :ref="overlayRef"
:searchTypes="searchTypes"
v-if="dataLoaded" v-if="dataLoaded"
v-on:bottom-nav-height-change="handleBottomNavHeightChange" v-on:bottom-nav-height-change="handleBottomNavHeightChange"
/> />
@@ -77,7 +84,8 @@ import FileType from './mixins/FileType.js';
import SaveAsZip from './mixins/SaveAsZip'; import SaveAsZip from './mixins/SaveAsZip';
import FocusedDataViewFinder from './mixins/FocusedDataViewFinder'; import FocusedDataViewFinder from './mixins/FocusedDataViewFinder';
import {DIRECTION} from './utils/utils'; import {DIRECTION} from './utils/utils';
import {NAVIGATION_STYLE} from './utils/consts'; import Searchbar from './Searchbar.vue';
import {NAVIGATION_STYLE, SEARCH_TYPE} from './utils/consts';
const APP_NAME = 'Winscope'; const APP_NAME = 'Winscope';
@@ -97,11 +105,17 @@ export default {
simplifyNames: true, simplifyNames: true,
displayDefaults: true, displayDefaults: true,
navigationStyle: NAVIGATION_STYLE.GLOBAL, navigationStyle: NAVIGATION_STYLE.GLOBAL,
flickerTraceView: false,
showFileTypes: [],
}), }),
overlayRef: 'overlay', overlayRef: 'overlay',
mainContentStyle: { mainContentStyle: {
'padding-bottom': `${CONTENT_BOTTOM_PADDING}px`, 'padding-bottom': `${CONTENT_BOTTOM_PADDING}px`,
}, },
presentTags: [],
presentErrors: [],
searchTypes: [SEARCH_TYPE.TIMESTAMP],
tagAndErrorTraces: false,
}; };
}, },
created() { created() {
@@ -113,8 +127,58 @@ export default {
window.removeEventListener('keydown', this.onKeyDown); window.removeEventListener('keydown', this.onKeyDown);
window.removeEventListener('scroll', this.onScroll); window.removeEventListener('scroll', this.onScroll);
}, },
methods: { methods: {
/** Get states from either tag files or error files */
getUpdatedStates(files) {
var states = [];
for (const file of files) {
states.push(...file.data);
}
return states;
},
/** Get tags from all uploaded tag files*/
getUpdatedTags() {
var tagStates = this.getUpdatedStates(this.tagFiles);
var tags = [];
tagStates.forEach(tagState => {
tagState.tags.forEach(tag => {
tag.timestamp = tagState.timestamp;
tags.push(tag);
});
});
return tags;
},
/** Get tags from all uploaded error files*/
getUpdatedErrors() {
var errorStates = this.getUpdatedStates(this.errorFiles);
var errors = [];
//TODO (b/196201487) add check if errors empty
errorStates.forEach(errorState => {
errorState.errors.forEach(error => {
error.timestamp = errorState.timestamp;
errors.push(error);
});
});
return errors;
},
/** Set flicker mode check for if there are tag/error traces uploaded*/
shouldUpdateTagAndErrorTraces() {
return this.tagFiles.length > 0 || this.errorFiles.length > 0;
},
/** Activate flicker search tab if tags/errors uploaded*/
updateSearchTypes() {
this.searchTypes = [SEARCH_TYPE.TIMESTAMP];
if (this.tagAndErrorTraces) this.searchTypes.push(SEARCH_TYPE.TAG);
},
/** Filter data view files by current show settings*/
updateShowFileTypes() {
this.store.showFileTypes = this.dataViewFiles
.filter((file) => file.show)
.map(file => file.type);
},
clear() { clear() {
this.store.showFileTypes = [];
this.$store.commit('clearFiles'); this.$store.commit('clearFiles');
}, },
onDataViewFocus(file) { onDataViewFocus(file) {
@@ -139,7 +203,12 @@ export default {
}, },
onDataReady(files) { onDataReady(files) {
this.$store.dispatch('setFiles', files); this.$store.dispatch('setFiles', files);
this.tagAndErrorTraces = this.shouldUpdateTagAndErrorTraces();
this.presentTags = this.getUpdatedTags();
this.presentErrors = this.getUpdatedErrors();
this.updateSearchTypes();
this.updateFocusedView(); this.updateFocusedView();
this.updateShowFileTypes();
}, },
setStatus(status) { setStatus(status) {
if (status) { if (status) {
@@ -158,7 +227,10 @@ export default {
}, },
computed: { computed: {
files() { files() {
return this.$store.getters.sortedFiles; return this.$store.getters.sortedFiles.map(file => {
if (this.hasDataView(file)) file.show = true;
return file;
});
}, },
prettyDump() { prettyDump() {
return JSON.stringify(this.dump, null, 2); return JSON.stringify(this.dump, null, 2);
@@ -174,7 +246,16 @@ export default {
return this.activeDataView; return this.activeDataView;
}, },
dataViewFiles() { dataViewFiles() {
return this.files.filter((f) => this.hasDataView(f)); return this.files.filter((file) => this.hasDataView(file));
},
tagFiles() {
return this.$store.getters.tagFiles;
},
errorFiles() {
return this.$store.getters.errorFiles;
},
timelineFiles() {
return this.$store.getters.timelineFiles;
}, },
}, },
watch: { watch: {
@@ -187,6 +268,7 @@ export default {
dataview: DataView, dataview: DataView,
datainput: DataInput, datainput: DataInput,
dataadb: DataAdb, dataadb: DataAdb,
searchbar: Searchbar,
}, },
}; };
</script> </script>
@@ -194,7 +276,7 @@ export default {
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@600&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@600&display=swap');
#app .md-app-container { #app .md-app-container {
/* Get rid of tranforms which prevent fixed position from being used */ /* Get rid of transforms which prevent fixed position from being used */
transform: none!important; transform: none!important;
min-height: 100vh; min-height: 100vh;
} }
@@ -240,20 +322,10 @@ export default {
margin-top: 1em margin-top: 1em
} }
h1, h1 {
h2 {
font-weight: normal; font-weight: normal;
} }
ul {
list-style-type: none;
padding: 0;
}
a {
color: #42b983;
}
.data-inputs { .data-inputs {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -292,5 +364,5 @@ a {
-webkit-hyphens: auto; -webkit-hyphens: auto;
hyphens: auto; hyphens: auto;
padding: 10px 10px 10px 10px; padding: 10px 10px 10px 10px;
} }
</style> </style>

View File

@@ -158,6 +158,9 @@ const TRACES = {
'window_trace': { 'window_trace': {
name: 'Window Manager', name: 'Window Manager',
}, },
'accessibility_trace': {
name: 'Accessibility',
},
'layers_trace': { 'layers_trace': {
name: 'Surface Flinger', name: 'Surface Flinger',
}, },

View File

@@ -16,10 +16,15 @@
<div @click="onClick($event)"> <div @click="onClick($event)">
<flat-card v-if="hasDataView(file)"> <flat-card v-if="hasDataView(file)">
<md-card-header> <md-card-header>
<button class="toggle-view-button" @click="toggleView">
<i aria-hidden="true" class="md-icon md-theme-default material-icons">
{{ isShowFileType(file.type) ? "expand_more" : "chevron_right" }}
</i>
</button>
<md-card-header-text> <md-card-header-text>
<div class="md-title"> <div class="md-title">
<md-icon>{{ TRACE_ICONS[file.type] }}</md-icon> <md-icon>{{ TRACE_ICONS[file.type] }}</md-icon>
{{file.type}} {{ file.type }}
</div> </div>
</md-card-header-text> </md-card-header-text>
<md-button <md-button
@@ -31,41 +36,47 @@
</md-button> </md-button>
</md-card-header> </md-card-header>
<AccessibilityTraceView <AccessibilityTraceView
v-if="showInAccessibilityTraceView(file)" v-if="showInAccessibilityTraceView(file) && isShowFileType(file.type)"
:store="store" :store="store"
:file="file" :file="file"
ref="view" ref="view"
/> />
<WindowManagerTraceView <WindowManagerTraceView
v-if="showInWindowManagerTraceView(file)" v-if="showInWindowManagerTraceView(file) && isShowFileType(file.type)"
:store="store" :store="store"
:file="file" :file="file"
:presentTags="presentTags"
:presentErrors="presentErrors"
ref="view" ref="view"
/> />
<SurfaceFlingerTraceView <SurfaceFlingerTraceView
v-else-if="showInSurfaceFlingerTraceView(file)" v-else-if="showInSurfaceFlingerTraceView(file) && isShowFileType(file.type)"
:store="store" :store="store"
:file="file" :file="file"
:presentTags="presentTags"
:presentErrors="presentErrors"
ref="view" ref="view"
/> />
<transactionsview <transactionsview
v-else-if="isTransactions(file)" v-else-if="isTransactions(file) && isShowFileType(file.type)"
:trace="file" :trace="file"
ref="view" ref="view"
/> />
<logview <logview
v-else-if="isLog(file)" v-else-if="isLog(file) && isShowFileType(file.type)"
:file="file" :file="file"
ref="view" ref="view"
/> />
<traceview <traceview
v-else-if="showInTraceView(file)" v-else-if="showInTraceView(file) && isShowFileType(file.type)"
:store="store" :store="store"
:file="file" :file="file"
:presentTags="[]"
:presentErrors="[]"
ref="view" ref="view"
/> />
<div v-else> <div v-else>
<h1 class="bad">Unrecognized DataType</h1> <h1 v-if="isShowFileType(file.type)" class="bad">Unrecognized DataType</h1>
</div> </div>
</flat-card> </flat-card>
@@ -150,8 +161,23 @@ export default {
// to component. // to component.
this.$emit('click', e); this.$emit('click', e);
}, },
/** Filter data view files by current show settings */
updateShowFileTypes() {
this.store.showFileTypes = this.dataViewFiles
.filter((file) => file.show)
.map(file => file.type);
}, },
props: ['store', 'file'], /** Expand or collapse data view */
toggleView() {
this.file.show = !this.file.show;
this.updateShowFileTypes();
},
/** Check if data view file should be shown */
isShowFileType(type) {
return this.store.showFileTypes.find(fileType => fileType===type);
},
},
props: ['store', 'file', 'presentTags', 'presentErrors', 'dataViewFiles'],
mixins: [FileType], mixins: [FileType],
components: { components: {
'traceview': TraceView, 'traceview': TraceView,
@@ -170,4 +196,18 @@ export default {
font-size: 4em; font-size: 4em;
color: red; color: red;
} }
.toggle-view-button {
background: none;
color: inherit;
border: none;
font: inherit;
cursor: pointer;
padding-right: 10px;
display: inline-block;
}
.md-title {
display: inline-block;
}
</style> </style>

View File

@@ -43,13 +43,28 @@
{{c.long}} {{c.long}}
</md-tooltip> </md-tooltip>
</div> </div>
<div class="flicker-tags" v-for="transition in transitions" :key="transition">
<Arrow
class="transition-arrow"
:style="{color: transitionArrowColor(transition)}"
/>
<md-tooltip md-direction="right"> {{transitionTooltip(transition)}} </md-tooltip>
</div>
<div class="flicker-tags" v-for="error in errors" :key="error.message">
<Arrow class="error-arrow"/>
<md-tooltip md-direction="right"> Error: {{error.message}} </md-tooltip>
</div>
</span> </span>
</template> </template>
<script> <script>
import Arrow from './components/TagDisplay/Arrow.vue';
import {transitionMap} from './utils/consts.js';
export default { export default {
name: 'DefaultTreeElement', name: 'DefaultTreeElement',
props: ['item', 'simplify-names'], props: ['item', 'simplify-names', 'errors', 'transitions'],
methods: { methods: {
chipClassForChip(c) { chipClassForChip(c) {
return [ return [
@@ -59,6 +74,15 @@ export default {
(c.type?.toString() || c.class?.toString() || 'default'), (c.type?.toString() || c.class?.toString() || 'default'),
]; ];
}, },
transitionArrowColor(transition) {
return transitionMap.get(transition).color;
},
transitionTooltip(transition) {
return transitionMap.get(transition).desc;
},
},
components: {
Arrow,
}, },
}; };
</script> </script>
@@ -100,4 +124,12 @@ span {
flex: 1 1 auto; flex: 1 1 auto;
width: 0; width: 0;
} }
.flicker-tags {
display: inline-block;
}
.error-arrow {
color: red;
}
</style> </style>

View File

@@ -5,7 +5,7 @@
</template> </template>
<script> <script>
import {VueContext} from 'vue-context'; import VueContext from 'vue-context';
export default { export default {
name: 'NodeContextMenu', name: 'NodeContextMenu',

View File

@@ -52,6 +52,15 @@
> >
<div class="nav-content"> <div class="nav-content">
<div class=""> <div class="">
<searchbar
class="search-bar"
v-if="search"
:searchTypes="searchTypes"
:store="store"
:presentTags="Object.freeze(presentTags)"
:presentErrors="Object.freeze(presentErrors)"
:timeline="mergedTimeline.timeline"
/>
<md-toolbar <md-toolbar
md-elevation="0" md-elevation="0"
class="md-transparent"> class="md-transparent">
@@ -66,16 +75,12 @@
</div> </div>
</div> </div>
<div class="active-timeline" v-show="minimized">
<md-field class="seek-timestamp-field">
<label>Search for timestamp</label>
<md-input v-model="searchTimestamp"></md-input>
</md-field>
<md-button <md-button
@click="updateSearchForTimestamp" @click="toggleSearch()"
>Search</md-button> class="drop-search"
>Show/hide search bar</md-button>
<div class="active-timeline" v-show="minimized">
<div <div
class="active-timeline-icon" class="active-timeline-icon"
@click="$refs.navigationTypeSelection.$el @click="$refs.navigationTypeSelection.$el
@@ -148,6 +153,10 @@
{{ seekTime }} {{ seekTime }}
</label> </label>
<timeline <timeline
:store="store"
:flickerMode="flickerMode"
:tags="Object.freeze(tags)"
:errors="Object.freeze(errors)"
:timeline="Object.freeze(minimizedTimeline.timeline)" :timeline="Object.freeze(minimizedTimeline.timeline)"
:selected-index="minimizedTimeline.selectedIndex" :selected-index="minimizedTimeline.selectedIndex"
:scale="scale" :scale="scale"
@@ -271,16 +280,17 @@ import TimelineSelection from './TimelineSelection.vue';
import DraggableDiv from './DraggableDiv.vue'; import DraggableDiv from './DraggableDiv.vue';
import VideoView from './VideoView.vue'; import VideoView from './VideoView.vue';
import MdIconOption from './components/IconSelection/IconSelectOption.vue'; import MdIconOption from './components/IconSelection/IconSelectOption.vue';
import Searchbar from './Searchbar.vue';
import FileType from './mixins/FileType.js'; import FileType from './mixins/FileType.js';
import {NAVIGATION_STYLE} from './utils/consts'; import {NAVIGATION_STYLE} from './utils/consts';
import {TRACE_ICONS} from '@/decode.js'; import {TRACE_ICONS, FILE_TYPES} from '@/decode.js';
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
import {nanos_to_string, string_to_nanos} from './transform.js'; import {nanos_to_string} from './transform.js';
export default { export default {
name: 'overlay', name: 'overlay',
props: ['store'], props: ['store', 'presentTags', 'presentErrors', 'tagAndErrorTraces', 'searchTypes'],
mixins: [FileType], mixins: [FileType],
data() { data() {
return { return {
@@ -301,7 +311,9 @@ export default {
crop: null, crop: null,
cropIntent: null, cropIntent: null,
TRACE_ICONS, TRACE_ICONS,
searchTimestamp: '', search: false,
tags: [],
errors: [],
}; };
}, },
created() { created() {
@@ -388,7 +400,8 @@ export default {
default: default:
const split = this.navigationStyle.split('-'); const split = this.navigationStyle.split('-');
if (split[0] !== NAVIGATION_STYLE.TARGETED) { if (split[0] !== NAVIGATION_STYLE.TARGETED) {
throw new Error('Unexpected navigation type'); console.warn('Unexpected navigation type; fallback to global');
return 'All timelines';
} }
const fileType = split[1]; const fileType = split[1];
@@ -410,7 +423,8 @@ export default {
default: default:
const split = this.navigationStyle.split('-'); const split = this.navigationStyle.split('-');
if (split[0] !== NAVIGATION_STYLE.TARGETED) { if (split[0] !== NAVIGATION_STYLE.TARGETED) {
throw new Error('Unexpected navigation type'); console.warn('Unexpected navigation type; fallback to global');
return 'public';
} }
const fileType = split[1]; const fileType = split[1];
@@ -419,6 +433,8 @@ export default {
} }
}, },
minimizedTimeline() { minimizedTimeline() {
this.updateFlickerMode(this.navigationStyle);
if (this.navigationStyle === NAVIGATION_STYLE.GLOBAL) { if (this.navigationStyle === NAVIGATION_STYLE.GLOBAL) {
return this.mergedTimeline; return this.mergedTimeline;
} }
@@ -436,12 +452,16 @@ export default {
return this.mergedTimeline; return this.mergedTimeline;
} }
if (this.navigationStyle.split('-')[0] === NAVIGATION_STYLE.TARGETED) { if (
this.navigationStyle.split('-').length >= 2
&& this.navigationStyle.split('-')[0] === NAVIGATION_STYLE.TARGETED
) {
return this.$store.state return this.$store.state
.traces[this.navigationStyle.split('-')[1]]; .traces[this.navigationStyle.split('-')[1]];
} }
throw new Error('Unexpected Navigation Style'); console.warn('Unexpected navigation type; fallback to global');
return this.mergedTimeline;
}, },
isCropped() { isCropped() {
return this.crop != null && return this.crop != null &&
@@ -450,6 +470,9 @@ export default {
multipleTraces() { multipleTraces() {
return this.timelineFiles.length > 1; return this.timelineFiles.length > 1;
}, },
flickerMode() {
return this.tags.length>0 || this.errors.length>0;
},
}, },
updated() { updated() {
this.$nextTick(() => { this.$nextTick(() => {
@@ -461,6 +484,9 @@ export default {
}); });
}, },
methods: { methods: {
toggleSearch() {
this.search = !(this.search);
},
emitBottomHeightUpdate() { emitBottomHeightUpdate() {
if (this.$refs.bottomNav) { if (this.$refs.bottomNav) {
const newHeight = this.$refs.bottomNav.$el.clientHeight; const newHeight = this.$refs.bottomNav.$el.clientHeight;
@@ -480,9 +506,10 @@ export default {
timelines.push(file.timeline); timelines.push(file.timeline);
} }
while (true) { var timelineToAdvance = 0;
while (timelineToAdvance !== undefined) {
timelineToAdvance = undefined;
let minTime = Infinity; let minTime = Infinity;
let timelineToAdvance;
for (let i = 0; i < timelines.length; i++) { for (let i = 0; i < timelines.length; i++) {
const timeline = timelines[i]; const timeline = timelines[i];
@@ -608,7 +635,9 @@ export default {
default: default:
const split = this.navigationStyle.split('-'); const split = this.navigationStyle.split('-');
if (split[0] !== NAVIGATION_STYLE.TARGETED) { if (split[0] !== NAVIGATION_STYLE.TARGETED) {
throw new Error('Unexpected navigation type'); console.warn('Unexpected navigation type; fallback to global');
navigationStyleFilter = (f) => true;
break;
} }
const fileType = split[1]; const fileType = split[1];
@@ -618,6 +647,43 @@ export default {
this.$store.commit('setNavigationFilesFilter', navigationStyleFilter); this.$store.commit('setNavigationFilesFilter', navigationStyleFilter);
}, },
updateFlickerMode(style) {
if (style === NAVIGATION_STYLE.GLOBAL ||
style === NAVIGATION_STYLE.CUSTOM) {
this.tags = this.presentTags;
this.errors = this.presentErrors;
} else if (style === NAVIGATION_STYLE.FOCUSED) {
if (this.focusedFile.timeline) {
this.tags = this.getTagTimelineComponents(this.presentTags, this.focusedFile);
this.errors = this.getTagTimelineComponents(this.presentErrors, this.focusedFile);
}
} else if (
style.split('-').length >= 2 &&
style.split('-')[0] === NAVIGATION_STYLE.TARGETED
) {
const file = this.$store.state.traces[style.split('-')[1]];
if (file.timeline) {
this.tags = this.getTagTimelineComponents(this.presentTags, file);
this.errors = this.getTagTimelineComponents(this.presentErrors, file);
}
//Unexpected navigation type or no timeline present in file
} else {
console.warn('Unexpected timeline or navigation type; no flicker mode available');
this.tags = [];
this.errors = [];
}
},
getTagTimelineComponents(items, file) {
if (file.type===FILE_TYPES.SURFACE_FLINGER_TRACE) {
return items.filter(item => item.layerId !== -1);
}
if (file.type===FILE_TYPES.WINDOW_MANAGER_TRACE) {
return items.filter(item => item.taskId !== -1);
}
// if focused file is not one supported by tags/errors
return [];
},
updateVideoOverlayWidth(width) { updateVideoOverlayWidth(width) {
this.videoOverlayExtraWidth = width; this.videoOverlayExtraWidth = width;
}, },
@@ -642,17 +708,6 @@ export default {
clearSelection() { clearSelection() {
this.crop = null; this.crop = null;
}, },
updateSearchForTimestamp() {
if (/^\d+$/.test(this.searchTimestamp)) {
var roundedTimestamp = parseInt(this.searchTimestamp);
} else {
var roundedTimestamp = string_to_nanos(this.searchTimestamp);
}
var closestTimestamp = this.mergedTimeline.timeline.reduce(function(prev, curr) {
return (Math.abs(curr-roundedTimestamp) < Math.abs(prev-roundedTimestamp) ? curr : prev);
});
this.$store.dispatch('updateTimelineTime', parseInt(closestTimestamp));
},
}, },
components: { components: {
'timeline': Timeline, 'timeline': Timeline,
@@ -661,6 +716,7 @@ export default {
'videoview': VideoView, 'videoview': VideoView,
'draggable-div': DraggableDiv, 'draggable-div': DraggableDiv,
'md-icon-option': MdIconOption, 'md-icon-option': MdIconOption,
'searchbar': Searchbar,
}, },
}; };
</script> </script>
@@ -864,4 +920,8 @@ export default {
margin-bottom: 15px; margin-bottom: 15px;
cursor: help; cursor: help;
} }
.drop-search:hover {
background-color: #9af39f;
}
</style> </style>

View File

@@ -0,0 +1,308 @@
<!-- Copyright (C) 2017 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-content class="searchbar">
<div class="search-timestamp" v-if="isTimestampSearch()">
<md-button
class="search-timestamp-button"
@click="updateSearchForTimestamp"
>
Navigate to timestamp
</md-button>
<md-field class="search-input">
<label>Enter timestamp</label>
<md-input v-model="searchInput" @keyup.enter.native="updateSearchForTimestamp" />
</md-field>
</div>
<div class="dropdown-content" v-if="isTagSearch()">
<table>
<tr class="header">
<th style="width: 10%">Global Start</th>
<th style="width: 10%">Global End</th>
<th style="width: 80%">Description</th>
</tr>
<tr v-for="item in filteredTransitionsAndErrors" :key="item">
<td
v-if="isTransition(item)"
class="inline-time"
@click="
setCurrentTimestamp(transitionStart(transitionTags(item.id)))
"
>
<span>{{ transitionTags(item.id)[0].desc }}</span>
</td>
<td
v-if="isTransition(item)"
class="inline-time"
@click="setCurrentTimestamp(transitionEnd(transitionTags(item.id)))"
>
<span>{{ transitionTags(item.id)[1].desc }}</span>
</td>
<td
v-if="isTransition(item)"
class="inline-transition"
:style="{color: transitionTextColor(item.transition)}"
@click="
setCurrentTimestamp(transitionStart(transitionTags(item.id)))
"
>
{{ transitionDesc(item.transition) }}
</td>
<td
v-if="!isTransition(item)"
class="inline-time"
@click="setCurrentTimestamp(item.timestamp)"
>
{{ errorDesc(item.timestamp) }}
</td>
<td v-if="!isTransition(item)">-</td>
<td
v-if="!isTransition(item)"
class="inline-error"
@click="setCurrentTimestamp(item.timestamp)"
>
Error: {{item.message}}
</td>
</tr>
</table>
<md-field class="search-input">
<label
>Filter by transition or error message. Click to navigate to closest
timestamp in active timeline.</label
>
<md-input v-model="searchInput"></md-input>
</md-field>
</div>
<div class="tab-container">
<md-button
v-for="searchType in searchTypes"
:key="searchType"
@click="setSearchType(searchType)"
:class="tabClass(searchType)"
>
{{ searchType }}
</md-button>
</div>
</md-content>
</template>
<script>
import { transitionMap, SEARCH_TYPE } from "./utils/consts";
import { nanos_to_string, string_to_nanos } from "./transform";
const regExpTimestampSearch = new RegExp(/^\d+$/);
export default {
name: "searchbar",
props: ["store", "presentTags", "timeline", "presentErrors", "searchTypes"],
data() {
return {
searchType: SEARCH_TYPE.TIMESTAMP,
searchInput: "",
};
},
methods: {
/** Set search type depending on tab selected */
setSearchType(searchType) {
this.searchType = searchType;
},
/** Set tab class to determine color highlight for active tab */
tabClass(searchType) {
var isActive = (this.searchType === searchType) ? 'active' : 'inactive';
return ['tab', isActive];
},
/** Filter all the tags present in the trace by the searchbar input */
filteredTags() {
var tags = [];
var filter = this.searchInput.toUpperCase();
this.presentTags.forEach((tag) => {
if (tag.transition.includes(filter)) tags.push(tag);
});
return tags;
},
/** Add filtered errors to filtered tags to integrate both into table*/
filteredTagsAndErrors() {
var tagsAndErrors = [...this.filteredTags()];
var filter = this.searchInput.toUpperCase();
this.presentErrors.forEach((error) => {
if (error.message.includes(filter)) tagsAndErrors.push(error);
});
// sort into chronological order
tagsAndErrors.sort((a, b) => (a.timestamp > b.timestamp ? 1 : -1));
return tagsAndErrors;
},
/** Each transition has two tags present
* Isolate the tags for the desire transition
* Add a desc to display the timestamps as strings
*/
transitionTags(id) {
var tags = this.filteredTags().filter((tag) => tag.id === id);
tags.forEach((tag) => {
tag.desc = nanos_to_string(tag.timestamp);
});
return tags;
},
/** Find the start as minimum timestamp in transition tags */
transitionStart(tags) {
var times = tags.map((tag) => tag.timestamp);
return times[0];
},
/** Find the end as maximum timestamp in transition tags */
transitionEnd(tags) {
var times = tags.map((tag) => tag.timestamp);
return times[times.length - 1];
},
/** Upon selecting a start/end tag in the dropdown;
* navigates to that timestamp in the timeline */
setCurrentTimestamp(timestamp) {
this.$store.dispatch("updateTimelineTime", timestamp);
},
/** Colour codes text of transition in dropdown */
transitionTextColor(transition) {
return transitionMap.get(transition).color;
},
/** Displays transition description rather than variable name */
transitionDesc(transition) {
return transitionMap.get(transition).desc;
},
/** Add a desc to display the error timestamps as strings */
errorDesc(timestamp) {
return nanos_to_string(timestamp);
},
/** Navigates to closest timestamp in timeline to search input*/
updateSearchForTimestamp() {
if (regExpTimestampSearch.test(this.searchInput)) {
var roundedTimestamp = parseInt(this.searchInput);
} else {
var roundedTimestamp = string_to_nanos(this.searchInput);
}
var closestTimestamp = this.timeline.reduce(function (prev, curr) {
return Math.abs(curr - roundedTimestamp) <
Math.abs(prev - roundedTimestamp)
? curr
: prev;
});
this.setCurrentTimestamp(closestTimestamp);
},
isTagSearch() {
return this.searchType === SEARCH_TYPE.TAG;
},
isTimestampSearch() {
return this.searchType === SEARCH_TYPE.TIMESTAMP;
},
isTransition(item) {
return item.stacktrace === undefined;
},
},
computed: {
filteredTransitionsAndErrors() {
var ids = [];
return this.filteredTagsAndErrors().filter((item) => {
if (this.isTransition(item) && !ids.includes(item.id)) {
item.transitionStart = true;
ids.push(item.id);
}
return !this.isTransition(item) || this.isTransition(item) && item.transitionStart;
});
},
},
};
</script>
<style>
.searchbar {
background-color: rgb(250, 243, 233) !important;
top: 0;
left: 0;
right: 0;
width: 100%;
margin-left: auto;
margin-right: auto;
bottom: 1px;
}
.tab-container {
padding: 0px 20px 0px 20px;
}
.tab.active {
background-color: rgb(236, 222, 202);
}
.tab.inactive {
background-color: rgb(250, 243, 233);
}
.search-timestamp {
padding: 5px 20px 0px 20px;
display: block;
}
.search-timestamp-button {
left: 0;
}
.dropdown-content {
padding: 5px 20px 0px 20px;
display: block;
}
.dropdown-content table {
overflow-y: scroll;
height: 150px;
display: block;
}
.dropdown-content table td {
padding: 5px;
}
.dropdown-content table th {
text-align: left;
padding: 5px;
}
.inline-time:hover {
background: #9af39f;
cursor: pointer;
}
.inline-transition {
font-weight: bold;
}
.inline-transition:hover {
background: #9af39f;
cursor: pointer;
}
.inline-error {
font-weight: bold;
color: red;
}
.inline-error:hover {
background: #9af39f;
cursor: pointer;
}
</style>

View File

@@ -14,15 +14,21 @@
--> -->
<template> <template>
<TraceView :store="store" :file="file" :summarizer="summarizer" /> <TraceView
:store="store"
:file="file"
:summarizer="summarizer"
:presentTags="presentTags"
:presentErrors="presentErrors"
/>
</template> </template>
<script> <script>
import TraceView from '@/TraceView.vue'; import TraceView from '@/TraceView.vue';
export default { export default {
name: 'WindowManagerTraceView', name: 'SurfaceFlingerTraceView',
props: ['store', 'file'], props: ['store', 'file', 'presentTags', 'presentErrors'],
components: { components: {
TraceView, TraceView,
}, },

View File

@@ -13,6 +13,22 @@
limitations under the License. limitations under the License.
--> -->
<template> <template>
<div class="timeline-container">
<div class="tag-timeline" v-if="flickerMode" :style="maxOverlap">
<transition-container
class="container"
v-for="transition in timelineTransitions"
:key="transition.type"
:startPos="transition.startPos"
:startTime="transition.startTime"
:endTime="transition.endTime"
:width="transition.width"
:color="transition.color"
:overlap="transition.overlap"
:tooltip="transition.tooltip"
:store="store"
/>
</div>
<svg <svg
width="100%" width="100%"
height="20" height="20"
@@ -39,15 +55,29 @@
:rx="corner" :rx="corner"
class="point selected" class="point selected"
/> />
<line
v-for="error in errorPositions"
:key="error"
:x1="`${error}%`"
:x2="`${error}%`"
y1="0"
y2="18px"
class="error"
/>
</svg> </svg>
</div>
</template> </template>
<script> <script>
import TimelineMixin from "./mixins/Timeline.js"; import TimelineMixin from "./mixins/Timeline.js";
import TransitionContainer from './components/TagDisplay/TransitionContainer.vue';
export default { export default {
name: "timeline", name: "timeline",
// TODO: Add indication of trim, at least for collasped timeline // TODO: Add indication of trim, at least for collasped timeline
props: ["selectedIndex", "crop", "disabled"], components: {
'transition-container': TransitionContainer,
},
props: ["selectedIndex", "crop", "disabled", "store"],
data() { data() {
return { return {
pointHeight: 15, pointHeight: 15,
@@ -55,7 +85,6 @@ export default {
}; };
}, },
mixins: [TimelineMixin], mixins: [TimelineMixin],
methods: {},
computed: { computed: {
timestamps() { timestamps() {
if (this.timeline.length == 1) { if (this.timeline.length == 1) {
@@ -66,10 +95,35 @@ export default {
selected() { selected() {
return this.timeline[this.selectedIndex]; return this.timeline[this.selectedIndex];
}, },
maxOverlap() {
if (!this.timelineTransitions) {
return {
marginTop: '0px',
}
}
var overlaps = [];
for (const transition in this.timelineTransitions) {
overlaps.push(this.timelineTransitions[transition].overlap);
}
return {
marginTop: (Math.max(...overlaps)+1)*10 + 'px',
}
},
} }
}; };
</script> </script>
<style scoped> <style scoped>
.timeline-container {
width: 100%;
}
.container:hover {
cursor: pointer;
}
.tag-timeline {
width: 100%;
position: relative;
height: 10px;
}
.timeline-svg .point { .timeline-svg .point {
cursor: pointer; cursor: pointer;
} }
@@ -78,9 +132,13 @@ export default {
cursor: not-allowed; cursor: not-allowed;
} }
.timeline-svg:not(.disabled) .point.selected { .timeline-svg:not(.disabled) .point.selected {
fill: rgb(240, 59, 59); fill: #b2f6faff;
} }
.timeline-svg.disabled .point.selected { .timeline-svg.disabled .point.selected {
fill: rgba(240, 59, 59, 0.596); fill: rgba(240, 59, 59, 0.596);
} }
.error {
stroke: rgb(255, 0, 0);
stroke-width: 2px;
}
</style> </style>

View File

@@ -42,6 +42,7 @@
</md-checkbox> </md-checkbox>
<md-checkbox v-model="store.onlyVisible">Only visible</md-checkbox> <md-checkbox v-model="store.onlyVisible">Only visible</md-checkbox>
<md-checkbox v-model="store.flattened">Flat</md-checkbox> <md-checkbox v-model="store.flattened">Flat</md-checkbox>
<md-checkbox v-if="hasTagsOrErrors" v-model="store.flickerTraceView">Flicker</md-checkbox>
<md-field md-inline class="filter"> <md-field md-inline class="filter">
<label>Filter...</label> <label>Filter...</label>
<md-input v-model="hierarchyPropertyFilterString"></md-input> <md-input v-model="hierarchyPropertyFilterString"></md-input>
@@ -55,6 +56,10 @@
:selected="hierarchySelected" :selected="hierarchySelected"
:filter="hierarchyFilter" :filter="hierarchyFilter"
:flattened="store.flattened" :flattened="store.flattened"
:onlyVisible="store.onlyVisible"
:flickerTraceView="store.flickerTraceView"
:presentTags="presentTags"
:presentErrors="presentErrors"
:items-clickable="true" :items-clickable="true"
:useGlobalCollapsedState="true" :useGlobalCollapsedState="true"
:simplify-names="store.simplifyNames" :simplify-names="store.simplifyNames"
@@ -165,7 +170,7 @@ function findEntryInTree(tree, id) {
export default { export default {
name: 'traceview', name: 'traceview',
props: ['store', 'file', 'summarizer'], props: ['store', 'file', 'summarizer', 'presentTags', 'presentErrors'],
data() { data() {
return { return {
propertyFilterString: '', propertyFilterString: '',
@@ -306,10 +311,30 @@ export default {
return prevEntry; return prevEntry;
}, },
/** Performs check for id match between entry and present tags/errors
* must be carried out for every present tag/error
*/
matchItems(flickerItems, entryItem) {
var match = false;
flickerItems.forEach(flickerItem => {
if (flickerItem.taskId===entryItem.taskId || flickerItem.layerId===entryItem.id) {
match = true;
}
});
return match;
},
/** Returns check for id match between entry and present tags/errors */
isEntryTagMatch(entryItem) {
return this.matchItems(this.presentTags, entryItem) || this.matchItems(this.presentErrors, entryItem);
},
}, },
created() { created() {
this.setData(this.file.data[this.file.selectedIndex ?? 0]); this.setData(this.file.data[this.file.selectedIndex ?? 0]);
}, },
destroyed() {
this.store.flickerTraceView = false;
},
watch: { watch: {
selectedIndex() { selectedIndex() {
this.setData(this.file.data[this.file.selectedIndex ?? 0]); this.setData(this.file.data[this.file.selectedIndex ?? 0]);
@@ -337,9 +362,12 @@ export default {
hierarchyFilter() { hierarchyFilter() {
const hierarchyPropertyFilter = const hierarchyPropertyFilter =
getFilter(this.hierarchyPropertyFilterString); getFilter(this.hierarchyPropertyFilterString);
return this.store.onlyVisible ? (c) => { var fil = this.store.onlyVisible ? (c) => {
return c.isVisible && hierarchyPropertyFilter(c); return c.isVisible && hierarchyPropertyFilter(c);
} : hierarchyPropertyFilter; } : hierarchyPropertyFilter;
return this.store.flickerTraceView ? (c) => {
return this.isEntryTagMatch(c);
} : fil;
}, },
propertyFilter() { propertyFilter() {
return getFilter(this.propertyFilterString); return getFilter(this.propertyFilterString);
@@ -363,6 +391,9 @@ export default {
return summary; return summary;
}, },
hasTagsOrErrors() {
return this.presentTags.length > 0 || this.presentErrors.length > 0;
},
}, },
components: { components: {
'tree-view': TreeView, 'tree-view': TreeView,

View File

@@ -31,7 +31,7 @@
<button <button
class="toggle-tree-btn" class="toggle-tree-btn"
@click="toggleTree" @click="toggleTree"
v-if="!isLeaf" v-if="!isLeaf && !flattened"
v-on:click.stop v-on:click.stop
> >
<i aria-hidden="true" class="md-icon md-theme-default material-icons"> <i aria-hidden="true" class="md-icon md-theme-default material-icons">
@@ -50,7 +50,12 @@
/> />
</div> </div>
<div v-else> <div v-else>
<DefaultTreeElement :item="item" :simplify-names="simplifyNames"/> <DefaultTreeElement
:item="item"
:simplify-names="simplifyNames"
:errors="errors"
:transitions="transitions"
/>
</div> </div>
</div> </div>
<div v-show="isCollapsed"> <div v-show="isCollapsed">
@@ -78,7 +83,7 @@
v-on:collapseAllOtherNodes="collapseAllOtherNodes" v-on:collapseAllOtherNodes="collapseAllOtherNodes"
/> />
<div class="children" v-if="children" v-show="!isCollapsed"> <div class="children" v-if="children" v-show="!isCollapsed" :style="childrenIndentation()">
<tree-view <tree-view
v-for="(c,i) in children" v-for="(c,i) in children"
:item="c" :item="c"
@@ -87,7 +92,11 @@
:key="i" :key="i"
:filter="childFilter(c)" :filter="childFilter(c)"
:flattened="flattened" :flattened="flattened"
:onlyVisible="onlyVisible"
:simplify-names="simplifyNames" :simplify-names="simplifyNames"
:flickerTraceView="flickerTraceView"
:presentTags="currentTags"
:presentErrors="currentErrors"
:force-flattened="applyingFlattened" :force-flattened="applyingFlattened"
v-show="filterMatches(c)" v-show="filterMatches(c)"
:items-clickable="itemsClickable" :items-clickable="itemsClickable"
@@ -112,7 +121,6 @@
<script> <script>
import DefaultTreeElement from './DefaultTreeElement.vue'; import DefaultTreeElement from './DefaultTreeElement.vue';
import NodeContextMenu from './NodeContextMenu.vue'; import NodeContextMenu from './NodeContextMenu.vue';
import {DiffType} from './utils/diff.js'; import {DiffType} from './utils/diff.js';
/* in px, must be kept in sync with css, maybe find a better solution... */ /* in px, must be kept in sync with css, maybe find a better solution... */
@@ -141,6 +149,10 @@ export default {
'useGlobalCollapsedState', 'useGlobalCollapsedState',
// Custom view to use to render the elements in the tree view // Custom view to use to render the elements in the tree view
'elementView', 'elementView',
'onlyVisible',
'flickerTraceView',
'presentTags',
'presentErrors',
], ],
data() { data() {
const isCollapsedByDefault = this.collapse ?? false; const isCollapsedByDefault = this.collapse ?? false;
@@ -161,6 +173,10 @@ export default {
[DiffType.MODIFIED]: '.', [DiffType.MODIFIED]: '.',
[DiffType.MOVED]: '.', [DiffType.MOVED]: '.',
}, },
currentTags: [],
currentErrors: [],
transitions: [],
errors: [],
}; };
}, },
watch: { watch: {
@@ -177,6 +193,10 @@ export default {
}, },
currentTimestamp() { currentTimestamp() {
// Update anything that is required to change when time changes. // Update anything that is required to change when time changes.
this.currentTags = this.getCurrentItems(this.presentTags);
this.currentErrors = this.getCurrentItems(this.presentErrors);
this.transitions = this.getCurrentTransitions();
this.errors = this.getCurrentErrorTags();
this.updateCollapsedDiffClass(); this.updateCollapsedDiffClass();
}, },
isSelected(isSelected) { isSelected(isSelected) {
@@ -277,7 +297,7 @@ export default {
} }
if (!this.isLeaf && e.detail % 2 === 0) { if (!this.isLeaf && e.detail % 2 === 0) {
// Double click collaspable node // Double click collapsable node
this.toggleTree(); this.toggleTree();
} else { } else {
this.select(); this.select();
@@ -408,6 +428,64 @@ export default {
child.closeAllChildrenContextMenus(); child.closeAllChildrenContextMenus();
} }
}, },
childrenIndentation() {
if (this.flattened || this.forceFlattened) {
return {
marginLeft: '0px',
paddingLeft: '0px',
marginTop: '0px',
}
} else {
//Aligns border with collapse arrows
return {
marginLeft: '12px',
paddingLeft: '11px',
borderLeft: '1px solid rgb(238, 238, 238)',
marginTop: '0px',
}
}
},
/** Check if tag/error id matches entry id */
isIdMatch(a, b) {
return a.taskId===b.taskId || a.layerId===b.id;
},
/** Performs check for id match between entry and present tags/errors
* exits once match has been found
*/
matchItems(flickerItems) {
var match = false;
flickerItems.every(flickerItem => {
if (this.isIdMatch(flickerItem, this.item)) {
match = true;
return false;
}
});
return match;
},
/** Returns check for id match between entry and present tags/errors */
isEntryTagMatch() {
return this.matchItems(this.currentTags) || this.matchItems(this.currentErrors);
},
getCurrentItems(items) {
if (!items) return [];
else return items.filter(item => item.timestamp===this.currentTimestamp);
},
getCurrentTransitions() {
var transitions = [];
var ids = [];
this.currentTags.forEach(tag => {
if (!ids.includes(tag.id) && this.isIdMatch(tag, this.item)) {
transitions.push(tag.transition);
ids.push(tag.id);
}
});
return transitions;
},
getCurrentErrorTags() {
return this.currentErrors.filter(error => this.isIdMatch(error, this.item));
},
}, },
computed: { computed: {
hasDiff() { hasDiff() {
@@ -464,11 +542,20 @@ export default {
return this.initialDepth || 0; return this.initialDepth || 0;
}, },
nodeOffsetStyle() { nodeOffsetStyle() {
const offest = levelOffset * (this.depth + this.isLeaf) + 'px'; const offset = levelOffset * (this.depth + this.isLeaf) + 'px';
var display = "";
if (!this.item.timestamp
&& this.flattened
&& (this.onlyVisible && !this.item.isVisible ||
this.flickerTraceView && !this.isEntryTagMatch())) {
display = 'none';
}
return { return {
marginLeft: '-' + offest, marginLeft: '-' + offset,
paddingLeft: offest, paddingLeft: offset,
display: display,
}; };
}, },
}, },
@@ -567,14 +654,6 @@ export default {
color: white; color: white;
} }
.children {
/* Aligns border with collapse arrows */
margin-left: 12px;
padding-left: 11px;
border-left: 1px solid rgb(238, 238, 238);
margin-top: 0px;
}
.tree-view .node.child-selected + .children { .tree-view .node.child-selected + .children {
border-left: 1px solid #b4b4b4; border-left: 1px solid #b4b4b4;
} }

View File

@@ -14,7 +14,13 @@
--> -->
<template> <template>
<TraceView :store="store" :file="file" :summarizer="summarizer" /> <TraceView
:store="store"
:file="file"
:summarizer="summarizer"
:presentTags="presentTags"
:presentErrors="presentErrors"
/>
</template> </template>
<script> <script>
@@ -22,7 +28,7 @@ import TraceView from "@/TraceView.vue"
export default { export default {
name: "WindowManagerTraceView", name: "WindowManagerTraceView",
props: ["store", "file"], props: ["store", "file", "presentTags", "presentErrors"],
components: { components: {
TraceView TraceView
}, },

View File

@@ -0,0 +1,33 @@
<!-- Copyright (C) 2020 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>
<div class="arrow"/>
</template>
<script>
export default {
name: 'arrow',
};
</script>
<style scoped>
.arrow {
display: inline-block;
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 10px solid;
}
</style>

View File

@@ -0,0 +1,113 @@
<!-- Copyright (C) 2020 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>
<div class="transition-container" :style="transitionStyle" @click="handleTransitionClick()">
<md-tooltip md-direction="left"> {{tooltip}} </md-tooltip>
<arrow class="arrow-start" :style="transitionComponentColor"/>
<div class="connector" :style="transitionComponentColor"/>
<arrow class="arrow-end" :style="transitionComponentColor"/>
</div>
</template>
<script>
import Arrow from './Arrow.vue';
import {LocalStore} from '../../localstore.js';
var transitionCount = false;
export default {
name: 'transition-container',
components: {
'arrow': Arrow,
},
props: {
'width': {
type: Number,
},
'startPos': {
type: Number,
},
'startTime': {
type: Number,
},
'endTime': {
type: Number,
},
'color': {
type: String,
},
'overlap': {
type: Number,
},
'tooltip': {
type: String,
},
'store': {
type: LocalStore,
},
},
methods: {
handleTransitionClick() {
if (transitionCount) {
this.$store.dispatch('updateTimelineTime', this.startTime);
transitionCount = false;
} else {
this.$store.dispatch('updateTimelineTime', this.endTime);
transitionCount = true;
}
},
},
computed: {
transitionStyle() {
return {
width: this.width + '%',
left: this.startPos + '%',
bottom: this.overlap * 100 + '%',
}
},
transitionComponentColor() {
return {
borderTopColor: this.color,
}
},
},
};
</script>
<style scoped>
.transition-container {
position: absolute;
height: 15px;
display: inline-flex;
}
.arrow-start {
position: absolute;
left: 0%;
}
.arrow-end {
position: absolute;
right: 0%;
}
.connector {
position: absolute;
display: inline-block;
width: auto;
height: 9px;
left: 5px;
right: 5px;
border-top: 1px solid;
}
</style>

View File

@@ -26,6 +26,8 @@ import jsonProtoDefsWl from 'WaylandSafePath/waylandtrace.proto';
import jsonProtoDefsSysUi from 'frameworks/base/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto'; import jsonProtoDefsSysUi from 'frameworks/base/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto';
import jsonProtoDefsLauncher from 'packages/apps/Launcher3/protos/launcher_trace_file.proto'; import jsonProtoDefsLauncher from 'packages/apps/Launcher3/protos/launcher_trace_file.proto';
import jsonProtoDefsIme from 'frameworks/base/core/proto/android/view/inputmethod/inputmethodeditortrace.proto'; import jsonProtoDefsIme from 'frameworks/base/core/proto/android/view/inputmethod/inputmethodeditortrace.proto';
import jsonProtoDefsTags from 'platform_testing/libraries/flicker/src/com/android/server/wm/proto/tags.proto';
import jsonProtoDefsErrors from 'platform_testing/libraries/flicker/src/com/android/server/wm/proto/errors.proto';
import protobuf from 'protobufjs'; import protobuf from 'protobufjs';
import {transform_accessibility_trace} from './transform_accessibility.js'; import {transform_accessibility_trace} from './transform_accessibility.js';
import {transform_transaction_trace} from './transform_transaction.js'; import {transform_transaction_trace} from './transform_transaction.js';
@@ -53,6 +55,9 @@ import SurfaceFlingerDump from '@/dumps/SurfaceFlinger.ts';
import WindowManagerDump from '@/dumps/WindowManager.ts'; import WindowManagerDump from '@/dumps/WindowManager.ts';
import WaylandDump from '@/dumps/Wayland.ts'; import WaylandDump from '@/dumps/Wayland.ts';
import TagTrace from '@/traces/TraceTag.ts';
import ErrorTrace from '@/traces/TraceError.ts';
const AccessibilityTraceMessage = lookup_type(jsonProtoDefsAccessibility, 'com.android.server.accessibility.AccessibilityTraceFileProto'); const AccessibilityTraceMessage = lookup_type(jsonProtoDefsAccessibility, 'com.android.server.accessibility.AccessibilityTraceFileProto');
const WmTraceMessage = lookup_type(jsonProtoDefsWm, 'com.android.server.wm.WindowManagerTraceFileProto'); const WmTraceMessage = lookup_type(jsonProtoDefsWm, 'com.android.server.wm.WindowManagerTraceFileProto');
const WmDumpMessage = lookup_type(jsonProtoDefsWm, 'com.android.server.wm.WindowManagerServiceDumpProto'); const WmDumpMessage = lookup_type(jsonProtoDefsWm, 'com.android.server.wm.WindowManagerServiceDumpProto');
@@ -67,6 +72,8 @@ const LauncherTraceMessage = lookup_type(jsonProtoDefsLauncher, 'com.android.lau
const InputMethodClientsTraceMessage = lookup_type(jsonProtoDefsIme, 'android.view.inputmethod.InputMethodClientsTraceFileProto'); const InputMethodClientsTraceMessage = lookup_type(jsonProtoDefsIme, 'android.view.inputmethod.InputMethodClientsTraceFileProto');
const InputMethodServiceTraceMessage = lookup_type(jsonProtoDefsIme, 'android.view.inputmethod.InputMethodServiceTraceFileProto'); const InputMethodServiceTraceMessage = lookup_type(jsonProtoDefsIme, 'android.view.inputmethod.InputMethodServiceTraceFileProto');
const InputMethodManagerServiceTraceMessage = lookup_type(jsonProtoDefsIme, 'android.view.inputmethod.InputMethodManagerServiceTraceFileProto'); const InputMethodManagerServiceTraceMessage = lookup_type(jsonProtoDefsIme, 'android.view.inputmethod.InputMethodManagerServiceTraceFileProto');
const TagTraceMessage = lookup_type(jsonProtoDefsTags, 'com.android.server.wm.flicker.FlickerTagTraceProto');
const ErrorTraceMessage = lookup_type(jsonProtoDefsErrors, 'com.android.server.wm.flicker.FlickerErrorTraceProto');
const ACCESSIBILITY_MAGIC_NUMBER = [0x09, 0x41, 0x31, 0x31, 0x59, 0x54, 0x52, 0x41, 0x43]; // .A11YTRAC const ACCESSIBILITY_MAGIC_NUMBER = [0x09, 0x41, 0x31, 0x31, 0x59, 0x54, 0x52, 0x41, 0x43]; // .A11YTRAC
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
@@ -79,6 +86,8 @@ const LAUNCHER_MAGIC_NUMBER = [0x09, 0x4C, 0x4E, 0x43, 0x48, 0x52, 0x54, 0x52, 0
const IMC_TRACE_MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x43, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMCTRACE const IMC_TRACE_MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x43, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMCTRACE
const IMS_TRACE_MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x53, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMSTRACE const IMS_TRACE_MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x53, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMSTRACE
const IMM_TRACE_MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x4d, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMMTRACE const IMM_TRACE_MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x4d, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMMTRACE
const TAG_TRACE_MAGIC_NUMBER = [0x09, 0x54, 0x41, 0x47, 0x54, 0x52, 0x41, 0x43, 0x45]; //.TAGTRACE
const ERROR_TRACE_MAGIC_NUMBER = [0x09, 0x45, 0x52, 0x52, 0x54, 0x52, 0x41, 0x43, 0x45]; //.ERRORTRACE
const FILE_TYPES = Object.freeze({ const FILE_TYPES = Object.freeze({
ACCESSIBILITY_TRACE: 'AccessibilityTrace', ACCESSIBILITY_TRACE: 'AccessibilityTrace',
@@ -96,6 +105,8 @@ const FILE_TYPES = Object.freeze({
IME_TRACE_CLIENTS: 'ImeTraceClients', IME_TRACE_CLIENTS: 'ImeTraceClients',
IME_TRACE_SERVICE: 'ImeTrace InputMethodService', IME_TRACE_SERVICE: 'ImeTrace InputMethodService',
IME_TRACE_MANAGERSERVICE: 'ImeTrace InputMethodManagerService', IME_TRACE_MANAGERSERVICE: 'ImeTrace InputMethodManagerService',
TAG_TRACE: 'TagTrace',
ERROR_TRACE: 'ErrorTrace',
}); });
const WINDOW_MANAGER_ICON = 'view_compact'; const WINDOW_MANAGER_ICON = 'view_compact';
@@ -108,6 +119,8 @@ const SYSTEM_UI_ICON = 'filter_none';
const LAUNCHER_ICON = 'filter_none'; const LAUNCHER_ICON = 'filter_none';
const IME_ICON = 'keyboard'; const IME_ICON = 'keyboard';
const ACCESSIBILITY_ICON = 'filter_none'; const ACCESSIBILITY_ICON = 'filter_none';
const TAG_ICON = 'details';
const TRACE_ERROR_ICON = 'warning';
const FILE_ICONS = { const FILE_ICONS = {
[FILE_TYPES.ACCESSIBILITY_TRACE]: ACCESSIBILITY_ICON, [FILE_TYPES.ACCESSIBILITY_TRACE]: ACCESSIBILITY_ICON,
@@ -125,6 +138,8 @@ const FILE_ICONS = {
[FILE_TYPES.IME_TRACE_CLIENTS]: IME_ICON, [FILE_TYPES.IME_TRACE_CLIENTS]: IME_ICON,
[FILE_TYPES.IME_TRACE_SERVICE]: IME_ICON, [FILE_TYPES.IME_TRACE_SERVICE]: IME_ICON,
[FILE_TYPES.IME_TRACE_MANAGERSERVICE]: IME_ICON, [FILE_TYPES.IME_TRACE_MANAGERSERVICE]: IME_ICON,
[FILE_TYPES.TAG_TRACE]: TAG_ICON,
[FILE_TYPES.ERROR_TRACE]: TRACE_ERROR_ICON,
}; };
function oneOf(dataType) { function oneOf(dataType) {
@@ -144,6 +159,8 @@ const TRACE_TYPES = Object.freeze({
IME_CLIENTS: 'ImeTrace Clients', IME_CLIENTS: 'ImeTrace Clients',
IME_SERVICE: 'ImeTrace InputMethodService', IME_SERVICE: 'ImeTrace InputMethodService',
IME_MANAGERSERVICE: 'ImeTrace InputMethodManagerService', IME_MANAGERSERVICE: 'ImeTrace InputMethodManagerService',
TAG: 'TagTrace',
ERROR: 'ErrorTrace',
}); });
const TRACE_INFO = { const TRACE_INFO = {
@@ -221,6 +238,18 @@ const TRACE_INFO = {
files: [oneOf(FILE_TYPES.IME_TRACE_MANAGERSERVICE)], files: [oneOf(FILE_TYPES.IME_TRACE_MANAGERSERVICE)],
constructor: ImeTraceManagerService, constructor: ImeTraceManagerService,
}, },
[TRACE_TYPES.TAG]: {
name: 'Tag',
icon: TAG_ICON,
files: [oneOf(FILE_TYPES.TAG_TRACE)],
constructor: TagTrace,
},
[TRACE_TYPES.ERROR]: {
name: 'Error',
icon: TRACE_ERROR_ICON,
files: [oneOf(FILE_TYPES.ERROR_TRACE)],
constructor: ErrorTrace,
},
}; };
const DUMP_TYPES = Object.freeze({ const DUMP_TYPES = Object.freeze({
@@ -262,6 +291,8 @@ export const TRACE_ICONS = {
[TRACE_TYPES.IME_CLIENTS]: IME_ICON, [TRACE_TYPES.IME_CLIENTS]: IME_ICON,
[TRACE_TYPES.IME_SERVICE]: IME_ICON, [TRACE_TYPES.IME_SERVICE]: IME_ICON,
[TRACE_TYPES.IME_MANAGERSERVICE]: IME_ICON, [TRACE_TYPES.IME_MANAGERSERVICE]: IME_ICON,
[TRACE_TYPES.TAG_TRACE]: TAG_ICON,
[TRACE_TYPES.ERROR_TRACE]: TRACE_ERROR_ICON,
[DUMP_TYPES.WINDOW_MANAGER]: WINDOW_MANAGER_ICON, [DUMP_TYPES.WINDOW_MANAGER]: WINDOW_MANAGER_ICON,
[DUMP_TYPES.SURFACE_FLINGER]: SURFACE_FLINGER_ICON, [DUMP_TYPES.SURFACE_FLINGER]: SURFACE_FLINGER_ICON,
@@ -275,7 +306,7 @@ const FILE_DECODERS = {
decoder: protoDecoder, decoder: protoDecoder,
decoderParams: { decoderParams: {
type: FILE_TYPES.ACCESSIBILITY_TRACE, type: FILE_TYPES.ACCESSIBILITY_TRACE,
protoType: AccessibilityTraceMessage, objTypeProto: AccessibilityTraceMessage,
transform: transform_accessibility_trace, transform: transform_accessibility_trace,
timeline: true, timeline: true,
}, },
@@ -285,7 +316,7 @@ const FILE_DECODERS = {
decoder: protoDecoder, decoder: protoDecoder,
decoderParams: { decoderParams: {
type: FILE_TYPES.WINDOW_MANAGER_TRACE, type: FILE_TYPES.WINDOW_MANAGER_TRACE,
protoType: WmTraceMessage, objTypeProto: WmTraceMessage,
transform: WindowManagerTrace.fromProto, transform: WindowManagerTrace.fromProto,
timeline: true, timeline: true,
}, },
@@ -296,7 +327,7 @@ const FILE_DECODERS = {
decoderParams: { decoderParams: {
type: FILE_TYPES.SURFACE_FLINGER_TRACE, type: FILE_TYPES.SURFACE_FLINGER_TRACE,
mime: 'application/octet-stream', mime: 'application/octet-stream',
protoType: SfTraceMessage, objTypeProto: SfTraceMessage,
transform: SurfaceFlingerTrace.fromProto, transform: SurfaceFlingerTrace.fromProto,
timeline: true, timeline: true,
}, },
@@ -307,7 +338,7 @@ const FILE_DECODERS = {
decoderParams: { decoderParams: {
type: FILE_TYPES.WAYLAND_TRACE, type: FILE_TYPES.WAYLAND_TRACE,
mime: 'application/octet-stream', mime: 'application/octet-stream',
protoType: WaylandTraceMessage, objTypeProto: WaylandTraceMessage,
transform: transform_wayland_trace, transform: transform_wayland_trace,
timeline: true, timeline: true,
}, },
@@ -318,8 +349,8 @@ const FILE_DECODERS = {
decoderParams: { decoderParams: {
type: FILE_TYPES.SURFACE_FLINGER_DUMP, type: FILE_TYPES.SURFACE_FLINGER_DUMP,
mime: 'application/octet-stream', mime: 'application/octet-stream',
protoType: SfDumpMessage, objTypeProto: [SfDumpMessage, SfTraceMessage],
transform: SurfaceFlingerDump.fromProto, transform: [SurfaceFlingerDump.fromProto, SurfaceFlingerTrace.fromProto],
timeline: true, timeline: true,
}, },
}, },
@@ -329,7 +360,7 @@ const FILE_DECODERS = {
decoderParams: { decoderParams: {
type: FILE_TYPES.WINDOW_MANAGER_DUMP, type: FILE_TYPES.WINDOW_MANAGER_DUMP,
mime: 'application/octet-stream', mime: 'application/octet-stream',
protoType: WmDumpMessage, objTypeProto: WmDumpMessage,
transform: WindowManagerDump.fromProto, transform: WindowManagerDump.fromProto,
timeline: true, timeline: true,
}, },
@@ -340,7 +371,7 @@ const FILE_DECODERS = {
decoderParams: { decoderParams: {
type: FILE_TYPES.WAYLAND_DUMP, type: FILE_TYPES.WAYLAND_DUMP,
mime: 'application/octet-stream', mime: 'application/octet-stream',
protoType: WaylandDumpMessage, objTypeProto: WaylandDumpMessage,
transform: transform_wl_outputstate, transform: transform_wl_outputstate,
timeline: true, timeline: true,
}, },
@@ -360,7 +391,7 @@ const FILE_DECODERS = {
decoderParams: { decoderParams: {
type: FILE_TYPES.TRANSACTIONS_TRACE, type: FILE_TYPES.TRANSACTIONS_TRACE,
mime: 'application/octet-stream', mime: 'application/octet-stream',
protoType: SfTransactionTraceMessage, objTypeProto: SfTransactionTraceMessage,
transform: transform_transaction_trace, transform: transform_transaction_trace,
timeline: true, timeline: true,
}, },
@@ -371,7 +402,7 @@ const FILE_DECODERS = {
decoderParams: { decoderParams: {
type: FILE_TYPES.PROTO_LOG, type: FILE_TYPES.PROTO_LOG,
mime: 'application/octet-stream', mime: 'application/octet-stream',
protoType: ProtoLogMessage, objTypeProto: ProtoLogMessage,
transform: transformProtolog, transform: transformProtolog,
timeline: true, timeline: true,
}, },
@@ -382,7 +413,7 @@ const FILE_DECODERS = {
decoderParams: { decoderParams: {
type: FILE_TYPES.SYSTEM_UI, type: FILE_TYPES.SYSTEM_UI,
mime: 'application/octet-stream', mime: 'application/octet-stream',
protoType: SystemUiTraceMessage, objTypeProto: SystemUiTraceMessage,
transform: transform_sysui_trace, transform: transform_sysui_trace,
timeline: true, timeline: true,
}, },
@@ -393,7 +424,7 @@ const FILE_DECODERS = {
decoderParams: { decoderParams: {
type: FILE_TYPES.LAUNCHER, type: FILE_TYPES.LAUNCHER,
mime: 'application/octet-stream', mime: 'application/octet-stream',
protoType: LauncherTraceMessage, objTypeProto: LauncherTraceMessage,
transform: transform_launcher_trace, transform: transform_launcher_trace,
timeline: true, timeline: true,
}, },
@@ -404,7 +435,7 @@ const FILE_DECODERS = {
decoderParams: { decoderParams: {
type: FILE_TYPES.IME_TRACE_CLIENTS, type: FILE_TYPES.IME_TRACE_CLIENTS,
mime: 'application/octet-stream', mime: 'application/octet-stream',
protoType: InputMethodClientsTraceMessage, objTypeProto: InputMethodClientsTraceMessage,
transform: transform_ime_trace_clients, transform: transform_ime_trace_clients,
timeline: true, timeline: true,
}, },
@@ -415,7 +446,7 @@ const FILE_DECODERS = {
decoderParams: { decoderParams: {
type: FILE_TYPES.IME_TRACE_SERVICE, type: FILE_TYPES.IME_TRACE_SERVICE,
mime: 'application/octet-stream', mime: 'application/octet-stream',
protoType: InputMethodServiceTraceMessage, objTypeProto: InputMethodServiceTraceMessage,
transform: transform_ime_trace_service, transform: transform_ime_trace_service,
timeline: true, timeline: true,
}, },
@@ -426,11 +457,31 @@ const FILE_DECODERS = {
decoderParams: { decoderParams: {
type: FILE_TYPES.IME_TRACE_MANAGERSERVICE, type: FILE_TYPES.IME_TRACE_MANAGERSERVICE,
mime: 'application/octet-stream', mime: 'application/octet-stream',
protoType: InputMethodManagerServiceTraceMessage, objTypeProto: InputMethodManagerServiceTraceMessage,
transform: transform_ime_trace_managerservice, transform: transform_ime_trace_managerservice,
timeline: true, timeline: true,
}, },
}, },
[FILE_TYPES.TAG_TRACE]: {
name: 'Tag trace',
decoder: protoDecoder,
decoderParams: {
type: FILE_TYPES.TAG_TRACE,
objTypeProto: TagTraceMessage,
transform: TagTrace.fromProto,
timeline: true,
},
},
[FILE_TYPES.ERROR_TRACE]: {
name: 'Error trace',
decoder: protoDecoder,
decoderParams: {
type: FILE_TYPES.ERROR_TRACE,
objTypeProto: ErrorTraceMessage,
transform: ErrorTrace.fromProto,
timeline: true,
},
},
}; };
function lookup_type(protoPath, type) { function lookup_type(protoPath, type) {
@@ -472,11 +523,33 @@ function modifyProtoFields(protoObj, displayDefaults) {
} }
function decodeAndTransformProto(buffer, params, displayDefaults) { function decodeAndTransformProto(buffer, params, displayDefaults) {
const decoded = params.protoType.decode(buffer); var objTypesProto = [];
var transforms = [];
if (!Array.isArray(params.objTypeProto)) {
objTypesProto = [params.objTypeProto];
transforms = [params.transform];
} else {
objTypesProto = params.objTypeProto;
transforms = params.transform;
}
// each trace or dump may have different processors, for example, until S, SF dumps
// returne a list of layers and winscope built a [LayerTraceEntry] from them.
// From S onwards, returns a LayerTrace object, iterating over multiple items allows
// winscope to handle both the new and legacy formats
// TODO Refactor the decode.js code into a set of decoders to clean up the code
for (var x = 0; x < objTypesProto.length; x++) {
const objType = objTypesProto[x];
const transform = transforms[x];
try {
const decoded = objType.decode(buffer);
modifyProtoFields(decoded, displayDefaults); modifyProtoFields(decoded, displayDefaults);
const transformed = params.transform(decoded); const transformed = transform(decoded);
return transformed; return transformed;
} catch (e) {
// check next parser
}
}
throw new UndetectableFileType('Unable to parse file');
} }
function protoDecoder(buffer, params, fileName, store) { function protoDecoder(buffer, params, fileName, store) {
@@ -568,6 +641,12 @@ function detectAndDecode(buffer, fileName, store) {
if (arrayStartsWith(buffer, IMM_TRACE_MAGIC_NUMBER)) { if (arrayStartsWith(buffer, IMM_TRACE_MAGIC_NUMBER)) {
return decodedFile(FILE_TYPES.IME_TRACE_MANAGERSERVICE, buffer, fileName, store); return decodedFile(FILE_TYPES.IME_TRACE_MANAGERSERVICE, buffer, fileName, store);
} }
if (arrayStartsWith(buffer, TAG_TRACE_MAGIC_NUMBER)) {
return decodedFile(FILE_TYPES.TAG_TRACE, buffer, fileName, store);
}
if (arrayStartsWith(buffer, ERROR_TRACE_MAGIC_NUMBER)) {
return decodedFile(FILE_TYPES.ERROR_TRACE, buffer, fileName, store);
}
// TODO(b/169305853): Add magic number at beginning of file for better auto detection // TODO(b/169305853): Add magic number at beginning of file for better auto detection
for (const [filetype, condition] of [ for (const [filetype, condition] of [

View File

@@ -16,7 +16,8 @@
import { FILE_TYPES, DUMP_TYPES } from "@/decode.js"; import { FILE_TYPES, DUMP_TYPES } from "@/decode.js";
import DumpBase from "./DumpBase"; import DumpBase from "./DumpBase";
import LayersTraceEntry from '../flickerlib/layers/LayerTraceEntry' import LayersTraceEntry from '../flickerlib/layers/LayerTraceEntry';
import LayersTrace from '../flickerlib/LayersTrace';
export default class SurfaceFlinger extends DumpBase { export default class SurfaceFlinger extends DumpBase {
sfDumpFile: any; sfDumpFile: any;
@@ -32,11 +33,14 @@ export default class SurfaceFlinger extends DumpBase {
return DUMP_TYPES.SURFACE_FLINGER; return DUMP_TYPES.SURFACE_FLINGER;
} }
static fromProto(proto: any): LayersTraceEntry { static fromProto(proto: any): LayersTrace {
return LayersTraceEntry.fromProto( const source = null;
/*protos */ proto.layers, const entry = LayersTraceEntry.fromProto(
/* protos */ proto.layers,
/* displays */ proto.displays,
/* timestamp */ 0, /* timestamp */ 0,
/* hwcBlob */ "" /* hwcBlob */ ""
); );
return new LayersTrace([entry], source);
} }
} }

View File

@@ -33,6 +33,8 @@ export default class WindowManager extends DumpBase {
} }
static fromProto(proto: any): WindowManagerTrace { static fromProto(proto: any): WindowManagerTrace {
return WindowManagerTrace.fromDump(proto); const source = null;
const state = WindowManagerTrace.fromDump(proto);
return new WindowManagerTrace([state], source);
} }
} }

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2020, 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.
*/
import { ErrorTrace } from "./common"
import ErrorState from "./errors/ErrorState"
ErrorTrace.fromProto = function (proto: any) {
const states = [];
for (const stateProto of proto.states) {
const transformedState = ErrorState.fromProto(
stateProto.errors,
stateProto.timestamp);
states.push(transformedState);
}
const source = null;
return new ErrorTrace(states, source);
}
export default ErrorTrace;

View File

@@ -22,6 +22,7 @@ LayersTrace.fromProto = function (proto: any): LayersTrace {
for (const entryProto of proto.entry) { for (const entryProto of proto.entry) {
const transformedEntry = LayerTraceEntry.fromProto( const transformedEntry = LayerTraceEntry.fromProto(
/* protos */ entryProto.layers.layers, /* protos */ entryProto.layers.layers,
/* displays */ entryProto.displays,
/* timestamp */ entryProto.elapsedRealtimeNanos, /* timestamp */ entryProto.elapsedRealtimeNanos,
/* hwcBlob */ entryProto.hwcBlob); /* hwcBlob */ entryProto.hwcBlob);

View File

@@ -44,6 +44,13 @@ export default class ObjectFormatter {
return obj; return obj;
} }
/**
* Get the true properties of an entry excluding functions, kotlin gernerated
* variables, explicitly excluded properties, and flicker objects already in
* the hierarchy that shouldn't be traversed when formatting the entry
* @param entry The entry for which we want to get the properties for
* @return The "true" properties of the entry as described above
*/
static getProperties(entry: any): string[] { static getProperties(entry: any): string[] {
var props = []; var props = [];
let obj = entry; let obj = entry;
@@ -58,13 +65,12 @@ export default class ObjectFormatter {
if (it.startsWith(`_`)) return false; if (it.startsWith(`_`)) return false;
// some predefined properties used only internally (e.g., children, ref, diff) // some predefined properties used only internally (e.g., children, ref, diff)
if (this.INVALID_ELEMENT_PROPERTIES.includes(it)) return false; if (this.INVALID_ELEMENT_PROPERTIES.includes(it)) return false;
// Flicker object properties or arrays
if (!entry[it]) return false; const value = entry[it];
const value = entry[it]
// only non-empty arrays of non-flicker objects (otherwise they are in hierarchy) // only non-empty arrays of non-flicker objects (otherwise they are in hierarchy)
if (Array.isArray(value) && value.length > 0) return !value[0].stableId if (Array.isArray(value) && value.length > 0) return !value[0].stableId;
// non-flicker object // non-flicker object
return !value.stableId; return !(value?.stableId);
}); });
properties.forEach(function (prop) { properties.forEach(function (prop) {
if (typeof(entry[prop]) !== 'function' && props.indexOf(prop) === -1) { if (typeof(entry[prop]) !== 'function' && props.indexOf(prop) === -1) {
@@ -76,6 +82,12 @@ export default class ObjectFormatter {
return props; return props;
} }
/**
* Format a Winscope entry to be displayed in the UI
* Accounts for different user display settings (e.g. hiding empty/default values)
* @param obj The raw object to format
* @return The formatted object
*/
static format(obj: any): {} { static format(obj: any): {} {
const properties = this.getProperties(obj); const properties = this.getProperties(obj);
const sortedProperties = properties.sort() const sortedProperties = properties.sort()
@@ -85,7 +97,14 @@ export default class ObjectFormatter {
const key = entry; const key = entry;
const value: any = obj[key]; const value: any = obj[key];
if (value || (this.displayDefaults && value !== undefined && value !== null)) { if (value === null || value === undefined) {
if (this.displayDefaults) {
result[key] = value
}
return
}
if (value || this.displayDefaults) {
// flicker obj // flicker obj
if (value.prettyPrint) { if (value.prettyPrint) {
const isEmpty = value.isEmpty === true; const isEmpty = value.isEmpty === true;

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2020, 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.
*/
import { TagTrace } from "./common"
import TagState from "./tags/TagState"
TagTrace.fromProto = function (proto: any): TagTrace {
const states = [];
for (const stateProto of proto.states) {
const transformedState = TagState.fromProto(
stateProto.timestamp,
stateProto.tags);
states.push(transformedState);
}
const source = null;
return new TagTrace(states, source);
}
export default TagTrace;

View File

@@ -66,6 +66,8 @@ const Matrix = require('flicker').com.android.server.wm.traces.common.layers.
Transform.Matrix; Transform.Matrix;
const Transform = require('flicker').com.android.server.wm.traces.common. const Transform = require('flicker').com.android.server.wm.traces.common.
layers.Transform; layers.Transform;
const Display = require('flicker').com.android.server.wm.traces.common.
layers.Display;
// Common // Common
const Size = require('flicker').com.android.server.wm.traces.common.Size; const Size = require('flicker').com.android.server.wm.traces.common.Size;
@@ -76,6 +78,16 @@ const Rect = require('flicker').com.android.server.wm.traces.common.Rect;
const RectF = require('flicker').com.android.server.wm.traces.common.RectF; const RectF = require('flicker').com.android.server.wm.traces.common.RectF;
const Region = require('flicker').com.android.server.wm.traces.common.Region; const Region = require('flicker').com.android.server.wm.traces.common.Region;
//Tags
const Tag = require('flicker').com.android.server.wm.traces.common.tags.Tag;
const TagState = require('flicker').com.android.server.wm.traces.common.tags.TagState;
const TagTrace = require('flicker').com.android.server.wm.traces.common.tags.TagTrace;
//Errors
const Error = require('flicker').com.android.server.wm.traces.common.errors.Error;
const ErrorState = require('flicker').com.android.server.wm.traces.common.errors.ErrorState;
const ErrorTrace = require('flicker').com.android.server.wm.traces.common.errors.ErrorTrace;
const EMPTY_BUFFER = new Buffer(0, 0, 0, 0); const EMPTY_BUFFER = new Buffer(0, 0, 0, 0);
const EMPTY_COLOR = new Color(-1, -1, -1, 0); const EMPTY_COLOR = new Color(-1, -1, -1, 0);
const EMPTY_RECT = new Rect(0, 0, 0, 0); const EMPTY_RECT = new Rect(0, 0, 0, 0);
@@ -226,6 +238,15 @@ export {
LayersTrace, LayersTrace,
Transform, Transform,
Matrix, Matrix,
Display,
// Tags
Tag,
TagState,
TagTrace,
// Errors
Error,
ErrorState,
ErrorTrace,
// Common // Common
Size, Size,
Buffer, Buffer,

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2021, 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.
*/
import { Error } from "../common"
Error.fromProto = function (proto: any): Error {
const error = new Error(
proto.stacktrace,
proto.message,
proto.layerId,
proto.windowToken,
proto.taskId
);
return error;
}
export default Error;

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2020, 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.
*/
import { ErrorState } from "../common";
import Error from './Error';
ErrorState.fromProto = function (protos: any[], timestamp: number): ErrorState {
const errors = protos.map(it => Error.fromProto(it));
const state = new ErrorState(errors, timestamp);
return state;
}
export default ErrorState;

View File

@@ -18,9 +18,11 @@ import LayersTrace from './LayersTrace';
import WindowManagerState from './WindowManagerState'; import WindowManagerState from './WindowManagerState';
import WindowManagerTrace from './WindowManagerTrace'; import WindowManagerTrace from './WindowManagerTrace';
import ObjectFormatter from './ObjectFormatter'; import ObjectFormatter from './ObjectFormatter';
import TagTrace from './TagTrace';
import ErrorTrace from './ErrorTrace';
/** /**
* Entry point into the flickerlib for Winscope. * Entry point into the flickerlib for Winscope.
* Expose everything we want Winscope to have access to here. * Expose everything we want Winscope to have access to here.
*/ */
export {ObjectFormatter, LayersTrace, WindowManagerState, WindowManagerTrace}; export {ObjectFormatter, LayersTrace, WindowManagerState, WindowManagerTrace, TagTrace, ErrorTrace};

View File

@@ -14,13 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
import { LayerTraceEntry, LayerTraceEntryBuilder } from "../common" import { Display, LayerTraceEntry, LayerTraceEntryBuilder, toRect, toSize, toTransform } from "../common"
import Layer from './Layer' import Layer from './Layer'
import { VISIBLE_CHIP, RELATIVE_Z_PARENT_CHIP, MISSING_LAYER } from '../treeview/Chips' import { VISIBLE_CHIP, RELATIVE_Z_PARENT_CHIP, MISSING_LAYER } from '../treeview/Chips'
LayerTraceEntry.fromProto = function (protos: any[], timestamp: number, hwcBlob: string, where: string = ''): LayerTraceEntry { LayerTraceEntry.fromProto = function (protos: any[], displayProtos: any[],
timestamp: number, hwcBlob: string, where: string = ''): LayerTraceEntry {
const layers = protos.map(it => Layer.fromProto(it)); const layers = protos.map(it => Layer.fromProto(it));
const builder = new LayerTraceEntryBuilder(timestamp, layers, hwcBlob, where); const displays = (displayProtos || []).map(it => newDisplay(it));
const builder = new LayerTraceEntryBuilder(timestamp, layers, displays, hwcBlob, where);
const entry: LayerTraceEntry = builder.build(); const entry: LayerTraceEntry = builder.build();
updateChildren(entry); updateChildren(entry);
@@ -63,4 +65,15 @@ function updateChildren(entry: LayerTraceEntry) {
}); });
} }
function newDisplay(proto: any): Display {
return new Display(
proto.id,
proto.name,
proto.layerStack,
toSize(proto.size),
toRect(proto.layerStackSpaceRect),
toTransform(proto.transform)
)
}
export default LayerTraceEntry; export default LayerTraceEntry;

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2021, 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.
*/
import { Tag } from "../common";
import TransitionType from "./TransitionType";
const transitionTypeMap = new Map([
['ROTATION', TransitionType.ROTATION],
['PIP_ENTER', TransitionType.PIP_ENTER],
['PIP_RESIZE', TransitionType.PIP_RESIZE],
['PIP_EXIT', TransitionType.PIP_EXIT],
['APP_LAUNCH', TransitionType.APP_LAUNCH],
['APP_CLOSE', TransitionType.APP_CLOSE],
['IME_APPEAR', TransitionType.IME_APPEAR],
['IME_DISAPPEAR', TransitionType.IME_DISAPPEAR],
]);
Tag.fromProto = function (proto: any): Tag {
const tag = new Tag(
proto.id,
transitionTypeMap.get(proto.transition),
proto.isStartTag,
proto.layerId,
proto.windowToken,
proto.taskId
);
return tag;
};
export default Tag;

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2020, 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.
*/
import { TagState } from "../common";
import Tag from './Tag';
TagState.fromProto = function (timestamp: number, protos: any[]): TagState {
const tags = protos.map(it => Tag.fromProto(it));
const state = new TagState(timestamp, tags);
return state;
}
export default TagState;

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2021, 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.
*/
enum TransitionType {
ROTATION = 'ROTATION',
PIP_ENTER = 'PIP_ENTER',
PIP_RESIZE ='PIP_RESIZE',
PIP_EXIT = 'PIP_EXIT',
APP_LAUNCH = 'APP_LAUNCH',
APP_CLOSE = 'APP_CLOSE',
IME_APPEAR = 'IME_APPEAR',
IME_DISAPPEAR = 'IME_DISAPPEAR',
};
export default TransitionType;

View File

@@ -66,6 +66,8 @@ const store = new Vuex.Store({
dumps: {}, dumps: {},
excludeFromTimeline: [ excludeFromTimeline: [
TRACE_TYPES.PROTO_LOG, TRACE_TYPES.PROTO_LOG,
TRACE_TYPES.TAG,
TRACE_TYPES.ERROR
], ],
activeFile: null, activeFile: null,
focusedFile: null, focusedFile: null,
@@ -93,6 +95,14 @@ const store = new Vuex.Store({
return Object.values(state.traces) return Object.values(state.traces)
.filter(file => !state.excludeFromTimeline.includes(file.type)); .filter(file => !state.excludeFromTimeline.includes(file.type));
}, },
tagFiles(state, getters) {
return Object.values(state.traces)
.filter(file => file.type === TRACE_TYPES.TAG);
},
errorFiles(state, getters) {
return Object.values(state.traces)
.filter(file => file.type === TRACE_TYPES.ERROR);
},
sortedTimelineFiles(state, getters) { sortedTimelineFiles(state, getters) {
return sortFiles(getters.timelineFiles); return sortFiles(getters.timelineFiles);
}, },

View File

@@ -33,14 +33,37 @@ export default {
document.body.removeChild(a); document.body.removeChild(a);
}, },
/**
* Returns the file name, if the file has an extension use its default,
* otherwise use ".mp4" for screen recording (name from proxy script) and
* ".winscope" for traces
* @param {*} fileName
*/
getFileName(fileName) {
var re = /(?:\.([^.]+))?$/;
var extension = re.exec(fileName)[1];
if (!extension) {
extension = "";
}
switch (extension) {
case "": {
if (fileName == "Screen recording") {
return fileName + ".mp4"
}
return fileName + ".winscope"
}
default: return fileName
}
},
async downloadAsZip(traces) { async downloadAsZip(traces) {
const zip = new JSZip(); const zip = new JSZip();
for (const trace of traces) { for (const trace of traces) {
const traceFolder = zip.folder(trace.type); const traceFolder = zip.folder(trace.type);
for (const file of trace.files) { for (const file of trace.files) {
var fileName = this.getFileName(file.filename);
const blob = await fetch(file.blobUrl).then((r) => r.blob()); const blob = await fetch(file.blobUrl).then((r) => r.blob());
traceFolder.file(file.filename, blob); traceFolder.file(fileName, blob);
} }
} }

View File

@@ -14,6 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
import _ from "lodash";
import { nanos_to_string } from "../transform";
import { transitionMap } from "../utils/consts";
/** /**
* Represents a continuous section of the timeline that is rendered into the * Represents a continuous section of the timeline that is rendered into the
@@ -33,10 +36,36 @@ class Block {
} }
} }
//Represents a continuous section of the tag display that relates to a specific transition
class Transition {
/**
* Create a transition.
* @param {number} startPos - The position of the start tag as a percentage
* of the timeline width.
* @param {number} startTime - The start timestamp in ms of the transition.
* @param {number} endTime - The end timestamp in ms of the transition.
* @param {number} width - The width of the transition as a percentage of the
* timeline width.
* @param {string} color - the color of transition depending on type.
* @param {number} overlap - number of transitions with which this transition overlaps.
* @param {string} tooltip - The tooltip of the transition, minus the type of transition.
*/
constructor(startPos, startTime, endTime, width, color, overlap, tooltip) {
this.startPos = startPos;
this.startTime = startTime;
this.endTime = endTime;
this.width = width;
this.color = color;
this.overlap = overlap;
this.tooltip = tooltip;
}
}
/** /**
* This Mixin should only be injected into components which have the following: * This Mixin should only be injected into components which have the following:
* - An element in the template referenced as 'timeline' (this.$refs.timeline). * - An element in the template referenced as 'timeline' (this.$refs.timeline).
*/ */
export default { export default {
name: 'timeline', name: 'timeline',
props: { props: {
@@ -53,6 +82,15 @@ export default {
'scale': { 'scale': {
type: Array, type: Array,
}, },
'tags': {
type: Array,
},
'errors': {
type: Array,
},
'flickerMode': {
type: Boolean,
}
}, },
data() { data() {
return { return {
@@ -99,6 +137,65 @@ export default {
return Object.freeze(blocks); return Object.freeze(blocks);
}, },
//Generates list of transitions to be displayed in flicker mode
timelineTransitions() {
const transitions = [];
//group tags by transition 'id' property
const groupedTags = _.mapValues(
_.groupBy(this.tags, 'id'), clist => clist.map(tag => _.omit(tag, 'id')))
;
for (const transitionId in groupedTags) {
const id = groupedTags[transitionId];
//there are at least two tags per id, maybe more if multiple traces
// determine which tag is the start (min of start times), which is end (max of end times)
const startTimes = id.filter(tag => tag.isStartTag).map(tag => tag.timestamp);
const endTimes = id.filter(tag => !tag.isStartTag).map(tag => tag.timestamp);
const transitionStartTime = Math.min(startTimes);
const transitionEndTime = Math.max(endTimes);
//do not freeze new transition, as overlap still to be handled (defaulted to 0)
const transition = this.generateTransition(
transitionStartTime,
transitionEndTime,
id[0].transition,
0
);
transitions.push(transition);
}
//sort transitions in ascending start position in order to handle overlap
transitions.sort((a, b) => (a.startPos > b.startPos) ? 1: -1);
//compare each transition to the ones that came before
for (let curr=0; curr<transitions.length; curr++) {
let overlapStore = [];
for (let prev=0; prev<curr; prev++) {
overlapStore.push(transitions[prev].overlap);
if (transitions[prev].startPos <= transitions[curr].startPos
&& transitions[curr].startPos <= transitions[prev].startPos+transitions[prev].width
&& transitions[curr].overlap === transitions[prev].overlap) {
transitions[curr].overlap++;
}
}
if (overlapStore.length>0
&& transitions[curr].overlap === Math.max(overlapStore)
) transitions[curr].overlap++;
}
return Object.freeze(transitions);
},
errorPositions() {
if (!this.flickerMode) return [];
const errorPositions = this.errors.map(error => this.position(error.timestamp));
return Object.freeze(errorPositions);
},
}, },
methods: { methods: {
position(item) { position(item) {
@@ -143,6 +240,10 @@ export default {
return pos * (this.crop.right - this.crop.left) + this.crop.left; return pos * (this.crop.right - this.crop.left) + this.crop.left;
}, },
objectWidth(startTs, endTs) {
return this.position(endTs) - this.position(startTs) + this.pointWidth;
},
/** /**
* Converts a position as a percentage of the timeline width to a timestamp. * Converts a position as a percentage of the timeline width to a timestamp.
* @param {number} position - target position as a percentage of the * @param {number} position - target position as a percentage of the
@@ -249,9 +350,25 @@ export default {
* scale parameter. * scale parameter.
*/ */
generateTimelineBlock(startTs, endTs) { generateTimelineBlock(startTs, endTs) {
const blockWidth = this.position(endTs) - this.position(startTs) + const blockWidth = this.objectWidth(startTs, endTs);
this.pointWidth;
return Object.freeze(new Block(this.position(startTs), blockWidth)); return Object.freeze(new Block(this.position(startTs), blockWidth));
}, },
/**
* Generate a transition object that can be used by the tag-timeline to render
* a transformed transition that starts at `startTs` and ends at `endTs`.
* @param {number} startTs - The timestamp at which the transition starts.
* @param {number} endTs - The timestamp at which the transition ends.
* @param {string} transitionType - The type of transition.
* @param {number} overlap - The degree to which the transition overlaps with others.
* @return {Transition} A transition object transformed to the timeline's crop and
* scale parameter.
*/
generateTransition(startTs, endTs, transitionType, overlap) {
const transitionWidth = this.objectWidth(startTs, endTs);
const transitionDesc = transitionMap.get(transitionType).desc;
const transitionColor = transitionMap.get(transitionType).color;
const tooltip = `${transitionDesc}. Start: ${nanos_to_string(startTs)}. End: ${nanos_to_string(endTs)}.`;
return new Transition(this.position(startTs), startTs, endTs, transitionWidth, transitionColor, overlap, tooltip);
},
}, },
}; };

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2020, 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.
*/
import { FILE_TYPES, TRACE_TYPES } from '@/decode.js';
import TraceBase from './TraceBase';
import { ErrorTrace } from '@/flickerlib';
export default class TraceError extends TraceBase {
errorTraceFile: Object;
constructor(files) {
const errorTraceFile = files[FILE_TYPES.ERROR_TRACE];
super(errorTraceFile.data, errorTraceFile.timeline, files);
this.errorTraceFile = errorTraceFile;
}
get type() {
return TRACE_TYPES.ERROR;
}
static fromProto(proto: any): ErrorTrace {
return ErrorTrace.fromProto(proto);
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2020, 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.
*/
import { FILE_TYPES, TRACE_TYPES } from '@/decode.js';
import TraceBase from './TraceBase';
import { TagTrace } from '@/flickerlib';
export default class TraceTag extends TraceBase {
tagTraceFile: Object;
constructor(files) {
const tagTraceFile = files[FILE_TYPES.TAG_TRACE];
super(tagTraceFile.data, tagTraceFile.timeline, files);
this.tagTraceFile = tagTraceFile;
}
get type() {
return TRACE_TYPES.TAG;
}
static fromProto(proto: any): TagTrace {
return TagTrace.fromProto(proto);
}
}

View File

@@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
import TransitionType from "../flickerlib/tags/TransitionType";
/** /**
* Should be kept in sync with ENUM is in Google3 under: * Should be kept in sync with ENUM is in Google3 under:
* google3/wireless/android/tools/android_bug_tool/extension/common/actions * google3/wireless/android/tools/android_bug_tool/extension/common/actions
@@ -31,6 +33,11 @@ const NAVIGATION_STYLE = {
TARGETED: 'Targeted', TARGETED: 'Targeted',
}; };
const SEARCH_TYPE = {
TAG: 'Transitions and Errors',
TIMESTAMP: 'Timestamp',
};
const logLevel = { const logLevel = {
INFO: 'info', INFO: 'info',
DEBUG: 'debug', DEBUG: 'debug',
@@ -38,6 +45,17 @@ const logLevel = {
WARN: 'warn', WARN: 'warn',
ERROR: 'error', ERROR: 'error',
WTF: 'wtf', WTF: 'wtf',
} };
export { WebContentScriptMessageType, NAVIGATION_STYLE, logLevel }; const transitionMap = new Map([
[TransitionType.ROTATION, {desc: 'Rotation', color: '#9900ffff'}],
[TransitionType.PIP_ENTER, {desc: 'Entering PIP mode', color: '#4a86e8ff'}],
[TransitionType.PIP_RESIZE, {desc: 'Resizing PIP mode', color: '#2b9e94ff'}],
[TransitionType.PIP_EXIT, {desc: 'Exiting PIP mode', color: 'darkblue'}],
[TransitionType.APP_LAUNCH, {desc: 'Launching app', color: '#ef6befff'}],
[TransitionType.APP_CLOSE, {desc: 'Closing app', color: '#d10ddfff'}],
[TransitionType.IME_APPEAR, {desc: 'IME appearing', color: '#ff9900ff'}],
[TransitionType.IME_DISAPPEAR, {desc: 'IME disappearing', color: '#ad6800ff'}],
])
export { WebContentScriptMessageType, NAVIGATION_STYLE, SEARCH_TYPE, logLevel, transitionMap };

View File

@@ -30,8 +30,9 @@ for arg in "$@"; do
esac esac
done done
outfileTrans=${outfile}_transactiontrace.pb WINSCOPE_EXT=.winscope
outfileSurf=${outfile}_layerstrace.pb outfileTrans=${outfile}_transactiontrace$WINSCOPE_EXT
outfileSurf=${outfile}_layerstrace$WINSCOPE_EXT
outfileTrans_abs="$(cd "$(dirname "$outfileTrans")"; pwd)/$(basename "$outfileTrans")" outfileTrans_abs="$(cd "$(dirname "$outfileTrans")"; pwd)/$(basename "$outfileTrans")"
outfileSurf_abs="$(cd "$(dirname "$outfileSurf")"; pwd)/$(basename "$outfileSurf")" outfileSurf_abs="$(cd "$(dirname "$outfileSurf")"; pwd)/$(basename "$outfileSurf")"
@@ -39,8 +40,8 @@ outfileSurf_abs="$(cd "$(dirname "$outfileSurf")"; pwd)/$(basename "$outfileSurf
if [ "$help" != "" ]; then if [ "$help" != "" ]; then
echo "usage: $0 [-h | --help] [OUTFILE]" echo "usage: $0 [-h | --help] [OUTFILE]"
echo echo
echo "Records Transaction traces (default transactiontrace.pb)." echo "Records Transaction traces (default transactiontrace$WINSCOPE_EXT)."
echo "Records Surface traces (default layerstrace.pb)." echo "Records Surface traces (default layerstrace$WINSCOPE_EXT)."
echo "To view the traces, use $WINSCOPE_URL." echo "To view the traces, use $WINSCOPE_URL."
exit 1 exit 1
fi fi
@@ -75,8 +76,8 @@ start_tracing
read -p "Press ENTER to stop recording" -s x read -p "Press ENTER to stop recording" -s x
echo echo
stop_tracing stop_tracing
adb exec-out su root cat /data/misc/wmtrace/transaction_trace.pb >"$outfileTrans" adb exec-out su root cat /data/misc/wmtrace/transaction_trace$WINSCOPE_EXT >"$outfileTrans"
adb exec-out su root cat /data/misc/wmtrace/layers_trace.pb >"$outfileSurf" adb exec-out su root cat /data/misc/wmtrace/layers_trace$WINSCOPE_EXT >"$outfileSurf"
echo echo
echo "To view the trace, go to $WINSCOPE_URL, and open" echo "To view the trace, go to $WINSCOPE_URL, and open"

View File

@@ -64,7 +64,7 @@ module.exports = {
}, },
}, },
{ {
test: /\.pb/, test: /\.(pb|winscope)/,
loader: 'file-loader', loader: 'file-loader',
options: { options: {
paths: [ paths: [