Skip to content

Commit 3ece49b

Browse files
committed
feat: elevated process priority, realtime thread
1 parent 99e485e commit 3ece49b

13 files changed

Lines changed: 241 additions & 87 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ build-release:
4949
cmake --preset=release
5050
cmake --build --preset=release
5151

52-
## test: 🧪 run google-test
52+
## test: 🧪 run unit tests
5353
.PHONY: test
5454
test:
5555
cmake --preset debug

README.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ A proof-of-concept, showcasing some modern c++ and some fintech concepts.
1212
- linux (tested with debian-trixie)
1313
- `stunnel` for TLS encryption (or a local proxy)
1414
- a Binance account, and an Ed25519 token with FIX read permissions enabled
15+
- `sudo` permissions (for elevated process and thread priorities)
1516

1617
## Run
1718
- start an SSL tunnel
@@ -24,7 +25,7 @@ A proof-of-concept, showcasing some modern c++ and some fintech concepts.
2425
- run:
2526
- download release
2627
- `chmod u+x tradercpp`
27-
- `./tradercpp`
28+
- `sudo ./tradercpp`
2829

2930
## Build Requirements
3031
- C++20
@@ -48,7 +49,7 @@ NB: this app uses `make` as a recipe book, but it's not essential:
4849
2. run an SSL tunnel (e.g. `stunnel binance/stunnel_prod.conf`)
4950
3. `make init`
5051
4. `make build-debug`
51-
5. `make withenv RECIPE=run-debug`
52+
5. `sudo make withenv RECIPE=run-debug`
5253

5354
## Test
5455
`make test`
@@ -149,9 +150,22 @@ NB: this app uses `make` as a recipe book, but it's not essential:
149150
- profiling (valgrind/cachegrind)
150151
- profile-guided optimization (pgo)
151152
- load test with mocked FIX server
152-
- set process priority
153-
- NIC affinity
154-
- QoS
153+
- CPU
154+
- ✅ process priority
155+
- ✅ FIX-thread "realtime"
156+
- ✅ FIX-thread CPU affinity
157+
- Disable hyperthreading
158+
- NIC
159+
- wired, kernel-bypass NICs
160+
- NIC IRQ affinity to same CPU as FIX
161+
- hardware queue affinity
162+
- QoS (mark packets)
163+
- AF_XDP (+ Zero-copy mode)
164+
- ~dedicated NIC + DPDK~
165+
- OS
166+
- vacate OS services / move IRQs for all system devices to the last CPU
167+
- RTOS
168+
- Memory locking
155169
- sparse arrays & flat matrix
156170
- memory-mapped files
157171
- (analyse) find Binance's server location for a low-latency connection
@@ -162,6 +176,7 @@ NB: this app uses `make` as a recipe book, but it's not essential:
162176
- RT OS
163177
- ✅ logging
164178
- ✅ fast
179+
- add console target for fatal messages
165180
- error handling
166181
- compiled out 'debug' logging for release builds
167182
- thread name in logs

src/binance/fix_app.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,11 @@ void FixApp::onLogon(const FIX::SessionID& sessionId) {
135135
utils::Threading::get_os_thread_id());
136136

