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