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