内存统计口径

整机内存占用

💡 整机内存占用 = 静态预留内存 + 内核占用内存 + 非Cached(oom_adj < 900)进程的Pss总和

静态预留内存 = DDR大小 - memTotal - GCMA

GCMA内存大小获取方式

  • 0.执行mount -t debugfs none /sys/kernel/debug;挂载debugfs
  • 1.先cd /sys/kernel/mm/cma/;
  • 2.遍历目录下每一个子目录<region name>,cat <region name>/gcma,过滤出值为1的region;
  • 3.cat /sys/kernel/debug/cma/<region name>/count;
  • 4.将所有gcma为1的region的count值累加,最后乘以4,单位为KB)。

内核占用内存 = Kernel(dumpsys meminfo中Used RAM:中的kernel值)- Dma_unmaped(dumpsys meminfo中DMA-BUF:的unmapped值)- GPU_private(dumpsys meminfo中的GPU:的private);它包含kernelCmaUsage,若内核没有使能CONFIG_CMA_FS,会导致dumpsys meminfo统计不到CMA的使用情况,内核内存就计算不准确。

内核占用内存也可以如下公式计算:

内核占用内存 = proc_meminfo.Shmem + proc_meminfo.SUnreclaim + proc_meminfo.VmallocUsed + proc_meminfo.PageTables + proc_meminfo.SecPageTables + proc_meminfo.KernelStack(当CONFIG_VMAP_STACK未使能时;其中iQoo已使能,小米P1已使能) + kernelCmaUsage(dumpsys meminfo中的 Kernel CMA值)

kernelCmaUsage有三种获取方式:

1)dumpsys meminfo中的 Kernel CMA值;

2)cat sys/kernel/mm/cma/<region name>/alloc_pages_success - cat sys/kernel/mm/cma/<region name>/release_pages_success,累加sys/kernel/mm/cma所有子目录,最后*4;

3)cat /sys/kernel/debug/cma/<region name>/used,累加/sys/kernel/debug/cma的所有子目录,最后*4;

进程Pss内存(root版本) = dumpsys_meminfo.TOTAL - dumpsys_meminfo.graphics + Gpu_memory(GL) + DMA_buf。

附件:shell脚本(token: Xd4SbqkQLocnhLxYpyEct0DynFh,下载权限不足,无法获取)

内存基线统计规则

白板流程图(内存基线统计方案思维导图):

整机内存基线

  • 方案一
    • 已达标部门内存基线
      • SR已达标进程内存基线(按OS4.0 实际占用 基线)
        • 非图形非DMA内存基线(按OS4.0 实际占用 基线)
        • 图形DMA内存基线(按OS4.0 实际占用 基线)
    • 未达标部门内存基线
      • SR已达标进程内存基线(按OS4.0 实际占用 基线)
        • 非图形非DMA内存基线(按OS4.0 实际占用 基线)
        • 图形DMA内存基线(按OS4.0 实际占用 基线)
      • SR未达标进程内存基线(按OS4.0 目标占用 基线)
  • 方案二
    • 已达标部门内存基线
      • SR已达标进程内存基线(按OS4.0 实际占用 基线)
        • 非图形非DMA内存基线(按OS4.0 实际占用 基线)
        • 图形DMA内存基线(按OS4.0 实际占用 基线)
    • 未达标部门内存基线(按OS4.0 目标占用 基线)
      • SR已达标进程内存基线(按OS4.0 实际占用 基线)
        • 非图形非DMA内存基线(按OS4.0 实际占用 基线)
        • 图形DMA内存基线(按OS4.0 实际占用 基线)
      • SR未达标进程暂不内存基线

内存基线分类表

进程内存

