“Love Thy Enemy” — How I instantly found energy glitches in Spotify by comparing it against Apple Music using Eagle

Our battery-drain testing (shown in Figure 1) in our previous blog revealed that in foreground music playback, Spotify performs far worse than all of its competitors, draining battery at alarming 55% and 23% faster than Apple Music and Google Play Music, respectively.

Figure 1. Battery draining test results for foreground playback

Now imagine that I am a developer of the Spotify app and am familiar and comfortable with all the major modules of the app developed over the years, and was shocked to see the gap. How do I go about figuring out the reason for the huge gap in order to eliminate the culprit and make Spotify battery drain come close to Apple Music?

It turns out from talking to developers we learned this is something many app developers have been trying to do — to figure out why is my app draining more battery than a competing app or than my app’s previous release two weeks ago.

To facilitate this task, we introduced the Energy Diffing feature to Eagle which allows instant, head-to-head comparison of battery usage of two apps at the source-code level. The intended usage, of course, is for competitive analysis of similar apps, e.g. Spotify and Apple Music, or different releases of the same app.

Eagle— source-code-level battery drain comparison

Eagle gives source-code-level insights into why one app execution consumed more energy than the other. The diffing is unidirectional: when we invoke DiffView on Eagle trace A against trace B [trace A — trace B], the output shows a table listing all the methods in trace A along with their corresponding difference in energy consumption and in the number of calls (between the two traces). The tabular view is accompanied by a call graph view (like in Figure 3) of trace A, where each method node’s color indicates the energy difference in the two traces; the redder the node, the more energy drain the method in trace A over in trace B.

Finding UI energy inefficiency in Spotify

Since we have a fairly efficient reference app at hand, Apple Music, we can analyze the energy inefficiency of Spotify by simply invoking Eagle on [Spotify — Apple Music], for the contested usage scenario, i.e., foreground music playback.

I ran the apps on Nexus 6 phone under LTE. During the experiment, both apps are on their respective player activities playing music for 1 minute each without any user interaction — as shown in Figure 2.

Figure 2: Screenshot of Spotify and Apple Music player screen.

Table 1: Table view output of Eagle on [Spotify — Apple Music] for 1-minute foreground music playback.

Figure 3: Call-graph output of Eagle on [Spotify — Apple Music] for 1-minute foreground music playback.

Table 1 shows the diffview’s table view output and figure 3 shows a pruned call graph after diffing [Spotify — Apple Music]. We immediately see here that the path containing “ProgressBar.setProgress()” brings most of the energy difference. Referring back to the table view, I found that the progress bar was actually set 1192 times more in Spotify compared to Apple music in the 1-minute play — Spotify updates the progress bar 21 times per second, while Apple music only updates progress bar once per second.

Such high frequency update is completely unnecessary if you do the simple math. A typical phone screen has 1024 pixels horizontally. A 4-minute song will advance the progress bar by roughly 4 pixels per second. In other words, in Spotify, four out of five consecutive updates do not even move the progress bar by a single pixel.

I reduced this update frequency to update every 200 ms (moving the progress bar by 1 pixel) by modifying and recompiling the apk. This does not bring any noticeable change in the UI, but extends the battery life from 5.3 hours to 11 hours as shown in Figure 1.

PS: Out of curiosity, I also diffed [Apple music — Spotify] using DiffView which outputs the annotated call graph shown in Figure 4. The root method node is green signifying that overall Apple music consumed less energy than Spotify, but still there are red nodes present in the layout hierarchy measurement. This is due to the parameter misconfiguration bug discussed in our previous blog which is not present in the Spotify app.

It is fascinating to see Eagle can also uncover energy inefficiencies of a more efficient app when comparing it to a less efficient app !

 

Figure 4: Call-graph output of Eagle on [Apple Music — Spotify] for 1 minute foreground music playback.

Drop us a line at sales@mobileenerlytics.com or visit our website.

 

Apple Music, the latest Music app, also has energy glitches (How we reduced its battery drain by 18%)

Apple music has recently entered the fray to become the newest addition to the crowded family to offer online music service, aiming to win over a large fraction of the music fans.

apple-methodology
Testing methodology

While the public is undergoing heated debating how Apple Music fairs against the incumbents in terms of features and business models, we performed a head-to-head comparison of Apple Music app with some of the most popular music apps in the energy efficiency dimension. Specifically, we repeated the same battery draining test for comparing top Streaming Music apps reported in our recent blog (see sidebar for methodology) on the Apple Music app.

