1 /*
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 
17 package com.android.server.pm;
18 
19 import android.util.ArrayMap;
20 import android.util.AtomicFile;
21 import android.util.Log;
22 
23 import com.android.internal.util.FastPrintWriter;
24 import com.android.internal.util.IndentingPrintWriter;
25 
26 import libcore.io.IoUtils;
27 
28 import java.io.BufferedReader;
29 import java.io.File;
30 import java.io.FileNotFoundException;
31 import java.io.FileOutputStream;
32 import java.io.IOException;
33 import java.io.InputStreamReader;
34 import java.io.OutputStreamWriter;
35 import java.io.Reader;
36 import java.io.Writer;
37 import java.util.HashMap;
38 import 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  */
46 class 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          */
PackageStats(String packageName)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 
getPackageName()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          */
getCompileTime(String codePath)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 
setCompileTime(String codePath, long compileTimeInMs)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 
getStoredPathFromCodePath(String codePath)103         private static String getStoredPathFromCodePath(String codePath) {
104             int lastSlash = codePath.lastIndexOf(File.separatorChar);
105             return codePath.substring(lastSlash + 1);
106         }
107 
dump(IndentingPrintWriter ipw)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 
CompilerStats()123     public CompilerStats() {
124         super("package-cstats.list", "CompilerStats_DiskWriter", /* lock */ false);
125         packageStats = new HashMap<>();
126     }
127 
getPackageStats(String packageName)128     public PackageStats getPackageStats(String packageName) {
129         synchronized (packageStats) {
130             return packageStats.get(packageName);
131         }
132     }
133 
setPackageStats(String packageName, PackageStats stats)134     public void setPackageStats(String packageName, PackageStats stats) {
135         synchronized (packageStats) {
136             packageStats.put(packageName, stats);
137         }
138     }
139 
createPackageStats(String packageName)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 
getOrCreatePackageStats(String packageName)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 
deletePackageStats(String packageName)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 
write(Writer out)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 
read(Reader r)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 
writeNow()252     void writeNow() {
253         writeNow(null);
254     }
255 
maybeWriteAsync()256     boolean maybeWriteAsync() {
257         return maybeWriteAsync(null);
258     }
259 
260     @Override
writeInternal(Void data)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);
268             write(osw);
269             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 
read()279     void read() {
280         read((Void)null);
281     }
282 
283     @Override
readInternal(Void data)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 }
296