blob: 8c2fc3ed1daeff87612c824e477a9ff2034d15ff [file] [log] [blame]
Andreas Gampe37e5fdc2016-07-12 22:42:41 -07001/*
2 * Copyright (C) 2016 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
17package com.android.server.pm;
18
19import android.util.ArrayMap;
20import android.util.AtomicFile;
21import android.util.Log;
22
23import com.android.internal.util.FastPrintWriter;
24import com.android.internal.util.IndentingPrintWriter;
25
26import libcore.io.IoUtils;
27
28import java.io.BufferedReader;
29import java.io.File;
30import java.io.FileNotFoundException;
31import java.io.FileOutputStream;
32import java.io.IOException;
33import java.io.InputStreamReader;
34import java.io.OutputStreamWriter;
35import java.io.Reader;
36import java.io.Writer;
37import java.util.HashMap;
38import java.util.Map;
39
40/**
41 * A class that collects, serializes and deserializes compiler-related statistics on a
42 * per-package per-code-path basis.
43 *
44 * Currently used to track compile times.
45 */
46class CompilerStats extends AbstractStatsBase<Void> {
47
48 private final static String COMPILER_STATS_VERSION_HEADER = "PACKAGE_MANAGER__COMPILER_STATS__";
49 private final static int COMPILER_STATS_VERSION = 1;
50
51 /**
52 * Class to collect all stats pertaining to one package.
53 */
54 static class PackageStats {
55
56 private final String packageName;
57
58 /**
59 * This map stores compile-times for all code paths in the package. The value
60 * is in milliseconds.
61 */
62 private final Map<String, Long> compileTimePerCodePath;
63
64 /**
65 * @param packageName
66 */
67 public PackageStats(String packageName) {
68 this.packageName = packageName;
69 // We expect at least one element in here, but let's make it minimal.
70 compileTimePerCodePath = new ArrayMap<>(2);
71 }
72
73 public String getPackageName() {
74 return packageName;
75 }
76
77 /**
78 * Return the recorded compile time for a given code path. Returns
79 * 0 if there is no recorded time.
80 */
81 public long getCompileTime(String codePath) {
82 String storagePath = getStoredPathFromCodePath(codePath);
83 synchronized (compileTimePerCodePath) {
84 Long l = compileTimePerCodePath.get(storagePath);
85 if (l == null) {
86 return 0;
87 }
88 return l;
89 }
90 }
91
92 public void setCompileTime(String codePath, long compileTimeInMs) {
93 String storagePath = getStoredPathFromCodePath(codePath);
94 synchronized (compileTimePerCodePath) {
95 if (compileTimeInMs <= 0) {
96 compileTimePerCodePath.remove(storagePath);
97 } else {
98 compileTimePerCodePath.put(storagePath, compileTimeInMs);
99 }
100 }
101 }
102
103 private static String getStoredPathFromCodePath(String codePath) {
104 int lastSlash = codePath.lastIndexOf(File.separatorChar);
105 return codePath.substring(lastSlash + 1);
106 }
107
108 public void dump(IndentingPrintWriter ipw) {
109 synchronized (compileTimePerCodePath) {
110 if (compileTimePerCodePath.size() == 0) {
111 ipw.println("(No recorded stats)");
112 } else {
113 for (Map.Entry<String, Long> e : compileTimePerCodePath.entrySet()) {
114 ipw.println(" " + e.getKey() + " - " + e.getValue());
115 }
116 }
117 }
118 }
119 }
120
121 private final Map<String, PackageStats> packageStats;
122
123 public CompilerStats() {
124 super("package-cstats.list", "CompilerStats_DiskWriter", /* lock */ false);
125 packageStats = new HashMap<>();
126 }
127
128 public PackageStats getPackageStats(String packageName) {
129 synchronized (packageStats) {
130 return packageStats.get(packageName);
131 }
132 }
133
134 public void setPackageStats(String packageName, PackageStats stats) {
135 synchronized (packageStats) {
136 packageStats.put(packageName, stats);
137 }
138 }
139
140 public PackageStats createPackageStats(String packageName) {
141 synchronized (packageStats) {
142 PackageStats newStats = new PackageStats(packageName);
143 packageStats.put(packageName, newStats);
144 return newStats;
145 }
146 }
147
148 public PackageStats getOrCreatePackageStats(String packageName) {
149 synchronized (packageStats) {
150 PackageStats existingStats = packageStats.get(packageName);
151 if (existingStats != null) {
152 return existingStats;
153 }
154
155 return createPackageStats(packageName);
156 }
157 }
158
159 public void deletePackageStats(String packageName) {
160 synchronized (packageStats) {
161 packageStats.remove(packageName);
162 }
163 }
164
165 // I/O
166
167 // The encoding is simple:
168 //
169 // 1) The first line is a line consisting of the version header and the version number.
170 //
171 // 2) The rest of the file is package data.
172 // 2.1) A package is started by any line not starting with "-";
173 // 2.2) Any line starting with "-" is code path data. The format is:
174 // '-'{code-path}':'{compile-time}
175
176 public void write(Writer out) {
177 @SuppressWarnings("resource")
178 FastPrintWriter fpw = new FastPrintWriter(out);
179
180 fpw.print(COMPILER_STATS_VERSION_HEADER);
181 fpw.println(COMPILER_STATS_VERSION);
182
183 synchronized (packageStats) {
184 for (PackageStats pkg : packageStats.values()) {
185 synchronized (pkg.compileTimePerCodePath) {
186 if (!pkg.compileTimePerCodePath.isEmpty()) {
187 fpw.println(pkg.getPackageName());
188
189 for (Map.Entry<String, Long> e : pkg.compileTimePerCodePath.entrySet()) {
190 fpw.println("-" + e.getKey() + ":" + e.getValue());
191 }
192 }
193 }
194 }
195 }
196
197 fpw.flush();
198 }
199
200 public boolean read(Reader r) {
201 synchronized (packageStats) {
202 // TODO: Could make this a final switch, then we wouldn't have to synchronize over
203 // the whole reading.
204 packageStats.clear();
205
206 try {
207 BufferedReader in = new BufferedReader(r);
208
209 // Read header, do version check.
210 String versionLine = in.readLine();
211 if (versionLine == null) {
212 throw new IllegalArgumentException("No version line found.");
213 } else {
214 if (!versionLine.startsWith(COMPILER_STATS_VERSION_HEADER)) {
215 throw new IllegalArgumentException("Invalid version line: " + versionLine);
216 }
217 int version = Integer.parseInt(
218 versionLine.substring(COMPILER_STATS_VERSION_HEADER.length()));
219 if (version != COMPILER_STATS_VERSION) {
220 // TODO: Upgrade older formats? For now, just reject and regenerate.
221 throw new IllegalArgumentException("Unexpected version: " + version);
222 }
223 }
224
225 // For simpler code, we ignore any data lines before the first package. We
226 // collect it in a fake package.
227 PackageStats currentPackage = new PackageStats("fake package");
228
229 String s = null;
230 while ((s = in.readLine()) != null) {
231 if (s.startsWith("-")) {
232 int colonIndex = s.indexOf(':');
233 if (colonIndex == -1 || colonIndex == 1) {
234 throw new IllegalArgumentException("Could not parse data " + s);
235 }
236 String codePath = s.substring(1, colonIndex);
237 long time = Long.parseLong(s.substring(colonIndex + 1));
238 currentPackage.setCompileTime(codePath, time);
239 } else {
240 currentPackage = getOrCreatePackageStats(s);
241 }
242 }
243 } catch (Exception e) {
244 Log.e(PackageManagerService.TAG, "Error parsing compiler stats", e);
245 return false;
246 }
247
248 return true;
249 }
250 }
251
252 void writeNow() {
253 writeNow(null);
254 }
255
256 boolean maybeWriteAsync() {
257 return maybeWriteAsync(null);
258 }
259
260 @Override
261 protected void writeInternal(Void data) {
262 AtomicFile file = getFile();
263 FileOutputStream f = null;
264
265 try {
266 f = file.startWrite();
267 OutputStreamWriter osw = new OutputStreamWriter(f);
Andreas Gampe06eb24d2016-07-15 18:12:02 -0700268 write(osw);
Andreas Gampe37e5fdc2016-07-12 22:42:41 -0700269 osw.flush();
270 file.finishWrite(f);
271 } catch (IOException e) {
272 if (f != null) {
273 file.failWrite(f);
274 }
275 Log.e(PackageManagerService.TAG, "Failed to write compiler stats", e);
276 }
277 }
278
279 void read() {
280 read((Void)null);
281 }
282
283 @Override
284 protected void readInternal(Void data) {
285 AtomicFile file = getFile();
286 BufferedReader in = null;
287 try {
288 in = new BufferedReader(new InputStreamReader(file.openRead()));
289 read(in);
290 } catch (FileNotFoundException expected) {
291 } finally {
292 IoUtils.closeQuietly(in);
293 }
294 }
295}