/* * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.stetho.dumpapp.plugins; import android.content.Context; import android.os.Debug; import com.facebook.stetho.common.Util; import com.facebook.stetho.dumpapp.DumpException; import com.facebook.stetho.dumpapp.DumpUsageException; import com.facebook.stetho.dumpapp.DumperContext; import com.facebook.stetho.dumpapp.DumperPlugin; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.util.Iterator; /** * Generates an hprof on the sdcard and returns to the client the absolute path to the file. * <p/> * For convenient usage, see: {@code scripts/hprof_dump.sh} */ public class HprofDumperPlugin implements DumperPlugin { private static final String NAME = "hprof"; private final Context mContext; public HprofDumperPlugin(Context context) { mContext = context; } @Override public String getName() { return NAME; } @Override public void dump(DumperContext dumpContext) throws DumpException { final PrintStream output = dumpContext.getStdout(); Iterator<String> argsIter = dumpContext.getArgsAsList().iterator(); String outputPath = argsIter.hasNext() ? argsIter.next() : null; if (outputPath == null) { usage(output); } else { if ("-".equals(outputPath)) { handlePipeOutput(output); } else { File outputFile = new File(outputPath); if (!outputFile.isAbsolute()) { outputFile = mContext.getFileStreamPath(outputPath); } writeHprof(outputFile); output.println("Wrote to " + outputFile); } } } private void handlePipeOutput(OutputStream output) throws DumpException { File hprofFile = mContext.getFileStreamPath("hprof-dump.hprof"); try { writeHprof(hprofFile); try { InputStream input = new FileInputStream(hprofFile); try { Util.copy(input, output, new byte[2048]); } finally { input.close(); } } catch (IOException e) { throw new DumpException("Failure copying " + hprofFile + " to dumper output"); } } finally { if (hprofFile.exists()) { hprofFile.delete(); } } } private void writeHprof(File outputPath) throws DumpException { try { // Test that we can write here. dumpHprofData appears to hang if it cannot write // to the target location on ART. truncateAndDeleteFile(outputPath); Debug.dumpHprofData(outputPath.getAbsolutePath()); } catch (IOException e) { throw new DumpException("Failure writing to " + outputPath + ": " + e.getMessage()); } } private static void truncateAndDeleteFile(File file) throws IOException { FileOutputStream out = new FileOutputStream(file); out.close(); if (!file.delete()) { throw new IOException("Failed to delete " + file); } } private void usage(PrintStream output) throws DumpUsageException { output.println("Usage: dumpapp hprof [ path ]"); output.println("Dump HPROF memory usage data from the running application."); output.println(); output.println("Where path can be any of:"); output.println(" - Output directly to stdout"); output.println(" <path> Full path to a writable file on the device"); output.println(" <filename> Relative filename that will be stored in the app internal storage"); throw new DumpUsageException("Missing path"); } }