基线类型内存分类QcomMTKXRing备注
图形+DMAGLUNMAPPED=cat /sys/class/kgsl/kgsl/proc/<pid>/gpumem_unmapped
MAPPED=cat /sys/class/kgsl/kgsl/proc/<pid>/gpumem_mapped
GL_KB= (UNMAPPED + MAPPED)/1024
GL_KB=4 * (cat /proc/mtk_mali/gpu_memory 2)GL_KB=4 * (cat /proc/xgpu/gpu_memory 2)GPU驱动分配的内存:BufferObject, GLcommand, GLTexture, MemPool, VBO
涉及的进程pid:
Qcom: adb shell ls -l /d/kgsl/proc/
MTK: adb shell cat /proc/mtk_mali/gpu_memory
XRing: adb shell cat /proc/xgpu/gpu_memory
图形+DMADMA(含EGL)dmabuf_dumpdmabuf_dumpdmabuf_dump通过gralloc等分配dmabuf内存:图形、多媒体、相机。
图形+DMANative堆图形内存GPU驱动
libGLESv2_adreno, vulkan.adreno, libadreno_, libgsl, libCB, libRCC, libllvm-qgl, libllvm-glnext
Display HAL
mapper.qti, libgrallocutilsng, libgralloc.qti, libgralloc.system.qti, libgralloccore, vendor.qti.hardware.display.snapalloc-impl, libsdmcore, libsdmextension, libqservice, eglSubDriverAndroid
GPU驱动
libGLES_mali, vulkan.mali, libmtk_mali
Graphics
mapper.mediatek, libgralloc_extra, libmtk_drvb, libmtk_colortm
libGLES_mali, vulkan.mali
mapper.xring, vendor.xring.hardware.display.composerext, vendor.xring.hardware.gamingplus
GPU驱动动态库和显示框架动态库分配的native内存
Graphics Framework: libhwui, libgui, libEGL, libGLESv2, libvulkan, libui, libskia, libnativewindow
HAL/AIDL: android.hardware.graphics.allocator-V2-ndk, android.hardware.graphics.composer3
小米显示特性(XRing): vendor.xiaomi.hardware.display.mihwcextension, vendor.xiaomi.hardware.displayfeature_aidl, vendor.xiaomi.hardware.pqservice
非图形非DMA1、showmap <pid>
2、从showmap结果中提取/dev/kgsl-*的Pss内存作为GfxDev
3、Pss + SwapPss - Native堆图形内存 - GfxDev
1、showmap <pid>
2、Pss + SwapPss - Native堆图形内存
1、showmap <pid>
2、Pss + SwapPss - Native堆图形内存
包括代码、数据、文件、线程栈、java堆、native堆及其他内存;但排除native堆图形内存和GfxDev内存

内核/静态/LostRAM(字段定义中,具体数值待填充)

进程/子系统基线类型
内核内核占用
内核驱动占用
内核进程占用
静态预留HLOS
静态预留器件
LostRAM-

Dumpsys meminfo

Framework计算方式详情备注
Total RAM=meminfo.MemTotal
Free RAM=cachedPss + MoreCachedSize(原生CachedSize) + meminfo.FreecachedPss = ADJ>=900的所有进程Pss之和
MoreCachedSize = meminfo.KReclaimable + meminfo.Buffers + meminfo.Cached
CachedSize = MoreCachedSize - meminfo.Mapped
小米:重复统计了Adj >= 900进程的meminfo.Mapped;
Google: 未包含被进程
Used RAM= totalPss - cachedPss + kernelUsedtotalPss = 所有进程Pss之和 - memtrack_graphics + dmabufMapped - memtrack_gls(当gpuUsage和gpuPrivateUsage都大于等于0时)
kernelUsed = meminfo.Shmem + meminfo.SUnreclaim + meminfo.VmallocUsed + meminfo.PageTables + meminfo.KernelStack(当CONFIG_VMAP_STACK未使能时) + ionUnmapped(当ionHeap和ionPool都大于等于0时;ionUnmapped = ionHeap - dmabufMapped)或dmabufUnmapped(dmabufUnmapped = totalExportedDmabuf - dmabufMapped)+ gpuPrivateUsage(当gpuUsage和gpuPrivateUsage都大于等于0时)+ kernelCmaUsage
华为mate60 pro(鸿蒙4.2对应android S):
totalPss = 所有进程Pss之和(Pss仅包括GL内存)
kernelUsed = meminfo.Shmem + meminfo.SUnreclaim + meminfo.VmallocUsed + meminfo.PageTables + meminfo.KernelStack + gpuPrivateUsage(当gpuUsage大于0时)
其他友商竞品与小米一致
Lost RAM

