import Foundation public struct RDEPUBTextPerformanceSample: Equatable { public var chapterHref: String public var renderDuration: TimeInterval public var paginateDuration: TimeInterval public var pageCount: Int public var attributedStringLength: Int public var cacheHit: Bool public init( chapterHref: String, renderDuration: TimeInterval, paginateDuration: TimeInterval, pageCount: Int, attributedStringLength: Int, cacheHit: Bool ) { self.chapterHref = chapterHref self.renderDuration = renderDuration self.paginateDuration = paginateDuration self.pageCount = pageCount self.attributedStringLength = attributedStringLength self.cacheHit = cacheHit } } public final class RDEPUBTextPerformanceSampler { public private(set) var samples: [RDEPUBTextPerformanceSample] = [] public var totalBuildDuration: TimeInterval = 0 public init() {} public func record(_ sample: RDEPUBTextPerformanceSample) { samples.append(sample) #if DEBUG print("[PERF] \(sample.chapterHref): render=\(formatMS(sample.renderDuration)) paginate=\(formatMS(sample.paginateDuration)) pages=\(sample.pageCount) cache=\(sample.cacheHit ? "HIT" : "MISS")") #endif } public func summary() -> String { let totalRender = samples.reduce(0) { $0 + $1.renderDuration } let totalPaginate = samples.reduce(0) { $0 + $1.paginateDuration } let hitCount = samples.filter(\.cacheHit).count return "[PERF] chapters=\(samples.count) render=\(formatMS(totalRender)) paginate=\(formatMS(totalPaginate)) total=\(formatMS(totalBuildDuration)) cacheHits=\(hitCount)/\(samples.count)" } public func reset() { samples.removeAll() totalBuildDuration = 0 } private func formatMS(_ duration: TimeInterval) -> String { String(format: "%.0fms", duration * 1000) } }