portico/Benchmarks/Sources/PorticoBench/main.swift

103 lines
3.4 KiB
Swift

import CBenchSupport
import Foundation
import Gtk
import Logging
import ProfileRecorderServer
@_spi(Portico) import Portico
#if canImport(Glibc)
import Glibc
#endif
// Portico benchmark driver.
//
// Runs one scenario group per invocation so a profile can be attributed to a
// single subsystem, or `all` for the full report.
//
// Profiling:
// PROFILE_RECORDER_SERVER_URL_PATTERN=unix:///tmp/portico-{PID}.sock \
// .build/release/PorticoBench all
// curl --unix-socket /tmp/portico-<pid>.sock \
// -sd '{"numberOfSamples":600,"timeInterval":"5 ms"}' \
// http://localhost/sample | swift demangle --compact > /tmp/portico.perf
//
// This is top-level code rather than `@main`: the workload must own the main
// thread synchronously (GTK is main-thread confined and the scenarios pump the
// GLib main context directly), so control never returns to the Swift
// concurrency runtime. The sampling server runs on its own NIO threads.
let group = CommandLine.arguments.dropFirst().first ?? "all"
if ProcessInfo.processInfo.environment["PROFILE_RECORDER_SERVER_URL_PATTERN"] != nil {
var logger = Logger(label: "portico-bench")
logger.logLevel = .error
let serverLogger = logger
_Concurrency.Task.detached {
try? await ProfileRecorderServer(configuration: .parseFromEnvironment())
.runIgnoringFailures(logger: serverLogger)
}
// Let the listener bind before the workload saturates the main thread.
usleep(400_000)
}
MainActor.assumeIsolated {
// GTK must be initialized before any widget is constructed. Doing it here
// keeps GType registration and theme loading out of every measurement.
guard Gtk.initCheck() else {
FileHandle.standardError.write(Data("gtk init failed; a display is required\n".utf8))
exit(1)
}
// Force lazy one-time initialization (GType registration for the widget
// classes used below, Swift metadata instantiation, malloc arena growth)
// before the first measurement window opens.
for _ in 0..<200 {
let ctx = MountContext()
_ = AnyView(Label("warm").hexpand(true)).makeWidget(ctx)
ctx.registry.teardown()
}
let wantMount = group == "all" || group == "mount"
let wantState = group == "all" || group == "state"
let wantList = group == "all" || group == "list"
let wantLoop = group == "all" || group == "loop"
if wantMount {
Scenarios.mountBareGtkLabel()
Scenarios.mountStaticLabel()
Scenarios.mountRawLabel()
Scenarios.mountModifierChain()
Scenarios.mountTree()
Scenarios.teardownTree()
}
if wantState {
Scenarios.stateChurnNoDependents()
Scenarios.stateChurnOneDependent()
Scenarios.stateChurnManyDependents()
Scenarios.subscriberChurn()
Scenarios.bindingProjection()
Scenarios.reactiveLabelUpdate()
Scenarios.setAlternatingHalfNoOp()
Scenarios.setIfChangedHalfNoOp()
Scenarios.twoWayPropertyChurn()
Scenarios.twoWayPropertyFanout()
Scenarios.trackerAttach()
Scenarios.trackerAttachObserving()
Scenarios.observationRegistryPressure()
Scenarios.observationMutation()
}
if wantList {
Scenarios.forEachMount()
Scenarios.forEachAppend()
Scenarios.forEachRemoveFirst()
Scenarios.forEachReverse()
Scenarios.conditionalFlip()
}
if wantLoop {
Scenarios.idleSourceChurn()
}
Harness.report()
}
exit(0)