图片展示的是Android系统中dumpsys meminfo代码片段。当满足特定条件时,代码会获取内存相关信息并打印。关键部分是使用memInfo对象获取cachedSizeKb、freeSizeKb、zramKb、kernelKb等内存大小,以及cachedPss、freePss等内存使用情况。代码还对lopts.isCompact条件分支进行判断,若为真则打印总RAM、cached RAM等信息,若为假则打印RAM、cached PSS等信息。该图片与文档中dumpsys meminfo内容紧密相关,展示了内存信息获取与打印的代码实现。

图片展示了一段C语言代码,用于计算和打印内存相关数据。代码中定义了多个内存大小变量,如ionHeap、ionPool、dmabufMapped等。通过if条件判断,计算并打印出ionHeap、ionPool、dmabufMapped等内存大小,还计算了totalExportedDmabuf、dmabufUnmapped等内存值。代码最后还处理了dmabufHeapExported内存值。该代码与文档中内存统计口径相关,用于获取内存使用情况数据。

图片展示的是Android系统中内存统计相关代码片段。代码用于获取和打印内存使用情况,包括DMA-BUF Heaps、GPU总使用量、GPU私有使用量、Kernel CMA使用量等。其中,DMA-BUF Heaps使用了Debug.getDbmbufHeapPoolsSizeKB()方法获取,GPU总使用量和私有使用量分别通过Debug.getGpuTotalUsageKb()和Debug.getGpuPrivateMemoryKb()获取,Kernel CMA使用量通过Debug.getKernelCmaUsageKb()获取。

图片展示的是Android系统中与内存统计相关的代码片段。代码中定义了多个内存统计变量,如lostRAM、kernelUsed等,并对这些变量进行计算和输出。其中,lostRAM通过减去其他内存值计算得出;kernelUsed等于kernelCmaUsage。

图片展示的是Android系统中Dumpsys meminfo命令相关代码片段。代码在判断是否简要输出时,若不简要,会打印ZRAM、Swap等内存信息;若简要,仅打印ZRAM、Swap总大小。接着获取ksm信息,若ksm共享、不共享、可变信息不为0,打印对应信息。最后获取进程列表的内存级别、缓存恢复阈值等信息。

图片展示的是Android系统中dumpsys meminfo命令相关代码片段。代码中使用if语句判断ksm相关常量值,若满足条件则打印ksm相关信息,如ksm共享、ksm共享未共享、ksm未共享、ksm可变等大小,以及tuning相关信息。还通过ActivityManager类获取内存类、大内存类等信息。

图片展示的是一段Java代码,涉及内存相关方法,如getCachedSizeKb()、getMoreCachedSizeKb()、getKernelUsedSizeKb()等。代码中对内存缓存大小获取及内核使用内存量计算等进行了定义,包含注释说明,如MEMINFO_RECLAIMABLE包含MEMINFO_SLAB_RECLAIMABLE和ION池等信息,还提及了在旧内核情况下的处理方式等。

ionHeap

图片展示的是Android系统中android_os_Debug_getIonHeapsSizeKb函数的代码。该函数用于获取Ion堆的大小,以KB为单位返回。函数首先初始化heapsSizeKb为-1,然后通过meminfo::ReadIonHeapsSizeKb(&size)调用内部函数读取Ion堆大小,若读取成功则将size赋值给heapsSizeKb,最后返回heapsSizeKb。

图片展示的是一个C++函数代码片段,用于读取ION堆的分配大小(以KB为单位)。函数名为ReadIonHeapsSizeKb,接受两个参数:uint64_t* size,用于接收分配大小;const std::string& path,可选参数,指定路径/sys/kernel/ion/total_heaps_kb路径,默认为该路径。

图片展示的是一个C++函数代码片段,函数名为ReadIonHeapsSizeKb,参数为uint64_t* size和const std::string& path,返回值为bool。函数内部调用了ReadSysfsFile函数。

图片展示的是一个名为ReadSysfsFile的函数代码。该函数接收一个字符串类型的path参数和一个uint64_t*类型的value参数。函数首先调用android::base::ReadFileToString读取指定路径的文件内容,若读取失败则记录错误信息并返回false。接着将读取到的内容转换为uint64_t类型赋值给value,若转换失败同样返回false。最后无论成功与否,函数均返回true。

