1 of 76

Android CI with

Anton Malinskiy

Development Manager & Principal Engineer, Agoda

2 of 76

Why CI/CD?

3 of 76

Phase 1: project init

4 of 76

Phase 2: more people

5 of 76

Phase 3: more teams

6 of 76

Phase 4+: more people

7 of 76

The Problem

A

B

C

develop/master

8 of 76

Git log of a large team

9 of 76

CI/CD

A

B

C

10 of 76

  1. Prerequisites
  2. Deployment of CI
  3. Unit testing
  4. Instrumentation testing
  5. Self-healing & monitoring

11 of 76

Prerequisites

12 of 76

Prerequisites: software

  • Docker image with build, test & deploy dependencies
    • JDK
    • gradle
    • android-sdk
    • etc (static analysis, custom scripts...)

13 of 76

Prerequisites: hardware

  • Bare-bone kubernetes cluster
    • usb devices
    • full control over the hardware is needed
  • KVM

14 of 76

Prerequisites: knowledge

  • Familiar with kubernetes entities like
    • Pod/Deployment
    • Service
    • Ingress
    • etc
  • Package manager, e.g. helm

15 of 76

Deployment of CI/CD

16 of 76

CI/CD installation on kubernetes

  • TeamCity/Jenkins
    • Server
    • Agents
  • Docker registry
    • Nexus
    • Artifactory
    • etc

17 of 76

CI/CD pipeline for infrastructure

  • Same pipeline for infrastructure as for the app

18 of 76

CI/CD pipeline for infrastructure

CI worker with ServiceAccount credentials

Kubernetes API

CI server

VCS

helm/kubectl deployment

Deployment change

19 of 76

Deployment of CI image

  • Scale via helm upgrade
  • Scale dynamically e.g. Horizontal Pod Autoscaler
  • Plugins for CI systems, e.g. teamcity-kubernetes-plugin

20 of 76

Unit testing

21 of 76

Unit testing

  • jUnit
    • Parallelizing via maxParallelForks
    • forkEvery as a stability fix

22 of 76

Unit testing

  • Robolectric
    • Slow ClassLoader initialization (e.g. @Config’s minsdk or maxsdk)

23 of 76

Unit testing

  • Mockito
    • Mocks cache not reused between JVM forks
    • Reported test time for Robolectric is broken

24 of 76

Unit testing: JVM considerations

  • Xmx/Xms

25 of 76

Unit testing: JVM considerations

  • Xmx/Xms
  • Forking the JVM

26 of 76

Unit testing: JVM considerations

  • Xmx/Xms
  • Forking the JVM
    • jUnit’s maxParallelForks, forkEvery
    • kotlin.compiler.execution.strategy="out-of-process"
    • gradle worker processes

27 of 76

Unit testing: JVM considerations

  • UnlockExperimentalVMOptions
    • UseCGroupMemoryLimitForHeap
    • MinRAMFraction
    • MaxRAMFraction
  • Test different garbage collectors
    • SerialGC
    • ConcMarkSweepGC

28 of 76

Unit testing: monitoring

29 of 76

What can you do with monitoring?

30 of 76

Unit testing: G1GC, OOM crash

31 of 76

Unit testing: ConcMarkSweep, maxParallelForks=1

32 of 76

Unit testing: ConcMarkSweep, maxParallelForks=4

33 of 76

Unit testing: SerialGC, maxParallelForks=4

34 of 76

Application deployment

  • Publishing API
    • gradle-play-publisher as a wrapper
    • no support for client side validation
    • Lot’s of retries
  • Kubernetes secrets
    • signing config
    • credentials
    • etc

35 of 76

Instrumented tests

36 of 76

The problem

Agent

Agent

Agent

Agent

Device

?

Device

?

Emulator?

Emulator?

?

37 of 76

Android connections

console 5554

adb 5555

Android

telnet ip console

adb connect ip:adb

adbd

adbd 5037

38 of 76

Real devices

  • Dedicated nodes with USB devices
  • DaemonSet with privileged container with access to:
    • /dev/bus/usb
    • /sys/bus/usb
    • /sys/devices

kubectl label nodes node-x.cluster.local openstf.io/provider=true

kubectl taint nodes node-x.cluster.local openstf.io/provider=true:NoSchedule

39 of 76

Emulators

Android requires custom kernel => virtualization

  • Linux
  • KVM
  • Privileged container

kubectl label nodes node-x.cluster.local openstf.io/emulator=kvm

40 of 76

Emulators: qemu

Android options:

  • adb+console ports
  • memory
  • screen multi-touch
  • camera-back/front none
  • skip-adb-auth
  • ….

qemu-system-i386 $ANDROID_OPTS -qemu $QEMU_OPTS

41 of 76

Emulators: config.ini

  • hw.cpu.arch
  • hw.cpu.ncore
  • hw.gpu.mode
  • hw.lcd.density
  • hw.lcd.height
  • hw.lcd.width
  • ...