137137
if (sessionId.getSessionQualifier() == PX_SESSION_QUALIFIER_) {
138-
utils::Threading::set_current_thread_affinity(PX_SESSION_CPU_AFFINITY_);
138+
utils::Threading::set_thread_affinity(PX_SESSION_CPU_AFFINITY_);
139+
utils::Threading::set_thread_realtime();
139140
subscribe_to_prices(sessionId);
140141
} else if (sessionId.getSessionQualifier() == TX_SESSION_QUALIFIER_) {
141-
utils::Threading::set_current_thread_affinity(TX_SESSION_CPU_AFFINITY_);
142+
utils::Threading::set_thread_affinity(TX_SESSION_CPU_AFFINITY_);
142143
subscribe_to_trades(sessionId);
143144
} else if (sessionId.getSessionQualifier() == OX_SESSION_QUALIFIER_) {
144145
// do nothing for order session

src/main.cpp

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,20 @@
77

88
#include "binance/config.h"
99
#include "binance/worker.h"
10-
#include "spdlog/cfg/env.h"
11-
#include "spdlog/sinks/basic_file_sink.h"
1210
#include "spdlog/spdlog.h"
1311
#include "ui/app/ui_app.h"
1412
#include "utils/crash.h"
1513
#include "utils/env.h"
14+
#include "utils/logging.h"
15+
#include "utils/process.h"
1616
#include "utils/threading.h"
1717

1818
int main() {
1919
try {
2020
utils::Threading::set_thread_name("main");
21-
22-
// log config
23-
spdlog::cfg::load_env_levels("LOG_LEVEL");
24-
const char* log_path = std::getenv("LOG_PATH");
25-
const auto logger = spdlog::basic_logger_mt("basic_logger", log_path);
26-
spdlog::set_default_logger(logger);
27-
// Set a global pattern without the logger name
28-
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [%t] %v");
29-
spdlog::flush_every(std::chrono::microseconds(100));
30-
spdlog::info("hello");
31-
utils::Env::log_current_architecture();
32-
33-
// configure error-handling
34-
utils::Crash::setup_crash_handlers();
21+
utils::Logging::configure();
22+
utils::Crash::configure_handlers();
23+
utils::Process::set_high_priority();
3524

3625
// Binance market data connectivity
3726
auto b_conf = binance::Config::from_env();
@@ -50,8 +39,8 @@ int main() {
5039
b_worker.stop();
5140
spdlog::info("goodbye");
5241
} catch (const std::exception& e) {
53-
spdlog::error("[EXCEPTION] Caught exception. ex [{}]", e.what());
42+
spdlog::critical("[EXCEPTION] Caught exception. ex [{}]", e.what());
5443
} catch (...) {
55-
spdlog::error("[EXCEPTION] Caught unknown exception");
44+
spdlog::critical("[EXCEPTION] Caught unknown exception");
5645
}
5746
}

src/utils/crash.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,15 @@ class Crash {
5656
_exit(1);
5757
}
5858

59-
/// @brief Setup function
60-
static void setup_crash_handlers() {
59+
/// @brief Sets up global crash and exception handlers.
60+
///
61+
/// Installs handlers for:
62+
/// - Unhandled exceptions (`std::terminate`)
63+
/// - Memory allocation failures (`std::bad_alloc`)
64+
/// - Fatal signals (SIGSEGV, SIGABRT, SIGFPE, SIGILL, and SIGBUS on POSIX)
65+
///
66+
/// @note Signals like SIGKILL and SIGSTOP cannot be caught.
67+
static void configure_handlers() {
6168
// Catch unhandled exceptions
6269
std::set_terminate(handle_terminate);
6370
// Catch std::bad_alloc (new OOM)

src/utils/double.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace utils {
1010

11+
/// @brief Double-precision manipulation functions
1112
struct Double {
1213
public:
1314
/// @brief Convert a double price to uint64 by multiplication and truncation

src/utils/env.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace utils {
1010

11+
/// @brief OS environment helpers
1112
struct Env {
1213
public:
1314
/// @brief load a variable from the environment, if it's not available, panic
@@ -50,7 +51,7 @@ struct Env {
5051
#endif
5152

5253
// log combined info
53-
spdlog::info("Compiled for {} on {}", ARCH, OS);
54+
spdlog::info("Compiled for [{}] on [{}]", ARCH, OS);
5455
}
5556

5657
/// @brief Cache line size for the current architecture

src/utils/logging.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#include "spdlog/cfg/env.h"
2+
#include "spdlog/sinks/basic_file_sink.h"
3+
#include "spdlog/sinks/stdout_color_sinks.h"
4+
5+
namespace utils {
6+
7+
/// @brief logger helpers
8+
struct Logging {
9+
public:
10+
static void configure() {
11+
// basic sink for general logging (file)
12+
const char* log_path = std::getenv("LOG_PATH");
13+
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_path, false);
14+
file_sink->set_level(spdlog::level::info);
15+
// additional sink for `critical` logging to console (as a convenience)
16+
auto stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
17+
stdout_sink->set_level(spdlog::level::critical);
18+
// combine sinks
19+
auto logger = std::make_shared<spdlog::logger>(
20+
"multi_sink", spdlog::sinks_init_list{file_sink, stdout_sink});
21+
spdlog::set_default_logger(logger);
22+
// read the `LOG_LEVEL` envar for the minimum severity to log (at the logger level)
23+
spdlog::cfg::load_env_levels("LOG_LEVEL");
24+
// Set a global pattern without the logger name
25+
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [%t] %v");
26+
spdlog::flush_every(std::chrono::seconds(1));
27+
spdlog::info("hello");
28+
utils::Env::log_current_architecture();
29+
}
30+
};
31+
} // namespace utils

src/utils/process.h

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
4+
#include <format>
5+
#include <stdexcept>
6+
7+
#include "spdlog/spdlog.h"
8+
9+
#if defined(_WIN32) || defined(_WIN64)
10+
#include <windows.h>
11+
#else
12+
#include <sys/resource.h>
13+
#include <unistd.h>
14+
#endif
15+
16+
namespace utils {
17+
18+
/// @brief application process helpers
19+
struct Process {
20+
public:
21+
/// @brief Sets the current process to run with high priority.
22+
///
23+
/// This function attempts to increase the execution priority of the current process.
24+
/// On Windows systems, it sets the process priority class to a higher level
25+
/// (e.g. `HIGH_PRIORITY_CLASS`). On Unix-like systems, it adjusts the process "nice"
26+
/// value. The function uses `utils::Process::set_process_priority()` to apply the
27+
/// change and logs the result.
28+
///
29+
/// @throws std::runtime_error If the process priority cannot be changed successfully.
30+
static void set_high_priority() {
31+
int priority = 0;
32+
#if defined(_WIN32) || defined(_WIN64)
33+
priority = 1; // HIGH_PRIORITY_CLASS
34+
#else
35+
priority = -20; // Unix nice value
36+
#endif
37+
utils::Process::set_process_priority(priority);
38+
}
39+
40+
private:
41+
/// @brief Set the priority of the current process.
42+
/// @param priority On Linux/macOS: nice value (-20 highest → +19 lowest)
43+
/// On Windows: 0=NORMAL, 1=HIGH, 2=REALTIME
44+
/// @throw std::runtime_error if priority cannot be set
45+
static void set_process_priority(int priority) {
46+
#if defined(_WIN32) || defined(_WIN64)
47+
DWORD win_prio;
48+
switch (priority) {
49+
case 0:
50+
win_prio = NORMAL_PRIORITY_CLASS;
51+
break;
52+
case 1:
53+
win_prio = HIGH_PRIORITY_CLASS;
54+
break;
55+
case 2:
56+
win_prio = REALTIME_PRIORITY_CLASS;
57+
break;
58+
default:
59+
win_prio = NORMAL_PRIORITY_CLASS;
60+
break;
61+
}
62+
if (!SetPriorityClass(GetCurrentProcess(), win_prio)) {
63+
throw std::runtime_error("failed to set windows process priority");
64+
}
65+
66+
#else
67+
int ret = setpriority(PRIO_PROCESS, 0, priority);
68+
if (ret != 0) {
69+
perror("setpriority");
70+
throw std::runtime_error(std::format(
71+
"failed to set posix process priority. value [{}], ret [{}]", priority, ret));
72+
}
73+
#endif
74+
}
75+
};
76+
77+
} // namespace utils

src/utils/testing.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
namespace utils {
55

6+
/// @brief testing helpers
67
class Testing {
78
public:
89
/// @brief Repeatedly evaluates a condition until it returns true or a timeout is

0 commit comments

Comments
 (0)