Repository: JetBrains/compose-multiplatform
File: benchmarks/multiplatform/benchmarks/src/commonMain/kotlin/Benchmarks.kt (Kotlin)

Code before:
                    repeat(Config.warmupCount) {
                        withFrameNanos { }
                    }
                    isWarmup = false
                    isEmptyScreen = true
                } else if (isEmptyScreen) {
                    delay(Config.emptyScreenDelay.milliseconds)
                    withFrameNanos {  }
                    isEmptyScreen = false
                } else {
                    val start = TimeSource.Monotonic.markNow()
                    val frames = MutableList(benchmark.frameCount) {
                        BenchmarkFrame(Duration.ZERO, Duration.ZERO)
                    }
                    // skip first frame waiting
                    withFrameNanos {  }
                    repeat(benchmark.frameCount) {
                        val frameStart = TimeSource.Monotonic.markNow()
                        withFrameNanos { }
                        val rawFrameTime = frameStart.elapsedNow()
                        // rawFrameTime isn't reliable for missed-frame checks: small timing inaccuracies can push
                        // it just over the frame budget even when the frame would effectively fit.
                        // Instead, estimate how many vsync intervals the frame took by rounding the measured time to
                        // the nearest multiple of nanosPerFrame.
                        // Note: this is only an estimate — we can’t determine here whether a frame was truly missed.
                        val normalizedFrameTime = ((rawFrameTime.inWholeNanoseconds + nanosPerFrame/2) / nanosPerFrame) * nanosPerFrame
                        frames[it] = BenchmarkFrame(normalizedFrameTime.nanoseconds, Duration.ZERO)
                    }
                    val duration = start.elapsedNow()
                    val stats = BenchmarkResult(
                        name = benchmark.name,
                        frameBudget = nanosPerFrame.nanoseconds,
                        conditions = BenchmarkConditions(benchmark.frameCount, 0),
                        averageFrameInfo = FrameInfo(duration / benchmark.frameCount, Duration.ZERO),
                        averageFPSInfo = FPSInfo(benchmark.frameCount.toDouble() / duration.toDouble(DurationUnit.SECONDS)),
                        frames = frames
                    ).generateStats()
                    if (!Config.reportAtTheEnd) {
                        stats.prettyPrint()
                    } else {
                        results.add(stats)
                    }
                    if (Config.saveStats()) {
                        saveBenchmarkStats(name = benchmark.name, stats = stats)
                    }
                    currentBenchmarkIndex++
                    isWarmup = Config.warmupCount > 0
                    isEmptyScreen = true
                }
            }
        }

Review comment (bug):
This also affects percentiles (and could affects FPS itself if we wouldn't use `averageFrameInfo`). This might give us a wrong value in cases:
- if the frame is 23ms, and we round it to 16ms (we miss a lot of accuracy)
- when we hardcode the refresh rate, not reading it from the current display
- the actual refresh rate isn't always strictly 60hz even if it returns 60.0 (it can be 59.98 and skew over time)

If we have an issue with missed frames, we can only fix its logic. What if we just multiply `frameBudget` by 1.5?

Code after:
                    repeat(Config.warmupCount) {
                        withFrameNanos { }
                    }
                    isWarmup = false
                    isEmptyScreen = true
                } else if (isEmptyScreen) {
                    delay(Config.emptyScreenDelay.milliseconds)
                    withFrameNanos {  }
                    isEmptyScreen = false
                } else {
                    val frames = MutableList(benchmark.frameCount) {
                        BenchmarkFrame(Duration.ZERO, Duration.ZERO)
                    }
                    // skip first frame waiting
                    withFrameNanos {  }
                    val start = TimeSource.Monotonic.markNow()
                    repeat(benchmark.frameCount) {
                        val frameStart = TimeSource.Monotonic.markNow()
                        withFrameNanos { }
                        val rawFrameTime = frameStart.elapsedNow()
                        // rawFrameTime isn't reliable for missed-frame checks: small timing inaccuracies can push
                        // it just over the frame budget even when the frame would effectively fit.
                        // Instead, estimate how many vsync intervals the frame took by rounding the measured time to
                        // the nearest multiple of nanosPerFrame.
                        // Note: this is only an estimate — we can’t determine here whether a frame was truly missed.
                        val normalizedFrameTime = ((rawFrameTime.inWholeNanoseconds + nanosPerFrame/2) / nanosPerFrame) * nanosPerFrame
                        frames[it] = BenchmarkFrame(normalizedFrameTime.nanoseconds, Duration.ZERO)
                    }
                    val duration = start.elapsedNow()
                    val stats = BenchmarkResult(
                        name = benchmark.name,
                        frameBudget = nanosPerFrame.nanoseconds,
                        conditions = BenchmarkConditions(benchmark.frameCount, 0),
                        averageFrameInfo = FrameInfo(duration / benchmark.frameCount, Duration.ZERO),
                        averageFPSInfo = FPSInfo(benchmark.frameCount.toDouble() / duration.toDouble(DurationUnit.SECONDS)),
                        frames = frames
                    ).generateStats()
                    if (!Config.reportAtTheEnd) {
                        stats.prettyPrint()
                    } else {
                        results.add(stats)
                    }
                    if (Config.saveStats()) {
                        saveBenchmarkStats(name = benchmark.name, stats = stats)
                    }
                    currentBenchmarkIndex++
                    isWarmup = Config.warmupCount > 0
                    isEmptyScreen = true
                }
            }
        }