/*
 * Copyright (C) 2023 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.am;

import static android.text.format.DateUtils.DAY_IN_MILLIS;

import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;

import android.annotation.NonNull;
import android.os.Build;
import android.os.Debug;
import android.os.FileUtils;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Slog;
import android.util.SparseBooleanArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.os.anr.AnrLatencyTracker;
import com.android.server.utils.AnrTimer;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;


/**
 * A helper for dumping stack traces.
 */
public class StackTracesDumpHelper {
    static final String TAG = TAG_WITH_CLASS_NAME ? "StackTracesDumpHelper" : TAG_AM;

    @GuardedBy("StackTracesDumpHelper.class")
    private static final SimpleDateFormat ANR_FILE_DATE_FORMAT =
            new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");

    static final String ANR_FILE_PREFIX = "anr_";
    static final String ANR_TEMP_FILE_PREFIX = "temp_anr_";

    public static final String ANR_TRACE_DIR = "/data/anr";
    private static final int NATIVE_DUMP_TIMEOUT_MS =
            2000 * Build.HW_TIMEOUT_MULTIPLIER; // 2 seconds;
    private static final int JAVA_DUMP_MINIMUM_SIZE = 100; // 100 bytes.
    // The time limit for a single process's dump
    private static final int TEMP_DUMP_TIME_LIMIT =
            10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; // 10 seconds


