cm6-performance.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { Transaction } from '@codemirror/state'
  2. import { EditorView } from '@codemirror/view'
  3. const TIMER_START_NAME = 'CM6-BeforeUpdate'
  4. const TIMER_END_NAME = 'CM6-AfterUpdate'
  5. const TIMER_MEASURE_NAME = 'CM6-Update'
  6. let latestDocLength = 0
  7. let performanceMeasureOptionsSupport = false
  8. // Check that performance.measure accepts an options object
  9. try {
  10. const testMeasureName = 'featureTest'
  11. performance.measure(testMeasureName, { start: performance.now() })
  12. performance.clearMeasures(testMeasureName)
  13. performanceMeasureOptionsSupport = true
  14. } catch (e) {}
  15. function isInputOrDelete(userEventType: string | undefined) {
  16. return (
  17. !!userEventType && ['input', 'delete'].includes(userEventType.split('.')[0])
  18. )
  19. }
  20. export function timedDispatch() {
  21. let userEventsSinceDomUpdateCount = 0
  22. return (
  23. view: EditorView,
  24. tr: Transaction,
  25. dispatchFn: (tr: Transaction) => void
  26. ) => {
  27. if (!performanceMeasureOptionsSupport) {
  28. dispatchFn(tr)
  29. return
  30. }
  31. performance.mark(TIMER_START_NAME)
  32. dispatchFn(tr)
  33. performance.mark(TIMER_END_NAME)
  34. const userEventType = tr.annotation(Transaction.userEvent)
  35. if (isInputOrDelete(userEventType)) {
  36. ++userEventsSinceDomUpdateCount
  37. performance.measure(TIMER_MEASURE_NAME, {
  38. start: TIMER_START_NAME,
  39. end: TIMER_END_NAME,
  40. detail: { userEventType, userEventsSinceDomUpdateCount },
  41. })
  42. // The `key` property ensures that the measurement task is only run once
  43. // per measure phase
  44. view.requestMeasure({
  45. key: 'inputEventCounter',
  46. read() {
  47. userEventsSinceDomUpdateCount = 0
  48. },
  49. })
  50. }
  51. latestDocLength = tr.state.doc.length
  52. }
  53. }
  54. function calculateMean(durations: number[]) {
  55. if (durations.length === 0) return 0
  56. const sum = durations.reduce((acc, entry) => acc + entry, 0)
  57. return sum / durations.length
  58. }
  59. function calculateMedian(sortedDurations: number[]) {
  60. if (sortedDurations.length === 0) return 0
  61. const middle = Math.floor(sortedDurations.length / 2)
  62. if (sortedDurations.length % 2 === 0) {
  63. return (sortedDurations[middle - 1] + sortedDurations[middle]) / 2
  64. }
  65. return sortedDurations[middle]
  66. }
  67. function calculate95thPercentile(sortedDurations: number[]) {
  68. if (sortedDurations.length === 0) return 0
  69. const index = Math.round((sortedDurations.length - 1) * 0.95)
  70. return sortedDurations[index]
  71. }
  72. function calculateMax(numbers: number[]) {
  73. return numbers.reduce((a, b) => Math.max(a, b), 0)
  74. }
  75. export function reportCM6Perf() {
  76. // Get entries triggered by keystrokes
  77. const cm6Entries = performance.getEntriesByName(
  78. TIMER_MEASURE_NAME,
  79. 'measure'
  80. ) as PerformanceMeasure[]
  81. const inputEvents = cm6Entries.filter(({ detail }) =>
  82. isInputOrDelete(detail.userEventType)
  83. )
  84. const inputDurations = inputEvents
  85. .map(({ duration }) => duration)
  86. .sort((a, b) => a - b)
  87. const max = calculateMax(inputDurations)
  88. const mean = calculateMean(inputDurations)
  89. const median = calculateMedian(inputDurations)
  90. const ninetyFifthPercentile = calculate95thPercentile(inputDurations)
  91. const maxUserEventsBetweenDomUpdates = calculateMax(
  92. inputEvents.map(e => e.detail.userEventsSinceDomUpdateCount)
  93. )
  94. performance.clearMeasures(TIMER_MEASURE_NAME)
  95. return {
  96. max,
  97. mean,
  98. median,
  99. ninetyFifthPercentile,
  100. maxUserEventsBetweenDomUpdates,
  101. docLength: latestDocLength,
  102. numberOfEntries: inputDurations.length,
  103. }
  104. }
  105. window._reportCM6Perf = () => {
  106. console.log(reportCM6Perf())
  107. }