How to automatically check memory usage of your app

Mobile App Performance Testing Image

20 December 2021 Leave a comment QA

To provide the best experience to your app users it’s a great idea to check the memory usage of your app.

In this article, we take a look at how this can be done on iOS and Android and also show how this process can be automated.

Checking memory usage of your Android app

ADB (Android Debug Bridge) provides an easy way to fetch a memory usage profile. You can execute ‘adb shell dumpsys meminfo’ to get the whole dump.
But what if you want to automatically check the memory consumption for every new release of your app? And what if you want to check the memory consumption at several places within your use cases?
I guess you’d rather automate the checks.

So here is how you can do it with Repeato.

Repeato provides the JS-Script step feature, which also allows you to execute ADB commands and process the returned information:

const packageName = 'com.google.android.calculator' // <- your app's packagename
const getValue = function(lines, keyword){
   const linesArr = lines.split('\\n')
   const theLine = linesArr.filter(line => line.trim().indexOf(keyword) === 0)[0]
   const cleaned = theLine.replace(keyword, '').replace(/\s+/g,' ').trim()
   const firstValue = cleaned.split(' ')[0]
   return parseInt(firstValue)
}
const meminfo = await deviceConnector.sendAdbCommand('shell dumpsys meminfo '+packageName)

// if you want to check certain values:
//const nativeHeapMem = getValue(meminfo, 'Native Heap')
//const dalvikHeapMem = getValue(meminfo, 'Dalvik Heap')
//const dalvikOtherMem = getValue(meminfo, 'Dalvik Other')

const totalMem = getValue(meminfo, 'TOTAL')

if(totalMem > 40000){ // adjust to your needs!
   throw new Error('App is consuming too much memory: '+totalMem)
}
return 'Total memory used (kilobytes): '+totalMem

To reuse this code in several different spots within your test setup, we recommend saving just this single step as a test to your library, so you can import it via a “Sub test step” wherever you might need to check the memory usage within your automation.

Here is a little screencast of what all of this looks like:

More about Android memory usage

Since interpreting the memory dump is far from obvious, here are some explanations of what the different rows and columns refer to. Be aware that every application in Android runs in a separate instance of its own VM (Dalvik or nowadays ART).

Native Heap: represents memory used by the process itself (Ex: Native C mallocs).
Dalvik Heap: represents memory allocated by the VM (Ex: Variables in your Android code).
Dalvik Other: is memory used for JIT and garbage collection.

Android may share memory among several processes (e.g. within common frameworks).
Clean memory: one that hasn’t changed since it was allocated or loaded from storage (Code of your application).
Dirty memory: is space used for computations.
Android does not have a swap mechanism so Dirty memory is also RAM that will be freed when the app exits.

RSS: Resident Set Size: Actual physical memory used (including memory occupied by shared libraries)
PSS: Proportional Set Size: Actual physical memory used (proportional allocation of memory occupied by shared libraries)

App Sumary explained:

Java: Memory from objects allocated from Java or Kotlin code.
Native: Memory from objects allocated from C or C++ code.
Even if you’re not using C++ in your app, you might see some native memory used here because the Android framework uses native memory to handle various tasks on your behalf, such as when handling image assets and other graphics—even though the code you’ve written is in Java or Kotlin
Graphics: Memory used for graphics buffer queues to display pixels to the screen, including GL surfaces, GL textures, and so on. (Note that this is memory shared with the CPU, not dedicated GPU memory.)
Stack: Memory used by both native and Java stacks in your app. This usually relates to how many threads your app is running.

Checking memory usage of your iOS app

Via Xcode

As your application runs in Xcode, the Debug navigator’s memory report displays its current and peak memory usage. A yellow area on the memory gauge signals elevated memory use that may prompt a warning. If memory consumption reaches the red zone, the app faces potential termination by iOS.

You can read more about viewing memory information via Xcode here: https://developer.apple.com/documentation/xcode/gathering-information-about-memory-use

Getting the iOS memory info programmatically

To fetch the memory info from inside of your app, you can use this swift class to fetch and print the memory info inside of your app. Then you can show the memory usage somewhere inside of your UI. You might only want to show it in your dev or test version of the app.

class Memory: NSObject {

    // From Quinn the Eskimo at Apple.
    // https://forums.developer.apple.com/thread/105088#357415

    class func memoryFootprint() -> Float? {
        // The `TASK_VM_INFO_COUNT` and `TASK_VM_INFO_REV1_COUNT` macros are too
        // complex for the Swift C importer, so we have to define them ourselves.
        let TASK_VM_INFO_COUNT = mach_msg_type_number_t(MemoryLayout.size / MemoryLayout.size)
        let TASK_VM_INFO_REV1_COUNT = mach_msg_type_number_t(MemoryLayout.offset(of: \task_vm_info_data_t.min_address)! / MemoryLayout.size)
        var info = task_vm_info_data_t()
        var count = TASK_VM_INFO_COUNT
        let kr = withUnsafeMutablePointer(to: &info) { infoPtr in
            infoPtr.withMemoryRebound(to: integer_t.self, capacity: Int(count)) { intPtr in
                task_info(mach_task_self_, task_flavor_t(TASK_VM_INFO), intPtr, &count)
            }
        }
        guard
            kr == KERN_SUCCESS,
            count >= TASK_VM_INFO_REV1_COUNT
            else { return nil }
        
        let usedBytes = Float(info.phys_footprint)
        return usedBytes
    }
    
    class func formattedMemoryFootprint() -> String
    {
        let usedBytes: UInt64? = UInt64(self.memoryFootprint() ?? 0)
        let usedMB = Double(usedBytes ?? 0) / 1024 / 1024
        let usedMBAsString: String = "\(usedMB)MB"
        return usedMBAsString
     }
}

To fetch memory information from outside of your app (for example if you don’t have the source code), you can use the xctrace command to generate a trace file.

This example starts the app with package id “app.repeato.film-o-matic” and traces the memory allocation for 5 seconds:

xctrace record --template 'Allocations' --device 3E1E88AD-729C-4575-84B4-221C5A59B4D6 --launch app.repeato.film-o-matic --time-limit 5s

After that you can just open the .trace file by double clicking it. The “Instruments” app will allow you to inspect the trace file:

Here are some more examples of what you can do with xctrace:

xctrace record --template 'Time Profiler' --all-processes --time-limit 5s
xctrace record --template 'Time Profiler' --all-processes --output 'recording.trace'
xctrace record --all-processes --append-run --output 'existing.trace'
xctrace record --template 'System Trace' --attach 4215
xctrace record --template 'Time Profiler' --device-name "Chad's iPhone" --attach 'Trailblazer'
xctrace record --template 'Metal System Trace' --device-name 'iPhone SE Simulator' --all-processes
xctrace record --template 'Allocations' --env KEY=VALUE --launch -- MyApp.app
xctrace record --template 'Time Profiler' --target-stdout - --launch -- /tmp/tool arg1 arg2
Batch

Like this article? there’s more where that came from.