    /**
     * If a stack trace dump file is configured, dump process stack traces.
     * @param firstPids of dalvik VM processes to dump stack traces for first
     * @param processCpuTracker
     * @param lastPids of dalvik VM processes to dump stack traces for last
     * @param nativePidsFuture optional future for a list of native pids to dump stack crawls
     * @param logExceptionCreatingFile optional writer to which we log errors creating the file
     * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on
     * @param latencyTracker the latency tracker instance of the current ANR.
     */
    public static File dumpStackTraces(
            ArrayList<Integer> firstPids,
            ProcessCpuTracker processCpuTracker,
            SparseBooleanArray lastPids,
            Future<ArrayList<Integer>> nativePidsFuture,
            StringWriter logExceptionCreatingFile,
            @NonNull Executor auxiliaryTaskExecutor,
            AnrLatencyTracker latencyTracker) {
        return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
                logExceptionCreatingFile, null, null, null, null, auxiliaryTaskExecutor, null,
                latencyTracker, null);
    }

    /**
     * If a stack trace dump file is configured, dump process stack traces.
     *
     * @param firstPids of dalvik VM processes to dump stack traces for first
     * @param processCpuTracker of dalvik VM processes
     * @param lastPids of dalvik VM processes to dump stack traces for last
     * @param nativePidsFuture optional future for a list of native pids to dump stack crawls
     * @param logExceptionCreatingFile optional writer to which we log errors creating the file
     * @param subject the subject of the dumped traces
     * @param criticalEventSection the critical event log, passed as a string
     * @param extraHeaders Optional, Extra headers added by the dumping method
     * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on
     * @param latencyTracker the latency tracker instance of the current ANR.
     */
    public static File dumpStackTraces(ArrayList<Integer> firstPids,
            ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
            Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
            String subject, String criticalEventSection,
            LinkedHashMap<String, String> extraHeaders, @NonNull Executor auxiliaryTaskExecutor,
            AnrLatencyTracker latencyTracker) {
        return dumpStackTraces(
                firstPids,
                processCpuTracker,
                lastPids,
                nativePidsFuture,
                logExceptionCreatingFile,
                /* firstPidEndOffset= */ null,
                subject,
                criticalEventSection,
                extraHeaders,
                auxiliaryTaskExecutor,
                /* firstPidFilePromise= */ null,
                latencyTracker,
                /* timeoutRecord= */ null);
    }


    /**
     * If a stack trace dump file is configured, dump process stack traces.
     *
     * @param firstPids of dalvik VM processes to dump stack traces for first
     * @param processCpuTracker of dalvik VM processes
     * @param lastPids of dalvik VM processes to dump stack traces for last
     * @param nativePidsFuture optional future for a list of native pids to dump stack crawls
     * @param logExceptionCreatingFile optional writer to which we log errors creating the file
     * @param subject the subject of the dumped traces
     * @param criticalEventSection the critical event log, passed as a string
     * @param extraHeaders Optional, Extra headers added by the dumping method
     * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on
     * @param firstPidEndOffset Optional, when it's set, it receives the start/end offset of the
     *     very first pid to be dumped.
     * @param latencyTracker the latency tracker instance of the current ANR.
     */
    public static File dumpStackTraces(
            ArrayList<Integer> firstPids,
            ProcessCpuTracker processCpuTracker,
            SparseBooleanArray lastPids,
            Future<ArrayList<Integer>> nativePidsFuture,
            StringWriter logExceptionCreatingFile,
            AtomicLong firstPidEndOffset,
            String subject,
            String criticalEventSection,
            LinkedHashMap<String, String> extraHeaders,
            @NonNull Executor auxiliaryTaskExecutor,
            Future<File> firstPidFilePromise,
            AnrLatencyTracker latencyTracker) {
        return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
                logExceptionCreatingFile, firstPidEndOffset, subject, criticalEventSection,
                extraHeaders, auxiliaryTaskExecutor, firstPidFilePromise, latencyTracker,
                null);
    }

    /**
     * @param timeoutRecord the timeout record of the current ANR.
     */
    public static File dumpStackTraces(
            ArrayList<Integer> firstPids,
            ProcessCpuTracker processCpuTracker,
            SparseBooleanArray lastPids,
            Future<ArrayList<Integer>> nativePidsFuture,
            StringWriter logExceptionCreatingFile,
            AtomicLong firstPidEndOffset,
            String subject,
            String criticalEventSection,
            LinkedHashMap<String, String> extraHeaders,
            @NonNull Executor auxiliaryTaskExecutor,
            Future<File> firstPidFilePromise,
            AnrLatencyTracker latencyTracker,
            TimeoutRecord timeoutRecord) {
        try {

            if (latencyTracker != null) {
                latencyTracker.dumpStackTracesStarted();
            }

            Slog.i(TAG, "dumpStackTraces pids=" + lastPids);

            // Measure CPU usage as soon as we're called in order to get a realistic sampling
            // of the top users at the time of the request.
            Supplier<ArrayList<Integer>> extraPidsSupplier = processCpuTracker != null
                    ? () -> getExtraPids(processCpuTracker, lastPids, latencyTracker) : null;
            Future<ArrayList<Integer>> extraPidsFuture = null;
            if (extraPidsSupplier != null) {
                extraPidsFuture =
                        CompletableFuture.supplyAsync(extraPidsSupplier, auxiliaryTaskExecutor);
            }

            final File tracesDir = new File(ANR_TRACE_DIR);

            // NOTE: We should consider creating the file in native code atomically once we've
            // gotten rid of the old scheme of dumping and lot of the code that deals with paths
            // can be removed.
            File tracesFile;
            try {
                tracesFile = createAnrDumpFile(tracesDir);
            } catch (IOException e) {
                Slog.w(TAG, "Exception creating ANR dump file:", e);
                if (logExceptionCreatingFile != null) {
                    logExceptionCreatingFile.append(
                            "----- Exception creating ANR dump file -----\n");
                    e.printStackTrace(new PrintWriter(logExceptionCreatingFile));
                }
                if (latencyTracker != null) {
                    latencyTracker.anrSkippedDumpStackTraces();
                }
                return null;
            }
            StringBuilder headers = new StringBuilder();
            if (subject != null) {
                headers.append("Subject: ").append(subject).append("\n");
            }
            if (timeoutRecord != null) {
                AnrTimer.ExpiredTimer expiredTimer = AnrTimer.expiredTimer(timeoutRecord);
                if (expiredTimer != null && expiredTimer.mDurationMs > 0) {
                    headers.append("Timeout: ")
                            .append(expiredTimer.mDurationMs)
                            .append("\n");
                }
                if (expiredTimer != null && expiredTimer.mStartMs > 0) {
                    headers.append("TimeoutStart: ")
                            .append(expiredTimer.mStartMs)
                            .append("\n");
                }
            }
            if (extraHeaders != null && !extraHeaders.isEmpty()) {
                headers.append(stringifyHeaders(extraHeaders)).append("\n\n");
            }
            if (criticalEventSection != null) {
                headers.append(criticalEventSection);
            }
            if (!headers.isEmpty()) {
                appendtoANRFile(tracesFile.getAbsolutePath(), headers.toString());
            }

            long firstPidEndPos = dumpStackTraces(
                    tracesFile.getAbsolutePath(), firstPids, nativePidsFuture,
                    extraPidsFuture, firstPidFilePromise, latencyTracker);
            if (firstPidEndOffset != null) {
                firstPidEndOffset.set(firstPidEndPos);
            }
            // Each set of ANR traces is written to a separate file and dumpstate will process
            // all such files and add them to a captured bug report if they're recent enough.
            maybePruneOldTraces(tracesDir);

            return tracesFile;
        } finally {
            if (latencyTracker != null) {
                latencyTracker.dumpStackTracesEnded();
            }
        }
    }

    /**
     * @return The end offset of the trace of the very first PID
     */
    public static long dumpStackTraces(String tracesFile,
            ArrayList<Integer> firstPids, Future<ArrayList<Integer>> nativePidsFuture,
            Future<ArrayList<Integer>> extraPidsFuture, Future<File> firstPidFilePromise,
            AnrLatencyTracker latencyTracker) {

        Slog.i(TAG, "Dumping to " + tracesFile);

        // We don't need any sort of inotify based monitoring when we're dumping traces via
        // tombstoned. Data is piped to an "intercept" FD installed in tombstoned so we're in full
        // control of all writes to the file in question.

        // We must complete all stack dumps within 20 seconds.
        long remainingTime = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;

        // As applications are usually interested with the ANR stack traces, but we can't share
        // with them the stack traces other than their own stacks. So after the very first PID is
        // dumped, remember the current file size.
        long firstPidEnd = -1;

        // Was the first pid copied from the temporary file that was created in the predump phase?
        boolean firstPidTempDumpCopied = false;

        // First copy the first pid's dump from the temporary file it was dumped into earlier,
        // The first pid should always exist in firstPids but we check the size just in case.
        if (firstPidFilePromise != null && firstPids != null && firstPids.size() > 0) {
            final int primaryPid = firstPids.get(0);
            final long start = SystemClock.elapsedRealtime();
            firstPidTempDumpCopied = copyFirstPidTempDump(tracesFile, firstPidFilePromise,
                    remainingTime, latencyTracker);
            final long timeTaken = SystemClock.elapsedRealtime() - start;
            remainingTime -= timeTaken;
            if (remainingTime <= 0) {
                Slog.e(TAG, "Aborting stack trace dump (currently copying primary pid" + primaryPid
                        + "); deadline exceeded.");
                return firstPidEnd;
            }
             // We don't copy ANR traces from the system_server intentionally.
            if (firstPidTempDumpCopied && primaryPid != ActivityManagerService.MY_PID) {
                firstPidEnd = new File(tracesFile).length();
            }
            // Append the Durations/latency comma separated array after the first PID.
            if (firstPidTempDumpCopied && latencyTracker != null) {
                appendtoANRFile(tracesFile,
                        latencyTracker.dumpAsCommaSeparatedArrayWithHeader());
            }
        }
        // Next collect all of the stacks of the most important pids.
        if (firstPids != null)  {
            if (latencyTracker != null) {
                latencyTracker.dumpingFirstPidsStarted();
            }

            int num = firstPids.size();
            for (int i = firstPidTempDumpCopied ? 1 : 0; i < num; i++) {
                final int pid = firstPids.get(i);
                // We don't copy ANR traces from the system_server intentionally.
                final boolean firstPid = i == 0 && ActivityManagerService.MY_PID != pid;
                Slog.i(TAG, "Collecting stacks for pid " + pid);
                final long timeTaken = dumpJavaTracesTombstoned(pid, tracesFile, remainingTime,
                        latencyTracker);
                remainingTime -= timeTaken;
                if (remainingTime <= 0) {
                    Slog.e(TAG, "Aborting stack trace dump (current firstPid=" + pid
                            + "); deadline exceeded.");
                    return firstPidEnd;
                }

                if (firstPid) {
                    firstPidEnd = new File(tracesFile).length();
                    // Full latency dump
                    if (latencyTracker != null) {
                        appendtoANRFile(tracesFile,
                                latencyTracker.dumpAsCommaSeparatedArrayWithHeader());
                    }
                }
                if (DEBUG_ANR) {
                    Slog.d(TAG, "Done with pid " + firstPids.get(i) + " in " + timeTaken + "ms");
                }
            }
            if (latencyTracker != null) {
                latencyTracker.dumpingFirstPidsEnded();
            }
        }

        // Next collect the stacks of the native pids
        ArrayList<Integer> nativePids = collectPids(nativePidsFuture, "native pids");

        Slog.i(TAG, "dumpStackTraces nativepids=" + nativePids);

        if (nativePids != null) {
            if (latencyTracker != null) {
                latencyTracker.dumpingNativePidsStarted();
            }
            for (int pid : nativePids) {
                Slog.i(TAG, "Collecting stacks for native pid " + pid);
                final long nativeDumpTimeoutMs = Math.min(NATIVE_DUMP_TIMEOUT_MS, remainingTime);

                if (latencyTracker != null) {
                    latencyTracker.dumpingPidStarted(pid);
                }
                final long start = SystemClock.elapsedRealtime();
                Debug.dumpNativeBacktraceToFileTimeout(
                        pid, tracesFile, (int) (nativeDumpTimeoutMs / 1000));
                final long timeTaken = SystemClock.elapsedRealtime() - start;
                if (latencyTracker != null) {
                    latencyTracker.dumpingPidEnded();
                }
                remainingTime -= timeTaken;
                if (remainingTime <= 0) {
                    Slog.e(TAG, "Aborting stack trace dump (current native pid=" + pid
                            + "); deadline exceeded.");
                    return firstPidEnd;
                }

                if (DEBUG_ANR) {
                    Slog.d(TAG, "Done with native pid " + pid + " in " + timeTaken + "ms");
                }
            }
            if (latencyTracker != null) {
                latencyTracker.dumpingNativePidsEnded();
            }
        }

        // Lastly, dump stacks for all extra PIDs from the CPU tracker.
        ArrayList<Integer> extraPids = collectPids(extraPidsFuture, "extra pids");

        if (extraPidsFuture != null) {
            try {
                extraPids = extraPidsFuture.get();
            } catch (ExecutionException e) {
                Slog.w(TAG, "Failed to collect extra pids", e.getCause());
            } catch (InterruptedException e) {
                Slog.w(TAG, "Interrupted while collecting extra pids", e);
            }
        }
        Slog.i(TAG, "dumpStackTraces extraPids=" + extraPids);

        if (extraPids != null) {
            if (latencyTracker != null) {
                latencyTracker.dumpingExtraPidsStarted();
            }
            for (int pid : extraPids) {
                Slog.i(TAG, "Collecting stacks for extra pid " + pid);
                final long timeTaken = dumpJavaTracesTombstoned(pid, tracesFile, remainingTime,
                        latencyTracker);
                remainingTime -= timeTaken;
                if (remainingTime <= 0) {
                    Slog.e(TAG, "Aborting stack trace dump (current extra pid=" + pid
                            + "); deadline exceeded.");
                    return firstPidEnd;
                }

                if (DEBUG_ANR) {
                    Slog.d(TAG, "Done with extra pid " + pid + " in " + timeTaken + "ms");
                }
            }
            if (latencyTracker != null) {
                latencyTracker.dumpingExtraPidsEnded();
            }
        }
        // Append the dumping footer with the current uptime
        appendtoANRFile(tracesFile, "----- dumping ended at " + SystemClock.uptimeMillis() + "\n");
        Slog.i(TAG, "Done dumping");

        return firstPidEnd;
    }

    /**
     * Dumps the supplied pid to a temporary file.
     * @param pid the PID to be dumped
     * @param latencyTracker the latency tracker instance of the current ANR.
     */
    public static File dumpStackTracesTempFile(int pid, AnrLatencyTracker latencyTracker) {
        try {
            if (latencyTracker != null) {
                latencyTracker.dumpStackTracesTempFileStarted();
            }

            File tmpTracesFile;
            try {
                tmpTracesFile = File.createTempFile(ANR_TEMP_FILE_PREFIX, ".txt",
                        new File(ANR_TRACE_DIR));
                Slog.d(TAG, "created ANR temporary file:" + tmpTracesFile.getAbsolutePath());
            } catch (IOException e) {
                Slog.w(TAG, "Exception creating temporary ANR dump file:", e);
                if (latencyTracker != null) {
                    latencyTracker.dumpStackTracesTempFileCreationFailed();
                }
                return null;
            }

            Slog.i(TAG, "Collecting stacks for pid " + pid + " into temporary file "
                    + tmpTracesFile.getName());
            if (latencyTracker != null) {
                latencyTracker.dumpingPidStarted(pid);
            }
            final long timeTaken = dumpJavaTracesTombstoned(pid, tmpTracesFile.getAbsolutePath(),
                    TEMP_DUMP_TIME_LIMIT);
            if (latencyTracker != null) {
                latencyTracker.dumpingPidEnded();
            }
            if (TEMP_DUMP_TIME_LIMIT <= timeTaken) {
                Slog.e(TAG, "Aborted stack trace dump (current primary pid=" + pid
                        + "); deadline exceeded.");
                if (latencyTracker != null) {
                    latencyTracker.dumpStackTracesTempFileTimedOut();
                }
            }
            if (DEBUG_ANR) {
                Slog.d(TAG, "Done with primary pid " + pid + " in " + timeTaken + "ms"
                        + " dumped into temporary file " + tmpTracesFile.getName());
            }
            return tmpTracesFile;
        } finally {
            if (latencyTracker != null) {
                latencyTracker.dumpStackTracesTempFileEnded();
            }
        }
    }

    private static boolean copyFirstPidTempDump(String tracesFile, Future<File> firstPidFilePromise,
            long timeLimitMs, AnrLatencyTracker latencyTracker) {

        boolean copySucceeded = false;
        try (FileOutputStream fos = new FileOutputStream(tracesFile, true))  {
            if (latencyTracker != null) {
                latencyTracker.copyingFirstPidStarted();
            }
            final File tempfile = firstPidFilePromise.get(timeLimitMs, TimeUnit.MILLISECONDS);
            if (tempfile != null) {
                Files.copy(tempfile.toPath(), fos);
                // Delete the temporary first pid dump file
                tempfile.delete();
                copySucceeded = true;
                return copySucceeded;
            }
            return false;
        } catch (ExecutionException e) {
            Slog.w(TAG, "Failed to collect the first pid's predump to the main ANR file",
                    e.getCause());
            return false;
        } catch (InterruptedException e) {
            Slog.w(TAG, "Interrupted while collecting the first pid's predump"
                    + " to the main ANR file", e);
            return false;
        } catch (IOException e) {
            Slog.w(TAG, "Failed to read the first pid's predump file", e);
            return false;
        } catch (TimeoutException e) {
            Slog.w(TAG, "Copying the first pid timed out", e);
            return false;
        } finally {
            if (latencyTracker != null) {
                latencyTracker.copyingFirstPidEnded(copySucceeded);
            }
        }
    }

    private static synchronized File createAnrDumpFile(File tracesDir) throws IOException {
        final String formattedDate = ANR_FILE_DATE_FORMAT.format(new Date());
        final File anrFile = new File(tracesDir, ANR_FILE_PREFIX + formattedDate);

        if (anrFile.createNewFile()) {
            FileUtils.setPermissions(anrFile.getAbsolutePath(), 0644, -1, -1); // -rw-r--r--
            return anrFile;
        } else {
            throw new IOException("Unable to create ANR dump file: createNewFile failed");
        }
    }

    private static ArrayList<Integer> getExtraPids(ProcessCpuTracker processCpuTracker,
            SparseBooleanArray lastPids, AnrLatencyTracker latencyTracker) {
        if (latencyTracker != null) {
            latencyTracker.processCpuTrackerMethodsCalled();
        }
        ArrayList<Integer> extraPids = new ArrayList<>();
        synchronized (processCpuTracker) {
            processCpuTracker.init();
        }
        try {
            Thread.sleep(200);
        } catch (InterruptedException ignored) {
        }

        synchronized (processCpuTracker) {
            processCpuTracker.update();
            // We'll take the stack crawls of just the top apps using CPU.
            final int workingStatsNumber = processCpuTracker.countWorkingStats();
            for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) {
                ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
                if (lastPids.indexOfKey(stats.pid) >= 0) {
                    if (DEBUG_ANR) {
                        Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
                    }

                    extraPids.add(stats.pid);
                } else {
                    Slog.i(TAG,
                            "Skipping next CPU consuming process, not a java proc: "
                            + stats.pid);
                }
            }
        }
        if (latencyTracker != null) {
            latencyTracker.processCpuTrackerMethodsReturned();
        }
        return extraPids;
    }

    /**
     * Prune all trace files that are more than a day old.
     *
     * NOTE: It might make sense to move this functionality to tombstoned eventually, along with a
     * shift away from anr_XX and tombstone_XX to a more descriptive name. We do it here for now
     * since it's the system_server that creates trace files for most ANRs.
     */
    private static void maybePruneOldTraces(File tracesDir) {
        final File[] files = tracesDir.listFiles();
        if (files == null) return;

        final int max = SystemProperties.getInt("tombstoned.max_anr_count", 64);
        final long now = System.currentTimeMillis();
        try {
            Arrays.sort(files, Comparator.comparingLong(File::lastModified).reversed());
            for (int i = 0; i < files.length; ++i) {
                if (i > max || (now - files[i].lastModified()) > DAY_IN_MILLIS) {
                    if (!files[i].delete()) {
                        Slog.w(TAG, "Unable to prune stale trace file: " + files[i]);
                    }
                }
            }
        } catch (IllegalArgumentException e) {
            // The modification times changed while we were sorting. Bail...
            // https://issuetracker.google.com/169836837
            Slog.w(TAG, "tombstone modification times changed while sorting; not pruning", e);
        }
    }

    private static long dumpJavaTracesTombstoned(int pid, String fileName, long timeoutMs,
            AnrLatencyTracker latencyTracker) {
        try {
            if (latencyTracker != null) {
                latencyTracker.dumpingPidStarted(pid);
            }
            return dumpJavaTracesTombstoned(pid, fileName, timeoutMs);
        } finally {
            if (latencyTracker != null) {
                latencyTracker.dumpingPidEnded();
            }
        }
    }

    /**
     * Dump java traces for process {@code pid} to the specified file. If java trace dumping
     * fails, a native backtrace is attempted. Note that the timeout {@code timeoutMs} only applies
     * to the java section of the trace, a further {@code NATIVE_DUMP_TIMEOUT_MS} might be spent
     * attempting to obtain native traces in the case of a failure. Returns the total time spent
     * capturing traces.
     */
    private static long dumpJavaTracesTombstoned(int pid, String fileName, long timeoutMs) {
        final long timeStart = SystemClock.elapsedRealtime();
        int headerSize = writeUptimeStartHeaderForPid(pid, fileName);
        boolean javaSuccess = Debug.dumpJavaBacktraceToFileTimeout(pid, fileName,
                (int) (timeoutMs / 1000));
        if (javaSuccess) {
            // Check that something is in the file, actually. Try-catch should not be necessary,
            // but better safe than sorry.
            try {
                long size = new File(fileName).length();
                if ((size - headerSize) < JAVA_DUMP_MINIMUM_SIZE) {
                    Slog.w(TAG, "Successfully created Java ANR file is empty!");
                    javaSuccess = false;
                }
            } catch (Exception e) {
                Slog.w(TAG, "Unable to get ANR file size", e);
                javaSuccess = false;
            }
        }
        if (!javaSuccess) {
            Slog.w(TAG, "Dumping Java threads failed, initiating native stack dump.");
            if (!Debug.dumpNativeBacktraceToFileTimeout(pid, fileName,
                    (NATIVE_DUMP_TIMEOUT_MS / 1000))) {
                Slog.w(TAG, "Native stack dump failed!");
            }
        }

        return SystemClock.elapsedRealtime() - timeStart;
    }

    private static int appendtoANRFile(String fileName, String text) {
        try (FileOutputStream fos = new FileOutputStream(fileName, true)) {
            byte[] header = text.getBytes(StandardCharsets.UTF_8);
            fos.write(header);
            return header.length;
        } catch (IOException e) {
            Slog.w(TAG, "Exception writing to ANR dump file:", e);
            return 0;
        }
    }

    /*
     * Writes a header containing the process id and the current system uptime.
     */
    private static int writeUptimeStartHeaderForPid(int pid, String fileName) {
        return appendtoANRFile(fileName, "----- dumping pid: " + pid + " at "
            + SystemClock.uptimeMillis() + "\n");
    }

    private static ArrayList<Integer> collectPids(Future<ArrayList<Integer>> pidsFuture,
            String logName) {

        ArrayList<Integer> pids = null;

        if (pidsFuture == null) {
            return pids;
        }
        try {
            pids = pidsFuture.get();
        } catch (ExecutionException e) {
            Slog.w(TAG, "Failed to collect " + logName, e.getCause());
        } catch (InterruptedException e) {
            Slog.w(TAG, "Interrupted while collecting " + logName , e);
        }
        return pids;
    }

    private static String stringifyHeaders(@NonNull LinkedHashMap<String, String> headers) {
        StringBuilder headersString = new StringBuilder();
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            headersString.append(entry.getKey())
                    .append(": ")
                    .append(entry.getValue())
                    .append("\n");
        }
        return headersString.toString();
    }

}
