SurfaceReplayer Documentation
===================
[go/SurfaceReplayer](go/SurfaceReplayer)
SurfaceReplayer is a playback mechanism that allows the replaying of traces recorded by
[SurfaceInterceptor](go/SurfaceInterceptor) from SurfaceFlinger. It specifically replays
* Creation and deletion of surfaces/displays
* Alterations to the surfaces/displays called Transactions
* Buffer Updates to surfaces
* VSync events
At their specified times to be as close to the original trace.
Usage
--------
###Creating a trace
SurfaceInterceptor is the mechanism used to create traces. The device needs to be rooted in order to
utilize it. To allow it to write to the device, run
`setenforce 0`
To start recording a trace, run
`service call SurfaceFlinger 1020 i32 1`
To stop recording, run
`service call SurfaceFlinger 1020 i32 0`
The default location for the trace is `/data/SurfaceTrace.dat`
###Executable
To replay a specific trace, execute
`/data/local/tmp/surfacereplayer /absolute/path/to/trace`
inside the android shell. This will replay the full trace and then exit. Running this command
outside of the shell by prepending `adb shell` will not allow for manual control and will not turn
off VSync injections if it interrupted in any way other than fully replaying the trace
The replay will not fill surfaces with their contents during the capture. Rather they are given a
random color which will be the same every time the trace is replayed. Surfaces modulate their color
at buffer updates.
**Options:**
- -m pause the replayer at the start of the trace for manual replay
- -t [Number of Threads] uses specified number of threads to queue up actions (default is 3)
- -s [Timestamp] switches to manual replay at specified timestamp
- -n Ignore timestamps and run through trace as fast as possible
- -l Indefinitely loop the replayer
- -h displays help menu
**Manual Replay:**
When replaying, if the user presses CTRL-C, the replay will stop and can be manually controlled
by the user. Pressing CTRL-C again will exit the replayer.
Manual replaying is similar to debugging in gdb. A prompt is presented and the user is able to
input commands to choose how to proceed by hitting enter after inputting a command. Pressing enter
without inputting a command repeats the previous command.
- n - steps the replayer to the next VSync event
- ni - steps the replayer to the next increment
- c - continues normal replaying
- c [milliseconds] - continue until specified number of milliseconds have passed
- s [timestamp] - continue and stop at specified timestamp
- l - list out timestamp of current increment
- h - displays help menu
###Shared Library
To use the shared library include these shared libraries
`libsurfacereplayer`
`libprotobuf-cpp-full`
`libutils`
And the static library
`libtrace_proto`
Include the replayer header at the top of your file
`#include <replayer/Replayer.h>`
There are two constructors for the replayer
`Replayer(std::string& filename, bool replayManually, int numThreads, bool wait, nsecs_t stopHere)`
`Replayer(Trace& trace, ... ditto ...)`
The first constructor takes in the filepath where the trace is located and loads in the trace
object internally.
- replayManually - **True**: if the replayer will immediately switch to manual replay at the start
- numThreads - Number of worker threads the replayer will use.
- wait - **False**: Replayer ignores waits in between increments
- stopHere - Time stamp of where the replayer should run to then switch to manual replay
The second constructor includes all of the same parameters but takes in a preloaded trace object.
To use add
`#include <frameworks/native/cmds/surfacereplayer/proto/src/trace.pb.h>`
To your file
After initializing the Replayer call
replayer.replay();
And the trace will start replaying. Once the trace is finished replaying, the function will return.
The layers that are visible at the end of the trace will remain on screen until the program
terminates.
**If VSyncs are broken after running the replayer** that means `enableVSyncInjections(false)` was
never executed. This can be fixed by executing
`service call SurfaceFlinger 23 i32 0`
in the android shell
Code Breakdown
-------------
The Replayer is composed of 5 components.
- The data format of the trace (Trace.proto)
- The Replayer object (Replayer.cpp)
- The synchronization mechanism to signal threads within the Replayer (Event.cpp)
- The scheduler for buffer updates per surface (BufferQueueScheduler.cpp)
- The Main executable (Main.cpp)
### Traces
Traces are represented as a protobuf message located in surfacereplayer/proto/src.
**Traces** contain *repeated* **Increments** (events that have occurred in SurfaceFlinger).
**Increments** contain the time stamp of when it occurred and a *oneof* which can be a
- Transaction
- SurfaceCreation
- SurfaceDeletion
- DisplayCreation
- DisplayDeleteion
- BufferUpdate
- VSyncEvent
- PowerModeUpdate
**Transactions** contain whether the transaction was synchronous or animated and *repeated*
**SurfaceChanges** and **DisplayChanges**
- **SurfaceChanges** contain an id of the surface being manipulated and can be changes such as
position, alpha, hidden, size, etc.
- **DisplayChanges** contain the id of the display being manipulated and can be changes such as
size, layer stack, projection, etc.
**Surface/Display Creation** contain the id of the surface/display and the name of the
surface/display
**Surface/Display Deletion** contain the id of the surface/display to be deleted
**Buffer Updates** contain the id of the surface who's buffer is being updated, the size of the
buffer, and the frame number.
**VSyncEvents** contain when the VSync event has occurred.
**PowerModeUpdates** contain the id of the display being updated and what mode it is being
changed to.
To output the contents of a trace in a readable format, execute
`**aprotoc** --decode=Trace \
-I=$ANDROID_BUILD_TOP/frameworks/native/cmds/surfacereplayer/proto/src \
$ANDROID_BUILD_TOP/frameworks/native/cmds/surfacereplayer/proto/src/trace.proto \
< **YourTraceFile.dat** > **YourOutputName.txt**`
###Replayer
Fundamentally the replayer loads a trace and iterates through each increment, waiting the required
amount of time until the increment should be executed, then executing the increment. The first
increment in a trace does not start at 0, rather the replayer treats its time stamp as time 0 and
goes from there.
Increments from the trace are played asynchronously rather than one by one, being dispatched by
the main thread, queued up in a thread pool and completed when the main thread deems they are
ready to finish execution.
When an increment is dispatched, it completes as much work as it can before it has to be
synchronized (e.g. prebaking a buffer for a BufferUpdate). When it gets to a critical action
(e.g. locking and pushing a buffer), it waits for the main thread to complete it using an Event
object. The main thread holds a queue of these Event objects and completes the
corresponding Event base on its time stamp. After completing an increment, the main thread will
dispatch another increment and continue.
The main thread's execution flow is outlined below
initReplay() //queue up the initial increments
while(!pendingIncrements.empty()) { //while increments remaining
event = pendingIncrement.pop();
wait(event.time_stamp(); //waitUntil it is time to complete this increment
event.complete() //signal to let event finish
if(increments remaing()) {
dispatchEvent() //queue up another increment
}
}
A worker thread's flow looks like so
//dispatched!
Execute non-time sensitive work here
...
event.readyToExecute() //time sensitive point...waiting for Main Thread
...
Finish execution
### Event
An Event is a simple synchronization mechanism used to facilitate communication between the main
and worker threads. Every time an increment is dispatched, an Event object is also created.
An Event can be in 4 different states:
- **SettingUp** - The worker is in the process of completing all non-time sensitive work
- **Waiting** - The worker is waiting on the main thread to signal it.
- **Signaled** - The worker has just been signaled by the main thread
- **Running** - The worker is running again and finishing the rest of its work.
When the main thread wants to finish the execution of a worker, the worker can either still be
**SettingUp**, in which the main thread will wait, or the worker will be **Waiting**, in which the
main thread will **Signal** it to complete. The worker thread changes itself to the **Running**
state once **Signaled**. This last step exists in order to communicate back to the main thread that
the worker thread has actually started completing its execution, rather than being preempted right
after signalling. Once this happens, the main thread schedules the next worker. This makes sure
there is a constant amount of workers running at one time.
This activity is encapsulated in the `readyToExecute()` and `complete()` functions called by the
worker and main thread respectively.
### BufferQueueScheduler
During a **BuferUpdate**, the worker thread will wait until **Signaled** to unlock and post a
buffer that has been prefilled during the **SettingUp** phase. However if there are two sequential
**BufferUpdates** that act on the same surface, both threads will try to lock a buffer and fill it,
which isn't possible and will cause a deadlock. The BufferQueueScheduler solves this problem by
handling when **BufferUpdates** should be scheduled, making sure that they don't overlap.
When a surface is created, a BufferQueueScheduler is also created along side it. Whenever a
**BufferUpdate** is read, it schedules the event onto its own internal queue and then schedules one
every time an Event is completed.
### Main
The main exectuable reads in the command line arguments. Creates the Replayer using those
arguments. Executes `replay()` on the Replayer. If there are no errors while replaying it will exit
gracefully, if there are then it will report the error and then exit.