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