Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 25 additions & 51 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
// swift-tools-version: 5.9
// swift-tools-version: 6.1

import PackageDescription

import class Foundation.ProcessInfo

// If the environment variable BENCHMARK_DISABLE_JEMALLOC is set, we'll build the package without Jemalloc support
let disableJemalloc = ProcessInfo.processInfo.environment["BENCHMARK_DISABLE_JEMALLOC"]

let package = Package(
name: "Benchmark",
platforms: [
Expand All @@ -21,14 +16,34 @@ let package = Package(
targets: ["Benchmark"]
),
],
traits: [
.trait(name: "Jemalloc"),
.default(enabledTraits: ["Jemalloc"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-system.git", .upToNextMajor(from: "1.1.0")),
.package(url: "https://github.com/apple/swift-argument-parser.git", "1.1.0" ..< "1.6.0"),
.package(url: "https://github.com/ordo-one/TextTable.git", .upToNextMajor(from: "0.0.1")),
.package(url: "https://github.com/HdrHistogram/hdrhistogram-swift.git", .upToNextMajor(from: "0.1.4")),
.package(url: "https://github.com/apple/swift-atomics.git", .upToNextMajor(from: "1.0.0")),
.package(url: "https://github.com/ordo-one/package-jemalloc.git", .upToNextMajor(from: "1.0.0")),
],
targets: [
.target(
name: "Benchmark",
dependencies: [
.product(name: "Histogram", package: "hdrhistogram-swift"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "SystemPackage", package: "swift-system"),
.byNameItem(name: "CDarwinOperatingSystemStats", condition: .when(platforms: [.macOS, .iOS])),
.byNameItem(name: "CLinuxOperatingSystemStats", condition: .when(platforms: [.linux])),
.product(name: "Atomics", package: "swift-atomics"),
"SwiftRuntimeHooks",
"BenchmarkShared",
.product(name: "jemalloc", package: "package-jemalloc", condition: .when(platforms: [.macOS, .linux], traits: ["Jemalloc"])),
],
swiftSettings: [.swiftLanguageMode(.v5)]
),
Comment on lines 32 to 46
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical Bug: Missing compilation condition definition for EnableJemalloc trait

The code uses #if EnableJemalloc in source files (MallocStatsProducer+jemalloc.swift and OperatingSystemAndMallocTests.swift), but the Benchmark target doesn't define EnableJemalloc as a compilation condition. Package traits only control dependency resolution - they don't automatically become preprocessor flags.

This causes:

  • The jemalloc dependency gets linked when the trait is enabled
  • But the code using jemalloc is never compiled (the #if EnableJemalloc blocks are always skipped)
  • This creates a broken state where the dependency exists but is unused

Fix: Add the compilation condition to swiftSettings:

.target(
    name: "Benchmark",
    dependencies: [...],
    swiftSettings: [
        .swiftLanguageMode(.v5),
        .define("EnableJemalloc", .when(traits: ["EnableJemalloc"]))
    ]
)

The same fix should be applied to BenchmarkTests target (lines 112-131) which also likely needs this compilation condition for its test code.

Suggested change
.target(
name: "Benchmark",
dependencies: [
.product(name: "Histogram", package: "hdrhistogram-swift"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "SystemPackage", package: "swift-system"),
.byNameItem(name: "CDarwinOperatingSystemStats", condition: .when(platforms: [.macOS, .iOS])),
.byNameItem(name: "CLinuxOperatingSystemStats", condition: .when(platforms: [.linux])),
.product(name: "Atomics", package: "swift-atomics"),
"SwiftRuntimeHooks",
"BenchmarkShared",
.product(name: "jemalloc", package: "package-jemalloc", condition: .when(platforms: [.macOS, .linux], traits: ["EnableJemalloc"])),
],
swiftSettings: [.swiftLanguageMode(.v5)]
),
.target(
name: "Benchmark",
dependencies: [
.product(name: "Histogram", package: "hdrhistogram-swift"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "SystemPackage", package: "swift-system"),
.byNameItem(name: "CDarwinOperatingSystemStats", condition: .when(platforms: [.macOS, .iOS])),
.byNameItem(name: "CLinuxOperatingSystemStats", condition: .when(platforms: [.linux])),
.product(name: "Atomics", package: "swift-atomics"),
"SwiftRuntimeHooks",
"BenchmarkShared",
.product(name: "jemalloc", package: "package-jemalloc", condition: .when(platforms: [.macOS, .linux], traits: ["EnableJemalloc"])),
],
swiftSettings: [
.swiftLanguageMode(.v5),
.define("EnableJemalloc", .when(traits: ["EnableJemalloc"]))
]
),

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

// Plugins used by users of the package

// The actual 'benchmark' command plugin
Expand Down Expand Up @@ -66,7 +81,8 @@ let package = Package(
"Benchmark",
"BenchmarkShared",
],
path: "Plugins/BenchmarkTool"
path: "Plugins/BenchmarkTool",
swiftSettings: [.swiftLanguageMode(.v5)]
),

// Tool that generates the boilerplate
Expand Down Expand Up @@ -111,50 +127,8 @@ let package = Package(

.testTarget(
name: "BenchmarkTests",
dependencies: ["Benchmark"]
dependencies: ["Benchmark"],
swiftSettings: [.swiftLanguageMode(.v5)]
),
]
)
// Check if this is a SPI build, then we need to disable jemalloc for macOS

let macOSSPIBuild: Bool // Disables jemalloc for macOS SPI builds as the infrastructure doesn't have jemalloc there

#if canImport(Darwin)
if let spiBuildEnvironment = ProcessInfo.processInfo.environment["SPI_BUILD"], spiBuildEnvironment == "1" {
macOSSPIBuild = true
print("Building for SPI@macOS, disabling Jemalloc")
} else {
macOSSPIBuild = false
}
#else
macOSSPIBuild = false
#endif

// Add Benchmark target dynamically

// Shared dependencies
var dependencies: [PackageDescription.Target.Dependency] = [
.product(name: "Histogram", package: "hdrhistogram-swift"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "SystemPackage", package: "swift-system"),
.byNameItem(name: "CDarwinOperatingSystemStats", condition: .when(platforms: [.macOS, .iOS])),
.byNameItem(name: "CLinuxOperatingSystemStats", condition: .when(platforms: [.linux])),
.product(name: "Atomics", package: "swift-atomics"),
"SwiftRuntimeHooks",
"BenchmarkShared",
]

if macOSSPIBuild == false { // jemalloc always disable for macOSSPIBuild
if let disableJemalloc, disableJemalloc != "false", disableJemalloc != "0" {
print("Jemalloc disabled through environment variable.")
} else {
package.dependencies += [
.package(url: "https://github.com/ordo-one/package-jemalloc.git", .upToNextMajor(from: "1.0.0"))
]
dependencies += [
.product(name: "jemalloc", package: "package-jemalloc", condition: .when(platforms: [.macOS, .linux]))
]
}
}

package.targets += [.target(name: "Benchmark", dependencies: dependencies)]
160 changes: 160 additions & 0 deletions Package@swift-5.9.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// swift-tools-version: 5.9

import PackageDescription

import class Foundation.ProcessInfo

// If the environment variable BENCHMARK_DISABLE_JEMALLOC is set, we'll build the package without Jemalloc support
let disableJemalloc = ProcessInfo.processInfo.environment["BENCHMARK_DISABLE_JEMALLOC"]

let package = Package(
name: "Benchmark",
platforms: [
.macOS(.v13),
.iOS(.v16),
],
products: [
.plugin(name: "BenchmarkCommandPlugin", targets: ["BenchmarkCommandPlugin"]),
.plugin(name: "BenchmarkPlugin", targets: ["BenchmarkPlugin"]),
.library(
name: "Benchmark",
targets: ["Benchmark"]
),
],
dependencies: [
.package(url: "https://github.com/apple/swift-system.git", .upToNextMajor(from: "1.1.0")),
.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMajor(from: "1.1.0")),
.package(url: "https://github.com/ordo-one/TextTable.git", .upToNextMajor(from: "0.0.1")),
.package(url: "https://github.com/HdrHistogram/hdrhistogram-swift.git", .upToNextMajor(from: "0.1.0")),
.package(url: "https://github.com/apple/swift-atomics.git", .upToNextMajor(from: "1.0.0")),
],
targets: [
// Plugins used by users of the package

// The actual 'benchmark' command plugin
.plugin(
name: "BenchmarkCommandPlugin",
capability: .command(
intent: .custom(
verb: "benchmark",
description: "Run the Benchmark performance test suite."
)
),
dependencies: [
"BenchmarkTool"
],
path: "Plugins/BenchmarkCommandPlugin"
),

// Plugin that generates the boilerplate needed to interface with the Benchmark infrastructure
.plugin(
name: "BenchmarkPlugin",
capability: .buildTool(),
dependencies: [
"BenchmarkBoilerplateGenerator"
],
path: "Plugins/BenchmarkPlugin"
),

// Tool that the plugin executes to perform the actual work, the real benchmark driver
.executableTarget(
name: "BenchmarkTool",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "SystemPackage", package: "swift-system"),
.product(name: "TextTable", package: "TextTable"),
"Benchmark",
"BenchmarkShared",
],
path: "Plugins/BenchmarkTool"
),

// Tool that generates the boilerplate
.executableTarget(
name: "BenchmarkBoilerplateGenerator",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "SystemPackage", package: "swift-system"),
],
path: "Plugins/BenchmarkBoilerplateGenerator"
),

