[MUSIC PLAYING]
TOMAS MYLNARIC: Welcome to another episode
of MAD Skills and Performance.
This time, we'll take a look at inspecting performance.
What do we mean by inspecting performance?
We define it as understanding what's happening in your app
and making sure it aligns with what you expect is happening.
There are three ways to inspect performance-- passive, manual,
and automated.
Let's take a look at each one of them now.
Passive inspection is when you don't
have to do any extra work to spot a performance
problem in your app.
You can either visually spot when you're up drops frames
or you can check Logcat for bits of information.
Dropped or frozen frames are also called visual jank
or jank, for short.
I've added some poorly performing code
into the now in Android sample app
to demonstrate what you can see in Logcat when jank happens.
You can filter Logcat messages by the choreographer tag
and search for skipped messages.
This gives you information on slow rendering, which
means at least 30 skips frames.
On devices rendering 90 frames per second,
it's 333 milliseconds, which is just a 1/3 of a second.
If your app skips even more frames,
lasting more than 700 milliseconds,
we call that a frozen frame.
You can, again, filter Logcat and search
for the OpenGL renderer tag and see if there's
any debris in the logs.
If your app freezes for over 5 seconds,
you get the infamous application not responding dialog or ANR.
I hope you haven't seen that one for a while.
To spot slow up startups, can use Logcat as well.
You can filter the activity task manager tag and message
starting with displayed to filter time to initial display
or TTFD.
That is how long it took to draw the first frame every time
you open an activity.
If you call the Activity.Report fully drawn method in your app,
then you can also filter messages
starting with fully drawn to see how long the time to fully
drawn or TTFD took.
Now, you may have noticed some performance problems
during development.
One source of issues can be measuring performance
under Android emulator.
While it has many nice features and use
cases to develop quickly, it's not the right tool
for measuring performance.
First, the Android emulator doesn't represent a real world
device.
And second, it shares the same system resources
as your host operating system.
If it's under heavy load, your apps
will perform worse and vice versa.
So make sure you measure performance
on a physical device.
Also, for development, you probably
built your app with debugable equals true.
Debugable builds are provide many features
useful for development like life update,
applying changes without reinstalling the app,
or working with the debugger.
But it adds performance overhead to your app
and makes performance unstable, which
means that sometimes, it will perform well
but other times, it may not.
So whenever you spot the performance issue,
make sure you verify it on a physical device
and on the released version of your app.
Ideally, try to simulate an environment
as close as possible to what you ship to your users.
OK.
Once you've spotted the problem and you're
able to reproduce it, you can inspect the performance
manually.
For that, you can use Android Studio profilers.
profiler is similar to debugger.
What the debugger is for stepping through code,
looking at parameter and property values,
the profiler is for inspecting CPU, memory, network usage,
as well as possible sources of jank.
Remember, you should run the profiler
on a non-debugable version of your app.
To enable profiling a release build, add the XML
tag to your Android manifest.
This tag is available from Android 10 API 29 onwards.
For more details on what types of profilers it enables,
check out our documentation.
Now that your app is correctly set up
to manually inspect performance, you can use system tracing.
It lets you collect and examine timing information
for everything that's happening on your device.
So you can understand what your app is doing
and see when things are taking longer than you'd expect.
If you'd like to dive deeper into manual performance
inspection, we encourage you to take a look at our earlier MAD
Skills Series on the topic.
In this series, Carmen shows you how
to use system tracing, how to get more information
with custom trace sections, how to use cost sampling profiling,
and more.
Automated inspection is the third way
to look at your app's performance.
With automated performance inspection,
you can reliably run the same steps in a flow
and even prevent a performance issue before it occurs.
And while it takes a little setup,
you get peace of mind when it comes to ensuring your apps
performance does not get worse.
You can get started with Jetpack Macrobenchmark library.
This library allows you to automate inspecting performance
of entire user flows.
This way, you can measure up start up
which is a key metric for user engagement
frame timing for investigating jank and other performance
metrics.
To get started with Macrobenchmark,
you can use the module wizard in Android Studio.
Navigate to the project pane, select a new module,
and select benchmark from the templates pane.
Then make sure you have macrobenchmarks selected,
fill in the details, and click Finish.
Android Studio takes care of creating the benchmark module,
modifying your build script by adding the additional build
type and adding the right dependencies.
This build type is based on the release
build type to keep performance as close as possible
to your production app.
If your app uses built flavors, you
can define which flavor to use when
building the benchmarks by defining a missing dimension
strategy.
You can define it in the default config
block of the benchmark module.
In the now in Android sample app,
we use flavor dimension named content type
with demo and prod options.
I'll use the demo here as it provides us with enough content
to be measured.
Alternatively, you can define your apps
build flavors in the benchmark module which allows
you to benchmark them all.
Now that we have our project set up,
we can start writing the benchmarks.
You write the benchmarks as regular instrumentation tests.
The newly added module comes with a basic benchmark test
that measures the startup.
Within the test class, you need to use the Macrobenchmark rule
and use the measure repeated method in your tests.
This method takes several parameters you need to define.
Let's go over them.
First, you need to specify a package name.
This is because the benchmarks are installed
as a separate test APK.
So we need to know which app should
be launched and benchmarked.
Next, define metrics.
Metrics are the main type of information
collected from the target app during benchmarking.
Here we specify a startup timing metric
that allows you to measure time to initial display and time
to fully display.
There are other metrics you can use to collect
different types of information.
For example, the frame timing metric
that allows you to measure the cost of producing each frame
and helps you to find jank in your app,
trace section metric that can measure any trace section
the ones defined by Android platform
or by the libraries use and even custom trace
sections you defined.
You can even capture under runs while playing audio
with the audio under run metric.
We're aiming to add more metrics in the future.
With the next parameter, you need
to set how many times the benchmarks should run.
The basic benchmark has the iterations parameters
set to five, but you might change the number
based on how noisy a particular metric is for your app.
In general, a higher number means more stable data
but at the cost of longer execution time.
The next parameter is startup mode.
With it, you can tell a Macrobenchmark
to start your app with cold, warm, or hot startup mode.
If you don't know where to begin, use cold.
This will give you the most data as this
is where the most work happens.
Warm or hot are helpful when you want to benchmark things that
don't require cold starts, such as catching mechanisms or user
flows that likely won't be started cold.
The test logic of your benchmark happens in the measure book.
Here you'll define the interactions with your app.
The Jetpack Macrobenchmark library
comes with several helper functions
to get you started quickly.
In the benchmarks, you should call press home
to reset the state of the device you run the benchmarks on.
Then you can call start activity and wait.
This function starts the default activity
and waits until the first frame is drawn.
You can also specify the intent of your custom activity
to be started.
These two function calls are enough to measure time
to initial display.
If you want to measure how long it takes for your app
to become interactive, you need to wait for a specific UI
state in the benchmark because by default, the benchmark would
stop execution once the first frame is drawn.
You can use UI automator through the device parameter
to interact with your app.
So here, let's wait until there is a specific UI
element visible on screen that we
know is visible once the UI is fully drawn and ready to use.
With this defined, the benchmarks
will wait for the content to be fully drawn
and will measure both TTID and TTFD.
Now that we have our benchmark well defined, we can run it
and the Macrobenchmark library will
take care of all things required to collect
the appropriate data based on the metrics you've defined.
First, make sure you have the appropriate built variant
selected.
In the case of now in Android, this is the demo benchmark.
You can run the benchmark with the run gutter
icon next to the test function or for the entire class.
Make sure you run the benchmarks on a real device.
In fact, if you try to run the benchmarks on an emulator,
it will fail at runtime with an error.
If you don't have a physical device
and want to try running the benchmarks,
you can suppress the error, but we only
recommend doing this during development
as benchmark data will be different from what
you see on real devices.
Once the benchmark is finished, the results
are provided directly in the Android Studio output pane
but also as a JSON file on device and in the build output.
So you can parse them and plot them
as part of your continuous integration.
Each of the numbers is actually linked
to the system tracing recorded during the benchmark, which
you can open and inspect what was
happening during the iteration.
Alternatively, you can run the benchmarks
from the terminal using the Gradle task
as any instrumentation tests.
Similarly to a startup, we can benchmark rendering
to inspect potentially janky scenarios.
For that, we will create another test class.
Let's name it scroll feed benchmark.
We annotate the class with run with annotation
to be a proper test class.
And there, we'll use the same Macrobenchmark rule
for running benchmarks.
Let's write a test method that selects a couple of authors
and then scrolls the feed content.
We'll use the same parameters as for the startup benchmark.
Except for matrix, because we'll use frame timing metric.
The interactions will be the same as for the app up.
But in this case, we'll need to add them
to the setup block instead of the measure block.
The setup block is super useful when
you have to prepare the state of your app
up to the point where your measurement should take place.
It's an optional parameter and not always needed.
In the measure block, we will find the feed list UI element.
Set the gesture margin so we don't
trigger system navigation.
And scroll to feed with the fling gesture.
Once we have that, we can run the benchmark the same way
as before on a physical device.
In this case, Android Studio will produce results
in percentiles.
You will see how long a frame took with the frame duration
metric.
And when running the benchmarks on Android 12
API 31 and higher, you'll also see
the frame overrun metric, which says
by how much time the frames were over the limit.
The number can be negative, meaning
there was extra time left to produce the frame.
Let's open a system trace to see what was
happening during the benchmark.
In Android Studio Chipmunk, we added a jank detection feature.
It allows you to see the frames that took longer than expected
in the system trays.
On a timeline here, we can see red sections.
These are the frames that exceeded the expected duration.
You can click on it to see what work
was done as part of that frame.
On the right side, you can also select the All Frames
tab to see frames produced during the system trace
and there you can select just the janky frames
to get the ones that caused a problem.
And that's it for how to inspect performance in your app.
I have shown you how to set up, configure,
and run your first Macrobenchmark tests.
In the next episode, we'll take a look
at how to improve app start up and run time
with baseline profiles.
So stay tuned and don't forget to subscribe to Android
Developers YouTube channel.
Thanks for watching.
Bye.
[MUSIC PLAYING]