Our testing results (shown in Figure 1) shows that in background music playback, Apple Music is trailing behind, draining battery 9% and 40% faster than Pandora and Google Play Music, respectively. In foreground playback (shown in Figure 2), however, it is leading the pack, draining 14% less power than the previous champion, Google Play Music.

apple-back
Figure 1. Battery draining test results for background playback
apple-front
Figure 2. Battery draining test results for foreground playback

Finding UI Energy bug in Apple Music playback

To find out whether there is room to improve the energy efficiency of Apple Music, we employed Eagle, the industry’s first source-code-level energy profiler. Specifically, we played music for 1 minute of Apple Music (in music player page as shown in Figure 3), and used Eagle to profile the energy consumption of the app process and break down the energy to each method in the source code.

Figure 3. Screenshot of Apple Music music player page

Figure 4 below shows a branch of the energy-drain-annotated call tree output by Eagle, that contains top energy consuming methods during the 1-minute music playback. Since the energy is inclusive, the method call at the bottom of the call tree is the method that actually consumes most of the energy. In this case, method ViewRootImpl.performMeasure(II)V is at the bottom, which suggests that Apple Music spends most of the energy in performing measurement of the UI hierarchy.

As we know, Android performs UI hierarchy measurement only when any view in the UI changes its size, typically due to content changing. However, during music playback in Apple Music, the only UI elements that have content updates are the song elapsed time text, remaining time text, and the progress bar, none of which changes its size in updating. This suggests an energy inefficiency in measuring the UI hierarchy.

Figure 4. A branch of the energy-drain-annotated call tree (by color) that contains the energy hotspot in Apple Music playback in foreground

Finding the root cause of the energy inefficiency boils down to finding which UI element is causing the UI hierarchy measurement energy. We find out this culprit UI element by searching for method View.requestLayout() in Eagle method-level profiling output, since whenever a UI element changes its size, it will invoke the View.requestLayout() method to inform the system that a UI hierarchy measurement is needed.

Eagle shows that the only parent of this method is TextView.checkForRelayout(), which in turn is only invoked by TextView.setText(). Given that the only changing text in the music player is the song elapsed time and remaining time texts, and the number of TextView.setText() invocation equals the number of times the two texts are updated (once per second), we finally figured out the reason for high UI measure hierarchy energy In Apple Music: both song elapsed time and remaining time are displayed as a TextView with “dynamic width” (i.e. wrap_content). As a result each time the time texts are refreshed, the Android system has to perform a UI measure hierarchy to adjust the UI to fit the size of the new contents, even though the new content has the same length as the old content.

The impact of the energy bug

Without the appsource code, we fixed the problem by setting breakpoint at TextView.checkForRelayout() method using jdb, and changing the layout width of the two time texts to static width upon hitting breakpoint.

To confirm the bug’s impact on energy drain, we repeated the battery drain test as before. The fix reduced the total energy of the Apple Music process, and extended the battery life in playback, by 18% (Figure 2 in orange).

As always, we plan to benchmark and publish the battery performance of the latest releases of popular music apps, and welcome streaming music app vendors and music fan to repeat these experiments on different handsets following the above simple methodology.

 

How I Cut Google Play Music Energy Drain by 15% with Mobile Enerlytics

Our previous blog on the battery-draining tests of three popular music apps Pandora, Spotify, and Google Play Music showed that the Google Play Music app is more energy-efficient and battery-friendly than competing apps, in streaming music from the Internet.

sidebarReaders in Reddit asked what about the energy drain when playing Music stored locally on the device, a feature supported by several apps such as Google Play Music. Such a feature allows users to enjoy listening to preloaded music when there is no network connection, or play prepaid albums.
On the contrary, our testing results (shown in Figure 1) shows that playing music from local actually drains slightly more battery than streaming music from the Internet, for Google Play Music 6.1! The battery-drain test was the same as before (see sidebar for methodology).

one
Figure 1. Time to drain the entire battery

Solving the mystery with Mobile Enerlytics

To understand why playing local music drains unexpectedly high energy, we used the Eagle source-code-level energy diagnostic tool. Eagle accurately breaks down the total energy drain of an app run at the source-code-level, e.g., by each app method.

The app energy breakdown by component output of Eagle (Figure 2) shows that while playing music from local indeed incurs no network energy, the app process incurs 56% higher CPU energy than streaming music from the Internet (in light blue).

two
Figure 2. Energy breakdown
table
Table 1.Top energy-draining methods in Google Play Music 6.1 when playing local music

