Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c13a5c92 authored by Andreas Gampe's avatar Andreas Gampe Committed by android-build-merger
Browse files

Merge \\"Frameworks/base: Add compiler stats to Package Manager\\" into nyc-mr1-dev am: f52ce76d

am: a2bff323

Change-Id: I0a1de16d538e11756df7bda4390c445d5591608e
parents d51413ec a2bff323
Loading
Loading
Loading
Loading
+126 −0
Original line number Original line Diff line number Diff line
/*
 * 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.os.Environment;
import android.os.SystemClock;
import android.util.AtomicFile;

import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

/**
 * A simple base class for statistics that need to be saved/restored from a dedicated file. This
 * class provides a base implementation that:
 * <ul>
 * <li>Provide an AtomicFile to the actual read/write code
 * <li>A background-thread write and a synchronous write
 * <li>Write-limiting for the background-thread (by default writes are at least 30 minutes apart)
 * <li>Can lock on the provided data object before writing
 * </ul>
 * For completion, a subclass needs to implement actual {@link #writeInternal(Object) writing} and
 * {@link #readInternal(Object) reading}.
 */
public abstract class AbstractStatsBase<T> {

    private static final int WRITE_INTERVAL_MS =
            (PackageManagerService.DEBUG_DEXOPT) ? 0 : 30*60*1000;
    private final Object mFileLock = new Object();
    private final AtomicLong mLastTimeWritten = new AtomicLong(0);
    private final AtomicBoolean mBackgroundWriteRunning = new AtomicBoolean(false);
    private final String mFileName;
    private final String mBackgroundThreadName;
    private final boolean mLock;

    protected AbstractStatsBase(String fileName, String threadName, boolean lock) {
        mFileName = fileName;
        mBackgroundThreadName = threadName;
        mLock = lock;
    }

    protected AtomicFile getFile() {
        File dataDir = Environment.getDataDirectory();
        File systemDir = new File(dataDir, "system");
        File fname = new File(systemDir, mFileName);
        return new AtomicFile(fname);
    }

    void writeNow(final T data) {
        writeImpl(data);
        mLastTimeWritten.set(SystemClock.elapsedRealtime());
    }

    boolean maybeWriteAsync(final T data) {
        if (SystemClock.elapsedRealtime() - mLastTimeWritten.get() < WRITE_INTERVAL_MS
            && !PackageManagerService.DEBUG_DEXOPT) {
            return false;
        }

        if (mBackgroundWriteRunning.compareAndSet(false, true)) {
            new Thread(mBackgroundThreadName) {
                @Override
                public void run() {
                    try {
                        writeImpl(data);
                        mLastTimeWritten.set(SystemClock.elapsedRealtime());
                    } finally {
                        mBackgroundWriteRunning.set(false);
                    }
                }
            }.start();
            return true;
        }

        return false;
    }

    private void writeImpl(T data) {
        if (mLock) {
            synchronized (data) {
                synchronized (mFileLock) {
                    writeInternal(data);
                }
            }
        } else {
            synchronized (mFileLock) {
                writeInternal(data);
            }
        }
    }

    protected abstract void writeInternal(T data);

    void read(T data) {
        if (mLock) {
            synchronized (data) {
                synchronized (mFileLock) {
                    readInternal(data);
                }
            }
        } else {
            synchronized (mFileLock) {
                readInternal(data);
            }
        }
        // We use the current time as last-written. read() is called on system server startup
        // (current situation), and we want to postpone I/O at boot.
        mLastTimeWritten.set(SystemClock.elapsedRealtime());
    }

