swift-nio/IntegrationTests/tests_04_performance/test_01_resources
Cory Benfield fd35cd9e52
Extend the integration test harness to track FDs (#2411)
* Extend the integration test harness to track FDs

Motivation

This patch extends the NIO integration test harness to track
file descriptors, in particular to search for leaks. This
change has been validated on Linux and Darwin, and in both cases
correctly diagnoses FD leaks.

The goal is to enable us to regression test for things like

Modifications

- Add support for hooking socket and close calls.
- Wire up this support into the test harness.
- Extend the test harness to handle the logging.
- Add new regression test for #2047.

Results

We can write regression tests for FD leaks.

* Disable FD checking in most builds.

I'm doing this for speed reasons

* Always print the leaked fds number
2023-04-26 08:38:18 -07:00
..
README.md add a simple leak detector (#255) 2018-03-29 18:43:49 +01:00
run-nio-alloc-counter-tests.sh Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
shared.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_1_reqs_1000_conn.swift new alloc counter tests (#996) 2019-05-09 14:27:29 +01:00
test_1000_addHandlers.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_1000_addHandlers_sync.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_1000_addRemoveHandlers.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_1000_autoReadGetAndSet.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_1000_autoReadGetAndSet_sync.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_1000_copying_bytebufferview_to_array.swift Add benchmarks for copying BBV to Array. (#2037) 2022-02-03 01:36:12 -08:00
test_1000_copying_circularbuffer_to_array.swift Add benchmarks for copying CircularBuffer to Array (#2058) 2022-03-07 08:39:28 -08:00
test_1000_getHandlers.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_1000_getHandlers_sync.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_1000_reqs_1_conn.swift new alloc counter tests (#996) 2019-05-09 14:27:29 +01:00
test_1000_rst_connections.swift Extend the integration test harness to track FDs (#2411) 2023-04-26 08:38:18 -07:00
test_1000_tcpbootstraps.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_1000_tcpconnections.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_1000_udp_reqs.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_1000_udpbootstraps.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_1000_udpconnections.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_1000000_asyncwriter.swift Add benchmarks for `NIOAsyncWriter` and `NIOAsyncSequenceProducer` (#2301) 2022-10-26 03:51:20 -07:00
test_10000000_asyncsequenceproducer.swift Add benchmarks for `NIOAsyncWriter` and `NIOAsyncSequenceProducer` (#2301) 2022-10-26 03:51:20 -07:00
test_bytebuffer_lots_of_rw.swift Add perf hooks for testing substring path. (#1976) 2021-10-13 12:08:37 +01:00
test_creating_10000_headers.swift new alloc counter tests (#996) 2019-05-09 14:27:29 +01:00
test_decode_1000_ws_frames.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_encode_1000_ws_frames.swift Measure allocations applying WS mask (#2333) 2022-12-13 18:05:27 +00:00
test_execute_hop_10000_tasks.swift Add an allocation test for execute which needs to hop threads. (#2047) 2022-02-17 09:21:40 +00:00
test_future_erase_result.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_future_lots_of_callbacks.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_get_100000_headers_canonical_form.swift Add performance and allocation tests for canonical form headers (#1953) 2021-09-13 16:28:07 +01:00
test_modifying_1000_circular_buffer_elements.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_modifying_byte_buffer_view.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_ping_pong_1000_reqs_1_conn.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_read_10000_chunks_from_file.swift atomics: make add/sub @discardableResult (#2048) 2022-02-17 08:03:12 -08:00
test_schedule_10000_tasks.swift Add baseline performance and allocation tests for scheduling tasks and executing (#2009) 2021-12-13 16:33:13 +00:00
test_schedule_and_run_10000_tasks.swift Add baseline performance and allocation tests for scheduling tasks and executing (#2009) 2021-12-13 16:33:13 +00:00
test_scheduling_10000_executions.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_udp_1_reqs_1000_conn.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00
test_udp_1000_reqs_1_conn.swift Move NIO to NIOPosix, make NIO a shell. (#1936) 2021-08-16 16:50:40 +01:00

README.md

Allocation Counting Test

This briefly describes how the allocation counting test works.

How does it work?

This is possibly the simplest implementation that counts memory allocations (malloc and friends) and frees (mostly free). It just maintains two atomic variables which count the number of mallocs and the number of frees respectively. We run a simple HTTP1 example -- 1000 requests and responses generated by a simple SwiftNIO based client and server -- and then evaluate the number of mallocs and frees. The difference mallocs - frees should be pretty much 0 and the number of mallocs should remain stable (or decrease) across commits. We can't establish a perfect baseline as the exact number of allocations depends on your operating system, libc and Swift version.

How are the functions hooked?

Usually in UNIX it's enough to just define a function, for example

void free(void *ptr) { ... }

in the main binary and all modules will use this free function instead of the real one from the libc. For Linux, this is exactly what we're doing, the bootstrap binary defines such a free function in its main.c. On Darwin (macOS/iOS/...) however that is not the case and you need to use dyld's interpose feature. The odd thing is that dyld's interposing only works if it's in a .dylib and not from a binary's main executable. Therefore we need to build a slightly strange SwiftPM package:

  • bootstrap: The main executable's main module (written in C) so we can hook the free function on Linux.
  • BootstrapSwift: A SwiftPM module (written in Swift) called in from bootstrap which implements the actual SwiftNIO benchmark (and therefore depends on the NIO module).
  • HookedFunctions: A separate SwiftPM package that builds a shared library (.so on Linux, .dylib on Darwin) which contains the replacement_malloc, replacement_free, etc functions which just increment an atomic integers representing the number of operations. On Darwin, we use DYLD_INTERPOSE in this module, interposing libc functions with our replacement_ functions. This needs to be a separate SwiftPM package as otherwise its code would just live inside of the bootstrap executable and the dyld interposing feature wouldn't work.
  • AtomicCounter: SwiftPM package (written in C) that implements the atomic counters. It needs to be a separate package as both BoostrapSwift (to read the allocation counter) as well as HookedFree (to increment the allocation counter) depend on it.

What benchmark is run?

We run a single TCP connection over which 1000 HTTP requests are made by a client written in NIO, responded to by a server also written in NIO. We re-run the benchmark 10 times and return the lowest number of allocations that has been made.

Why do I have to set a baseline?

By default this test should always succeed as it doesn't actually compare the number of allocations to a certain number. The reason is that this number varies ever so slightly between operating systems and Swift versions. At the time of writing on macOS we got roughly 326k allocations and on Linux 322k allocations for 1000 HTTP requests & responses. To set a baseline simply run

export MAX_ALLOCS_ALLOWED_1000_reqs_1_conn=327000

or similar to set the maximum number of allocations allowed. If the benchmark exceeds these allocations the test will fail.