// Tool that simply generates the man page for the BenchmarkPlugin as we can't use SAP in it... :-/
.executableTarget(
name: "BenchmarkHelpGenerator",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
"BenchmarkShared",
],
path: "Plugins/BenchmarkHelpGenerator"
),

// Getting OS specific information
.target(
name: "CDarwinOperatingSystemStats",
dependencies: [],
path: "Platform/CDarwinOperatingSystemStats"
),

// Getting OS specific information
.target(
name: "CLinuxOperatingSystemStats",
dependencies: [],
path: "Platform/CLinuxOperatingSystemStats"
),

// Hooks for ARC
.target(name: "SwiftRuntimeHooks"),

// Shared definitions
.target(name: "BenchmarkShared"),

.testTarget(
name: "BenchmarkTests",
dependencies: ["Benchmark"]
),
]
)
// Check if this is a SPI build, then we need to disable jemalloc for macOS

let macOSSPIBuild: Bool // Disables jemalloc for macOS SPI builds as the infrastructure doesn't have jemalloc there

#if canImport(Darwin)
if let spiBuildEnvironment = ProcessInfo.processInfo.environment["SPI_BUILD"], spiBuildEnvironment == "1" {
macOSSPIBuild = true
print("Building for SPI@macOS, disabling Jemalloc")
} else {
macOSSPIBuild = false
}
#else
macOSSPIBuild = false
#endif

