11cb0ef41Sopenharmony_ci// Copyright 2020 the V8 project authors. All rights reserved.
21cb0ef41Sopenharmony_ci// Use of this source code is governed by a BSD-style license that can be
31cb0ef41Sopenharmony_ci// found in the LICENSE file.
41cb0ef41Sopenharmony_ci
51cb0ef41Sopenharmony_ciimport {groupBy} from './helper.mjs'
61cb0ef41Sopenharmony_ci
71cb0ef41Sopenharmony_ciclass Timeline {
81cb0ef41Sopenharmony_ci  // Class:
91cb0ef41Sopenharmony_ci  _model;
101cb0ef41Sopenharmony_ci  // Array of #model instances:
111cb0ef41Sopenharmony_ci  _values;
121cb0ef41Sopenharmony_ci  // Current selection, subset of #values:
131cb0ef41Sopenharmony_ci  _selection;
141cb0ef41Sopenharmony_ci  _breakdown;
151cb0ef41Sopenharmony_ci
161cb0ef41Sopenharmony_ci  constructor(model, values = [], startTime = null, endTime = null) {
171cb0ef41Sopenharmony_ci    this._model = model;
181cb0ef41Sopenharmony_ci    this._values = values;
191cb0ef41Sopenharmony_ci    this.startTime = startTime;
201cb0ef41Sopenharmony_ci    this.endTime = endTime;
211cb0ef41Sopenharmony_ci    if (values.length > 0) {
221cb0ef41Sopenharmony_ci      if (startTime === null) this.startTime = values[0].time;
231cb0ef41Sopenharmony_ci      if (endTime === null) this.endTime = values[values.length - 1].time;
241cb0ef41Sopenharmony_ci    } else {
251cb0ef41Sopenharmony_ci      if (startTime === null) this.startTime = 0;
261cb0ef41Sopenharmony_ci      if (endTime === null) this.endTime = 0;
271cb0ef41Sopenharmony_ci    }
281cb0ef41Sopenharmony_ci  }
291cb0ef41Sopenharmony_ci
301cb0ef41Sopenharmony_ci  get model() {
311cb0ef41Sopenharmony_ci    return this._model;
321cb0ef41Sopenharmony_ci  }
331cb0ef41Sopenharmony_ci
341cb0ef41Sopenharmony_ci  get all() {
351cb0ef41Sopenharmony_ci    return this._values;
361cb0ef41Sopenharmony_ci  }
371cb0ef41Sopenharmony_ci
381cb0ef41Sopenharmony_ci  get selection() {
391cb0ef41Sopenharmony_ci    return this._selection;
401cb0ef41Sopenharmony_ci  }
411cb0ef41Sopenharmony_ci
421cb0ef41Sopenharmony_ci  get selectionOrSelf() {
431cb0ef41Sopenharmony_ci    return this._selection ?? this;
441cb0ef41Sopenharmony_ci  }
451cb0ef41Sopenharmony_ci
461cb0ef41Sopenharmony_ci  set selection(value) {
471cb0ef41Sopenharmony_ci    this._selection = value;
481cb0ef41Sopenharmony_ci  }
491cb0ef41Sopenharmony_ci
501cb0ef41Sopenharmony_ci  selectTimeRange(startTime, endTime) {
511cb0ef41Sopenharmony_ci    const items = this.range(startTime, endTime);
521cb0ef41Sopenharmony_ci    this._selection = new Timeline(this._model, items, startTime, endTime);
531cb0ef41Sopenharmony_ci  }
541cb0ef41Sopenharmony_ci
551cb0ef41Sopenharmony_ci  clearSelection() {
561cb0ef41Sopenharmony_ci    this._selection = undefined;
571cb0ef41Sopenharmony_ci  }
581cb0ef41Sopenharmony_ci
591cb0ef41Sopenharmony_ci  getChunks(windowSizeMs) {
601cb0ef41Sopenharmony_ci    return this.chunkSizes(windowSizeMs);
611cb0ef41Sopenharmony_ci  }
621cb0ef41Sopenharmony_ci
631cb0ef41Sopenharmony_ci  get values() {
641cb0ef41Sopenharmony_ci    return this._values;
651cb0ef41Sopenharmony_ci  }
661cb0ef41Sopenharmony_ci
671cb0ef41Sopenharmony_ci  count(filter) {
681cb0ef41Sopenharmony_ci    return this.all.reduce((sum, each) => {
691cb0ef41Sopenharmony_ci      return sum + (filter(each) === true ? 1 : 0);
701cb0ef41Sopenharmony_ci    }, 0);
711cb0ef41Sopenharmony_ci  }
721cb0ef41Sopenharmony_ci
731cb0ef41Sopenharmony_ci  filter(predicate) {
741cb0ef41Sopenharmony_ci    return this.all.filter(predicate);
751cb0ef41Sopenharmony_ci  }
761cb0ef41Sopenharmony_ci
771cb0ef41Sopenharmony_ci  push(event) {
781cb0ef41Sopenharmony_ci    let time = event.time;
791cb0ef41Sopenharmony_ci    if (!this.isEmpty() && this.last().time > time) {
801cb0ef41Sopenharmony_ci      // Invalid insertion order, might happen without --single-process,
811cb0ef41Sopenharmony_ci      // finding insertion point.
821cb0ef41Sopenharmony_ci      let insertionPoint = this.find(time);
831cb0ef41Sopenharmony_ci      this._values.splice(insertionPoint, event);
841cb0ef41Sopenharmony_ci    } else {
851cb0ef41Sopenharmony_ci      this._values.push(event);
861cb0ef41Sopenharmony_ci    }
871cb0ef41Sopenharmony_ci    if (time > 0) {
881cb0ef41Sopenharmony_ci      this.endTime = Math.max(this.endTime, time);
891cb0ef41Sopenharmony_ci      if (this.startTime === 0) {
901cb0ef41Sopenharmony_ci        this.startTime = time;
911cb0ef41Sopenharmony_ci      } else {
921cb0ef41Sopenharmony_ci        this.startTime = Math.min(this.startTime, time);
931cb0ef41Sopenharmony_ci      }
941cb0ef41Sopenharmony_ci    }
951cb0ef41Sopenharmony_ci  }
961cb0ef41Sopenharmony_ci
971cb0ef41Sopenharmony_ci  at(index) {
981cb0ef41Sopenharmony_ci    return this._values[index];
991cb0ef41Sopenharmony_ci  }
1001cb0ef41Sopenharmony_ci
1011cb0ef41Sopenharmony_ci  isEmpty() {
1021cb0ef41Sopenharmony_ci    return this.size() === 0;
1031cb0ef41Sopenharmony_ci  }
1041cb0ef41Sopenharmony_ci
1051cb0ef41Sopenharmony_ci  size() {
1061cb0ef41Sopenharmony_ci    return this._values.length;
1071cb0ef41Sopenharmony_ci  }
1081cb0ef41Sopenharmony_ci
1091cb0ef41Sopenharmony_ci  get length() {
1101cb0ef41Sopenharmony_ci    return this._values.length;
1111cb0ef41Sopenharmony_ci  }
1121cb0ef41Sopenharmony_ci
1131cb0ef41Sopenharmony_ci  slice(startIndex, endIndex) {
1141cb0ef41Sopenharmony_ci    return this._values.slice(startIndex, endIndex);
1151cb0ef41Sopenharmony_ci  }
1161cb0ef41Sopenharmony_ci
1171cb0ef41Sopenharmony_ci  first() {
1181cb0ef41Sopenharmony_ci    return this._values[0];
1191cb0ef41Sopenharmony_ci  }
1201cb0ef41Sopenharmony_ci
1211cb0ef41Sopenharmony_ci  last() {
1221cb0ef41Sopenharmony_ci    return this._values[this._values.length - 1];
1231cb0ef41Sopenharmony_ci  }
1241cb0ef41Sopenharmony_ci
1251cb0ef41Sopenharmony_ci  * [Symbol.iterator]() {
1261cb0ef41Sopenharmony_ci    yield* this._values;
1271cb0ef41Sopenharmony_ci  }
1281cb0ef41Sopenharmony_ci
1291cb0ef41Sopenharmony_ci  duration() {
1301cb0ef41Sopenharmony_ci    return this.endTime - this.startTime;
1311cb0ef41Sopenharmony_ci  }
1321cb0ef41Sopenharmony_ci
1331cb0ef41Sopenharmony_ci  forEachChunkSize(count, fn) {
1341cb0ef41Sopenharmony_ci    if (this.isEmpty()) return;
1351cb0ef41Sopenharmony_ci    const increment = this.duration() / count;
1361cb0ef41Sopenharmony_ci    let currentTime = this.startTime;
1371cb0ef41Sopenharmony_ci    let index = 0;
1381cb0ef41Sopenharmony_ci    for (let i = 0; i < count - 1; i++) {
1391cb0ef41Sopenharmony_ci      const nextTime = currentTime + increment;
1401cb0ef41Sopenharmony_ci      const nextIndex = this.findLast(nextTime, index);
1411cb0ef41Sopenharmony_ci      fn(index, nextIndex, currentTime, nextTime);
1421cb0ef41Sopenharmony_ci      index = nextIndex + 1;
1431cb0ef41Sopenharmony_ci      currentTime = nextTime;
1441cb0ef41Sopenharmony_ci    }
1451cb0ef41Sopenharmony_ci    fn(index, this._values.length - 1, currentTime, this.endTime);
1461cb0ef41Sopenharmony_ci  }
1471cb0ef41Sopenharmony_ci
1481cb0ef41Sopenharmony_ci  chunkSizes(count) {
1491cb0ef41Sopenharmony_ci    const chunks = [];
1501cb0ef41Sopenharmony_ci    this.forEachChunkSize(count, (start, end) => chunks.push(end - start));
1511cb0ef41Sopenharmony_ci    return chunks;
1521cb0ef41Sopenharmony_ci  }
1531cb0ef41Sopenharmony_ci
1541cb0ef41Sopenharmony_ci  chunks(count, predicate = undefined) {
1551cb0ef41Sopenharmony_ci    const chunks = [];
1561cb0ef41Sopenharmony_ci    this.forEachChunkSize(count, (start, end, startTime, endTime) => {
1571cb0ef41Sopenharmony_ci      let items = this._values.slice(start, end + 1);
1581cb0ef41Sopenharmony_ci      if (predicate !== undefined) items = items.filter(predicate);
1591cb0ef41Sopenharmony_ci      chunks.push(new Chunk(chunks.length, startTime, endTime, items));
1601cb0ef41Sopenharmony_ci    });
1611cb0ef41Sopenharmony_ci    return chunks;
1621cb0ef41Sopenharmony_ci  }
1631cb0ef41Sopenharmony_ci
1641cb0ef41Sopenharmony_ci  // Return all entries in ({startTime}, {endTime}]
1651cb0ef41Sopenharmony_ci  range(startTime, endTime) {
1661cb0ef41Sopenharmony_ci    const firstIndex = this.find(startTime);
1671cb0ef41Sopenharmony_ci    if (firstIndex < 0) return [];
1681cb0ef41Sopenharmony_ci    const lastIndex = this.find(endTime, firstIndex + 1);
1691cb0ef41Sopenharmony_ci    return this._values.slice(firstIndex, lastIndex);
1701cb0ef41Sopenharmony_ci  }
1711cb0ef41Sopenharmony_ci
1721cb0ef41Sopenharmony_ci  // Return the first index with element.time >= time.
1731cb0ef41Sopenharmony_ci  find(time, offset = 0) {
1741cb0ef41Sopenharmony_ci    return this.findFirst(time, offset);
1751cb0ef41Sopenharmony_ci  }
1761cb0ef41Sopenharmony_ci
1771cb0ef41Sopenharmony_ci  findFirst(time, offset = 0) {
1781cb0ef41Sopenharmony_ci    return this._find(this._values, each => each.time - time, offset);
1791cb0ef41Sopenharmony_ci  }
1801cb0ef41Sopenharmony_ci
1811cb0ef41Sopenharmony_ci  // Return the last index with element.time <= time.
1821cb0ef41Sopenharmony_ci  findLast(time, offset = 0) {
1831cb0ef41Sopenharmony_ci    const nextTime = time + 1;
1841cb0ef41Sopenharmony_ci    let index = (this.last().time <= nextTime) ?
1851cb0ef41Sopenharmony_ci        this.length :
1861cb0ef41Sopenharmony_ci        this.findFirst(nextTime + 1, offset);
1871cb0ef41Sopenharmony_ci    // Typically we just have to look at the previous element.
1881cb0ef41Sopenharmony_ci    while (index > 0) {
1891cb0ef41Sopenharmony_ci      index--;
1901cb0ef41Sopenharmony_ci      if (this._values[index].time <= time) return index;
1911cb0ef41Sopenharmony_ci    }
1921cb0ef41Sopenharmony_ci    return -1;
1931cb0ef41Sopenharmony_ci  }
1941cb0ef41Sopenharmony_ci
1951cb0ef41Sopenharmony_ci  // Return the first index for which compareFn(item) is >= 0;
1961cb0ef41Sopenharmony_ci  _find(array, compareFn, offset = 0) {
1971cb0ef41Sopenharmony_ci    let minIndex = offset;
1981cb0ef41Sopenharmony_ci    let maxIndex = array.length - 1;
1991cb0ef41Sopenharmony_ci    while (minIndex < maxIndex) {
2001cb0ef41Sopenharmony_ci      const midIndex = minIndex + (((maxIndex - minIndex) / 2) | 0);
2011cb0ef41Sopenharmony_ci      if (compareFn(array[midIndex]) < 0) {
2021cb0ef41Sopenharmony_ci        minIndex = midIndex + 1;
2031cb0ef41Sopenharmony_ci      } else {
2041cb0ef41Sopenharmony_ci        maxIndex = midIndex;
2051cb0ef41Sopenharmony_ci      }
2061cb0ef41Sopenharmony_ci    }
2071cb0ef41Sopenharmony_ci    return minIndex;
2081cb0ef41Sopenharmony_ci  }
2091cb0ef41Sopenharmony_ci
2101cb0ef41Sopenharmony_ci  getBreakdown(keyFunction, collect = false) {
2111cb0ef41Sopenharmony_ci    if (keyFunction || collect) {
2121cb0ef41Sopenharmony_ci      if (!keyFunction) {
2131cb0ef41Sopenharmony_ci        keyFunction = each => each.type;
2141cb0ef41Sopenharmony_ci      }
2151cb0ef41Sopenharmony_ci      return groupBy(this._values, keyFunction, collect);
2161cb0ef41Sopenharmony_ci    }
2171cb0ef41Sopenharmony_ci    if (this._breakdown === undefined) {
2181cb0ef41Sopenharmony_ci      this._breakdown = groupBy(this._values, each => each.type);
2191cb0ef41Sopenharmony_ci    }
2201cb0ef41Sopenharmony_ci    return this._breakdown;
2211cb0ef41Sopenharmony_ci  }
2221cb0ef41Sopenharmony_ci
2231cb0ef41Sopenharmony_ci  depthHistogram() {
2241cb0ef41Sopenharmony_ci    return this._values.histogram(each => each.depth);
2251cb0ef41Sopenharmony_ci  }
2261cb0ef41Sopenharmony_ci
2271cb0ef41Sopenharmony_ci  fanOutHistogram() {
2281cb0ef41Sopenharmony_ci    return this._values.histogram(each => each.children.length);
2291cb0ef41Sopenharmony_ci  }
2301cb0ef41Sopenharmony_ci
2311cb0ef41Sopenharmony_ci  forEach(fn) {
2321cb0ef41Sopenharmony_ci    return this._values.forEach(fn);
2331cb0ef41Sopenharmony_ci  }
2341cb0ef41Sopenharmony_ci}
2351cb0ef41Sopenharmony_ci
2361cb0ef41Sopenharmony_ci// ===========================================================================
2371cb0ef41Sopenharmony_ciclass Chunk {
2381cb0ef41Sopenharmony_ci  constructor(index, start, end, items) {
2391cb0ef41Sopenharmony_ci    this.index = index;
2401cb0ef41Sopenharmony_ci    this.start = start;
2411cb0ef41Sopenharmony_ci    this.end = end;
2421cb0ef41Sopenharmony_ci    this.items = items;
2431cb0ef41Sopenharmony_ci    this.height = 0;
2441cb0ef41Sopenharmony_ci  }
2451cb0ef41Sopenharmony_ci
2461cb0ef41Sopenharmony_ci  isEmpty() {
2471cb0ef41Sopenharmony_ci    return this.items.length === 0;
2481cb0ef41Sopenharmony_ci  }
2491cb0ef41Sopenharmony_ci
2501cb0ef41Sopenharmony_ci  last() {
2511cb0ef41Sopenharmony_ci    return this.at(this.size() - 1);
2521cb0ef41Sopenharmony_ci  }
2531cb0ef41Sopenharmony_ci
2541cb0ef41Sopenharmony_ci  first() {
2551cb0ef41Sopenharmony_ci    return this.at(0);
2561cb0ef41Sopenharmony_ci  }
2571cb0ef41Sopenharmony_ci
2581cb0ef41Sopenharmony_ci  at(index) {
2591cb0ef41Sopenharmony_ci    return this.items[index];
2601cb0ef41Sopenharmony_ci  }
2611cb0ef41Sopenharmony_ci
2621cb0ef41Sopenharmony_ci  size() {
2631cb0ef41Sopenharmony_ci    return this.items.length;
2641cb0ef41Sopenharmony_ci  }
2651cb0ef41Sopenharmony_ci
2661cb0ef41Sopenharmony_ci  get length() {
2671cb0ef41Sopenharmony_ci    return this.items.length;
2681cb0ef41Sopenharmony_ci  }
2691cb0ef41Sopenharmony_ci
2701cb0ef41Sopenharmony_ci  yOffset(event) {
2711cb0ef41Sopenharmony_ci    // items[0]   == oldest event, displayed at the top of the chunk
2721cb0ef41Sopenharmony_ci    // items[n-1] == youngest event, displayed at the bottom of the chunk
2731cb0ef41Sopenharmony_ci    return ((this.indexOf(event) + 0.5) / this.size()) * this.height;
2741cb0ef41Sopenharmony_ci  }
2751cb0ef41Sopenharmony_ci
2761cb0ef41Sopenharmony_ci  indexOf(event) {
2771cb0ef41Sopenharmony_ci    return this.items.indexOf(event);
2781cb0ef41Sopenharmony_ci  }
2791cb0ef41Sopenharmony_ci
2801cb0ef41Sopenharmony_ci  has(event) {
2811cb0ef41Sopenharmony_ci    if (this.isEmpty()) return false;
2821cb0ef41Sopenharmony_ci    return this.first().time <= event.time && event.time <= this.last().time;
2831cb0ef41Sopenharmony_ci  }
2841cb0ef41Sopenharmony_ci
2851cb0ef41Sopenharmony_ci  next(chunks) {
2861cb0ef41Sopenharmony_ci    return this.findChunk(chunks, 1);
2871cb0ef41Sopenharmony_ci  }
2881cb0ef41Sopenharmony_ci
2891cb0ef41Sopenharmony_ci  prev(chunks) {
2901cb0ef41Sopenharmony_ci    return this.findChunk(chunks, -1);
2911cb0ef41Sopenharmony_ci  }
2921cb0ef41Sopenharmony_ci
2931cb0ef41Sopenharmony_ci  findChunk(chunks, delta) {
2941cb0ef41Sopenharmony_ci    let i = this.index + delta;
2951cb0ef41Sopenharmony_ci    let chunk = chunks[i];
2961cb0ef41Sopenharmony_ci    while (chunk && chunk.size() === 0) {
2971cb0ef41Sopenharmony_ci      i += delta;
2981cb0ef41Sopenharmony_ci      chunk = chunks[i];
2991cb0ef41Sopenharmony_ci    }
3001cb0ef41Sopenharmony_ci    return chunk;
3011cb0ef41Sopenharmony_ci  }
3021cb0ef41Sopenharmony_ci
3031cb0ef41Sopenharmony_ci  getBreakdown(keyFunction) {
3041cb0ef41Sopenharmony_ci    return groupBy(this.items, keyFunction);
3051cb0ef41Sopenharmony_ci  }
3061cb0ef41Sopenharmony_ci
3071cb0ef41Sopenharmony_ci  filter() {
3081cb0ef41Sopenharmony_ci    return this.items.filter(map => !map.parent || !this.has(map.parent));
3091cb0ef41Sopenharmony_ci  }
3101cb0ef41Sopenharmony_ci}
3111cb0ef41Sopenharmony_ci
3121cb0ef41Sopenharmony_ciexport {Timeline, Chunk};
313