ionPool

图片展示的是Android系统中android_os_Debug_getIonPoolsSizeKb函数的代码。该函数用于获取Ion池大小(以KB为单位)。函数首先初始化poolsSizeKb为-1,然后通过meminfo::ReadIonPoolsSizeKb函数读取Ion池大小,若读取成功则将size赋值给poolsSizeKb,最后返回poolsSizeKb。

图片展示的是一个C++函数代码片段,函数名为ReadIonPoolsSizeKb,用于读取ION池分配大小(以KB为单位)。函数接受两个参数:uint64_t* size,用于接收读取到的大小值;const std::string& path,为读取路径,默认为"/sys/kernel/ion/total_pools_kb"。

图片展示了一段代码内容,主要是名为ReadIonPoolsSizeKb的函数,函数接收一个指向uint64_t类型的指针size和一个const std::string&类型的路径path作为参数,函数体内通过调用ReadSysfsFile函数,并传入path和size参数,最后返回ReadSysfsFile函数的执行结果。

totalExportedDmabuf

图片展示的是Android系统中android_os_Debug_getDmabufTotalExportedKb函数的代码。该函数用于获取总导出的dmabuf大小,以KB为单位。函数首先初始化dmabufTotalSizeKb为-1,然后通过dmabufinfo::GetDmabufTotalExportedKb(&size)获取大小,若获取成功则将size赋值给dmabufTotalSizeKb,最后返回dmabufTotalSizeKb。

图片展示的是Android系统中与DMA-BUF相关的函数代码。函数名为GetDmabufTotalExportedKb,用于计算所有导出的DMA-BUF总大小,以KB为单位。该函数不读取或解析DMA-BUF sysfs统计的其余部分以提高性能。函数参数包括uint64_t类型的total_exported,以及const std::string&类型的path,当前设置为"/sys/kernel/dmabuf/buffers"。此函数仅用于单元测试,不供客户端使用。

图片展示的是一个C++函数代码,用于获取dmabuf_total_exported_kb值。函数名为GetDmabufTotalExportedKb,接受uint64_t* total_exported和const std::string& dmabuf_sysfs_stats_path两个参数。代码首先打开指定路径的目录,若无法访问则返回false。接着遍历目录内容,对每个项,通过拼接路径读取其"/size"下的size值,累加到total_exported中。最后将total_exported除以1024并返回true。

dmabufMapped

图片展示的是Android系统中android_os_Debug_getDmabufMappedSizeKb函数的代码实现。该函数用于获取DmaBuf映射的大小。代码首先初始化dmabufPss为0,然后通过opendir打开/proc目录,若失败则返回false。接着循环读取目录中的文件,将文件名转换为进程ID,若ID为0则跳过。若读取映射失败则记录错误。最后遍历DmaBuf信息,将每个DmaBuf的大小除以1024累加到dmabufPss中,最终返回dmabufPss。

图片展示的是一个C++函数的代码片段,函数名为ReadDmaBufMapRefs。该函数用于将特定进程的已映射dmabuf对象添加到现有向量中,若向量中已有匹配inode的元素,则更新引用计数。函数成功返回true,否则返回false。函数参数包括进程ID、dmabuf对象向量、procfs路径和dmabuf_sysfs路径。

图片展示的是一个名为ReadDmaBufMapRefs的函数代码,用于读取dma_bufs映射引用。函数接受进程ID、std::vector<DmaBuffer*>类型的dmabufs、std::string类型的procs_path和dma_bufs_sysfs_path参数。首先打开映射文件,若失败则返回false。接着处理映射,若为dma_buf,则处理映射到现有对象的引用;若未直接找到,尝试创建新对象并添加到dmabufs。若为FileDmaBuf,检查映射引用,若不为空则返回。若创建新DmaBuffer,尝试从sysfs获取出口器名称,最后更新DmaBuffer引用。

图片展示的是一个C++代码片段,用于处理内存统计相关数据。代码中使用getline函数逐行读取文件内容,若读取的行数据无法通过android::procinfo::ReadMapFileContent函数解析,将记录错误信息并返回false。若所有行均能正常解析,则返回true。

gpuUsage

