uiFrames.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import {Rect} from 'sentry/utils/profiling/speedscope';
  2. import {
  3. makeFormatter,
  4. makeFormatTo,
  5. makeTimelineFormatter,
  6. } from 'sentry/utils/profiling/units/units';
  7. export type UIFrameNode = {
  8. duration: number;
  9. end: number;
  10. node: Profiling.MeasurementValue;
  11. start: number;
  12. type: 'slow' | 'frozen';
  13. };
  14. type FrameRenders = NonNullable<Profiling.Schema['measurements']>;
  15. function sortFramesByStartedTime(
  16. a: Profiling.MeasurementValue,
  17. b: Profiling.MeasurementValue
  18. ) {
  19. return a.elapsed_since_start_ns - a.value - (b.elapsed_since_start_ns - b.value);
  20. }
  21. class UIFrames {
  22. frames: ReadonlyArray<UIFrameNode> = [];
  23. toUnit: string = 'nanoseconds';
  24. minFrameDuration: number = Number.MAX_SAFE_INTEGER;
  25. configSpace: Rect = Rect.Empty();
  26. formatter = makeFormatter('nanoseconds');
  27. timelineFormatter = makeTimelineFormatter('nanoseconds');
  28. constructor(
  29. frames: {
  30. frozen: FrameRenders['frozen_frame_renders'];
  31. slow: FrameRenders['slow_frame_renders'];
  32. },
  33. options: {unit: string},
  34. configSpace?: Rect
  35. ) {
  36. const unit = frames.frozen?.unit || frames.slow?.unit || 'nanoseconds';
  37. const slowOrDefaultFrames = frames.slow ?? {values: [], unit};
  38. const frozenOrDefaultFrames = frames.frozen ?? {values: [], unit};
  39. this.toUnit = options.unit;
  40. this.frames = this.buildFramesIntervalTree(
  41. slowOrDefaultFrames,
  42. frozenOrDefaultFrames
  43. );
  44. this.configSpace = configSpace ?? Rect.Empty();
  45. this.timelineFormatter = makeTimelineFormatter(this.toUnit);
  46. this.formatter = makeFormatter(this.toUnit);
  47. }
  48. static Empty = new UIFrames(
  49. {frozen: undefined, slow: undefined},
  50. {unit: 'nanoseconds'}
  51. );
  52. isEmpty(): boolean {
  53. return this === UIFrames.Empty;
  54. }
  55. buildFramesIntervalTree(
  56. slowFrames: NonNullable<FrameRenders['slow_frame_renders']>,
  57. frozenFrames: NonNullable<FrameRenders['frozen_frame_renders']>
  58. ): ReadonlyArray<UIFrameNode> {
  59. const frames: UIFrameNode[] = [];
  60. const toSlowFinalUnit = makeFormatTo(slowFrames.unit, this.toUnit);
  61. const toFrozenFinalUnit = makeFormatTo(frozenFrames.unit, this.toUnit);
  62. const slowFramesQueue = [...slowFrames.values].sort(sortFramesByStartedTime);
  63. const frozenFramesQueue = [...frozenFrames.values].sort(sortFramesByStartedTime);
  64. while (slowFramesQueue.length > 0 || frozenFramesQueue.length > 0) {
  65. const nextType = !slowFramesQueue.length
  66. ? 'frozen'
  67. : !frozenFramesQueue.length
  68. ? 'slow'
  69. : slowFramesQueue[0].elapsed_since_start_ns - slowFramesQueue[0].value <
  70. frozenFramesQueue[0].elapsed_since_start_ns - frozenFramesQueue[0].value
  71. ? 'slow'
  72. : 'frozen';
  73. // Being lazy, but we could reverse and pop to avoid shift which is O(n)
  74. const frame =
  75. nextType === 'slow' ? slowFramesQueue.shift()! : frozenFramesQueue.shift()!;
  76. const unitFn = nextType === 'slow' ? toSlowFinalUnit : toFrozenFinalUnit;
  77. frames.push({
  78. start: unitFn(frame.elapsed_since_start_ns - frame.value),
  79. end: unitFn(frame.elapsed_since_start_ns),
  80. duration: unitFn(frame.value),
  81. node: frame,
  82. type: nextType,
  83. });
  84. this.minFrameDuration = Math.min(this.minFrameDuration, unitFn(frame.value));
  85. }
  86. return frames;
  87. }
  88. }
  89. export {UIFrames};