Skip to content

Commit

Permalink
Add allocation counter tests. (#153)
Browse files Browse the repository at this point in the history
Motivation:

To do proper performance work it's necessary to have insight into what
programs are actually doing. One of the two major axes the SwiftNIO
project uses to measure is heap allocations. Currently swift-nio-ssl has
no introspection of heap allocations, so doing informed performance work
is very hard.

Modifications:

- Added the allocation counter test framework.
- Added two allocation counter tests.

Result:

Better idea of what's going on in heap allocations.
  • Loading branch information
Lukasa committed Dec 2, 2019
1 parent f2ac69d commit b0a34db
Show file tree
Hide file tree
Showing 6 changed files with 320 additions and 0 deletions.
14 changes: 14 additions & 0 deletions IntegrationTests/tests_02_allocation_counters/defines.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash
##===----------------------------------------------------------------------===##
##
## This source file is part of the SwiftNIO open source project
##
## Copyright (c) 2019 Apple Inc. and the SwiftNIO project authors
## Licensed under Apache License v2.0
##
## See LICENSE.txt for license information
## See CONTRIBUTORS.txt for the list of SwiftNIO project authors
##
## SPDX-License-Identifier: Apache-2.0
##
##===----------------------------------------------------------------------===##
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/bin/bash
##===----------------------------------------------------------------------===##
##
## This source file is part of the SwiftNIO open source project
##
## Copyright (c) 2017-2019 Apple Inc. and the SwiftNIO project authors
## Licensed under Apache License v2.0
##
## See LICENSE.txt for license information
## See CONTRIBUTORS.txt for the list of SwiftNIO project authors
##
## SPDX-License-Identifier: Apache-2.0
##
##===----------------------------------------------------------------------===##

source defines.sh

set -eu
here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

all_tests=()
for file in "$here/test_01_resources/"test_*.swift; do
test_name=$(basename "$file")
test_name=${test_name#test_*}
test_name=${test_name%*.swift}
all_tests+=( "$test_name" )
done

"$here/test_01_resources/run-nio-ssl-alloc-counter-tests.sh" -t "$tmp" > "$tmp/output"

for test in "${all_tests[@]}"; do
cat "$tmp/output" # helps debugging
total_allocations=$(grep "^test_$test.total_allocations:" "$tmp/output" | cut -d: -f2 | sed 's/ //g')
not_freed_allocations=$(grep "^test_$test.remaining_allocations:" "$tmp/output" | cut -d: -f2 | sed 's/ //g')
max_allowed_env_name="MAX_ALLOCS_ALLOWED_$test"

info "$test: allocations not freed: $not_freed_allocations"
info "$test: total number of mallocs: $total_allocations"

assert_less_than "$not_freed_allocations" 5 # allow some slack
assert_greater_than "$not_freed_allocations" -5 # allow some slack
assert_greater_than "$total_allocations" 1000
if [[ -z "${!max_allowed_env_name+x}" ]]; then
if [[ -z "${!max_allowed_env_name+x}" ]]; then
warn "no reference number of allocations set (set to \$$max_allowed_env_name)"
warn "to set current number:"
warn " export $max_allowed_env_name=$total_allocations"
fi
else
assert_less_than_or_equal "$total_allocations" "${!max_allowed_env_name}"
fi
done
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/bin/bash
##===----------------------------------------------------------------------===##
##
## This source file is part of the SwiftNIO open source project
##
## Copyright (c) 2019 Apple Inc. and the SwiftNIO project authors
## Licensed under Apache License v2.0
##
## See LICENSE.txt for license information
## See CONTRIBUTORS.txt for the list of SwiftNIO project authors
##
## SPDX-License-Identifier: Apache-2.0
##
##===----------------------------------------------------------------------===##

set -eu
here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

tmp_dir="/tmp"

while getopts "t:" opt; do
case "$opt" in
t)
tmp_dir="$OPTARG"
;;
*)
exit 1
;;
esac
done

nio_checkout=$(mktemp -d "$tmp_dir/.swift-nio_XXXXXX")
(
cd "$nio_checkout"
git clone --depth 1 https://github.com/apple/swift-nio
)

shift $((OPTIND-1))

tests_to_run=("$here"/test_*.swift)

