| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.pm; |
| |
| import android.util.ArrayMap; |
| import android.util.AtomicFile; |
| import android.util.Log; |
| |
| import com.android.internal.util.FastPrintWriter; |
| import com.android.internal.util.IndentingPrintWriter; |
| |
| import libcore.io.IoUtils; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.io.OutputStreamWriter; |
| import java.io.Reader; |
| import java.io.Writer; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| /** |
| * A class that collects, serializes and deserializes compiler-related statistics on a |
| * per-package per-code-path basis. |
| * |
| * Currently used to track compile times. |
| */ |
| class CompilerStats extends AbstractStatsBase<Void> { |
| |
| private final static String COMPILER_STATS_VERSION_HEADER = "PACKAGE_MANAGER__COMPILER_STATS__"; |
| private final static int COMPILER_STATS_VERSION = 1; |
| |
| /** |
| * Class to collect all stats pertaining to one package. |
| */ |
| static class PackageStats { |
| |
| private final String packageName; |
| |
| /** |
| * This map stores compile-times for all code paths in the package. The value |
| * is in milliseconds. |
| */ |
| private final Map<String, Long> compileTimePerCodePath; |
| |
| /** |
| * @param packageName |
| */ |
| public PackageStats(String packageName) { |
| this.packageName = packageName; |
| // We expect at least one element in here, but let's make it minimal. |
| compileTimePerCodePath = new ArrayMap<>(2); |
| } |
| |
| public String getPackageName() { |
| return packageName; |
| } |
| |
| /** |
| * Return the recorded compile time for a given code path. Returns |
| * 0 if there is no recorded time. |
| */ |
| public long getCompileTime(String codePath) { |
| String storagePath = getStoredPathFromCodePath(codePath); |
| synchronized (compileTimePerCodePath) { |
| Long l = compileTimePerCodePath.get(storagePath); |
| if (l == null) { |
| return 0; |
| } |
| return l; |
| } |
| } |
| |
| public void setCompileTime(String codePath, long compileTimeInMs) { |
| String storagePath = getStoredPathFromCodePath(codePath); |
| synchronized (compileTimePerCodePath) { |
| if (compileTimeInMs <= 0) { |
| compileTimePerCodePath.remove(storagePath); |
| } else { |
| compileTimePerCodePath.put(storagePath, compileTimeInMs); |
| } |
| } |
| } |
| |
| private static String getStoredPathFromCodePath(String codePath) { |
| int lastSlash = codePath.lastIndexOf(File.separatorChar); |
| return codePath.substring(lastSlash + 1); |
| } |
| |
| public void dump(IndentingPrintWriter ipw) { |
| synchronized (compileTimePerCodePath) { |
| if (compileTimePerCodePath.size() == 0) { |
| ipw.println("(No recorded stats)"); |
| } else { |
| for (Map.Entry<String, Long> e : compileTimePerCodePath.entrySet()) { |
| ipw.println(" " + e.getKey() + " - " + e.getValue()); |
| } |
| } |
| } |
| } |
| } |
| |
| private final Map<String, PackageStats> packageStats; |
| |
| public CompilerStats() { |
| super("package-cstats.list", "CompilerStats_DiskWriter", /* lock */ false); |
| packageStats = new HashMap<>(); |
| } |
| |
| public PackageStats getPackageStats(String packageName) { |
| synchronized (packageStats) { |
| return packageStats.get(packageName); |
| } |
| } |
| |
| public void setPackageStats(String packageName, PackageStats stats) { |
| synchronized (packageStats) { |
| packageStats.put(packageName, stats); |
| } |
| } |
| |
| public PackageStats createPackageStats(String packageName) { |
| synchronized (packageStats) { |
| PackageStats newStats = new PackageStats(packageName); |
| packageStats.put(packageName, newStats); |
| return newStats; |
| } |
| } |
| |
| public PackageStats getOrCreatePackageStats(String packageName) { |
| synchronized (packageStats) { |
| PackageStats existingStats = packageStats.get(packageName); |
| if (existingStats != null) { |
| return existingStats; |
| } |
| |
| return createPackageStats(packageName); |
| } |
| } |
| |
| public void deletePackageStats(String packageName) { |
| synchronized (packageStats) { |
| packageStats.remove(packageName); |
| } |
| } |
| |
| // I/O |
| |
| // The encoding is simple: |
| // |
| // 1) The first line is a line consisting of the version header and the version number. |
| // |
| // 2) The rest of the file is package data. |
| // 2.1) A package is started by any line not starting with "-"; |
| // 2.2) Any line starting with "-" is code path data. The format is: |
| // '-'{code-path}':'{compile-time} |
| |
| public void write(Writer out) { |
| @SuppressWarnings("resource") |
| FastPrintWriter fpw = new FastPrintWriter(out); |
| |
| fpw.print(COMPILER_STATS_VERSION_HEADER); |
| fpw.println(COMPILER_STATS_VERSION); |
| |
| synchronized (packageStats) { |
| for (PackageStats pkg : packageStats.values()) { |
| synchronized (pkg.compileTimePerCodePath) { |
| if (!pkg.compileTimePerCodePath.isEmpty()) { |
| fpw.println(pkg.getPackageName()); |
| |
| for (Map.Entry<String, Long> e : pkg.compileTimePerCodePath.entrySet()) { |
| fpw.println("-" + e.getKey() + ":" + e.getValue()); |
| } |
| } |
| } |
| } |
| } |
| |
| fpw.flush(); |
| } |
| |
| public boolean read(Reader r) { |
| synchronized (packageStats) { |
| // TODO: Could make this a final switch, then we wouldn't have to synchronize over |
| // the whole reading. |
| packageStats.clear(); |
| |
| try { |
| BufferedReader in = new BufferedReader(r); |
| |
| // Read header, do version check. |
| String versionLine = in.readLine(); |
| if (versionLine == null) { |
| throw new IllegalArgumentException("No version line found."); |
| } else { |
| if (!versionLine.startsWith(COMPILER_STATS_VERSION_HEADER)) { |
| throw new IllegalArgumentException("Invalid version line: " + versionLine); |
| } |
| int version = Integer.parseInt( |
| versionLine.substring(COMPILER_STATS_VERSION_HEADER.length())); |
| if (version != COMPILER_STATS_VERSION) { |
| // TODO: Upgrade older formats? For now, just reject and regenerate. |
| throw new IllegalArgumentException("Unexpected version: " + version); |
| } |
| } |
| |
| // For simpler code, we ignore any data lines before the first package. We |
| // collect it in a fake package. |
| PackageStats currentPackage = new PackageStats("fake package"); |
| |
| String s = null; |
| while ((s = in.readLine()) != null) { |
| if (s.startsWith("-")) { |
| int colonIndex = s.indexOf(':'); |
| if (colonIndex == -1 || colonIndex == 1) { |
| throw new IllegalArgumentException("Could not parse data " + s); |
| } |
| String codePath = s.substring(1, colonIndex); |
| long time = Long.parseLong(s.substring(colonIndex + 1)); |
| currentPackage.setCompileTime(codePath, time); |
| } else { |
| currentPackage = getOrCreatePackageStats(s); |
| } |
| } |
| } catch (Exception e) { |
| Log.e(PackageManagerService.TAG, "Error parsing compiler stats", e); |
| return false; |
| } |
| |
| return true; |
| } |
| } |
| |
| void writeNow() { |
| writeNow(null); |
| } |
| |
| boolean maybeWriteAsync() { |
| return maybeWriteAsync(null); |
| } |
| |
| @Override |
| protected void writeInternal(Void data) { |
| AtomicFile file = getFile(); |
| FileOutputStream f = null; |
| |
| try { |
| f = file.startWrite(); |
| OutputStreamWriter osw = new OutputStreamWriter(f); |
| write(osw); |
| osw.flush(); |
| file.finishWrite(f); |
| } catch (IOException e) { |
| if (f != null) { |
| file.failWrite(f); |
| } |
| Log.e(PackageManagerService.TAG, "Failed to write compiler stats", e); |
| } |
| } |
| |
| void read() { |
| read((Void)null); |
| } |
| |
| @Override |
| protected void readInternal(Void data) { |
| AtomicFile file = getFile(); |
| BufferedReader in = null; |
| try { |
| in = new BufferedReader(new InputStreamReader(file.openRead())); |
| read(in); |
| } catch (FileNotFoundException expected) { |
| } finally { |
| IoUtils.closeQuietly(in); |
| } |
| } |
| } |