1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.server.stats.pull; 17 18 import android.os.FileUtils; 19 import android.util.Slog; 20 import android.util.SparseArray; 21 22 import com.android.internal.annotations.VisibleForTesting; 23 24 import java.io.File; 25 import java.io.IOException; 26 import java.util.ArrayList; 27 import java.util.Collections; 28 import java.util.List; 29 import java.util.Objects; 30 import java.util.regex.Matcher; 31 import java.util.regex.Pattern; 32 33 /** 34 * Utility methods for reading ion memory stats. 35 * TODO: Consider making package private after puller migration 36 */ 37 public final class IonMemoryUtil { 38 private static final String TAG = "IonMemoryUtil"; 39 40 /** Path to debugfs file for the system ion heap. */ 41 private static final String DEBUG_SYSTEM_ION_HEAP_FILE = "/sys/kernel/debug/ion/heaps/system"; 42 43 private static final Pattern ION_HEAP_SIZE_IN_BYTES = 44 Pattern.compile("\n\\s*total\\s*(\\d+)\\s*\n"); 45 private static final Pattern PROCESS_ION_HEAP_SIZE_IN_BYTES = 46 Pattern.compile("\n\\s+\\S+\\s+(\\d+)\\s+(\\d+)"); 47 IonMemoryUtil()48 private IonMemoryUtil() {} 49 50 /** 51 * Reads size of the system ion heap from debugfs. 52 * 53 * Returns value of the total size in bytes of the system ion heap from 54 * /sys/kernel/debug/ion/heaps/system. 55 */ readSystemIonHeapSizeFromDebugfs()56 public static long readSystemIonHeapSizeFromDebugfs() { 57 return parseIonHeapSizeFromDebugfs(readFile(DEBUG_SYSTEM_ION_HEAP_FILE)); 58 } 59 60 /** 61 * Parses the ion heap size from the contents of a file under /sys/kernel/debug/ion/heaps in 62 * debugfs. The returned value is in bytes. 63 */ 64 @VisibleForTesting parseIonHeapSizeFromDebugfs(String contents)65 static long parseIonHeapSizeFromDebugfs(String contents) { 66 if (contents.isEmpty()) { 67 return 0; 68 } 69 final Matcher matcher = ION_HEAP_SIZE_IN_BYTES.matcher(contents); 70 try { 71 return matcher.find() ? Long.parseLong(matcher.group(1)) : 0; 72 } catch (NumberFormatException e) { 73 Slog.e(TAG, "Failed to parse value", e); 74 return 0; 75 } 76 } 77 78 /** 79 * Reads process allocation sizes on the system ion heap from debugfs. 80 * 81 * Returns values of allocation sizes in bytes on the system ion heap from 82 * /sys/kernel/debug/ion/heaps/system. 83 */ readProcessSystemIonHeapSizesFromDebugfs()84 public static List<IonAllocations> readProcessSystemIonHeapSizesFromDebugfs() { 85 return parseProcessIonHeapSizesFromDebugfs(readFile(DEBUG_SYSTEM_ION_HEAP_FILE)); 86 } 87 88 /** 89 * Parses per-process allocation sizes on the ion heap from the contents of a file under 90 * /sys/kernel/debug/ion/heaps in debugfs. 91 */ 92 @VisibleForTesting parseProcessIonHeapSizesFromDebugfs(String contents)93 static List<IonAllocations> parseProcessIonHeapSizesFromDebugfs(String contents) { 94 if (contents.isEmpty()) { 95 return Collections.emptyList(); 96 } 97 98 final Matcher m = PROCESS_ION_HEAP_SIZE_IN_BYTES.matcher(contents); 99 final SparseArray<IonAllocations> entries = new SparseArray<>(); 100 while (m.find()) { 101 try { 102 final int pid = Integer.parseInt(m.group(1)); 103 final long sizeInBytes = Long.parseLong(m.group(2)); 104 IonAllocations allocations = entries.get(pid); 105 if (allocations == null) { 106 allocations = new IonAllocations(); 107 entries.put(pid, allocations); 108 } 109 allocations.pid = pid; 110 allocations.totalSizeInBytes += sizeInBytes; 111 allocations.count += 1; 112 allocations.maxSizeInBytes = Math.max(allocations.maxSizeInBytes, sizeInBytes); 113 } catch (NumberFormatException e) { 114 Slog.e(TAG, "Failed to parse value", e); 115 } 116 } 117 118 final List<IonAllocations> result = new ArrayList<>(entries.size()); 119 for (int i = 0; i < entries.size(); i++) { 120 result.add(entries.valueAt(i)); 121 } 122 return result; 123 } 124 readFile(String path)125 private static String readFile(String path) { 126 try { 127 final File file = new File(path); 128 return FileUtils.readTextFile(file, 0 /* max */, null /* ellipsis */); 129 } catch (IOException e) { 130 Slog.e(TAG, "Failed to read file", e); 131 return ""; 132 } 133 } 134 135 /** Summary information about process ion allocations. */ 136 public static final class IonAllocations { 137 /** PID these allocations belong to. */ 138 public int pid; 139 /** Size of all individual allocations added together. */ 140 public long totalSizeInBytes; 141 /** Number of allocations. */ 142 public int count; 143 /** Size of the largest allocation. */ 144 public long maxSizeInBytes; 145 146 @Override equals(Object o)147 public boolean equals(Object o) { 148 if (this == o) return true; 149 if (o == null || getClass() != o.getClass()) return false; 150 IonAllocations that = (IonAllocations) o; 151 return pid == that.pid && totalSizeInBytes == that.totalSizeInBytes 152 && count == that.count && maxSizeInBytes == that.maxSizeInBytes; 153 } 154 155 @Override hashCode()156 public int hashCode() { 157 return Objects.hash(pid, totalSizeInBytes, count, maxSizeInBytes); 158 } 159 160 @Override toString()161 public String toString() { 162 return "IonAllocations{" 163 + "pid=" + pid 164 + ", totalSizeInBytes=" + totalSizeInBytes 165 + ", count=" + count 166 + ", maxSizeInBytes=" + maxSizeInBytes 167 + '}'; 168 } 169 } 170 } 171