Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Coldstart - why should you care and how? (DroidCon 2016, NY)

Coldstart - why should you care and how? (DroidCon 2016, NY)

How can you quantify, measuring and analyzing startup time on a continuous basis? In this talk, we start up with a concrete definition of coldstart and describe techniques to measure it both as a part of continuous integration and from the field.

We then look at how to visualize and analyze this data using standard Android performance tools and visualization tools we built. And to end it off, we talk about some strategies we've used to optimize the startup process.

Vikram Bodicherla

September 29, 2016
Tweet

More Decks by Vikram Bodicherla

Other Decks in Programming

Transcript

  1. Users want fast • 20% ➔ apps should load instantaneously

    • 36% ➔ slow performance = company sucks AppDynamics
  2. Users want fast • 20% ➔ apps should load instantaneously

    • 36% ➔ slow performance = company sucks • 86% ➔ uninstall at least 1 sluggish app after one use AppDynamics
  3. What’s fast enough? • > 3 seconds ➔ 53% drop-off

    (Google) • < 2 seconds ➔ top 40% of the apps (NimbleDroid)
  4. MIP first • First most important pixel • Your MIP

    definition is built into your UI design
  5. The plan • Define • Measure • A DIY performance-degradation

    alerting system • Fail early • Performance catalog
  6. The plan • Define • Measure • A DIY performance-degradation

    alerting system • Fail early • Performance catalog
  7. The plan • Define • Measure • A DIY performance-degradation

    alerting system • Fail early • Performance catalog
  8. What’s coldstart? • User taps on the application to •

    no content • cached data* • fresh data
  9. What’s coldstart? • Apps can startup in multiple ways •

    cold starts, warm starts, notifications, deep links etc
  10. What’s coldstart? • Apps can startup in multiple ways •

    cold starts, warm starts, notifications, deep links etc • Keep special cases in mind • first launch, on-boarding flows, network failures
  11. 
 appInteractive = layoutLoaded - firstLineOfCode; Time First line of

    code Cached data Fresh content Icon tap Layout loaded
  12. 
 cachedDataLoaded = cachedDataLoaded - firstLineOfCode; Time First line of

    code Cached data Fresh content Icon tap Layout loaded
  13. coldstart = freshContent - firstLineOfCode; Time First line of code

    Cached data Fresh content Icon tap Layout loaded
  14. public class Stopwatch { public void markEvent(String event) { //Record

    event time SystemClock.elapsedRealtime(); } }
  15. public class Stopwatch { public void markEvent(String event) { //Record

    event time SystemClock.elapsedRealtime(); } } SystemClock.currentTimeMillis(); SystemClock.uptimeMillis(); SystemClock.elapsedRealtimeNanos();
  16. public class Stopwatch { public void markEvent(String event) { //Record

    event time SystemClock.elapsedRealtime(); } } SystemClock.currentTimeMillis(); SystemClock.uptimeMillis(); SystemClock.elapsedRealtimeNanos();
  17. public class Stopwatch { public void markEvent(String event) { //Record

    event time SystemClock.elapsedRealtime(); } } SystemClock.currentTimeMillis(); SystemClock.uptimeMillis(); SystemClock.elapsedRealtimeNanos();
  18. public class Stopwatch { public void markEvent(String event) { //Record

    event time SystemClock.elapsedRealtime(); } } SystemClock.currentTimeMillis(); SystemClock.uptimeMillis(); SystemClock.elapsedRealtimeNanos();
  19. public class ColdstartRegister { public void updateState(Event event) {
 stopwatch.markEvent(event.name());

    if(stopwatch.isMarked(Event.TICKERS_LOADED) && stopwatch.isMarked(Event.QUOTES_LOADED)){
 //Report cachedData
 }
 } }
  20. public class ColdstartRegister { public void updateState(Event event) {
 stopwatch.markEvent(event.name());

    if(stopwatch.isMarked(Event.TICKERS_LOADED) && stopwatch.isMarked(Event.QUOTES_LOADED)){
 //Report cachedData
 }
 } }
  21. public class ColdstartRegister { public void updateState(Event event) {
 stopwatch.markEvent(event.name());

    if(stopwatch.isMarked(Event.TICKERS_LOADED) && stopwatch.isMarked(Event.QUOTES_LOADED)){
 //Report cachedData
 }
 } }
  22. public class MyApp extends Application {
 private ColdstartRegister register;
 


    @Override
 public void onCreate() {
 super.onCreate();
 }
 } Application Activity TrendingTickers Quotes
  23. Application Activity TrendingTickers Quotes public class MyApp extends Application {


    private ColdstartRegister register;
 
 @Override
 public void onCreate() {
 super.onCreate(); reporter.updateState(Event.APP_ON_CREATE_BEGIN);
 }
 }
  24. public class MyApp extends Application {
 private ColdstartRegister register;
 


    @Override
 public void onCreate() {
 super.onCreate(); reporter.updateState(Event.APP_ON_CREATE_BEGIN); //Do your work
 }
 } Application Activity TrendingTickers Quotes
  25. public class MyApp extends Application {
 private ColdstartRegister register;
 


    @Override
 public void onCreate() {
 super.onCreate(); reporter.updateState(Event.APP_ON_CREATE_BEGIN); //Do your work reporter.updateState(Event.APP_ON_CREATE_END);
 }
 } Application Activity TrendingTickers Quotes
  26. public class MyActivity extends Activity {
 private ColdstartRegister register;
 


    @Override
 public void onCreate() {
 super.onCreate();
 }
 } Application Activity TrendingTickers Quotes
  27. public class MyActivity extends Activity {
 private ColdstartRegister register;
 


    @Override
 public void onCreate() {
 super.onCreate(); reporter.updateState(Event.ACT_ON_CREATE_BEGIN);
 }
 } Application Activity TrendingTickers Quotes
  28. public class MyActivity extends Activity {
 private ColdstartRegister register;
 


    @Override
 public void onCreate() {
 super.onCreate(); reporter.updateState(Event.ACT_ON_CREATE_BEGIN); 
 fetchTrendingTickers(…, new Listener() {
 @Override
 public void onResponse(final Response response) { showTrendingTickers(response.getTickers()); } });
 }
 } Application Activity TrendingTickers Quotes
  29. public class MyActivity extends Activity {
 private ColdstartRegister register;
 


    @Override
 public void onCreate() {
 super.onCreate(); reporter.updateState(Event.ACT_ON_CREATE_BEGIN); 
 fetchTrendingTickers(…, new Listener() {
 @Override
 public void onResponse(final Response response) { showTrendingTickers(response.getTickers()); reporter.updateState(Event.TICKERS_LOADED); } });
 }
 } Application Activity TrendingTickers Quotes
  30. public class MyActivity extends Activity {
 private ColdstartRegister register;
 


    @Override
 public void onCreate() { …
 } @Override
 public void onResume() {
 super.onResume(); }
 } Application Activity TrendingTickers Quotes
  31. public class MyActivity extends Activity {
 private ColdstartRegister register;
 


    @Override
 public void onCreate() { …
 } @Override
 public void onResume() {
 super.onResume(); reporter.updateState(Event.APP_INTERACTIVE);
 }
 } Application Activity TrendingTickers Quotes
  32. public class MyActivity extends Activity {
 private ColdstartRegister register;
 


    public void loadQuotes(List<String> symbols) {
 fetchQuotes(…, new Listener() {
 @Override
 public void onResponse(final Response response) { updateQuotes(response.getQuotes()); reporter.updateState(Event.QUOTES_LOADED); } });
 }
 } Application Activity TrendingTickers Quotes
  33. Capturing phase information public class Stopwatch { public void startPhase(String

    phase) { } public void endPhase(String phase) { } public void markEvent(String phase, String event) { } }
  34. public class MyActivity extends Activity {
 private ColdstartRegister register;
 


    @Override
 public void onCreate() {
 super.onCreate(); … 
 fetchTrendingTickers(…, new Listener() {
 @Override
 public void onResponse(final Response response) { showTrendingTickers(response.getTickers()); … } });
 }
 } Application Activity TrendingTickers Quotes
  35. public class MyActivity extends Activity {
 private ColdstartRegister register;
 


    @Override
 public void onCreate() {
 super.onCreate(); … reporter.startPhase(“fetchTrendingTickers”);
 fetchTrendingTickers(…, new Listener() {
 @Override
 public void onResponse(final Response response) { showTrendingTickers(response.getTickers()); … reporter.endPhase(“fetchTrendingTickers”); } });
 }
 } Application Activity TrendingTickers Quotes
  36. public class MyActivity extends Activity {
 private ColdstartRegister register;
 


    @Override
 public void onCreate() {
 super.onCreate(); … reporter.startPhase(“fetchTrendingTickers”);
 fetchTrendingTickers(…, new Listener() {
 @Override
 public void onResponse(final Response response) { showTrendingTickers(response.getTickers()); … reporter.endPhase(“fetchTrendingTickers”); } });
 }
 } Application Activity TrendingTickers Quotes
  37. Extracting data from the Stopwatch public class Stopwatch { …

    } public interface Reporter { public void reportAll(…) { } }
  38. Extracting data from the Stopwatch public class Stopwatch { …

    public void reportAll(Reporter dumper) { //Dump all events } } public interface Reporter { … }
  39. The plan • Define • Measure • A DIY performance-degradation

    alerting system • Fail early • Performance catalog
  40. … POST /performance Telemetry {
 "coldstart": 600, "model": “SM-G900P", "version":

    “0.1",
 "phases": [
 {
 "name": "phase1",
 "start": 3,
 "dur": 45
 }
 ...
 {
 "name": "phase2",
 "start": 78,
 "dur": 331
 }
 ]
 }
  41. … Telemetry POST /performance {
 "coldstart": 720, "model": “LG-H811", "version":

    “0.1",
 "phases": [
 {
 "name": "phase1",
 "start": 8,
 "dur": 33
 }
 ...
 {
 "name": "phase2",
 "start": 64,
 "dur": 298
 }
 ]
 }
  42. … {
 "coldstart": 600, "model": “LG-E977", "version": “0.1",
 "phases": [


    {
 "name": "phase1",
 "start": 11,
 "dur": 84
 }
 ...
 {
 "name": "phase2",
 "start": 66,
 "dur": 293
 }
 ]
 } Telemetry POST /performance
  43. {
 "agg_phases": [
 {
 "name": "phase1",
 "agg_start": 3,
 "agg_dur": 45


    }
 ...
 {
 "name": "phase2",
 "agg_start": 78,
 "agg_dur": 331
 }
 ], "version": “0.1”
 } Telemetry GET /aggregate_phases?v=0.1
  44. Build your own phase visualizer <html> <body> <div id="timeline" style="height:

    180px;”/> </body> </html> https://developers.google.com/chart/interactive/docs/gallery/timeline
  45. Build your own phase visualizer <html> <body> <div id="timeline" style="height:

    180px;”/> </body> </html> var chart = new google.visualization.Timeline(container); https://developers.google.com/chart/interactive/docs/gallery/timeline
  46. Build your own phase visualizer <html> <body> <div id="timeline" style="height:

    180px;”/> </body> </html> var chart = new google.visualization.Timeline(container); var dataTable = new google.visualization.DataTable(); https://developers.google.com/chart/interactive/docs/gallery/timeline
  47. Build your own phase visualizer <html> <body> <div id="timeline" style="height:

    180px;”/> </body> </html> var chart = new google.visualization.Timeline(container); var dataTable = new google.visualization.DataTable(); https://developers.google.com/chart/interactive/docs/gallery/timeline
  48. Build your own phase visualizer <html> <body> <div id="timeline" style="height:

    180px;”/> </body> </html> var chart = new google.visualization.Timeline(container); var dataTable = new google.visualization.DataTable(); dataTable.addRows([
 [ 'Phase1', start, end],
 [ 'Phase2', start, end]]); https://developers.google.com/chart/interactive/docs/gallery/timeline
  49. Build your own phase visualizer <html> <body> <div id="timeline" style="height:

    180px;”/> </body> </html> var chart = new google.visualization.Timeline(container); var dataTable = new google.visualization.DataTable(); dataTable.addRows([
 [ 'Phase1', start, end],
 [ 'Phase2', start, end]]); chart.draw(dataTable); https://developers.google.com/chart/interactive/docs/gallery/timeline
  50. Build your own phase visualizer <html> <body> <div id="timeline" style="height:

    180px;”/> </body> </html> var chart = new google.visualization.Timeline(container); var dataTable = new google.visualization.DataTable(); dataTable.addRows([
 [ 'Phase1', start, end],
 [ 'Phase2', start, end]]); chart.draw(dataTable); https://developers.google.com/chart/interactive/docs/gallery/timeline
  51. The plan • Define • Measure • A DIY performance-degradation

    alerting system • Fail early • Performance catalog
  52. Responding to alerts Telemetry Remote fix? Fix it! Yes Cancel

    that vacation No GET /aggregate_coldstart
  53. The plan • Define • Measure • A DIY performance-degradation

    alerting system • Fail early • Performance catalog
  54. Fail early • Catch regressions as you write code on

    your CI • Regression = considerable uptick in coldstart
  55. Fail early • Catch regressions as you write code on

    your CI • Regression = considerable uptick in coldstart • Remove variances - server latency
  56. Fail early • Catch regressions as you write code on

    your CI • Regression = considerable uptick in coldstart • Remove variances - server latency ./gradlew checkForColdstartRegression
  57. Fail early ./gradlew checkForColdstartRegression App code App HTTP server Stetho

    coldstart Coldstart history coldstart - previousColdstart > threshold? Get coldstart:commit
  58. Fail early ./gradlew checkForColdstartRegression App code App HTTP server Stetho

    coldstart Coldstart history coldstart - previousColdstart > threshold? yes Get coldstart:commit
  59. Fail early ./gradlew checkForColdstartRegression App code App HTTP server Stetho

    coldstart Coldstart history coldstart - previousColdstart > threshold? Fail job yes Get coldstart:commit
  60. Fail early ./gradlew checkForColdstartRegression App code App HTTP server Stetho

    coldstart Coldstart history coldstart - previousColdstart > threshold? Fail job yes no Get coldstart:commit
  61. Fail early ./gradlew checkForColdstartRegression App code App HTTP server Stetho

    coldstart Coldstart history coldstart - previousColdstart > threshold? Fail job Pass job yes no Get coldstart:commit
  62. Fail early ./gradlew checkForColdstartRegression App code App HTTP server Stetho

    coldstart Coldstart history Write commit:coldstart coldstart - previousColdstart > threshold? Fail job Pass job yes no Get coldstart:commit
  63. Fail early ./gradlew checkForColdstartRegression App code App HTTP server Stetho

    coldstart Coldstart history coldstart - previousColdstart > threshold? Fail job Pass job yes no
  64. Fail early ./gradlew checkForColdstartRegression App code App HTTP server Stetho

    coldstart Coldstart history coldstart - previousColdstart > threshold? Fail job Pass job yes no Write commit:coldstart Get coldstart:commit
  65. A few moving parts here ./gradlew checkForColdstartRegression App code App

    HTTP server Stetho coldstart Coldstart history coldstart - previousColdstart > threshold? Fail job Pass job yes no
  66. A few moving parts here ./gradlew checkForColdstartRegression App code App

    HTTP server Stetho coldstart Coldstart history coldstart - previousColdstart > threshold? Fail job Pass job yes no Write commit:coldstart Get coldstart:commit
  67. A few moving parts here ./gradlew checkForColdstartRegression App code App

    HTTP server Stetho coldstart Coldstart history coldstart - previousColdstart > threshold? Fail job Pass job yes no
  68. A few moving parts here ./gradlew checkForColdstartRegression App code App

    HTTP server Stetho coldstart Coldstart history coldstart - previousColdstart > threshold? Fail job Pass job yes no Write commit:coldstart Get coldstart:commit
  69. Local HTTP server android { buildTypes {
 production {
 buildConfigField

    “String", ‘“URL_HOST”’,“myhost.com” } coldstartTest {
 buildConfigField “String", ‘“URL_HOST”’,“127.0.0.1” } } } dependencies { coldstartTestCompile ‘com.nanohttpd:nanohttpd-webserver:2.+’ }
  70. Local HTTP server android { buildTypes {
 production {
 buildConfigField

    “String", ‘“URL_HOST”’,“myhost.com” } coldstartTest {
 buildConfigField “String", ‘“URL_HOST”’,“127.0.0.1” } } } dependencies { coldstartTestCompile ‘com.nanohttpd:nanohttpd-webserver:2.+’ }
  71. Local HTTP server android { buildTypes {
 production {
 buildConfigField

    “String", ‘“URL_HOST”’,“myhost.com” } coldstartTest {
 buildConfigField “String", ‘“URL_HOST”’,“127.0.0.1” } } } dependencies { coldstartTestCompile ‘com.nanohttpd:nanohttpd-webserver:2.+’ }
  72. Local HTTP server android { buildTypes {
 production {
 buildConfigField

    “String", ‘“URL_HOST”’,“myhost.com” } coldstartTest {
 buildConfigField “String", ‘“URL_HOST”’,“127.0.0.1” } } } dependencies { coldstartTestCompile ‘com.nanohttpd:nanohttpd-webserver:2.+’ }
  73. Local HTTP server public class ColdStartApp extends MyApp {
 


    @Override
 public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 }
 }
  74. Local HTTP server public class ColdStartApp extends MyApp {
 


    @Override
 public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState); new MyHTTPD().start(); }
 
 private class MyHTTPD extends NanoHTTPD {
 public MyHTTPD() throws IOException {
 super(PORT, null);
 }
 
 @Override
 public Response serve(String uri, …) { serveFileBasedOnUri(uri); }
 }
 }
  75. Local HTTP server public class ColdStartApp extends MyApp {
 


    @Override
 public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState); new MyHTTPD().start(); }
 
 private class MyHTTPD extends NanoHTTPD {
 public MyHTTPD() throws IOException {
 super(PORT, null);
 }
 
 @Override
 public Response serve(String uri, …) { serveFileBasedOnUri(uri); }
 }
 }
  76. Local HTTP server public class ColdStartApp extends MyApp {
 


    @Override
 public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState); new MyHTTPD().start(); }
 
 private class MyHTTPD extends NanoHTTPD {
 public MyHTTPD() throws IOException {
 super(PORT, null);
 }
 
 @Override
 public Response serve(String uri, …) { serveFileBasedOnUri(uri); }
 }
 }
  77. The plan • Define • Measure • A DIY performance-degradation

    alerting system • Fail early • Performance catalog
  78. Your APIs • Make your APIs fast • Get a

    first-page API built • Enable data compression on the server-side • Take a look at your caching policies
  79. Some of the more obvious ones • Avoid reflection at

    all costs • Batch network calls • Know what your SDKs do
  80. Some more of the more obvious ones • Avoid resource

    loading • Ensure that your UI thread and workers are working in sync • Avoid image manipulation and processing
  81. Some micro-optimizations • Flatten your UI • Be careful with

    handler.postAtFrontOfQueue(new Runnable()) • Try and stay under the 65K method limit