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

Cash App & Gradle: A Journey in Android Develop...

Cash App & Gradle: A Journey in Android Developer Productivity

A case study on how Cash App and Gradle have been working together to optimize Cash App builds, find bottlenecks, and improve developer productivity. John Rodriguez from Square and Rooz Mohazzabi from Gradle will share wins and findings from their journey on...

* How Cash App does builds
* Biggest wins and findings debugging Cash App local and CI builds
* Best performance practices for builds at scale
* Optimizing for your expensive build tasks, i.e., assemble, lint, etc
* Tracking down bottlenecks in 3rd-party plugins
* Speeding up local builds with the remote build cache during pandemic

Video: https://www.droidcon.com/2021/11/17/cash-app-gradle-a-journey-in-android-developer-productivity/

John Rodriguez

October 28, 2021
Tweet

More Decks by John Rodriguez

Other Decks in Programming

Transcript

  1. What we discovered - Discovered negative savings from the remote

    cache 
 
 - After optimizations and applying the cache fix plugin, most instances of negative savings were eliminated, even in East Coast regions. 
 
 - Wire plugin cacheability issues 
 
 - The configuration for enabling the remote cache was not working properly 
 
 - Unnecessary VPN issues causing long build times
  2. Remote Cache Saving CI Builds an average ~8 min per

    build CI Compute Resource Savings from remote cache: 55 Days & 16 hours per week
  3. apply plugin: 'com.android.library ' apply plugin: 'org.jetbrains.kotlin.android ' apply plugin:

    'org.jetbrains.kotlin.kapt ' apply plugin: 'org.jetbrains.kotlin.plugin.parcelize ' apply plugin: 'app.cash.paparazzi ' apply plugin: 'app.cash.treehouse'
  4. JavaCompile task A.class A.java JDK 11 4 GB CACHED echo

    “org.gradle.caching=true” >> gradle.properties
  5. private void addOsXMetadata() { buildScan.background { def json = new

    JsonSlurper( ) def ip = exec('curl', '-s', 'https://ipinfo.io/ip' ) def ge o if (ip) { geo = json.parseText ( exec('curl', '-s', “https://json.geoiplookup.io/$ip” ) ) } if (ip && geo) { value 'OS: Location', "${geo.city}, ${geo.region}, ${geo.country} " value 'OS: Coordinates', "${geo.latitude}, ${geo.longitude} " } else { value 'OS: Location', 'Unknown ' } } }
  6. NYC Developers Saving 1 min 12 sec of build execution

    time per build from the remote build cache 
 Wall Clock Time: 38 sec of eng time savings
  7. SF Developers Saving 3 min 41 sec of build execution

    time per build from the remote build cache Wall Clock Time: 3 min 10 sec eng time savings
  8. SF vs NYC difference in remote cache avoidance savings: 2

    min 12 sec East Coast Local AssembleDebug builds: ~709 per week Remote Cache Node in the East Coast would result in... 709 * 2 min 12 sec = 1559.8 min of saved eng time per week * 48 work weeks per year = 1247.84 hours of saved eng time per year
  9. @Overrid e <T extends Task> T create ( String name,

    Class<T> type, Action<? super T> configuratio n ) throws InvalidUserDataException;
  10. @Overrid e <T extends Task> T create ( String name,

    Class<T> type, Action<? super T> configuratio n ) throws InvalidUserDataException;
  11. @Overrid e <T extends Task> T create ( String name,

    Class<T> type, Action<? super T> configuratio n ) throws InvalidUserDataException; @Overrid e TaskProvider<Task> register ( String name, Action<? super Task> configurationActio n ) throws InvalidUserDataException ;
  12. @Overrid e <T extends Task> T create ( String name,

    Class<T> type, Action<? super T> configuratio n ) throws InvalidUserDataException; @Overrid e TaskProvider<Task> register ( String name, Action<? super Task> configurationActio n ) throws InvalidUserDataException ;
  13. buildCache { remote(HttpBuildCache) { if (!isCi) { def pingCache =

    'ping -c1 -W1 -q gradle-enterprise.aws.square.com'.execute( ) def isCacheUnreachable = !pingCache.waitFor(2, TimeUnit.SECONDS) | | pingCache.exitValue() == 6 8 if (isCacheUnreachable) { throw new GradleException(... ) } url = 'https://gradle-build-cache.aws.square.com/cache/ ' }
  14. boolean isVpnCheckEnabled = hasProperty('com.squareup.cash.vpn.check.enabled') ? getProperty('com.squareup.cash.vpn.check.enabled').toBoolean() : true buildCache {

    remote(HttpBuildCache) { if (!isCi) { def pingCache = 'ping -c1 -W1 -q gradle-enterprise.aws.square.com'.execute( ) def isCacheUnreachable = !pingCache.waitFor(2, TimeUnit.SECONDS) | | pingCache.exitValue() == 6 8 if (isCacheUnreachable && isVpnCheckEnabled) { throw new GradleException(... ) } url = 'https://gradle-build-cache.aws.square.com/cache/ ' }
  15. void addGradleBuildMetadata() { def filteredProperties = project.propertie s .findAll {

    def key = it.ke y key.startsWith('android.') | | key.startsWith('org.gradle.') | | key.startsWith('com.squareup.') | | key.startsWith('app.cash.') | | key.startsWith('kotlin.') | | key.startsWith('kapt.' ) } filteredProperties.sort()*.ke y .forEach { key - > println("$key = ${filteredProperties[key]}" ) buildScan.value("Property: $key", "${filteredProperties[key]}" ) } }
  16. Recap of Build Performance Essentials Build Scans = your best

    friend Daemon Parallel Max Workers Configuration On Demand