// Add Benchmark target dynamically

// Shared dependencies
var dependencies: [PackageDescription.Target.Dependency] = [
.product(name: "Histogram", package: "hdrhistogram-swift"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "SystemPackage", package: "swift-system"),
.byNameItem(name: "CDarwinOperatingSystemStats", condition: .when(platforms: [.macOS, .iOS])),
.byNameItem(name: "CLinuxOperatingSystemStats", condition: .when(platforms: [.linux])),
.product(name: "Atomics", package: "swift-atomics"),
"SwiftRuntimeHooks",
"BenchmarkShared",
]

if macOSSPIBuild == false { // jemalloc always disable for macOSSPIBuild
if let disableJemalloc, disableJemalloc != "false", disableJemalloc != "0" {
print("Jemalloc disabled through environment variable.")
} else {
package.dependencies += [
.package(url: "https://github.com/ordo-one/package-jemalloc.git", .upToNextMajor(from: "1.0.0"))
]
dependencies += [
.product(name: "jemalloc", package: "package-jemalloc", condition: .when(platforms: [.macOS, .linux]))
]
}
}

package.targets += [.target(name: "Benchmark", dependencies: dependencies)]
6 changes: 3 additions & 3 deletions Plugins/BenchmarkCommandPlugin/BenchmarkCommandPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
import PackagePlugin

#if canImport(Darwin)
import Darwin
@preconcurrency import Darwin
#elseif canImport(Glibc)
import Glibc
@preconcurrency import Glibc
#elseif canImport(Musl)
import Musl
@preconcurrency import Musl
#else
#error("Unsupported Platform")
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ extension TimeUnits: ExpressibleByArgument {}

@main
struct Benchmark: AsyncParsableCommand {
static var configuration = CommandConfiguration(
static let configuration = CommandConfiguration(
abstract: "Run benchmarks or update, compare or check performance baselines",
usage: """
swift package benchmark <command>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import Foundation

#if canImport(jemalloc)
#if Jemalloc
import jemalloc

// We currently register a number of MIB:s that aren't in use that
Expand Down
2 changes: 1 addition & 1 deletion Tests/BenchmarkTests/OperatingSystemAndMallocTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ final class OperatingSystemAndMallocTests: XCTestCase {
blackHole(operatingSystemStatsProducer.metricSupported(.throughput))
}

#if canImport(jemalloc)
#if Jemalloc
func testMallocProducerLeaks() throws {
let startMallocStats = MallocStatsProducer.makeMallocStats()

Expand Down