android-emulator-runner: Why running the action on Linux VMs is slow

Hi! Thanks for trying out this action.

I’d like to share a few words on the recently added Linux support to give you a bit of context on why it’s much slower than running on macos.

In general if you run this action on a Linux VM (e.g. ubuntu-latest), you’ll get very few benefits over other CI solutions (perhaps other than easier configuration and less scripting) as there’s no hardware acceleration on ubuntu VMs provided by GitHub Actions.

While you can sort of have some versions of the emulators running without hardware acceleration, in practice you really need hardware acceleration enabled to run the Android Emulator, period. In fact I almost believe that the emulator binary should just not start at all and throw an error if it can’t be hardware-accelerated, otherwise it defeats the purpose of having these modern x86, x86_64 system images.

To give you a bit more context, I created this action solely to take advantage of the hardware acceleration support on the macos VM (enabled by HAXM which is installed on macos VMs), and never intended to add Linux support cause I know it won’t be a good experience (at least not better than what you already have) without hardware acceleration.

I wrote this article about running Android emulator on CI which should give you more context on running instrumentation tests on CI in general and why the lack of hardware acceleration support from the host machine is still the biggest challenge for running fast and stable instrumented tests on CI.

I understand this might be a bit of a disappointment for some of you, but unfortunately this is a much bigger problem than what can be solved / improved by a GitHub Action.

If you looked at the code, there’s no magic at all. It basically automates the following process and makes it a little easier to configure for the common use cases:

  1. Install / update the required Android SDK components including build-tools, platform-tools, platform (for the required API level), emulator and system-images (for the required API level).
  2. Create a new instance of AVD with the provided configurations.
  3. Launch a new Emulator with the provided configurations.
  4. Wait until the Emulator is booted and ready for use.
  5. Run a custom script provided by user once the Emulator is up and running - e.g. ./gradlew connectedCheck.
  6. Kill the Emulator and finish the action.

As soon as you see “Emulator booted.” in the log, the job of this action is basically done as from this point on the environment is handed over to your script so anything that works / doesn’t work would not have much to do with the action itself, which only makes sure a live Emulator instance is available in the background.


For those of you who are not fortunate enough to be able to use the macOS VM, as an alternative I’d encourage you to have a look at Cirrus CI which provides KVM-enabled Linux VMs and pricing seems pretty reasonable (100% free for opensource projects). I wrote a long article going over all the features relevant to Android and how you can optimize your pipeline etc. I also created some templates to help you get started.

Hope that gives you some context regarding the Linux support, why it’s expected to be slow and the alternative you might want to look at.

Thanks!

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 39
  • Comments: 27 (8 by maintainers)

Commits related to this issue

Most upvoted comments

@JavierSegoviaCordoba I can do 😄, to be a bit more centralised for everyone watching I wrote up where we are at under https://github.com/actions/runner-images/issues/183

If y’all do any testing I would ❤️ feedback on the perf bits, I saw better perf on the 4-core than on the Macs which netted out pretty well 😃

Hey all, sorry I haven’t followed up. We are actively working on migration plans still for the existing 2-core machines to enable this. I am sorry it has taken longer than I expected 😞 and please keep an eye here for me to follow up.

Just did some benchmarking of the 4, 8, 16, 32 core linux agent with KVM enabled vs macOS

The project I used was a non-compose project with about 10 UI tests. This was a fully fledged and shipped android application and not a sample app.

