Skip to content

Commit 5ba318c

Browse files
authored
Merge pull request #213 from CESNET/workers-cpu-affinity
Workers cpu affinity
2 parents 4be259b + 9c6c5df commit 5ba318c

File tree

5 files changed

+130
-21
lines changed

5 files changed

+130
-21
lines changed

include/ipfixprobe/utils.hpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
#include <stdexcept>
4040
#include <cstdint>
4141
#include <sys/time.h>
42+
#include <vector>
43+
#include <sstream>
4244

4345
namespace ipxp {
4446

@@ -152,6 +154,23 @@ T str2num(std::string str, typename std::enable_if<is_uint<T>()>::type * = nullp
152154
*/
153155
uint64_t timeval2usec(const struct timeval& tv);
154156

157+
/**
158+
* @brief Convert vector to string, e.g. for error messages
159+
*/
160+
template <typename T>
161+
std::string vec2str(const std::vector<T> &vec) {
162+
std::stringstream ss;
163+
bool first = true;
164+
for (auto &item : vec)
165+
{
166+
if (!first)
167+
ss << ", ";
168+
ss << item;
169+
first = false;
170+
}
171+
return ss.str();
172+
}
173+
155174
}
156175

157176
#endif /* IPXP_UTILS_HPP */

init/ipfixprobed

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,31 @@ if [ -e "$CONFFILE" ]; then
1313
exit 1
1414
fi
1515

16+
if [ ! -z "$DPDK_LCORES" ]; then
17+
DPDK_LCORES="--lcores $DPDK_LCORES"
18+
fi
19+
20+
if [ -n "$DPDK_OPTS" -a "${DPDK_OPTS:0:1}" != ";" ]; then
21+
DPDK_OPTS=";$DPDK_OPTS"
22+
fi
23+
24+
# create array with input workers affinities
25+
if [ ! -z "$DPDK_INPUT_WORKER_CPUS" ]; then
26+
if `declare -p DPDK_INPUT_WORKER_CPUS > /dev/null 2>/dev/null`; then
27+
if [ "${#DPDK_INPUT_WORKER_CPUS[@]}" -ne "$DPDK_QUEUES_COUNT" ]; then
28+
echo "DPDK_INPUT_WORKER_CPUS length must be the same as queues count."
29+
exit 1
30+
fi
31+
fi
32+
fi
33+
for ((i = 0; i < DPDK_QUEUES_COUNT; i++)); do
34+
if [ ! -z "$DPDK_INPUT_WORKER_CPUS" ]; then
35+
affinities[i]="@${DPDK_INPUT_WORKER_CPUS[$i]}"
36+
else
37+
affinities[i]=""
38+
fi
39+
done
40+
1641
# set up DPDK interface(s)
1742
if [ "$DPDK_RING" = "1" ]; then
1843
# checks
@@ -24,19 +49,19 @@ if [ -e "$CONFFILE" ]; then
2449
echo "Missing DPDK_RING_STARTIDX in configuration of DPDK_RING mode, using 0."
2550
DPDK_RING_STARTIDX=0
2651
fi
52+
2753
# mring interfaces
28-
dpdkinput=("-i" "dpdk-ring;r=$(printf "$DPDK_RING_PATTERN" "$DPDK_RING_STARTIDX");e=--lcores $DPDK_LCORES $DPDK_EXTRA_EAL")
54+
dpdkinput=("-i" "dpdk-ring${affinities[0]};r=$(printf "$DPDK_RING_PATTERN" "$DPDK_RING_STARTIDX")${DPDK_OPTS};e=$DPDK_LCORES $DPDK_EXTRA_EAL")
55+
plugin_idx=1
2956
for ((ifc=($DPDK_RING_STARTIDX+1); ifc<($DPDK_RING_STARTIDX + $DPDK_QUEUES_COUNT);ifc++)); do
30-
dpdkinput+=("-i" "dpdk-ring;r=$(printf "$DPDK_RING_PATTERN" "$ifc")")
57+
dpdkinput+=("-i" "dpdk-ring${affinities[$plugin_idx]};r=$(printf "$DPDK_RING_PATTERN" "$ifc")")
58+
((plugin_idx++))
3159
done
3260
else
3361
# DPDK port interface
34-
if [ -n "$DPDK_PORTOPTS" -a "${DPDK_PORTOPTS:0:1}" != ";" ]; then
35-
DPDK_PORTOPTS=";$DPDK_PORTOPTS"
36-
fi
37-
dpdkinput=("-i" "dpdk;p=${DPDK_PORT}${DPDK_PORTOPTS};q=$DPDK_QUEUES_COUNT;e=--lcores $DPDK_LCORES $DPDK_EXTRA_EAL -a $DPDK_DEVICE")
62+
dpdkinput=("-i" "dpdk${affinities[0]};p=${DPDK_PORT}${DPDK_OPTS};q=$DPDK_QUEUES_COUNT;e=$DPDK_LCORES $DPDK_EXTRA_EAL -a $DPDK_DEVICE")
3863
for ((ifc=1; ifc<$DPDK_QUEUES_COUNT;ifc++)); do
39-
dpdkinput+=("-i" "dpdk")
64+
dpdkinput+=("-i" "dpdk${affinities[$ifc]}")
4065
done
4166
fi
4267
fi
@@ -88,23 +113,19 @@ if [ -e "$CONFFILE" ]; then
88113
NON_BLOCKING_TCP_PARAM="non-blocking-tcp";
89114
fi
90115

91-
output="-o ipfix;host=${HOST:-127.0.0.1};port=${PORT:-4739};id=${LINK:-0};dir=${DIR:-0};${UDP_PARAM};${NON_BLOCKING_TCP_PARAM};template=${TEMPLATE_REFRESH_RATE:-300}"
92-
93-
telemetry=""
94-
if [ "$USE_FUSE" = "1" ]; then
95-
telemetry="-t ${FUSE_MOUNT_POINT}"
116+
output_affinity=""
117+
if [ ! -z "$OUTPUT_WORKER_CPU" ]; then
118+
output_affinity="@$OUTPUT_WORKER_CPU"
96119
fi
97120

98-
exec /usr/bin/ipfixprobe "${dpdkinput[@]}" $input $storage $process $output $telemetry
121+
output="-o ipfix$output_affinity;host=${HOST:-127.0.0.1};port=${PORT:-4739};id=${LINK:-0};dir=${DIR:-0};${UDP_PARAM};${NON_BLOCKING_TCP_PARAM};template=${TEMPLATE_REFRESH_RATE:-300}"
99122

100123
telemetry=""
101124
if [ "$USE_FUSE" = "1" ]; then
102125
telemetry="-t ${FUSE_MOUNT_POINT}"
103126
fi
104127

105-
exec /usr/bin/ipfixprobe "${dpdkinput[@]}" $input $storage $process $output $telemetry
106-
107-
exec /usr/bin/ipfixprobe "${dpdkinput[@]}" $input $storage $process $output
128+
exec /usr/bin/ipfixprobe "${dpdkinput[@]}" $input $storage $process $output $telemetry $EXTRA_ARGS
108129
else
109130
echo "Configuration file '$CONFFILE' does not exist, exitting." >&2
110131
exit 1

init/link0.conf.example

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@
6464
# Set mapping of DPDK lcores to threads:
6565
#DPDK_LCORES="(0-7)@(0,2,4,6,8,10,12,14)"
6666

67+
# Set input workers CPU affinity, each worker is mapped on single core
68+
# array must have the same size as DPDK_QUEUES_COUNT
69+
# when DPDK_INPUT_WORKER_CPUS is specified, DPDK_LCORES does not affect input workers
70+
#DPDK_INPUT_WORKER_CPUS=(0 2 4 6 8 10 12 14)
71+
6772
# Extra options for DPDK EAL, passed to e= option of `dpdk` or `dpdk-ring` plugin.
6873
# * Use --file-prefix to separate DPDK application into new namespace.
6974
# * Use --proc-type=secondary for Option B) to receive packets via mrings created
@@ -189,7 +194,13 @@ NON_BLOCKING_TCP=no
189194
# Export ipfix template every N seconds (UDP)
190195
TEMPLATE_REFRESH_RATE=300
191196

197+
# Define output worker (thread) affinity, e.g. CPU core isolated from the scheduler
198+
#OUTPUT_WORKER_CPU=12
199+
192200
####### Fuse telemetry
193201

194202
USE_FUSE=0
195203
FUSE_MOUNT_POINT="/var/run/ipfixprobe"
204+
205+
# Specify any extra global arguments, e.g. size of input queue
206+
#EXTRA_ARGS="-q 2048"

ipfixprobe.cpp

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ void print_help(ipxp_conf_t &conf, const std::string &arg)
138138
}
139139
}
140140

