mirror of
https://github.com/kean/Pulse.git
synced 2026-05-30 21:07:33 +00:00
157 lines
5.9 KiB
Swift
157 lines
5.9 KiB
Swift
// The MIT License (MIT)
|
|
//
|
|
// Copyright (c) 2020-2024 Alexander Grebenyuk (github.com/kean).
|
|
|
|
#if !os(watchOS)
|
|
|
|
import SwiftUI
|
|
import Pulse
|
|
|
|
extension TimingViewModel {
|
|
convenience init(task: NetworkTaskEntity) {
|
|
self.init(sections: makeTimingSections(task: task))
|
|
}
|
|
|
|
convenience init?(transaction: NetworkTransactionMetricsEntity, task: NetworkTaskEntity) {
|
|
guard let interval = task.taskInterval else { // Anchor to task
|
|
return nil
|
|
}
|
|
let sections = makeTimingRows(transaction: transaction, taskInterval: interval)
|
|
guard !sections.isEmpty else {
|
|
return nil
|
|
}
|
|
self.init(sections: sections)
|
|
}
|
|
}
|
|
|
|
private func makeTimingSections(task: NetworkTaskEntity) -> [TimingRowSectionViewModel] {
|
|
guard let taskInterval = task.taskInterval else {
|
|
return []
|
|
}
|
|
|
|
var sections = [TimingRowSectionViewModel]()
|
|
|
|
var currentURL: String?
|
|
|
|
for transaction in task.orderedTransactions {
|
|
let rows = makeTimingRows(transaction: transaction, taskInterval: taskInterval)
|
|
guard !rows.isEmpty else {
|
|
continue
|
|
}
|
|
|
|
if task.redirectCount > 0, let url = transaction.request.url, currentURL != url {
|
|
currentURL = url
|
|
sections.append(TimingRowSectionViewModel(title: url, items: [], isHeader: true))
|
|
}
|
|
|
|
sections += rows
|
|
}
|
|
|
|
sections.append(TimingRowSectionViewModel(title: "Total", items: [
|
|
_makeRow(title: "Total", color: .systemGray2, from: taskInterval.start, to: taskInterval.end, taskInterval: taskInterval)
|
|
]))
|
|
|
|
return sections
|
|
}
|
|
|
|
private func _makeRow(title: String, color: UXColor, from: Date, to: Date?, taskInterval: DateInterval) -> TimingRowViewModel {
|
|
let start = CGFloat(from.timeIntervalSince(taskInterval.start) / taskInterval.duration)
|
|
let to = to ?? taskInterval.end
|
|
let duration = to.timeIntervalSince(from)
|
|
let length = CGFloat(duration / taskInterval.duration)
|
|
let value = DurationFormatter.string(from: duration)
|
|
return TimingRowViewModel(title: title, value: value, color: color, start: CGFloat(start), length: length)
|
|
}
|
|
|
|
private func makeTimingRows(transaction: NetworkTransactionMetricsEntity, taskInterval: DateInterval) -> [TimingRowSectionViewModel] {
|
|
var sections = [TimingRowSectionViewModel]()
|
|
|
|
func makeRow(title: String, color: UXColor, from: Date, to: Date?) -> TimingRowViewModel {
|
|
_makeRow(title: title, color: color, from: from, to: to, taskInterval: taskInterval)
|
|
}
|
|
|
|
let timing = transaction.timing
|
|
|
|
switch transaction.fetchType {
|
|
case .localCache:
|
|
if let requestStartDate = timing.requestStartDate,
|
|
let responseEndDate = timing.responseEndDate {
|
|
let section = TimingRowSectionViewModel(
|
|
title: "Local Cache",
|
|
items: [
|
|
makeRow(title: "Lookup", color: .systemTeal, from: requestStartDate, to: responseEndDate)
|
|
])
|
|
sections.append(section)
|
|
}
|
|
case .networkLoad:
|
|
var scheduling: [TimingRowViewModel] = []
|
|
var connection: [TimingRowViewModel] = []
|
|
var response: [TimingRowViewModel] = []
|
|
|
|
let earliestEventDate = timing.domainLookupStartDate ?? timing.connectStartDate ?? timing.requestStartDate
|
|
|
|
// Scheduling Section
|
|
|
|
if let fetchStartDate = timing.fetchStartDate, let endDate = earliestEventDate,
|
|
endDate.timeIntervalSince(fetchStartDate) > 0.001 {
|
|
scheduling.append(makeRow(title: "Queued", color: .systemGray4, from: fetchStartDate, to: endDate))
|
|
}
|
|
|
|
// Connection Section
|
|
|
|
if let domainLookupStartDate = timing.domainLookupStartDate {
|
|
connection.append(makeRow(title: "DNS", color: .systemPurple, from: domainLookupStartDate, to: timing.domainLookupEndDate))
|
|
}
|
|
if let connectStartDate = timing.connectStartDate {
|
|
connection.append(makeRow(title: "TCP", color: .systemYellow, from: connectStartDate, to: timing.connectEndDate))
|
|
}
|
|
if let secureConnectionStartDate = timing.secureConnectionStartDate {
|
|
connection.append(makeRow(title: "Secure", color: .systemRed, from: secureConnectionStartDate, to: timing.secureConnectionEndDate))
|
|
}
|
|
|
|
// Response Section
|
|
|
|
let latestPreviousEndDate = timing.requestEndDate ?? timing.connectEndDate ?? timing.domainLookupEndDate ?? timing.fetchStartDate
|
|
|
|
if let requestStartDate = timing.requestStartDate, let requestEndDate = timing.requestEndDate {
|
|
response.append(makeRow(title: "Request", color: .systemGreen, from: requestStartDate, to: requestEndDate))
|
|
}
|
|
if let requestEndDate = latestPreviousEndDate, let responseStartDate = timing.responseStartDate {
|
|
response.append(makeRow(title: "Waiting", color: .systemGray3, from: requestEndDate, to: responseStartDate))
|
|
}
|
|
if let responseStartDate = timing.responseStartDate {
|
|
response.append(makeRow(title: "Download", color: .systemBlue, from: responseStartDate, to: timing.responseEndDate))
|
|
}
|
|
|
|
// Commit sections
|
|
|
|
if !scheduling.isEmpty {
|
|
sections.append(TimingRowSectionViewModel(title: "Scheduling", items: scheduling))
|
|
}
|
|
if !connection.isEmpty {
|
|
sections.append(TimingRowSectionViewModel(title: "Connection", items: connection))
|
|
}
|
|
if !response.isEmpty {
|
|
sections.append(TimingRowSectionViewModel(title: "Response", items: response))
|
|
}
|
|
default:
|
|
return []
|
|
}
|
|
|
|
return sections
|
|
}
|
|
|
|
extension URLSessionTaskMetrics.ResourceFetchType {
|
|
var title: String {
|
|
switch self {
|
|
case .networkLoad: return "Network Load"
|
|
case .localCache: return "Cache Lookup"
|
|
case .serverPush: return "Server Push"
|
|
case .unknown: return "Unknown Fetch Type"
|
|
default: return "Unknown Fetch Type"
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|