1/* 2 * Copyright (C) 2022 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 17import {assertDefined} from 'common/assert_utils'; 18import {Timestamp} from 'common/time/time'; 19import {AbstractParser} from 'parsers/legacy/abstract_parser'; 20import {LogMessage} from 'parsers/protolog/log_message'; 21import {ParserProtologUtils} from 'parsers/protolog/parser_protolog_utils'; 22import root from 'protos/protolog/udc/json'; 23import {com} from 'protos/protolog/udc/static'; 24import {TraceType} from 'trace/trace_type'; 25import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 26import configJson32 from '../../../../configs/services.core.protolog32.json'; // eslint-disable-line no-restricted-imports 27import configJson64 from '../../../../configs/services.core.protolog64.json'; // eslint-disable-line no-restricted-imports 28 29type ProtoLogMessage = com.android.internal.protolog.IProtoLogMessage; 30 31class ParserProtoLog extends AbstractParser<PropertyTreeNode, ProtoLogMessage> { 32 private static readonly ProtoLogFileProto = root.lookupType( 33 'com.android.internal.protolog.ProtoLogFileProto', 34 ); 35 private static readonly MAGIC_NUMBER = [ 36 0x09, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x4c, 0x4f, 0x47, 37 ]; // .PROTOLOG 38 private static readonly PROTOLOG_32_BIT_VERSION = '1.0.0'; 39 private static readonly PROTOLOG_64_BIT_VERSION = '2.0.0'; 40 41 private realToBootTimeOffsetNs: bigint | undefined; 42 43 override getTraceType(): TraceType { 44 return TraceType.PROTO_LOG; 45 } 46 47 override getMagicNumber(): number[] { 48 return ParserProtoLog.MAGIC_NUMBER; 49 } 50 51 override getRealToMonotonicTimeOffsetNs(): bigint | undefined { 52 return undefined; 53 } 54 55 override getRealToBootTimeOffsetNs(): bigint | undefined { 56 return this.realToBootTimeOffsetNs; 57 } 58 59 override decodeTrace(buffer: Uint8Array): ProtoLogMessage[] { 60 const fileProto = ParserProtoLog.ProtoLogFileProto.decode( 61 buffer, 62 ) as com.android.internal.protolog.IProtoLogFileProto; 63 64 if (fileProto.version === ParserProtoLog.PROTOLOG_32_BIT_VERSION) { 65 if (configJson32.version !== ParserProtoLog.PROTOLOG_32_BIT_VERSION) { 66 const message = `Unsupported ProtoLog JSON config version ${configJson32.version} expected ${ParserProtoLog.PROTOLOG_32_BIT_VERSION}`; 67 console.log(message); 68 throw new TypeError(message); 69 } 70 } else if (fileProto.version === ParserProtoLog.PROTOLOG_64_BIT_VERSION) { 71 if (configJson64.version !== ParserProtoLog.PROTOLOG_64_BIT_VERSION) { 72 const message = `Unsupported ProtoLog JSON config version ${configJson64.version} expected ${ParserProtoLog.PROTOLOG_64_BIT_VERSION}`; 73 console.log(message); 74 throw new TypeError(message); 75 } 76 } else { 77 const message = 'Unsupported ProtoLog trace version'; 78 console.log(message); 79 throw new TypeError(message); 80 } 81 82 this.realToBootTimeOffsetNs = 83 BigInt( 84 assertDefined(fileProto.realTimeToElapsedTimeOffsetMillis).toString(), 85 ) * 1000000n; 86 87 if (!fileProto.log) { 88 return []; 89 } 90 91 fileProto.log.sort((a: ProtoLogMessage, b: ProtoLogMessage) => { 92 return Number(a.elapsedRealtimeNanos) - Number(b.elapsedRealtimeNanos); 93 }); 94 95 return fileProto.log; 96 } 97 98 protected override getTimestamp(entry: ProtoLogMessage): Timestamp { 99 return this.timestampConverter.makeTimestampFromBootTimeNs( 100 BigInt(assertDefined(entry.elapsedRealtimeNanos).toString()), 101 ); 102 } 103 104 override processDecodedEntry( 105 index: number, 106 entry: ProtoLogMessage, 107 ): PropertyTreeNode { 108 let messageHash = assertDefined(entry.messageHash).toString(); 109 let config: ProtologConfig | undefined = undefined; 110 if (messageHash !== null && messageHash !== '0') { 111 config = assertDefined(configJson64) as ProtologConfig; 112 } else { 113 messageHash = assertDefined(entry.messageHashLegacy).toString(); 114 config = assertDefined(configJson32) as ProtologConfig; 115 } 116 117 const message: ConfigMessage | undefined = config.messages[messageHash]; 118 const tag: string | undefined = message 119 ? config.groups[message.group].tag 120 : undefined; 121 122 const logMessage = this.makeLogMessage(entry, message, tag); 123 return ParserProtologUtils.makeMessagePropertiesTree( 124 logMessage, 125 this.timestampConverter, 126 this.getRealToMonotonicTimeOffsetNs() !== undefined, 127 ); 128 } 129 130 private makeLogMessage( 131 entry: ProtoLogMessage, 132 message: ConfigMessage | undefined, 133 tag: string | undefined, 134 ): LogMessage { 135 if (!message || !tag) { 136 return this.makeLogMessageWithoutFormat(entry); 137 } 138 try { 139 return this.makeLogMessageWithFormat(entry, message, tag); 140 } catch (error) { 141 if (error instanceof FormatStringMismatchError) { 142 return this.makeLogMessageWithoutFormat(entry); 143 } 144 throw this.createParsingError((error as Error).message); 145 } 146 } 147 148 private makeLogMessageWithFormat( 149 entry: ProtoLogMessage, 150 message: ConfigMessage, 151 tag: string, 152 ): LogMessage { 153 let text = ''; 154 155 const strParams: string[] = assertDefined(entry.strParams); 156 let strParamsIdx = 0; 157 const sint64Params: Array<bigint> = assertDefined(entry.sint64Params).map( 158 (param) => BigInt(param.toString()), 159 ); 160 let sint64ParamsIdx = 0; 161 const doubleParams: number[] = assertDefined(entry.doubleParams); 162 let doubleParamsIdx = 0; 163 const booleanParams: boolean[] = assertDefined(entry.booleanParams); 164 let booleanParamsIdx = 0; 165 166 const messageFormat = message.message; 167 for (let i = 0; i < messageFormat.length; ) { 168 if (messageFormat[i] === '%') { 169 if (i + 1 >= messageFormat.length) { 170 // Should never happen - protologtool checks for that 171 throw this.createParsingError('invalid format string'); 172 } 173 switch (messageFormat[i + 1]) { 174 case '%': 175 text += '%'; 176 break; 177 case 'd': 178 text += this.getParam(sint64Params, sint64ParamsIdx++).toString(10); 179 break; 180 case 'o': 181 text += this.getParam(sint64Params, sint64ParamsIdx++).toString(8); 182 break; 183 case 'x': 184 text += this.getParam(sint64Params, sint64ParamsIdx++).toString(16); 185 break; 186 case 'f': 187 text += this.getParam(doubleParams, doubleParamsIdx++).toFixed(6); 188 break; 189 case 'e': 190 text += this.getParam( 191 doubleParams, 192 doubleParamsIdx++, 193 ).toExponential(); 194 break; 195 case 'g': 196 text += this.getParam(doubleParams, doubleParamsIdx++).toString(); 197 break; 198 case 's': 199 text += this.getParam(strParams, strParamsIdx++); 200 break; 201 case 'b': 202 text += this.getParam(booleanParams, booleanParamsIdx++).toString(); 203 break; 204 default: 205 // Should never happen - protologtool checks for that 206 throw this.createParsingError( 207 'invalid format string conversion: ' + messageFormat[i + 1], 208 ); 209 } 210 i += 2; 211 } else { 212 text += messageFormat[i]; 213 i += 1; 214 } 215 } 216 217 return { 218 text, 219 tag, 220 level: message.level, 221 at: message.at, 222 timestamp: BigInt(assertDefined(entry.elapsedRealtimeNanos).toString()), 223 }; 224 } 225 226 private getParam<T>(arr: T[], idx: number): T { 227 if (arr.length <= idx) { 228 throw this.createParsingError('no param for format string conversion'); 229 } 230 return arr[idx]; 231 } 232 233 private makeLogMessageWithoutFormat(entry: ProtoLogMessage): LogMessage { 234 const text = 235 assertDefined(entry.messageHash).toString() + 236 ' - [' + 237 assertDefined(entry.strParams).toString() + 238 '] [' + 239 assertDefined(entry.sint64Params).toString() + 240 '] [' + 241 assertDefined(entry.doubleParams).toString() + 242 '] [' + 243 assertDefined(entry.booleanParams).toString() + 244 ']'; 245 246 return { 247 text, 248 tag: 'INVALID', 249 level: 'invalid', 250 at: '', 251 timestamp: BigInt(assertDefined(entry.elapsedRealtimeNanos).toString()), 252 }; 253 } 254 255 private createParsingError(msg: string) { 256 return new Error(`Protolog parsing error: ${msg}`); 257 } 258} 259 260class FormatStringMismatchError extends Error { 261 constructor(message: string) { 262 super(message); 263 } 264} 265 266interface ProtologConfig { 267 version: string; 268 messages: {[key: string]: ConfigMessage}; 269 groups: {[key: string]: {tag: string}}; 270} 271 272interface ConfigMessage { 273 message: string; 274 level: string; 275 group: string; 276 at: string; 277} 278 279export {ParserProtoLog}; 280