1 /*
<lambda>null2  * Copyright (C) 2019 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.protolog.tool
18 
19 import com.android.json.stream.JsonReader
20 import com.android.server.protolog.common.InvalidFormatStringException
21 import com.android.server.protolog.common.LogDataType
22 import com.android.server.protolog.ProtoLogMessage
23 import com.android.server.protolog.ProtoLogFileProto
24 import java.io.BufferedReader
25 import java.io.InputStream
26 import java.io.InputStreamReader
27 import java.io.PrintStream
28 import java.lang.Exception
29 import java.text.SimpleDateFormat
30 import java.util.Date
31 import java.util.Locale
32 
33 /**
34  * Implements a simple parser/viewer for binary ProtoLog logs.
35  * A binary log is translated into Android "LogCat"-like text log.
36  */
37 class LogParser(private val configParser: ViewerConfigParser) {
38     companion object {
39         private val dateFormat = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
40         private val magicNumber =
41                 ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
42                         ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
43     }
44 
45     private fun printTime(time: Long, offset: Long, ps: PrintStream) {
46         ps.print(dateFormat.format(Date(time / 1000000 + offset)) + " ")
47     }
48 
49     private fun printFormatted(
50         protoLogMessage: ProtoLogMessage,
51         configEntry: ViewerConfigParser.ConfigEntry,
52         ps: PrintStream
53     ) {
54         val strParmIt = protoLogMessage.strParamsList.iterator()
55         val longParamsIt = protoLogMessage.sint64ParamsList.iterator()
56         val doubleParamsIt = protoLogMessage.doubleParamsList.iterator()
57         val boolParamsIt = protoLogMessage.booleanParamsList.iterator()
58         val args = mutableListOf<Any>()
59         val format = configEntry.messageString
60         val argTypes = LogDataType.parseFormatString(format)
61         try {
62             argTypes.forEach {
63                 when (it) {
64                     LogDataType.BOOLEAN -> args.add(boolParamsIt.next())
65                     LogDataType.LONG -> args.add(longParamsIt.next())
66                     LogDataType.DOUBLE -> args.add(doubleParamsIt.next())
67                     LogDataType.STRING -> args.add(strParmIt.next())
68                     null -> throw NullPointerException()
69                 }
70             }
71         } catch (ex: NoSuchElementException) {
72             throw InvalidFormatStringException("Invalid format string in config", ex)
73         }
74         if (strParmIt.hasNext() || longParamsIt.hasNext() ||
75                 doubleParamsIt.hasNext() || boolParamsIt.hasNext()) {
76             throw RuntimeException("Invalid format string in config - no enough matchers")
77         }
78         val formatted = format.format(*(args.toTypedArray()))
79         ps.print("${configEntry.level} ${configEntry.tag}: $formatted\n")
80     }
81 
82     private fun printUnformatted(protoLogMessage: ProtoLogMessage, ps: PrintStream, tag: String) {
83         ps.println("$tag: ${protoLogMessage.messageHash} - ${protoLogMessage.strParamsList}" +
84                 " ${protoLogMessage.sint64ParamsList} ${protoLogMessage.doubleParamsList}" +
85                 " ${protoLogMessage.booleanParamsList}")
86     }
87 
88     fun parse(protoLogInput: InputStream, jsonConfigInput: InputStream, ps: PrintStream) {
89         val jsonReader = JsonReader(BufferedReader(InputStreamReader(jsonConfigInput)))
90         val config = configParser.parseConfig(jsonReader)
91         val protoLog = ProtoLogFileProto.parseFrom(protoLogInput)
92 
93         if (protoLog.magicNumber != magicNumber) {
94             throw InvalidInputException("ProtoLog file magic number is invalid.")
95         }
96         if (protoLog.version != Constants.VERSION) {
97             throw InvalidInputException("ProtoLog file version not supported by this tool," +
98                     " log version ${protoLog.version}, viewer version ${Constants.VERSION}")
99         }
100 
101         protoLog.logList.forEach { log ->
102             printTime(log.elapsedRealtimeNanos, protoLog.realTimeToElapsedTimeOffsetMillis, ps)
103             if (log.messageHash !in config) {
104                 printUnformatted(log, ps, "UNKNOWN")
105             } else {
106                 val conf = config.getValue(log.messageHash)
107                 try {
108                     printFormatted(log, conf, ps)
109                 } catch (ex: Exception) {
110                     printUnformatted(log, ps, "INVALID")
111                 }
112             }
113         }
114     }
115 }
116