图片展示的是Android系统中android_os_Debug_getGpuTotalUsageKb函数的代码。该函数接收JNIEnv*和jobject参数,用于获取GPU总使用量。代码中,先获取sizeKb变量,若meminfo::ReadGpuTotalUsageKb读取成功,则将size赋值给sizeKb,最后返回sizeKb。

图片展示的是一个C语言函数ReadGpuTotalUsageKb的代码。该函数接收一个uint64_t类型的指针参数size,用于返回GPU总使用量。注释说明gpu_mem_total tracepoint将PID 0定义为全局总和,当前Android设备GPU ID 0即可满足需求,未来若设备有多个GPU,需检查所有GPU ID。函数通过调用ReadProcessGpuUsageKb函数来获取GPU总使用量。

图片展示的是一个C++函数代码,用于读取GPU使用情况。函数名为ReadProcessGpuUsageKb,接受uint32_t类型的pid和gpu_id参数。代码首先检查是否满足特定条件,然后定义常量kBpfGpuMemTotalMap。接着通过BpfMapR0读取GPU内存使用情况,若读取成功则获取gpu_mem值,若失败则返回0。最后,若size不为0则将其值除以1024,函数返回true。

gpuPrivateUsage

图片展示的是Android系统中获取GPU私有内存的代码实现。代码首先检查是否支持GPU私有内存,然后创建memtrack_proc结构体,若失败则返回错误。接着获取GPU私有内存总量,若旧HAL实现不支持则返回-1,否则返回内存值除以1024。

图片展示的是一个C++函数代码,用于判断HAL是否支持GPU私有内存。函数名为halSupportsGpuPrivateMemory,首先获取ro.product.first_api_level和ro.build.version.sdk的整数值,分别赋值给productApiLevel和boardApiLevel。接着,通过std::min函数取两者最小值,判断其是否大于等于_ANDROID_API_S_,最终返回判断结果。

图片展示的是memtrack_proc_get函数的代码。该函数接收memtrack_proc指针p和pid_t类型的参数pid。首先检查p是否为NULL,若为NULL则返回-EINVAL。接着将p中pid赋值给p->pid,遍历kMemtrackTypes.size()个类型,调用memtrack_proc_get_type函数获取类型相关信息,若返回值不为0则返回该值。最后调用memtrack_proc_sanity_check函数进行检查。

图片展示的是一个C++函数代码,名为memtrack_proc_get_type,用于获取进程的内存跟踪类型。函数接收memtrack_proc_type *t、pid_t pid和MemtrackType type三个参数。首先获取IMemtrack代理服务,若失败返回-1。接着调用服务的getMemory方法获取内存记录,若失败也返回-1。最后遍历记录,将sizeInBytes和flags赋值给t->records数组对应位置。

图片展示的是一个C语言函数memtrack_proc_gl_pss的代码。函数接受一个memtrack_proc类型的指针参数p,返回值类型为ssize_t。在函数内部,先将MemtrackType中的GL类型存入std::vector<MemtrackType>类型的变量types中,然后调用memtrack_proc_sum函数,传入p、types、以及(uint32_t)MemtrackRecord::FLAG_SMAPS_UNACCOUNTED作为参数。

图片展示的是一个C++函数memtrack_proc_sum的代码。函数接受memtrack_proc指针p、类型向量types和uint32_t flags参数。首先初始化sum为0,接着遍历types向量,获取每个type对应的records向量,再遍历records向量,若records[j].flags与flags相等,则将records[j].sizeInBytes累加到sum中。最后返回sum。

kernelCmaUsage

图片展示的是Android系统中android_os_Debug_getKernelCmaUsageKb函数的代码。该函数用于获取内核CMA(Contiguous Memory Allocator)的使用情况,单位为KB。函数首先初始化totalKernelCmaUsageKb为-1,接着调用meminfo::ReadKernelCmaUsageKb读取内核CMA使用大小,若读取成功则将读取值赋给totalKernelCmaUsageKb,最后返回该值。

图片展示的是一个C++函数代码片段,用于读取内核驱动程序通过CMA分配的内存总量,单位为KB。函数名为ReadKernelCmaUsageKb,参数包括uint64_t* size用于接收内存总量,以及const std::string& cma_stats_sysfs_path,其默认值为"/sys/kernel/mm/cma",用于指定CMA状态的sysfs路径。

