| // Copyright (C) 2018 The Android Open Source Project | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //      http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 |  | 
 | import {BigintMath} from './bigint_math'; | 
 | import {Brand} from './brand'; | 
 | import {assertTrue} from './logging'; | 
 |  | 
 | // The |time| type represents trace time in the same units and domain as trace | 
 | // processor (i.e. typically boot time in nanoseconds, but most of the UI should | 
 | // be completely agnostic to this). | 
 | export type time = Brand<bigint, 'time'>; | 
 |  | 
 | // The |duration| type is used to represent the duration of time between two | 
 | // |time|s. The domain is irrelevant because a duration is relative. | 
 | export type duration = bigint; | 
 |  | 
 | // The conversion factor for converting between different time units. | 
 | const TIME_UNITS_PER_SEC = 1e9; | 
 | const TIME_UNITS_PER_MILLISEC = 1e6; | 
 |  | 
 | export class Time { | 
 |   // Negative time is never found in a trace - so -1 is commonly used as a flag | 
 |   // to represent a value is undefined or unset, without having to use a | 
 |   // nullable or union type. | 
 |   static readonly INVALID = Time.fromRaw(-1n); | 
 |  | 
 |   // The min and max possible values, considering times cannot be negative. | 
 |   static readonly MIN = Time.fromRaw(0n); | 
 |   static readonly MAX = Time.fromRaw(BigintMath.INT64_MAX); | 
 |  | 
 |   static readonly ZERO = Time.fromRaw(0n); | 
 |  | 
 |   // Cast a bigint to a |time|. Supports potentially |undefined| values. | 
 |   // I.e. it performs the following conversions: | 
 |   // - `bigint` -> `time` | 
 |   // - `bigint|undefined` -> `time|undefined` | 
 |   // | 
 |   // Use this function with caution. The function is effectively a no-op in JS, | 
 |   // but using it tells TypeScript that "this value is a time value". It's up to | 
 |   // the caller to ensure the value is in the correct units and time domain. | 
 |   // | 
 |   // If you're reaching for this function after doing some maths on a |time| | 
 |   // value and it's decayed to a |bigint| consider using the static math methods | 
 |   // in |Time| instead, as they will do the appropriate casting for you. | 
 |   static fromRaw(v: bigint): time; | 
 |   static fromRaw(v?: bigint): time|undefined; | 
 |   static fromRaw(v?: bigint): time|undefined { | 
 |     return v as (time | undefined); | 
 |   } | 
 |  | 
 |   // Convert seconds (number) to a time value. | 
 |   // Note: number -> BigInt conversion is relatively slow. | 
 |   static fromSeconds(seconds: number): time { | 
 |     return Time.fromRaw(BigInt(Math.floor(seconds * TIME_UNITS_PER_SEC))); | 
 |   } | 
 |  | 
 |   // Convert time value to seconds and return as a number (i.e. float). | 
 |   // Warning: This function is lossy, i.e. precision is lost when converting | 
 |   // BigInt -> number. | 
 |   // Note: BigInt -> number conversion is relatively slow. | 
 |   static toSeconds(t: time): number { | 
 |     return Number(t) / TIME_UNITS_PER_SEC; | 
 |   } | 
 |  | 
 |   // Convert milliseconds (number) to a time value. | 
 |   // Note: number -> BigInt conversion is relatively slow. | 
 |   static fromMillis(millis: number): time { | 
 |     return Time.fromRaw(BigInt(Math.floor(millis * TIME_UNITS_PER_MILLISEC))); | 
 |   } | 
 |  | 
 |   // Convert time value to milliseconds and return as a number (i.e. float). | 
 |   // Warning: This function is lossy, i.e. precision is lost when converting | 
 |   // BigInt -> number. | 
 |   // Note: BigInt -> number conversion is relatively slow. | 
 |   static toMillis(t: time): number { | 
 |     return Number(t) / TIME_UNITS_PER_MILLISEC; | 
 |   } | 
 |  | 
 |   // Convert a Date object to a time value, given an offset from the unix epoch. | 
 |   // Note: number -> BigInt conversion is relatively slow. | 
 |   static fromDate(d: Date, offset: duration): time { | 
 |     const millis = d.getTime(); | 
 |     const t = Time.fromMillis(millis); | 
 |     return Time.add(t, offset); | 
 |   } | 
 |  | 
 |   // Convert time value to a Date object, given an offset from the unix epoch. | 
 |   // Warning: This function is lossy, i.e. precision is lost when converting | 
 |   // BigInt -> number. | 
 |   // Note: BigInt -> number conversion is relatively slow. | 
 |   static toDate(t: time, offset: duration): Date { | 
 |     const timeSinceEpoch = Time.sub(t, offset); | 
 |     const millis = Time.toMillis(timeSinceEpoch); | 
 |     return new Date(millis); | 
 |   } | 
 |  | 
 |   // Find the closest previous midnight for a given time value. | 
 |   static getLatestMidnight(time: time, offset: duration): time { | 
 |     const date = Time.toDate(time, offset); | 
 |     const floorDay = new Date(Date.UTC( | 
 |       date.getUTCFullYear(), | 
 |       date.getUTCMonth(), | 
 |       date.getUTCDate(), | 
 |     )); | 
 |  | 
 |     return Time.fromDate(floorDay, offset); | 
 |   } | 
 |  | 
 |   static add(t: time, d: duration): time { | 
 |     return Time.fromRaw(t + d); | 
 |   } | 
 |  | 
 |   static sub(t: time, d: duration): time { | 
 |     return Time.fromRaw(t - d); | 
 |   } | 
 |  | 
 |   static diff(a: time, b: time): duration { | 
 |     return a - b; | 
 |   } | 
 |  | 
 |   static min(a: time, b: time): time { | 
 |     return Time.fromRaw(BigintMath.min(a, b)); | 
 |   } | 
 |  | 
 |   static max(a: time, b: time): time { | 
 |     return Time.fromRaw(BigintMath.max(a, b)); | 
 |   } | 
 |  | 
 |   static quantFloor(a: time, b: duration): time { | 
 |     return Time.fromRaw(BigintMath.quantFloor(a, b)); | 
 |   } | 
 |  | 
 |   static quantCeil(a: time, b: duration): time { | 
 |     return Time.fromRaw(BigintMath.quantCeil(a, b)); | 
 |   } | 
 |  | 
 |   static quant(a: time, b: duration): time { | 
 |     return Time.fromRaw(BigintMath.quant(a, b)); | 
 |   } | 
 |  | 
 |   // Format time as seconds. | 
 |   static formatSeconds(time: time): string { | 
 |     return Time.toSeconds(time).toString() + ' s'; | 
 |   } | 
 |  | 
 |   static toTimecode(time: time): Timecode { | 
 |     return new Timecode(time); | 
 |   } | 
 | } | 
 |  | 
 | export class Duration { | 
 |   // The min and max possible duration values - durations can be negative. | 
 |   static MIN = BigintMath.INT64_MIN; | 
 |   static MAX = BigintMath.INT64_MAX; | 
 |   static ZERO = 0n; | 
 |  | 
 |   // Cast a bigint to a |duration|. Supports potentially |undefined| values. | 
 |   // I.e. it performs the following conversions: | 
 |   // - `bigint` -> `duration` | 
 |   // - `bigint|undefined` -> `duration|undefined` | 
 |   // | 
 |   // Use this function with caution. The function is effectively a no-op in JS, | 
 |   // but using it tells TypeScript that "this value is a duration value". It's | 
 |   // up to the caller to ensure the value is in the correct units. | 
 |   // | 
 |   // If you're reaching for this function after doing some maths on a |duration| | 
 |   // value and it's decayed to a |bigint| consider using the static math methods | 
 |   // in |duration| instead, as they will do the appropriate casting for you. | 
 |   static fromRaw(v: bigint): duration; | 
 |   static fromRaw(v?: bigint): duration|undefined; | 
 |   static fromRaw(v?: bigint): duration|undefined { | 
 |     return v as (duration | undefined); | 
 |   } | 
 |  | 
 |   static min(a: duration, b: duration): duration { | 
 |     return BigintMath.min(a, b); | 
 |   } | 
 |  | 
 |   static max(a: duration, b: duration): duration { | 
 |     return BigintMath.max(a, b); | 
 |   } | 
 |  | 
 |   static fromMillis(millis: number) { | 
 |     return BigInt(Math.floor((millis / 1e3) * TIME_UNITS_PER_SEC)); | 
 |   } | 
 |  | 
 |   // Convert time to seconds as a number. | 
 |   // Use this function with caution. It loses precision and is slow. | 
 |   static toSeconds(d: duration) { | 
 |     return Number(d) / TIME_UNITS_PER_SEC; | 
 |   } | 
 |  | 
 |   // Print duration as as human readable string - i.e. to only a handful of | 
 |   // significant figues. | 
 |   // Use this when readability is more desireable than precision. | 
 |   // Examples: 1234 -> 1.23ns | 
 |   //           123456789 -> 123ms | 
 |   //           123,123,123,123,123 -> 34h 12m | 
 |   //           1,000,000,023 -> 1 s | 
 |   //           1,230,000,023 -> 1.2 s | 
 |   static humanise(dur: duration) { | 
 |     const sec = Duration.toSeconds(dur); | 
 |     const units = ['s', 'ms', 'us', 'ns']; | 
 |     const sign = Math.sign(sec); | 
 |     let n = Math.abs(sec); | 
 |     let u = 0; | 
 |     while (n < 1 && n !== 0 && u < units.length - 1) { | 
 |       n *= 1000; | 
 |       u++; | 
 |     } | 
 |     return `${sign < 0 ? '-' : ''}${Math.round(n * 10) / 10}${units[u]}`; | 
 |   } | 
 |  | 
 |   // Print duration with absolute precision. | 
 |   static format(duration: duration): string { | 
 |     let result = ''; | 
 |     if (duration < 1) return '0s'; | 
 |     const unitAndValue: [string, bigint][] = [ | 
 |       ['h', 3_600_000_000_000n], | 
 |       ['m', 60_000_000_000n], | 
 |       ['s', 1_000_000_000n], | 
 |       ['ms', 1_000_000n], | 
 |       ['us', 1_000n], | 
 |       ['ns', 1n], | 
 |     ]; | 
 |     unitAndValue.forEach(([unit, unitSize]) => { | 
 |       if (duration >= unitSize) { | 
 |         const unitCount = duration / unitSize; | 
 |         result += unitCount.toLocaleString() + unit + ' '; | 
 |         duration = duration % unitSize; | 
 |       } | 
 |     }); | 
 |     return result.slice(0, -1); | 
 |   } | 
 |  | 
 |   static formatSeconds(dur: duration): string { | 
 |     return Duration.toSeconds(dur).toString() + ' s'; | 
 |   } | 
 | } | 
 |  | 
 | // This class takes a time and converts it to a set of strings representing a | 
 | // time code where each string represents a group of time units formatted with | 
 | // an appropriate number of leading zeros. | 
 | export class Timecode { | 
 |   public readonly sign: string; | 
 |   public readonly days: string; | 
 |   public readonly hours: string; | 
 |   public readonly minutes: string; | 
 |   public readonly seconds: string; | 
 |   public readonly millis: string; | 
 |   public readonly micros: string; | 
 |   public readonly nanos: string; | 
 |  | 
 |   constructor(time: time) { | 
 |     this.sign = time < 0 ? '-' : ''; | 
 |  | 
 |     const absTime = BigintMath.abs(time); | 
 |  | 
 |     const days = (absTime / 86_400_000_000_000n); | 
 |     const hours = (absTime / 3_600_000_000_000n) % 24n; | 
 |     const minutes = (absTime / 60_000_000_000n) % 60n; | 
 |     const seconds = (absTime / 1_000_000_000n) % 60n; | 
 |     const millis = (absTime / 1_000_000n) % 1_000n; | 
 |     const micros = (absTime / 1_000n) % 1_000n; | 
 |     const nanos = absTime % 1_000n; | 
 |  | 
 |     this.days = days.toString(); | 
 |     this.hours = hours.toString().padStart(2, '0'); | 
 |     this.minutes = minutes.toString().padStart(2, '0'); | 
 |     this.seconds = seconds.toString().padStart(2, '0'); | 
 |     this.millis = millis.toString().padStart(3, '0'); | 
 |     this.micros = micros.toString().padStart(3, '0'); | 
 |     this.nanos = nanos.toString().padStart(3, '0'); | 
 |   } | 
 |  | 
 |   // Get the upper part of the timecode formatted as: [-]DdHH:MM:SS. | 
 |   get dhhmmss(): string { | 
 |     const days = this.days === '0' ? '' : `${this.days}d`; | 
 |     return `${this.sign}${days}${this.hours}:${this.minutes}:${this.seconds}`; | 
 |   } | 
 |  | 
 |   // Get the subsecond part of the timecode formatted as: mmm uuu nnn. | 
 |   // The "space" char is configurable but defaults to a normal space. | 
 |   subsec(spaceChar: string = ' '): string { | 
 |     return `${this.millis}${spaceChar}${this.micros}${spaceChar}${this.nanos}`; | 
 |   } | 
 |  | 
 |   // Formats the entire timecode to a string. | 
 |   toString(spaceChar: string = ' '): string { | 
 |     return `${this.dhhmmss}.${this.subsec(spaceChar)}`; | 
 |   } | 
 | } | 
 |  | 
 |  | 
 | export function currentDateHourAndMinute(): string { | 
 |   const date = new Date(); | 
 |   return `${date.toISOString().substr(0, 10)}-${date.getHours()}-${ | 
 |     date.getMinutes()}`; | 
 | } | 
 |  | 
 | // Create a Time value from an arbitrary SQL value. | 
 |  | 
 |  | 
 | // Represents a half-open interval of time in the form [start, end). | 
 | // E.g. interval contains all time values which are >= start and < end. | 
 | export interface Span<TimeT, DurationT = TimeT> { | 
 |   get start(): TimeT; | 
 |   get end(): TimeT; | 
 |   get duration(): DurationT; | 
 |   get midpoint(): TimeT; | 
 |   contains(span: TimeT|Span<TimeT, DurationT>): boolean; | 
 |   intersectsInterval(span: Span<TimeT, DurationT>): boolean; | 
 |   intersects(a: TimeT, b: TimeT): boolean; | 
 |   equals(span: Span<TimeT, DurationT>): boolean; | 
 |   add(offset: DurationT): Span<TimeT, DurationT>; | 
 |   pad(padding: DurationT): Span<TimeT, DurationT>; | 
 | } | 
 |  | 
 | export class TimeSpan implements Span<time, duration> { | 
 |   static readonly ZERO = new TimeSpan(Time.ZERO, Time.ZERO); | 
 |   readonly start: time; | 
 |   readonly end: time; | 
 |  | 
 |   constructor(start: time, end: time) { | 
 |     assertTrue( | 
 |       start <= end, | 
 |       `Span start [${start}] cannot be greater than end [${end}]`); | 
 |     this.start = start; | 
 |     this.end = end; | 
 |   } | 
 |  | 
 |   static fromTimeAndDuration(start: time, duration: duration): TimeSpan { | 
 |     return new TimeSpan(start, Time.add(start, duration)); | 
 |   } | 
 |  | 
 |   get duration(): duration { | 
 |     return this.end - this.start; | 
 |   } | 
 |  | 
 |   get midpoint(): time { | 
 |     return Time.fromRaw((this.start + this.end) / 2n); | 
 |   } | 
 |  | 
 |   contains(x: time|Span<time, duration>): boolean { | 
 |     if (typeof x === 'bigint') { | 
 |       return this.start <= x && x < this.end; | 
 |     } else { | 
 |       return this.start <= x.start && x.end <= this.end; | 
 |     } | 
 |   } | 
 |  | 
 |   intersectsInterval(span: Span<time, duration>): boolean { | 
 |     return !(span.end <= this.start || span.start >= this.end); | 
 |   } | 
 |  | 
 |   intersects(start: time, end: time): boolean { | 
 |     return !(end <= this.start || start >= this.end); | 
 |   } | 
 |  | 
 |   equals(span: Span<time, duration>): boolean { | 
 |     return this.start === span.start && this.end === span.end; | 
 |   } | 
 |  | 
 |   add(x: duration): Span<time, duration> { | 
 |     return new TimeSpan(Time.add(this.start, x), Time.add(this.end, x)); | 
 |   } | 
 |  | 
 |   pad(padding: duration): Span<time, duration> { | 
 |     return new TimeSpan( | 
 |       Time.sub(this.start, padding), Time.add(this.end, padding)); | 
 |   } | 
 | } | 
 |  | 
 | // Print the date only for a given date in ISO format. | 
 | export function toISODateOnly(date: Date) { | 
 |   const year = date.getUTCFullYear(); | 
 |   const month = String(date.getUTCMonth() + 1).padStart(2, '0'); | 
 |   const day = String(date.getUTCDate()).padStart(2, '0'); | 
 |  | 
 |   return `${year}-${month}-${day}`; | 
 | } |