To find the root causes for the high CPU energy in playing local music, we next looked at the method-level energy breakdown of Eagle. Table 1 shows the top 10 (inclusive) energy draining methods during 1-minute local music playing in Google Play Music output by Eagle. We see that at top of the list are standard Android framework methods of class android.os.Handler and android.view.Choreographer, which perform generic message and callback handling for the app and will always be the top methods for most Android apps. What is interesting is that there are 3 methods of class android.graphics.drawable.AnimationDrawable among the top 10 (in blue), which indicates that the app spends a major chunk of energy in rendering animations.

However, there is no visible animation in the music player view we profiled, shown in the app screenshot in Figure 3(a). In fact, the only animations during music playing are the playing indicators in the album screen and play queue screen, circled in Figure 3(b) and 3(c). All three screens belong to the same activity and users can navigate between them with 1 or 2 clicks. Our observations suggest that Google Play Music spends a major chunk of the CPU energy on rendering animations that are invisible to users, which could be an “invisible animation UI energy bug”.

taylor
Figure 3: Screenshots of Google Play Music app

The impact of the energy bug

By hacking the app apk, we confirmed the energy bug is due to the app failing to consider the visibility of the play indicator animations, which results in a considerable amount of energy spent on animations that are invisible to users.

To confirm the bug’s impact on energy drain, we removed it by making the following 2 changes to the app:

  1. Adding code to check whether the play indicator is visible to user before starting its animation;
  2. Adding visibility change callback (i.e. onVisibilityChanged()) in the play indicator to handle its visibility change, i.e. start, stop its animation when it becomes visible, invisible, respectively.

We then repeated the battery drain test as before. The fix reduced the CPU energy of the Google Play Music process by 4.3X (Figure 2 in blue), and the total app energy drain by 15% (Figure 1).

Interestingly, after we learned the intricacies of the invisible animation energy bug in playing local music, we went back to the online streaming playing mode, and found the bug also exists in Google Play Music when streaming music from the Internet. The impact on energy drain is less, since there is only one invisible animation in play queue screen, as opposed to multiple invisible animation in both play queue screen and album screen when playing local music.

We have reported this issue to Google Play Music team.


Do you have an app?

user      No.

dev      Yes, I develop apps.

sales      Yes, I market apps.

[Icons]

How I Reduced the Idle Energy Drain of 2048 app by 87% using Eagle

If you haven’t heard of the 2048 game, this puzzle game got insanely popular after a hacker news article. Today, if we search 2048 on Google Play we get a listing of more than 250 apps!

Searching 2048 in Estar shows that the second most popular version of 2048, Estoty Entertainment Lab app, with more than 10 million downloads and average rating of 4.4, has only 3 energy stars compared to 4 or 5 of its counterparts.

In this blog post, I show how I used Eagle to study the energy consumption behavior of this app and reduced its idle energy by 87%. I fire up Eagle, connect the phone, and start energy profiling 2048 app. I play the game for a short while and look at the results. In addition to fine-grained energy breakdown among program entities such as threads and methods, Eagle also accurately reenacts the power draw over time of every major power-draining phone component. Browsing through these power lines, one thing that instantly caught my attention was that while playing the game, even though I had been idle most of the time, thinking next move and not interacting with app, the CPU and GPU power consumption of the app never dropped — the app continues to drain high power while I was idle.

So, next I collect another energy profile where I do not interact with the app at all. The app is just sitting idle on the screen not animating, not doing anything. As earlier, I saw that CPU and GPU again continue to drain excessive power.

Going back to the method energy breakdown by Eagle quite clearly shows that the app called library method nativeRender 1617 times even when the screen had no new content to show.

Thus Eagle has exactly pinpointed that I need to look at calls to Cocos2dxRenderer.nativeRender from Cocos2dxRenderer.onDrawFrame method for reducing the energy of this app. To fix this problem and generate a new app apk, I use apktool to decompile the app and edit dalvik bytecode files, in particular Cocos2dxRenderer.smali. Looking at Cocos2dxRenderer.smali file, we see that onDrawFrame directly calls nativeRender method without checking if the app has any new content to draw. Right after the frame is rendered, onDrawFrame is called again, creating a loop of repeated drawing.

The fix is simply that whenever there is a user action, such as pressing a key, increment a counter by a value corresponding to the number of times nativeRender should be called. Then, onDrawFrame decrements the counter and only calls nativeRender iff counter > 0. Even though bytecode is more verbose than Java, I just had to add 44 lines of code in Cocos2dxRenderer.smali file. The full patch can be found here.

Time for testing! I recompile the app using apktool, install it on the phone and collect idling energy profile. Now, I find that the CPU and GPU are indeed idle when the use is idle, and whether the user actively playing the game or being idle, the app gameplay has no noticeable difference.