Loading core/java/com/android/internal/os/MemcgProcMemoryUtil.java 0 → 100644 +118 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.os; import static android.os.Process.PROC_OUT_LONG; import android.annotation.Nullable; import android.os.Process; import android.util.Log; import java.io.BufferedReader; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public final class MemcgProcMemoryUtil { private static final String TAG = "MemcgProcMemoryUtil"; private static final int[] MEMCG_MEMORY_FORMAT = new int[] {PROC_OUT_LONG}; private static final String CGROUP_ROOT = "/sys/fs/cgroup"; private static final String PROC_ROOT = "/proc/"; private MemcgProcMemoryUtil() {} /** * Reads memcg accounting of memory stats of a process. * * Returns values of memory.current, memory.swap in bytes or -1 if not available. */ public static MemcgMemorySnapshot readMemcgMemorySnapshot(int pid) { String cgroupPath = getCgroupPathForPid(pid); if (cgroupPath == null) { return null; } return readMemcgMemorySnapshot(cgroupPath); } /** * Gets the cgroup v2 path for a given process ID (pid). * * @param pid The process ID. * @return The cgroup v2 path string, or null if not found or an error occurs. */ @Nullable private static String getCgroupPathForPid(int pid) { Path cgroupPathFile = Paths.get(PROC_ROOT, String.valueOf(pid), "cgroup"); try (BufferedReader reader = Files.newBufferedReader(cgroupPathFile)) { String line; while ((line = reader.readLine()) != null) { if (line.startsWith("0::")) { return line.substring(3); } } } catch (IOException e) { Log.d(TAG, "Failed to read cgroup file for pid " + pid, e); } return null; } private static MemcgMemorySnapshot readMemcgMemorySnapshot(String cgroupPath) { Path fullMemcgPath = Paths.get(CGROUP_ROOT, cgroupPath); final MemcgMemorySnapshot snapshot = new MemcgMemorySnapshot(); long[] currentMemoryOutput = new long[1]; String memoryCurrentPath = fullMemcgPath.resolve("memory.current").toString(); if (Process.readProcFile( memoryCurrentPath, MEMCG_MEMORY_FORMAT, null, currentMemoryOutput, null )) { snapshot.memcgMemoryInBytes = currentMemoryOutput[0]; } else { Log.d(TAG, "Failed to read memory.current for " + cgroupPath); return null; } long[] currentSwapMemoryOutput = new long[1]; String memorySwapPath = fullMemcgPath.resolve("memory.swap.current").toString(); if (Process.readProcFile( memorySwapPath, MEMCG_MEMORY_FORMAT, null, currentSwapMemoryOutput, null )) { snapshot.memcgSwapMemoryInBytes = currentSwapMemoryOutput[0]; } else { Log.d(TAG, "Failed to read memory.current for " + cgroupPath); return null; } return snapshot; } public static final class MemcgMemorySnapshot { public long memcgMemoryInBytes; public long memcgSwapMemoryInBytes; } } core/java/com/android/internal/os/OWNERS +3 −0 Original line number Original line Diff line number Diff line Loading @@ -19,3 +19,6 @@ per-file TimeoutRecord.java = file:/PERFORMANCE_OWNERS # ApplicationSharedMemory # ApplicationSharedMemory per-file *ApplicationSharedMemory* = file:/PERFORMANCE_OWNERS per-file *ApplicationSharedMemory* = file:/PERFORMANCE_OWNERS # Memcg memory accounting per-file MemcgProcMemoryUtil.java = file:/PERFORMANCE_OWNERS services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +44 −0 Original line number Original line Diff line number Diff line Loading @@ -50,6 +50,7 @@ import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID; import static android.util.MathUtils.constrain; import static android.util.MathUtils.constrain; import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID; import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID; import static com.android.internal.os.MemcgProcMemoryUtil.readMemcgMemorySnapshot; import static com.android.internal.os.ProcfsMemoryUtil.DmaBufType; import static com.android.internal.os.ProcfsMemoryUtil.DmaBufType; import static com.android.internal.os.ProcfsMemoryUtil.getProcessCmdlines; import static com.android.internal.os.ProcfsMemoryUtil.getProcessCmdlines; import static com.android.internal.os.ProcfsMemoryUtil.readCmdlineFromProcfs; import static com.android.internal.os.ProcfsMemoryUtil.readCmdlineFromProcfs; Loading @@ -76,6 +77,7 @@ import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STA import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY; import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY; import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN; import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN; import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller; import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller; import static com.android.server.stats.Flags.addMemcgInformationPuller; import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs; import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs; import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs; import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs; import static com.android.server.stats.pull.netstats.NetworkStatsUtils.fromPublicNetworkStats; import static com.android.server.stats.pull.netstats.NetworkStatsUtils.fromPublicNetworkStats; Loading Loading @@ -213,6 +215,7 @@ import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeRea import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader; import com.android.internal.os.LooperStats; import com.android.internal.os.LooperStats; import com.android.internal.os.MemcgProcMemoryUtil.MemcgMemorySnapshot; import com.android.internal.os.PowerProfile; import com.android.internal.os.PowerProfile; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.ProcfsMemoryUtil; import com.android.internal.os.ProcfsMemoryUtil; Loading Loading @@ -468,6 +471,10 @@ public class StatsPullAtomService extends SystemService { */ */ public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER = public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER = addMobileBytesTransferByProcStatePuller(); addMobileBytesTransferByProcStatePuller(); public static final boolean ENABLE_MEMCG_STATS_PULLER = addMemcgInformationPuller(); private static final ArrayMap<String, Integer> mPreviousThermalThrottlingStatus = private static final ArrayMap<String, Integer> mPreviousThermalThrottlingStatus = new ArrayMap<>(); new ArrayMap<>(); // Puller locks // Puller locks Loading Loading @@ -853,6 +860,8 @@ public class StatsPullAtomService extends SystemService { return pullCachedAppsHighWatermark(atomTag, data); return pullCachedAppsHighWatermark(atomTag, data); case FrameworkStatsLog.PRESSURE_STALL_INFORMATION: case FrameworkStatsLog.PRESSURE_STALL_INFORMATION: return pullPressureStallInformation(atomTag, data); return pullPressureStallInformation(atomTag, data); case FrameworkStatsLog.MEMCG_MEMORY_SNAPSHOT: return pullMemcgProcessMemoryInformation(atomTag, data); default: default: throw new UnsupportedOperationException("Unknown tagId=" + atomTag); throw new UnsupportedOperationException("Unknown tagId=" + atomTag); } } Loading Loading @@ -1053,6 +1062,9 @@ public class StatsPullAtomService extends SystemService { registerCachedAppsHighWatermarkPuller(); registerCachedAppsHighWatermarkPuller(); registerPressureStallInformation(); registerPressureStallInformation(); registerBatteryLife(); registerBatteryLife(); if (ENABLE_MEMCG_STATS_PULLER) { registerMemcgInformation(); } } } private void initMobileDataStatsPuller() { private void initMobileDataStatsPuller() { Loading Loading @@ -5234,6 +5246,38 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; return StatsManager.PULL_SUCCESS; } } private void registerMemcgInformation() { int tagId = FrameworkStatsLog.MEMCG_MEMORY_SNAPSHOT; mStatsManager.setPullAtomCallback( tagId, new PullAtomMetadata.Builder() .setCoolDownMillis(MILLIS_PER_SEC) .build(), DIRECT_EXECUTOR, mStatsCallbackImpl ); } int pullMemcgProcessMemoryInformation(int atomTag, List<StatsEvent> pulledData) { List<ProcessMemoryState> managedProcessList = LocalServices.getService(ActivityManagerInternal.class) .getMemoryStateForProcesses(); for (ProcessMemoryState managedProcess : managedProcessList) { final MemcgMemorySnapshot snapshot = readMemcgMemorySnapshot(managedProcess.pid); if (snapshot == null) { continue; } pulledData.add(FrameworkStatsLog.buildStatsEvent( atomTag, managedProcess.uid, managedProcess.processName, snapshot.memcgMemoryInBytes / 1024, snapshot.memcgSwapMemoryInBytes / 1024 )); } return StatsManager.PULL_SUCCESS; } private int toProtoPsiResourceType(PsiData.ResourceType resourceType) { private int toProtoPsiResourceType(PsiData.ResourceType resourceType) { if (resourceType == PsiData.ResourceType.CPU) { if (resourceType == PsiData.ResourceType.CPU) { return PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_CPU; return PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_CPU; Loading services/core/java/com/android/server/stats/stats_flags.aconfig +7 −0 Original line number Original line Diff line number Diff line Loading @@ -41,3 +41,10 @@ flag { bug: "398327159" bug: "398327159" is_fixed_read_only: true is_fixed_read_only: true } } flag { name: "add_memcg_information_puller" namespace: "statsd" description: "Adds memcgv2 atom logging" bug: "434920925" } No newline at end of file Loading
core/java/com/android/internal/os/MemcgProcMemoryUtil.java 0 → 100644 +118 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.os; import static android.os.Process.PROC_OUT_LONG; import android.annotation.Nullable; import android.os.Process; import android.util.Log; import java.io.BufferedReader; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public final class MemcgProcMemoryUtil { private static final String TAG = "MemcgProcMemoryUtil"; private static final int[] MEMCG_MEMORY_FORMAT = new int[] {PROC_OUT_LONG}; private static final String CGROUP_ROOT = "/sys/fs/cgroup"; private static final String PROC_ROOT = "/proc/"; private MemcgProcMemoryUtil() {} /** * Reads memcg accounting of memory stats of a process. * * Returns values of memory.current, memory.swap in bytes or -1 if not available. */ public static MemcgMemorySnapshot readMemcgMemorySnapshot(int pid) { String cgroupPath = getCgroupPathForPid(pid); if (cgroupPath == null) { return null; } return readMemcgMemorySnapshot(cgroupPath); } /** * Gets the cgroup v2 path for a given process ID (pid). * * @param pid The process ID. * @return The cgroup v2 path string, or null if not found or an error occurs. */ @Nullable private static String getCgroupPathForPid(int pid) { Path cgroupPathFile = Paths.get(PROC_ROOT, String.valueOf(pid), "cgroup"); try (BufferedReader reader = Files.newBufferedReader(cgroupPathFile)) { String line; while ((line = reader.readLine()) != null) { if (line.startsWith("0::")) { return line.substring(3); } } } catch (IOException e) { Log.d(TAG, "Failed to read cgroup file for pid " + pid, e); } return null; } private static MemcgMemorySnapshot readMemcgMemorySnapshot(String cgroupPath) { Path fullMemcgPath = Paths.get(CGROUP_ROOT, cgroupPath); final MemcgMemorySnapshot snapshot = new MemcgMemorySnapshot(); long[] currentMemoryOutput = new long[1]; String memoryCurrentPath = fullMemcgPath.resolve("memory.current").toString(); if (Process.readProcFile( memoryCurrentPath, MEMCG_MEMORY_FORMAT, null, currentMemoryOutput, null )) { snapshot.memcgMemoryInBytes = currentMemoryOutput[0]; } else { Log.d(TAG, "Failed to read memory.current for " + cgroupPath); return null; } long[] currentSwapMemoryOutput = new long[1]; String memorySwapPath = fullMemcgPath.resolve("memory.swap.current").toString(); if (Process.readProcFile( memorySwapPath, MEMCG_MEMORY_FORMAT, null, currentSwapMemoryOutput, null )) { snapshot.memcgSwapMemoryInBytes = currentSwapMemoryOutput[0]; } else { Log.d(TAG, "Failed to read memory.current for " + cgroupPath); return null; } return snapshot; } public static final class MemcgMemorySnapshot { public long memcgMemoryInBytes; public long memcgSwapMemoryInBytes; } }
core/java/com/android/internal/os/OWNERS +3 −0 Original line number Original line Diff line number Diff line Loading @@ -19,3 +19,6 @@ per-file TimeoutRecord.java = file:/PERFORMANCE_OWNERS # ApplicationSharedMemory # ApplicationSharedMemory per-file *ApplicationSharedMemory* = file:/PERFORMANCE_OWNERS per-file *ApplicationSharedMemory* = file:/PERFORMANCE_OWNERS # Memcg memory accounting per-file MemcgProcMemoryUtil.java = file:/PERFORMANCE_OWNERS
services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +44 −0 Original line number Original line Diff line number Diff line Loading @@ -50,6 +50,7 @@ import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID; import static android.util.MathUtils.constrain; import static android.util.MathUtils.constrain; import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID; import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID; import static com.android.internal.os.MemcgProcMemoryUtil.readMemcgMemorySnapshot; import static com.android.internal.os.ProcfsMemoryUtil.DmaBufType; import static com.android.internal.os.ProcfsMemoryUtil.DmaBufType; import static com.android.internal.os.ProcfsMemoryUtil.getProcessCmdlines; import static com.android.internal.os.ProcfsMemoryUtil.getProcessCmdlines; import static com.android.internal.os.ProcfsMemoryUtil.readCmdlineFromProcfs; import static com.android.internal.os.ProcfsMemoryUtil.readCmdlineFromProcfs; Loading @@ -76,6 +77,7 @@ import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STA import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY; import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY; import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN; import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN; import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller; import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller; import static com.android.server.stats.Flags.addMemcgInformationPuller; import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs; import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs; import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs; import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs; import static com.android.server.stats.pull.netstats.NetworkStatsUtils.fromPublicNetworkStats; import static com.android.server.stats.pull.netstats.NetworkStatsUtils.fromPublicNetworkStats; Loading Loading @@ -213,6 +215,7 @@ import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeRea import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader; import com.android.internal.os.LooperStats; import com.android.internal.os.LooperStats; import com.android.internal.os.MemcgProcMemoryUtil.MemcgMemorySnapshot; import com.android.internal.os.PowerProfile; import com.android.internal.os.PowerProfile; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.ProcfsMemoryUtil; import com.android.internal.os.ProcfsMemoryUtil; Loading Loading @@ -468,6 +471,10 @@ public class StatsPullAtomService extends SystemService { */ */ public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER = public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER = addMobileBytesTransferByProcStatePuller(); addMobileBytesTransferByProcStatePuller(); public static final boolean ENABLE_MEMCG_STATS_PULLER = addMemcgInformationPuller(); private static final ArrayMap<String, Integer> mPreviousThermalThrottlingStatus = private static final ArrayMap<String, Integer> mPreviousThermalThrottlingStatus = new ArrayMap<>(); new ArrayMap<>(); // Puller locks // Puller locks Loading Loading @@ -853,6 +860,8 @@ public class StatsPullAtomService extends SystemService { return pullCachedAppsHighWatermark(atomTag, data); return pullCachedAppsHighWatermark(atomTag, data); case FrameworkStatsLog.PRESSURE_STALL_INFORMATION: case FrameworkStatsLog.PRESSURE_STALL_INFORMATION: return pullPressureStallInformation(atomTag, data); return pullPressureStallInformation(atomTag, data); case FrameworkStatsLog.MEMCG_MEMORY_SNAPSHOT: return pullMemcgProcessMemoryInformation(atomTag, data); default: default: throw new UnsupportedOperationException("Unknown tagId=" + atomTag); throw new UnsupportedOperationException("Unknown tagId=" + atomTag); } } Loading Loading @@ -1053,6 +1062,9 @@ public class StatsPullAtomService extends SystemService { registerCachedAppsHighWatermarkPuller(); registerCachedAppsHighWatermarkPuller(); registerPressureStallInformation(); registerPressureStallInformation(); registerBatteryLife(); registerBatteryLife(); if (ENABLE_MEMCG_STATS_PULLER) { registerMemcgInformation(); } } } private void initMobileDataStatsPuller() { private void initMobileDataStatsPuller() { Loading Loading @@ -5234,6 +5246,38 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; return StatsManager.PULL_SUCCESS; } } private void registerMemcgInformation() { int tagId = FrameworkStatsLog.MEMCG_MEMORY_SNAPSHOT; mStatsManager.setPullAtomCallback( tagId, new PullAtomMetadata.Builder() .setCoolDownMillis(MILLIS_PER_SEC) .build(), DIRECT_EXECUTOR, mStatsCallbackImpl ); } int pullMemcgProcessMemoryInformation(int atomTag, List<StatsEvent> pulledData) { List<ProcessMemoryState> managedProcessList = LocalServices.getService(ActivityManagerInternal.class) .getMemoryStateForProcesses(); for (ProcessMemoryState managedProcess : managedProcessList) { final MemcgMemorySnapshot snapshot = readMemcgMemorySnapshot(managedProcess.pid); if (snapshot == null) { continue; } pulledData.add(FrameworkStatsLog.buildStatsEvent( atomTag, managedProcess.uid, managedProcess.processName, snapshot.memcgMemoryInBytes / 1024, snapshot.memcgSwapMemoryInBytes / 1024 )); } return StatsManager.PULL_SUCCESS; } private int toProtoPsiResourceType(PsiData.ResourceType resourceType) { private int toProtoPsiResourceType(PsiData.ResourceType resourceType) { if (resourceType == PsiData.ResourceType.CPU) { if (resourceType == PsiData.ResourceType.CPU) { return PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_CPU; return PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_CPU; Loading
services/core/java/com/android/server/stats/stats_flags.aconfig +7 −0 Original line number Original line Diff line number Diff line Loading @@ -41,3 +41,10 @@ flag { bug: "398327159" bug: "398327159" is_fixed_read_only: true is_fixed_read_only: true } } flag { name: "add_memcg_information_puller" namespace: "statsd" description: "Adds memcgv2 atom logging" bug: "434920925" } No newline at end of file