图片展示的是一个C++函数代码,用于读取CMA(Chunked Memory Allocation)相关内存使用情况。函数名为ReadKernelCmaUsageKb,接受uint64_t* size和const std::string& cma_stats_sysfs_path参数。代码中通过遍历目录获取alloc_pages_success和release_pages_success文件路径,分别读取其内容,计算并更新totalKernelCmaUsageKb变量,最后将totalKernelCmaUsageKb转换为KB并返回。

应用的gpu内存和dma内存

图片展示的是dumpsys meminfo命令的输出结果,重点突出Gfx dev、EGL mtrack、GL mtrack、Graphics等内存相关数据。其中Gfx dev内存为2424 KB,EGL mtrack内存为4848 KB,GL mtrack内存为384 KB,Graphics内存为7652 KB。

💡 Dumpsys meminfo中应用GLmtrack和EGLmtrack都是Memtrack通过各芯片厂商实现的Hal从内核节点中获取的;

图片展示了Android系统中获取应用GPU内存和DMA内存的流程图。从dumpApplicationMemoryUsage开始,经getMemoryInfo等步骤,最终通过getGpuPrivateMemoryKb获取GPU私有内存,再通过android_os_Debug_getGpuPrivateMemoryKb获取GPU私有内存KB。流程中还涉及Memtrack、MemtrackProxy等对象,以及HAL、JNI等接口。

GLmtrack

1)Qcom:仅统计了进程 unmapped的gpumem(cat sys/class/kgsl/kgsl/proc/5796/gpumem_unmapped,393216Bytes = 384KB,对应dumpsys meminfo截图中GLmtrack)

图片展示了在Linux系统中cat命令操作sys/class/kgsl/kgsl/proc/5796目录下gpumem_unmapped和gpumem_mapped文件的内容。其中,gpumem_unmapped文件显示393216Bytes=384KB,对应dumpsys meminfo截图中GLmtrack;gpumem_mapped文件显示13729792Bytes=13280KB。

2)MTK:统计kctx的used_pages(cat /proc/mtk_mali/gpu_memory | grep <pid>)

3)Xring:统计kctx的used_pages(cat /proc/xgpu/gpu_memory | grep <pid>)

EGLmtrack

1)Qcom:统计进程分配的ion内存(cat /d/kgsl/proc/5796/mem | grep ion)

图片展示的是在Qcom平台下,通过cat命令读取d/kgsl/proc/5796/mem文件,再grep ion过滤出相关内存信息的内容。显示了多个内存块的详细信息,包括inode、wlb、type、surface、size等参数,如inode为708688的内存块,type为ion,surface为23,size为998等。

2)MTK:统计进程正使用的按比例分摊的dma内存(echo “pid: <pid>” > proc/dma_heap/rss_pid, cat proc/dma_heap/rss_pid, 取userspace_pss)

3)Xring:统计进程(待补充)

dmabuf

dmabuf_dump命令导出的结果中按pid匹配找到进程的dmabuf信息块,对比EGLmtrack(例如:高通平台 cat /d/kgsl/proc/5796/mem | grep ion),可以看到完全被dmabuf包含(按Inode匹配)

图片展示的是Android系统中dmabuf_dump命令导出的结果,按pid匹配找到进程的dmabuf信息。

💡 为了准确评估应用业务的内存占用,需要将gpu内存和dmabuf内存按showmap的Pss内存分摊方式,归属给具体业务进程,请芯片厂商考虑修改memtrack统计方式,或评估是否认可如下计算方式(先剔除memtrace返回的GL和EGL,然后通过从kgsl内核节点和dmabuf_dump命令获取gpu和dmabuf的Pss值):

进程Pss内存 = PSS(dumpsys) + SwapPss(dumpsys) - Graphic(dumpsys) + unmapped_gpumem(cat sys/class/kgsl/kgsl/proc/5796/gpumem_unmapped) + mapped_gpumem(cat sys/class/kgsl/kgsl/proc/5796/gpumem_mapped) + dmabuf_pss(dmabuf_dump结果中匹配进程pid)