The 4 core is super cheap and stable, and still significantly faster than macOS. (7min46sec vs 14min22sec total build + test time

The 8 core seemed to be the most cost-efficient and was about 33% faster than the 4 core.

The 16 core was a slight build improvement above the 8 core but almost the same testing time.

The 32 core had negligible increases from the 16 core and the testing time sometimes still took longer.

Seems like the 8 core will be what I’ll be trying to use, and is still only .032 cents per minute compared to the .08 cents per minute of the macOS agent.

4 Core .016 cents per minute Build 3m59s Test 3m47s Total 7m46s

8 Core .032 cents per minute Build 2m41s Test 2m47s Total 5m28s

16 Core .064 cents per minute Build 2m13s Test 2m50s Total 5m3s

32 Core: .128 cents per minute Build 2m4s Test 2m59s Total 5m3s

MacOS .08 cents per minute Build 5m15s Test 9m7s Total 14m22s

I actually ran this test with GMD and not ReactiveCircus but will do more benchmarking with avd caching + reactive circus later.

        managedDevices {
            devices {
                pixel2api30 (ManagedVirtualDevice) {
                    device = "Pixel 2"
                    apiLevel = 30
                    systemImageSource = "google"
                }
            }
        }

Linux is certainly not slow anymore! 😍

@nebuk89 - Alternatively, it would be awesome if the 4-core linux runner was free for public repos. In my case, when we switched from macOS to Linux for the KVM support, we drastically improved the stability of our test runs, but we also went from completely free to having a monthly cost associated with this. Our repo is public because its for an open source library, not an application.

FWIW, we started using a 4-core larger runner for our Android tests and it’s been working great so far.

See https://github.com/getsentry/sentry-dotnet/blob/main/.github/workflows/device-tests-android.yml#L46

The results are interesting. It’s not any slower than macOS, but it’s not dramatically faster either. However - it does seem to be more stable. When we were running on macOS, we frequently got failures booting the emulator before our tests could run at all. That doesn’t seem to be happening after the switch.

The downside is that the macOS runner was free (for public repos) and the 4-core Linux runner costs $0.016/min. We matrix for multiple versions of android, and run often, so it adds up.

I ran a test on the Ubuntu 20.04 64 core “large runner” and from what I could tell there was no KVM support.

See the run here: https://github.com/Expensify/App/actions/runs/3276676725/jobs/5393009096

Relevant Logs:

ProbeKVM: This user doesn't have permissions to use KVM (/dev/kvm).
  The KVM line in /etc/group is: [kvm:x:108:]
  
  If the current user has KVM permissions,
  the KVM line in /etc/group should end with ":" followed by your username.
  
  If we see LINE_NOT_FOUND, the kvm group may need to be created along with permissions:
      sudo groupadd -r kvm
      # Then ensure /lib/udev/rules.d/50-udev-default.rules contains something like:
      # KERNEL=="kvm", GROUP="kvm", MODE="0660"
      # and then run:
      sudo gpasswd -a $USER kvm
  
  If we see kvm:... but no username at the end, running the following command may allow KVM access:
      sudo gpasswd -a $USER kvm
  
  You may need to log out and back in for changes to take effect.
  
  WARNING | x86 emulation may not work without hardware acceleration!

I’m happy to run any further tests if I’ve misconfigured anything in the test, just let me know. cc @hannojg

I am glad this has helped folks out! Always feel free to reach out in the future ❤️ (I am working on 2-core support for KVM support currently so watch this space as well 😃 )

One gotcha I should mention. When first switching over, nothing worked. It crashed immediately when trying to start the emulator. The culprit turned out to be caching. We were using an AVD image built on macOS on the Linux runner. Easy fix - make sure that runner.os is part of the cache key.

@ychescale9 It seems like the new emulator only has that default Hypervisor Framework when called from the new tooling location, e.g. $ANDROID_HOME/emulator/emulator -accel-check and not from $ANDROID_HOME/tools/emulator – So, this means that VM Acceleration is always enabled by default when using the new emulator tooling from that $ANDROID_HOME/emulator/emulator location with the macOS agents on Github.

This is good information, and I will put in a PR to update the README. But, we still know how flakey things are without Graphics Acceleration as well.

So, it seems we don’t need HAXM – because that is only for fallback. But this is still less ideal than having both GPU + VM Acceleration.

This means that on Github Actions, MacOS Agents will have an advantage over Linux Agents when CPU/GPU stats are equal. (The tradeoff is that macOS agents are 10x more expensive than base ubuntu agents, 0.08 cents per minute vs 0.008 cents per minute)

I am trying to find time to benchmark a medium size UI Test repo on a larger ubuntu agent (beta - 16 core for example) vs the macOS agent.

PR here: https://github.com/ReactiveCircus/android-emulator-runner/pull/292

There is no better option at the moment @sisoje. Neither Linux nor Windows instances provided by Github action support hardware-accelerated VMs.

i just saw that windows machine has HAXM component installed https://github.com/actions/virtual-environments/blob/2378e1c967bc72fdf555f9b33e8c99446c06b4c4/images/win/Windows2016-Readme.md maybe thats the way to go (windows is still way cheaper than macOS)

This issue is no longer relevant as running hardware accelerated emulators with the upgraded free Linux runner is much faster than than the macOS runners:

https://github.blog/2024-01-17-github-hosted-runners-double-the-power-for-open-source/ https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/

On the KVM setup, we use the bits mentioned here: https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners

- name: Enable KVM group perms
  run: |
      echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
      sudo udevadm control --reload-rules
      sudo udevadm trigger --name-match=kvm

@nebuk89 can’t you share anything here?

I came to a similar conclusion as @alexlapa. HAXM doesn’t do anything on macOS now. See https://github.com/ReactiveCircus/android-emulator-runner/discussions/286

Looks like some people are running their tests using ubuntu and are happy with https://github.com/ably/ably-flutter/issues/232#issuecomment-1054020409, even if it is slower. Me I would be interested too, because we have a very short tests suite and because the cost. I found their config here https://github.com/ably/ably-flutter/blob/2481f2ba55caf64c9f3df3e34afea1e5b0db966c/.github/workflows/flutter_integration.yaml