if [[ $# -gt 0 ]]; then
tests_to_run=("$@")
fi

"$nio_checkout/swift-nio/IntegrationTests/allocation-counter-tests-framework/run-allocation-counter.sh" \
-p "$here/../../.." \
-m NIO \
-m NIOSSL \
-s "$here/shared.swift" \
-t "$tmp_dir" \
-d <( echo '.package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"),' ) \
"${tests_to_run[@]}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2019 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import Foundation
import NIO
import NIOSSL

class BackToBackEmbeddedChannel {
private(set) var client: EmbeddedChannel
private(set) var server: EmbeddedChannel
private var loop: EmbeddedEventLoop


init() {
self.loop = EmbeddedEventLoop()
self.client = EmbeddedChannel(loop: self.loop)
self.server = EmbeddedChannel(loop: self.loop)
}

func run() {
self.loop.run()
}

func interactInMemory() throws {
var workToDo = true

while workToDo {
workToDo = false

self.loop.run()
let clientDatum = try self.client.readOutbound(as: IOData.self)
let serverDatum = try self.server.readOutbound(as: IOData.self)

if let clientMsg = clientDatum {
try self.server.writeInbound(clientMsg)
workToDo = true
}

if let serverMsg = serverDatum {
try self.client.writeInbound(serverMsg)
workToDo = true
}
}
}
}


extension BackToBackEmbeddedChannel {
enum Error: Swift.Error {
case nonCleanExit
}
}


extension NIOSSLCertificate {
static func forTesting() throws -> NIOSSLCertificate {
return try .init(bytes: certificatePemBytes, format: .pem)
}
}


extension NIOSSLPrivateKey {
static func forTesting() throws -> NIOSSLPrivateKey {
return try .init(bytes: keyPemBytes, format: .pem)
}
}


fileprivate let certificatePemBytes = Array("""
-----BEGIN CERTIFICATE-----
MIIBTzCB9qADAgECAhQkvv72Je/v+B/cgJ53f84O82z6WTAKBggqhkjOPQQDAjAU
MRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTkxMTI3MTAxMjMwWhcNMjkxMTI0MTAx
MjMwWjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjOPQMB
BwNCAAShtZ9TRt7I+7Y0o99XUkrgSYmUmpr4K8CB0IkTCX6b1tXp3Xqs1V5BckTd
qrls+zsm3AfeiNBb9EDdxiX9DdzuoyYwJDAUBgNVHREEDTALgglsb2NhbGhvc3Qw
DAYDVR0TAQH/BAIwADAKBggqhkjOPQQDAgNIADBFAiAKxYON+YTnIHNR0R6SLP8R
R7hjsjV5NDs18XLoeRnA1gIhANwyggmE6NQW/r9l59fexj/ZrjaS3jYOTNCfC1Lo
5NgJ
-----END CERTIFICATE-----
""".utf8)


fileprivate let keyPemBytes = Array("""
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgCn182hBmYVMAiNPO
+7w05F40SlAqqxgBEYJZOeK47aihRANCAAShtZ9TRt7I+7Y0o99XUkrgSYmUmpr4
K8CB0IkTCX6b1tXp3Xqs1V5BckTdqrls+zsm3AfeiNBb9EDdxiX9Ddzu
-----END PRIVATE KEY-----
""".utf8)
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2019 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import NIO
import NIOSSL

func run(identifier: String) {
let serverContext = try! NIOSSLContext(configuration: .forServer(certificateChain: [.certificate(.forTesting())], privateKey: .privateKey(.forTesting())))
let clientContext = try! NIOSSLContext(configuration: .forClient(trustRoots: .certificates([.forTesting()])))

let dummyAddress = try! SocketAddress(ipAddress: "1.2.3.4", port: 5678)
let backToBack = BackToBackEmbeddedChannel()
let serverHandler = try! NIOSSLServerHandler(context: serverContext)
let clientHandler = try! NIOSSLClientHandler(context: clientContext, serverHostname: "localhost")
try! backToBack.client.pipeline.addHandler(clientHandler).wait()
try! backToBack.server.pipeline.addHandler(serverHandler).wait()

// To trigger activation of both channels we use connect().
try! backToBack.client.connect(to: dummyAddress).wait()
try! backToBack.server.connect(to: dummyAddress).wait()

try! backToBack.interactInMemory()

// Let's try 512 bytes.
var buffer = backToBack.client.allocator.buffer(capacity: 512)
buffer.writeBytes(repeatElement(0, count: 512))

measure(identifier: identifier) {
for _ in 0..<1000 {
// A vector of 100 writes.
for _ in 0..<100 {
backToBack.client.write(buffer, promise: nil)
}
backToBack.client.flush()

try! backToBack.interactInMemory()

// Pull any data out of the server to avoid ballooning in memory.
while let _ = try! backToBack.server.readInbound(as: ByteBuffer.self) { }
}

return 1000
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2019 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import NIO
import NIOSSL

func run(identifier: String) {
let serverContext = try! NIOSSLContext(configuration: .forServer(certificateChain: [.certificate(.forTesting())], privateKey: .privateKey(.forTesting())))
let clientContext = try! NIOSSLContext(configuration: .forClient(trustRoots: .certificates([.forTesting()])))

let dummyAddress = try! SocketAddress(ipAddress: "1.2.3.4", port: 5678)

measure(identifier: identifier) {
for _ in 0..<1000 {
let backToBack = BackToBackEmbeddedChannel()
let serverHandler = try! NIOSSLServerHandler(context: serverContext)
let clientHandler = try! NIOSSLClientHandler(context: clientContext, serverHostname: "localhost")
try! backToBack.client.pipeline.addHandler(clientHandler).wait()
try! backToBack.server.pipeline.addHandler(serverHandler).wait()

// To trigger activation of both channels we use connect().
try! backToBack.client.connect(to: dummyAddress).wait()
try! backToBack.server.connect(to: dummyAddress).wait()

try! backToBack.interactInMemory()

// Ok, now do shutdown.
backToBack.client.close(promise: nil)
try! backToBack.interactInMemory()
try! backToBack.client.closeFuture.wait()
try! backToBack.server.closeFuture.wait()
}

return 1000
}
}

0 comments on commit b0a34db

Please sign in to comment.