1 /*
<lambda>null2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.dump
18 
19 import android.content.Context
20 import android.icu.text.SimpleDateFormat
21 import android.util.Log
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.dump.DumpHandler.Companion.dumpEntries
24 import com.android.systemui.log.LogBuffer
25 import com.android.systemui.util.io.Files
26 import com.android.systemui.util.time.SystemClock
27 import java.io.IOException
28 import java.io.PrintWriter
29 import java.io.UncheckedIOException
30 import java.nio.file.Path
31 import java.nio.file.Paths
32 import java.nio.file.StandardOpenOption.CREATE
33 import java.nio.file.StandardOpenOption.TRUNCATE_EXISTING
34 import java.nio.file.attribute.BasicFileAttributes
35 import java.util.Locale
36 import java.util.concurrent.TimeUnit
37 import javax.inject.Inject
38 
39 /**
40  * Dumps all [LogBuffer]s to a file
41  *
42  * Intended for emergencies, i.e. we're about to crash. This file can then be read at a later date
43  * (usually in a bug report).
44  */
45 @SysUISingleton
46 class LogBufferEulogizer(
47     private val dumpManager: DumpManager,
48     private val systemClock: SystemClock,
49     private val files: Files,
50     private val logPath: Path,
51     private val minWriteGap: Long,
52     private val maxLogAgeToDump: Long,
53 ) {
54     @Inject
55     constructor(
56         context: Context,
57         dumpManager: DumpManager,
58         systemClock: SystemClock,
59         files: Files,
60     ) : this(
61         dumpManager,
62         systemClock,
63         files,
64         Paths.get(context.filesDir.toPath().toString(), "log_buffers.txt"),
65         MIN_WRITE_GAP,
66         MAX_AGE_TO_DUMP,
67     )
68 
69     /**
70      * Dumps all active log buffers to a file
71      *
72      * The file will be prefaced by the [reason], which will then be returned (presumably so it can
73      * be thrown).
74      */
75     fun <T : Throwable> record(reason: T): T {
76         val start = systemClock.uptimeMillis()
77         var duration = 0L
78 
79         Log.i(TAG, "Performing emergency dump of log buffers")
80 
81         val millisSinceLastWrite = getMillisSinceLastWrite(logPath)
82         if (millisSinceLastWrite < minWriteGap) {
83             Log.w(TAG, "Cannot dump logs, last write was only $millisSinceLastWrite ms ago")
84             return reason
85         }
86 
87         try {
88             val writer = files.newBufferedWriter(logPath, CREATE, TRUNCATE_EXISTING)
89             writer.use { out ->
90                 val pw = PrintWriter(out)
91 
92                 pw.println(DATE_FORMAT.format(systemClock.currentTimeMillis()))
93                 pw.println()
94                 pw.println("Dump triggered by exception:")
95                 reason.printStackTrace(pw)
96                 val buffers = dumpManager.getLogBuffers()
97                 dumpEntries(buffers, pw)
98                 duration = systemClock.uptimeMillis() - start
99                 pw.println()
100                 pw.println("Buffer eulogy took ${duration}ms")
101             }
102         } catch (e: Exception) {
103             Log.e(TAG, "Exception while attempting to dump buffers, bailing", e)
104         }
105 
106         Log.i(TAG, "Buffer eulogy took ${duration}ms")
107 
108         return reason
109     }
110 
111     /** If a eulogy file is present, writes its contents to [pw]. */
112     fun readEulogyIfPresent(pw: PrintWriter) {
113         try {
114             val millisSinceLastWrite = getMillisSinceLastWrite(logPath)
115             if (millisSinceLastWrite > maxLogAgeToDump) {
116                 Log.i(
117                     TAG,
118                     "Not eulogizing buffers; they are " +
119                         TimeUnit.HOURS.convert(millisSinceLastWrite, TimeUnit.MILLISECONDS) +
120                         " hours old"
121                 )
122                 return
123             }
124 
125             files.lines(logPath).use { s ->
126                 pw.println()
127                 pw.println()
128                 pw.println("=============== BUFFERS FROM MOST RECENT CRASH ===============")
129                 s.forEach { line -> pw.println(line) }
130             }
131         } catch (e: IOException) {
132             // File doesn't exist, okay
133         } catch (e: UncheckedIOException) {
134             Log.e(TAG, "UncheckedIOException while dumping the core", e)
135         }
136     }
137 
138     private fun getMillisSinceLastWrite(path: Path): Long {
139         val stats =
140             try {
141                 files.readAttributes(path, BasicFileAttributes::class.java)
142             } catch (e: IOException) {
143                 // File doesn't exist
144                 null
145             }
146         return systemClock.currentTimeMillis() - (stats?.lastModifiedTime()?.toMillis() ?: 0)
147     }
148 }
149 
150 private const val TAG = "BufferEulogizer"
151 private val MIN_WRITE_GAP = TimeUnit.MINUTES.toMillis(5)
152 private val MAX_AGE_TO_DUMP = TimeUnit.HOURS.toMillis(48)
153 private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
154