blob: 603cb672175dd9d0ec199fcc8e253b420177f4f7 [file] [log] [blame]
/*
* 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.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
import javax.inject.Singleton
/**
* 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).
*/
@Singleton
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)