summaryrefslogtreecommitdiff
path: root/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
blob: 0eab1afc4119720a70d0dc365671fd6217b91fb6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/*
 * Copyright (C) 2020 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.systemui.dump

import android.content.Context
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.util.io.Files
import com.android.systemui.util.time.SystemClock
import java.io.IOException
import java.io.PrintWriter
import java.io.UncheckedIOException
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardOpenOption.CREATE
import java.nio.file.StandardOpenOption.TRUNCATE_EXISTING
import java.nio.file.attribute.BasicFileAttributes
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
import javax.inject.Inject

/**
 * Dumps all [LogBuffer]s to a file
 *
 * Intended for emergencies, i.e. we're about to crash. This file can then be read at a later date
 * (usually in a bug report).
 */
@SysUISingleton
class LogBufferEulogizer(
    private val dumpManager: DumpManager,
    private val systemClock: SystemClock,
    private val files: Files,
    private val logPath: Path,
    private val minWriteGap: Long,
    private val maxLogAgeToDump: Long
) {
    @Inject constructor(
        context: Context,
        dumpManager: DumpManager,
        systemClock: SystemClock,
        files: Files
    ) : this(
        dumpManager,
        systemClock,
        files,
        Paths.get(context.filesDir.toPath().toString(), "log_buffers.txt"),
        MIN_WRITE_GAP,
        MAX_AGE_TO_DUMP
    )

    /**
     * Dumps all active log buffers to a file
     *
     * The file will be prefaced by the [reason], which will then be returned (presumably so it can
     * be thrown).
     */
    fun <T : Exception> record(reason: T): T {
        val start = systemClock.uptimeMillis()
        var duration = 0L

        Log.i(TAG, "Performing emergency dump of log buffers")

        val millisSinceLastWrite = getMillisSinceLastWrite(logPath)
        if (millisSinceLastWrite < minWriteGap) {
            Log.w(TAG, "Cannot dump logs, last write was only $millisSinceLastWrite ms ago")
            return reason
        }

        try {
            val writer = files.newBufferedWriter(logPath, CREATE, TRUNCATE_EXISTING)
            writer.use { out ->
                val pw = PrintWriter(out)

                pw.println(DATE_FORMAT.format(systemClock.currentTimeMillis()))
                pw.println()
                pw.println("Dump triggered by exception:")
                reason.printStackTrace(pw)
                dumpManager.dumpBuffers(pw, 0)
                duration = systemClock.uptimeMillis() - start
                pw.println()
                pw.println("Buffer eulogy took ${duration}ms")
            }
        } catch (e: Exception) {
            Log.e(TAG, "Exception while attempting to dump buffers, bailing", e)
        }

        Log.i(TAG, "Buffer eulogy took ${duration}ms")

        return reason
    }

    /**
     * If a eulogy file is present, writes its contents to [pw].
     */
    fun readEulogyIfPresent(pw: PrintWriter) {
        try {
            val millisSinceLastWrite = getMillisSinceLastWrite(logPath)
            if (millisSinceLastWrite > maxLogAgeToDump) {
                Log.i(TAG, "Not eulogizing buffers; they are " +
                        TimeUnit.HOURS.convert(millisSinceLastWrite, TimeUnit.MILLISECONDS) +
                        " hours old")
                return
            }

            files.lines(logPath).use { s ->
                pw.println()
                pw.println()
                pw.println("=============== BUFFERS FROM MOST RECENT CRASH ===============")
                s.forEach { line ->
                    pw.println(line)
                }
            }
        } catch (e: IOException) {
            // File doesn't exist, okay
        } catch (e: UncheckedIOException) {
            Log.e(TAG, "UncheckedIOException while dumping the core", e)
        }
    }

    private fun getMillisSinceLastWrite(path: Path): Long {
        val stats = try {
            files.readAttributes(path, BasicFileAttributes::class.java)
        } catch (e: IOException) {
            // File doesn't exist
            null
        }
        return systemClock.currentTimeMillis() - (stats?.lastModifiedTime()?.toMillis() ?: 0)
    }
}

private const val TAG = "BufferEulogizer"
private val MIN_WRITE_GAP = TimeUnit.MINUTES.toMillis(5)
private val MAX_AGE_TO_DUMP = TimeUnit.HOURS.toMillis(48)
private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)