141-
void process_plugin_argline(const std::string &args, std::string &plugin, std::string &params)
141+
void process_plugin_argline(const std::string &args, std::string &plugin, std::string &params, std::vector<int> &affinity)
142142
{
143143
size_t delim;
144144

@@ -148,6 +148,16 @@ void process_plugin_argline(const std::string &args, std::string &plugin, std::s
148148
plugin = params.substr(0, delim);
149149
params.erase(0, delim == std::string::npos ? delim : delim + 1);
150150

151+
delim = plugin.find('@');
152+
if (delim != std::string::npos) {
153+
try {
154+
affinity.emplace_back(std::stoi(plugin.substr(delim + 1)));
155+
} catch (const std::invalid_argument &ex) {
156+
throw IPXPError("CPU affinity must be single number: " + std::string(ex.what()));
157+
}
158+
}
159+
plugin = plugin.substr(0, delim);
160+
151161
trim_str(plugin);
152162
trim_str(params);
153163
}
@@ -168,6 +178,28 @@ telemetry::Content get_ipx_ring_telemetry(ipx_ring_t* ring)
168178
return dict;
169179
}
170180

181+
void set_thread_details(pthread_t thread, const std::string &name, const std::vector<int> &affinity)
182+
{
183+
// Set thread name and affinity
184+
if (name.length() > 0) {
185+
pthread_setname_np(thread, name.substr(0, 15).c_str());
186+
}
187+
if (affinity.size() > 0) {
188+
cpu_set_t cpuset;
189+
CPU_ZERO(&cpuset);
190+
for (auto cpu : affinity) {
191+
CPU_SET(cpu, &cpuset);
192+
}
193+
int ret = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset);
194+
if (ret != 0) {
195+
throw IPXPError(
196+
"pthread_setaffinity_np failed, CPU(s) "
197+
+ vec2str(affinity) + " probably cannot be set"
198+
);
199+
}
200+
}
201+
}
202+
171203
bool process_plugin_args(ipxp_conf_t &conf, IpfixprobeOptParser &parser)
172204
{
173205
auto deleter = [&](OutputPlugin::Plugins *p) {
@@ -183,18 +215,27 @@ bool process_plugin_args(ipxp_conf_t &conf, IpfixprobeOptParser &parser)
183215
std::string output_params = "";
184216

185217
if (parser.m_storage.size()) {
186-
process_plugin_argline(parser.m_storage[0], storage_name, storage_params);
218+
std::vector<int> affinity;
219+
process_plugin_argline(parser.m_storage[0], storage_name, storage_params, affinity);
220+
if (affinity.size() != 0) {
221+
throw IPXPError("cannot set CPU affinity for storage plugin (storage plugin is invoked inside input threads)");
222+
}
187223
}
224+
std::vector<int> output_worker_affinity;
188225
if (parser.m_output.size()) {
189-
process_plugin_argline(parser.m_output[0], output_name, output_params);
226+
process_plugin_argline(parser.m_output[0], output_name, output_params, output_worker_affinity);
190227
}
191228

192229
// Process
193230
for (auto &it : parser.m_process) {
194231
ProcessPlugin *process_plugin = nullptr;
195232
std::string process_params;
196233
std::string process_name;
197-
process_plugin_argline(it, process_name, process_params);
234+
std::vector<int> affinity;
235+
process_plugin_argline(it, process_name, process_params, affinity);
236+
if (affinity.size() != 0) {
237+
throw IPXPError("cannot set CPU affinity for process plugin (process plugins are invoked inside input threads)");
238+
}
198239
for (auto &it : *process_plugins) {
199240
std::string plugin_name = it.first;
200241
if (plugin_name == process_name) {
@@ -272,6 +313,7 @@ bool process_plugin_args(ipxp_conf_t &conf, IpfixprobeOptParser &parser)
272313
output_stats,
273314
output_queue
274315
};
316+
set_thread_details(tmp.thread->native_handle(), "out_" + output_name, output_worker_affinity);
275317
conf.outputs.push_back(tmp);
276318
conf.output_fut.push_back(output_res->get_future());
277319
}
@@ -286,7 +328,8 @@ bool process_plugin_args(ipxp_conf_t &conf, IpfixprobeOptParser &parser)
286328
StoragePlugin *storage_plugin = nullptr;
287329
std::string input_params;
288330
std::string input_name;
289-
process_plugin_argline(it, input_name, input_params);
331+
std::vector<int> affinity;
332+
process_plugin_argline(it, input_name, input_params, affinity);
290333

291334
auto input_plugin_dir = input_dir->addDir(input_name);
292335
auto pipeline_queue_dir = pipeline_dir->addDir("queues")->addDir(std::to_string(pipeline_idx));
@@ -358,6 +401,7 @@ bool process_plugin_args(ipxp_conf_t &conf, IpfixprobeOptParser &parser)
358401
storage_process_plugins
359402
}
360403
};
404+
set_thread_details(tmp.input.thread->native_handle(), "in_"+ std::to_string(pipeline_idx) + "_" + input_name, affinity);
361405
conf.pipelines.push_back(tmp);
362406
pipeline_idx++;
363407
}
@@ -654,6 +698,7 @@ int run(int argc, char *argv[])
654698
conf.pkt_bufsize = parser.m_pkt_bufsize;
655699
conf.max_pkts = parser.m_max_pkts;
656700

