How to automatically check memory usage of your app

20 December 2021 katharina 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.

<span class="token keyword">class</span> <span class="token class-name">Memory</span><span class="token punctuation">:</span> <span class="token builtin">NSObject</span> <span class="token punctuation">{</span>

    <span class="token comment">// From Quinn the Eskimo at Apple.</span>
    <span class="token comment">// https://forums.developer.apple.com/thread/105088#357415</span>

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

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 <span class="token string">'Allocations'</span> --device 3E1E88AD-729C-4575-84B4-221C5A59B4D6 --launch app.repeato.film-o-matic --time-limit 5s
Bash

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:

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

Tags: , , ,

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