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