• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {ElapsedTimestamp, RealTimestamp, Timestamp, TimestampType} from 'trace/timestamp';
18
19export class TimeUtils {
20  static compareFn(a: Timestamp, b: Timestamp): number {
21    if (a.getType() !== b.getType()) {
22      throw new Error('Attempted to compare two timestamps with different type');
23    }
24    return Number(a.getValueNs() - b.getValueNs());
25  }
26
27  static format(timestamp: Timestamp, hideNs = false): string {
28    switch (timestamp.getType()) {
29      case TimestampType.ELAPSED: {
30        return TimeUtils.nanosecondsToHumanElapsed(timestamp.getValueNs(), hideNs);
31      }
32      case TimestampType.REAL: {
33        return TimeUtils.nanosecondsToHumanReal(timestamp.getValueNs(), hideNs);
34      }
35      default: {
36        throw Error('Unhandled timestamp type');
37      }
38    }
39  }
40
41  private static nanosecondsToHumanElapsed(timestampNanos: number | bigint, hideNs = true): string {
42    timestampNanos = BigInt(timestampNanos);
43    const units = TimeUtils.units;
44
45    let leftNanos = timestampNanos;
46    const parts: Array<{value: bigint; unit: string}> = units
47      .slice()
48      .reverse()
49      .map(({nanosInUnit, unit}) => {
50        let amountOfUnit = BigInt(0);
51        if (leftNanos >= nanosInUnit) {
52          amountOfUnit = leftNanos / BigInt(nanosInUnit);
53        }
54        leftNanos = leftNanos % BigInt(nanosInUnit);
55        return {value: amountOfUnit, unit};
56      });
57
58    if (hideNs) {
59      parts.pop();
60    }
61
62    // Remove all 0ed units at start
63    while (parts.length > 1 && parts[0].value === 0n) {
64      parts.shift();
65    }
66
67    return parts.map((part) => `${part.value}${part.unit}`).join('');
68  }
69
70  private static nanosecondsToHumanReal(timestampNanos: number | bigint, hideNs = true): string {
71    timestampNanos = BigInt(timestampNanos);
72    const ms = timestampNanos / 1000000n;
73    const extraNanos = timestampNanos % 1000000n;
74    const formattedTimestamp = new Date(Number(ms)).toISOString().replace('Z', '');
75
76    if (hideNs) {
77      return formattedTimestamp;
78    } else {
79      return `${formattedTimestamp}${extraNanos.toString().padStart(6, '0')}`;
80    }
81  }
82
83  static parseHumanElapsed(timestampHuman: string): Timestamp {
84    if (!TimeUtils.HUMAN_ELAPSED_TIMESTAMP_REGEX.test(timestampHuman)) {
85      throw Error('Invalid elapsed timestamp format');
86    }
87
88    const units = TimeUtils.units;
89
90    const usedUnits = timestampHuman.split(/[0-9]+/).filter((it) => it !== '');
91    const usedValues = timestampHuman
92      .split(/[a-z]+/)
93      .filter((it) => it !== '')
94      .map((it) => Math.floor(Number(it)));
95
96    let ns = BigInt(0);
97
98    for (let i = 0; i < usedUnits.length; i++) {
99      const unit = usedUnits[i];
100      const value = usedValues[i];
101      const unitData = units.find((it) => it.unit === unit)!;
102      ns += BigInt(unitData.nanosInUnit) * BigInt(value);
103    }
104
105    return new ElapsedTimestamp(ns);
106  }
107
108  static parseHumanReal(timestampHuman: string): Timestamp {
109    if (!TimeUtils.HUMAN_REAL_TIMESTAMP_REGEX.test(timestampHuman)) {
110      throw Error('Invalid real timestamp format');
111    }
112
113    // Add trailing Z if it isn't there yet
114    if (timestampHuman[timestampHuman.length - 1] !== 'Z') {
115      timestampHuman += 'Z';
116    }
117
118    // Date.parse only considers up to millisecond precision
119    let nanoSeconds = 0;
120    if (timestampHuman.includes('.')) {
121      const milliseconds = timestampHuman.split('.')[1].replace('Z', '');
122      nanoSeconds = Math.floor(Number(milliseconds.padEnd(9, '0').slice(3)));
123    }
124
125    return new RealTimestamp(
126      BigInt(Date.parse(timestampHuman)) * BigInt(TimeUtils.TO_NANO['ms']) + BigInt(nanoSeconds)
127    );
128  }
129
130  static formattedKotlinTimestamp(time: any, timestampType: TimestampType): string {
131    if (timestampType === TimestampType.ELAPSED) {
132      return TimeUtils.format(new ElapsedTimestamp(BigInt(time.elapsedNanos.toString())));
133    }
134
135    if (timestampType === TimestampType.REAL) {
136      return TimeUtils.format(new RealTimestamp(BigInt(time.unixNanos.toString())));
137    }
138
139    throw new Error('Unsupported timestamp');
140  }
141
142  static TO_NANO = {
143    ns: 1,
144    ms: 1000000,
145    s: 1000000 * 1000,
146    m: 1000000 * 1000 * 60,
147    h: 1000000 * 1000 * 60 * 60,
148    d: 1000000 * 1000 * 60 * 60 * 24,
149  };
150
151  static units = [
152    {nanosInUnit: TimeUtils.TO_NANO['ns'], unit: 'ns'},
153    {nanosInUnit: TimeUtils.TO_NANO['ms'], unit: 'ms'},
154    {nanosInUnit: TimeUtils.TO_NANO['s'], unit: 's'},
155    {nanosInUnit: TimeUtils.TO_NANO['m'], unit: 'm'},
156    {nanosInUnit: TimeUtils.TO_NANO['h'], unit: 'h'},
157    {nanosInUnit: TimeUtils.TO_NANO['d'], unit: 'd'},
158  ];
159
160  // (?=.) checks there is at least one character with a lookahead match
161  static readonly HUMAN_ELAPSED_TIMESTAMP_REGEX =
162    /^(?=.)([0-9]+d)?([0-9]+h)?([0-9]+m)?([0-9]+s)?([0-9]+ms)?([0-9]+ns)?$/;
163  static readonly HUMAN_REAL_TIMESTAMP_REGEX =
164    /^[0-9]{4}-((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01])|(0[469]|11)-(0[1-9]|[12][0-9]|30)|(02)-(0[1-9]|[12][0-9]))T(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9]):(0[0-9]|[1-5][0-9])\.[0-9]{3}([0-9]{6})?Z?$/;
165  static readonly NS_TIMESTAMP_REGEX = /^\s*[0-9]+(\s?ns)?\s*$/;
166}
167