103 lines
3.4 KiB
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)
|