Home | History | Annotate | Download | only in pm
      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          */
     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);
    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 
    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 }
    296