1/*
2 * Copyright (C) 2023 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 {StringUtils} from 'common/string_utils';
18import {Timestamp} from 'common/time';
19import {AbstractParser} from 'parsers/legacy/abstract_parser';
20import {TraceType} from 'trace/trace_type';
21import {PropertyTreeBuilderFromProto} from 'trace/tree_node/property_tree_builder_from_proto';
22import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
23
24class ParserEventLog extends AbstractParser<PropertyTreeNode> {
25  private static readonly MAGIC_NUMBER_STRING = 'EventLog';
26  private static readonly MAGIC_NUMBER: number[] = Array.from(
27    new TextEncoder().encode(ParserEventLog.MAGIC_NUMBER_STRING),
28  );
29
30  override getTraceType(): TraceType {
31    return TraceType.EVENT_LOG;
32  }
33
34  override getMagicNumber(): number[] {
35    return ParserEventLog.MAGIC_NUMBER;
36  }
37
38  override getRealToMonotonicTimeOffsetNs(): bigint | undefined {
39    return undefined;
40  }
41
42  override getRealToBootTimeOffsetNs(): bigint | undefined {
43    return undefined;
44  }
45
46  override decodeTrace(buffer: Uint8Array): Event[] {
47    const decodedLogs = this.decodeByteArray(buffer);
48    const events = this.parseLogs(decodedLogs);
49    return events.sort((a: Event, b: Event) => {
50      return a.eventTimestamp < b.eventTimestamp ? -1 : 1;
51    });
52  }
53
54  protected override getTimestamp(entry: Event): Timestamp {
55    return this.timestampConverter.makeTimestampFromRealNs(
56      entry.eventTimestamp,
57    );
58  }
59
60  override processDecodedEntry(index: number, entry: Event): PropertyTreeNode {
61    return new PropertyTreeBuilderFromProto()
62      .setData(entry)
63      .setRootId('EventLogTrace')
64      .setRootName('event')
65      .build();
66  }
67
68  private decodeByteArray(bytes: Uint8Array): string[] {
69    const allLogsString = new TextDecoder().decode(bytes);
70    const splitLogs = allLogsString.split('\n');
71
72    const firstIndexOfEventLogTrace = splitLogs.findIndex((substring) => {
73      return (
74        !substring.includes(ParserEventLog.MAGIC_NUMBER_STRING) &&
75        !substring.includes('beginning of events') &&
76        !StringUtils.isBlank(substring)
77      );
78    });
79
80    const lastIndexOfEventLogTrace = splitLogs.findIndex((substring, index) => {
81      return (
82        index > firstIndexOfEventLogTrace && StringUtils.isBlank(substring)
83      );
84    });
85
86    if (lastIndexOfEventLogTrace === -1) {
87      return splitLogs.slice(firstIndexOfEventLogTrace);
88    }
89    return splitLogs.slice(firstIndexOfEventLogTrace, lastIndexOfEventLogTrace);
90  }
91
92  private parseLogs(input: string[]): Event[] {
93    return input.map((log) => {
94      const [metaData, eventData] = log
95        .split(':', 2)
96        .map((string) => string.trim());
97      const [rawTimestamp, uid, pid, tid, priority, tag] = metaData
98        .split(' ')
99        .filter((substring) => substring.length > 0);
100      const timestampNs = BigInt(rawTimestamp.replace('.', ''));
101      return {
102        eventTimestamp: timestampNs,
103        pid: Number(pid),
104        uid: Number(uid),
105        tid: Number(tid),
106        tag,
107        eventData,
108      };
109    });
110  }
111}
112
113interface Event {
114  eventTimestamp: bigint;
115  pid: number;
116  uid: number;
117  tid: number;
118  tag: string;
119  eventData: string;
120}
121
122export {ParserEventLog};
123