701+
set_thread_details(pthread_self(), "", parser.m_cpu_mask);
657702

658703
try {
659704
if (process_plugin_args(conf, parser)) {

ipfixprobe.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class IpfixprobeOptParser : public OptionsParser {
9090
bool m_help;
9191
std::string m_help_str;
9292
bool m_version;
93+
std::vector<int> m_cpu_mask;
9394

9495
IpfixprobeOptParser() : OptionsParser("ipfixprobe", "flow exporter supporting various custom IPFIX elements"),
9596
m_pid(""), m_appfs_mount_point(""), m_daemon(false),
@@ -170,6 +171,18 @@ class IpfixprobeOptParser : public OptionsParser {
170171
m_version = true;
171172
return true;
172173
}, OptionFlags::NoArgument);
174+
register_option("-C", "--cpus", "CPU_LIST", "Set global CPU mask for main thread and subthreads", [this](const char *arg) {
175+
try {
176+
std::stringstream ss(arg);
177+
std::string tmp;
178+
while (std::getline(ss, tmp, ',')) {
179+
m_cpu_mask.emplace_back(str2num<uint16_t>(tmp));
180+
}
181+
return true;
182+
} catch (std::invalid_argument &e) {
183+
return false;
184+
}
185+
});
173186
}
174187
};
175188

0 commit comments

Comments
 (0)