ANDROID_CONFIG=” \

skin.name=600x1024;\

hw.lcd.density=160; \

hw.lcd.height=600; \

hw.lcd.width=1024; \

hw.device.name=7in WSVGA (Tablet); \

avd.ini.displayname=7 WSVGA (Tablet) API 23;”

42 of 76

Emulators: GPU rendering

  • auto
  • host
  • angle_indirect (windows)
  • swiftshader_indirect
  • off

43 of 76

Emulators: image size

Main contributors:

  • userdata.img (~1Gi)
  • system.img (~3Gi)

tar.gz

44 of 76

Emulators: unique id trick

Default mode

TCP/IP mode

$ adb devices

List of devices attached

emulator-5554 device

$ adb devices

List of devices attached

10.2.5.177:5555 device

emulator-5554 device

45 of 76

Emulators: additional tips

  • x86 is faster than x86_64
  • VNC is available for the whole container X system
  • Multi-touch support is enabled by default

46 of 76

The problem

Agent

Agent

Agent

Agent

Device

Device

Emulator

Emulator

?

47 of 76

Instrumented tests: OpenSTF

  • Unified scalable architecture for providing Android devices
  • WebUI
  • Backed by RethinkDB
  • Remote adb connections
  • VNC-like connections
  • + many more

48 of 76

Instrumented tests: OpenSTF

  • Deployment
  • Scaling
  • Monitoring
  • Self-healing
  • Upgrades
  • Rollbacks

49 of 76

Connecting to remote devices

Agent

Agent

Agent

Agent

Device

Device

Emulator

Emulator

?

50 of 76

Connecting to remote devices

  • Connects via TCP/IP
  • Not stable
  • Wraps adb connect
  • Multiple devices support
  • Daemon mode
  • External metrics
  • Custom notes support

adb connect 10.0.0.1:5555

stf-client connect -n 30 -f sdk:25

stf-client connect -n 4 -f notes:tablet

51 of 76

Connecting to remote devices

52 of 76

Connecting to remote devices

Agent

?

Agent

?

Agent�?

Agent�?

Device

Device

Emulator

Emulator

53 of 76

Test runner

54 of 76

Test runner

  • Sharding doesn’t really work in current runners
    • Assumption: test duration is equal for all tests

55 of 76

Test runner

  • Sharding doesn’t really work in current runners
    • Assumption: test duration is equal for all tests
  • Reconnect to device in real-time during test run

56 of 76

Test runner

  • Sharding doesn’t really work in current runners
    • Assumption: test duration is equal for all tests
  • Reconnect to device in real-time during test run
  • Rerun test on DIFFERENT device if test fails
  • Prefer running flaky tests first
  • Prefer running long tests first
  • Batch tests in groups

57 of 76

Test runner

58 of 76

Test runner

59 of 76

Test runner

60 of 76

Test runner

61 of 76

Everything eventually fails

62 of 76

Stability: usb devices, software

  • /dev/bus/usb driver needs rebind
  • adb devices doesn’t return device
  • adb devices returns the device in Unauthorized/Offline state
  • stf-client can’t connect to device (shell health-check fails)
  • ….

63 of 76

Stability: usb devices, hardware

  • USB cable issues
  • Battery discharges
  • Battery fails

64 of 76

Stability: emulators

  • adb devices returns the device in Unauthorized/Offline state
  • Emulator gets too hungry for resources
  • Emulator’s adb server takes over

65 of 76

Stability: remote connection

  • stf-client can’t connect to device (shell health-check fails)
  • network issues
  • ...

66 of 76

Monitoring

Metrics are collected from

  • adb-butler (side-container)
    • Devices in adb devices output
  • stf-client
    • Failed connections
  • openstf
    • Battery temperature
    • Present devices

67 of 76

Monitoring

68 of 76

Monitoring

69 of 76

Monitoring

70 of 76

Performance statistics: unit tests

Duration [p90]

18 minutes

Count

16_000+

Total tests run

180_000_000+

71 of 76

Performance statistics: instrumented tests

Kakao

Duration for 1 device

10 hours

Duration for 30 devices [p90]

30 minutes

Duration for 20 devices [p90]

35 minutes

Count

1_100+

Total tests run

6_000_000+

72 of 76

Licensing issue

  • Can’t redistribute Android SDK
  • Not feasible to compile from AOSP

make PROXY=registry-url/ build tag push

73 of 76

Links

74 of 76

Q&A

75 of 76

Cost, 60 devices, 24/7, 1 year

Firebase test lab

$5 device/hour

365x24x60=525600 hours

Total: $2_628_000

In-house solution

$700 x 60 devices

$80k support engineer

$20k servers + $5k etc

Total: $147_000

¢28 device/hour

76 of 76

Android CI

with