    protected abstract void readInternal(T data);
}
+294 −0
Original line number Original line Diff line number Diff line
/*
 * 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);
            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);
        }
    }
}
+4 −2
Original line number Original line Diff line number Diff line
@@ -225,7 +225,8 @@ public class OtaDexoptService extends IOtaDexopt.Stub {


        optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles,
        optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles,
                null /* ISAs */, false /* checkProfiles */,
                null /* ISAs */, false /* checkProfiles */,
                getCompilerFilterForReason(compilationReason));
                getCompilerFilterForReason(compilationReason),
                null /* CompilerStats.PackageStats */);


        mCommandsForCurrentPackage = collectingConnection.commands;
        mCommandsForCurrentPackage = collectingConnection.commands;
        if (mCommandsForCurrentPackage.isEmpty()) {
        if (mCommandsForCurrentPackage.isEmpty()) {
@@ -271,7 +272,8 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
                mPackageManagerService.mInstaller, mPackageManagerService.mInstallLock, mContext);
                mPackageManagerService.mInstaller, mPackageManagerService.mInstallLock, mContext);
        optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles, null /* ISAs */,
        optimizer.performDexOpt(nextPackage, nextPackage.usesLibraryFiles, null /* ISAs */,
                false /* checkProfiles */,
                false /* checkProfiles */,
                getCompilerFilterForReason(PackageManagerService.REASON_AB_OTA));
                getCompilerFilterForReason(PackageManagerService.REASON_AB_OTA),
                mPackageManagerService.getOrCreateCompilerPackageStats(nextPackage));
    }
    }


    private void moveAbArtifacts(Installer installer) {
    private void moveAbArtifacts(Installer installer) {
+12 −3
Original line number Original line Diff line number Diff line
@@ -90,7 +90,8 @@ class PackageDexOptimizer {
     * synchronized on {@link #mInstallLock}.
     * synchronized on {@link #mInstallLock}.
     */
     */
    int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries,
    int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries,
            String[] instructionSets, boolean checkProfiles, String targetCompilationFilter) {
            String[] instructionSets, boolean checkProfiles, String targetCompilationFilter,
            CompilerStats.PackageStats packageStats) {
        synchronized (mInstallLock) {
        synchronized (mInstallLock) {
            final boolean useLock = mSystemReady;
            final boolean useLock = mSystemReady;
            if (useLock) {
            if (useLock) {
@@ -99,7 +100,7 @@ class PackageDexOptimizer {
            }
            }
            try {
            try {
                return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles,
                return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles,
                        targetCompilationFilter);
                        targetCompilationFilter, packageStats);
            } finally {
            } finally {
                if (useLock) {
                if (useLock) {
                    mDexoptWakeLock.release();
                    mDexoptWakeLock.release();
@@ -150,7 +151,8 @@ class PackageDexOptimizer {
    }
    }


    private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries,
    private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries,
            String[] targetInstructionSets, boolean checkProfiles, String targetCompilerFilter) {
            String[] targetInstructionSets, boolean checkProfiles, String targetCompilerFilter,
            CompilerStats.PackageStats packageStats) {
        final String[] instructionSets = targetInstructionSets != null ?
        final String[] instructionSets = targetInstructionSets != null ?
                targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
                targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);


@@ -254,10 +256,17 @@ class PackageDexOptimizer {
                        | DEXOPT_BOOTCOMPLETE);
                        | DEXOPT_BOOTCOMPLETE);


                try {
                try {
                    long startTime = System.currentTimeMillis();

                    mInstaller.dexopt(path, sharedGid, pkg.packageName, dexCodeInstructionSet,
                    mInstaller.dexopt(path, sharedGid, pkg.packageName, dexCodeInstructionSet,
                            dexoptNeeded, oatDir, dexFlags, targetCompilerFilter, pkg.volumeUuid,
                            dexoptNeeded, oatDir, dexFlags, targetCompilerFilter, pkg.volumeUuid,
                            sharedLibrariesPath);
                            sharedLibrariesPath);
                    performedDexOpt = true;
                    performedDexOpt = true;

                    if (packageStats != null) {
                        long endTime = System.currentTimeMillis();
                        packageStats.setCompileTime(path, (int)(endTime - startTime));
                    }
                } catch (InstallerException e) {
                } catch (InstallerException e) {
                    Slog.w(TAG, "Failed to dexopt", e);
                    Slog.w(TAG, "Failed to dexopt", e);
                    successfulDexOpt = false;
                    successfulDexOpt = false;
+69 −210

File changed.

Preview size limit exceeded, changes collapsed.

Loading