Skip to content

Commit

Permalink
Merge pull request #1045 from Igalia/extend-find-limit
Browse files Browse the repository at this point in the history
Extend find-limit to support multiple NICs.
  • Loading branch information
tsyesika committed Apr 17, 2018
2 parents a1121cc + bcda559 commit 44bb708
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 80 deletions.
14 changes: 11 additions & 3 deletions src/program/loadtest/find-limit/README
@@ -1,4 +1,5 @@
Usage: find-limit [OPTIONS] DEVICE PCAP-FILE
Usage: find-limit [OPTIONS] <PCAP-FILE> <TX-NAME> <RX-NAME> <PCI> [<PCAP-FILE> <TX-NAME> <RX-NAME> <PCI>]...
find-limit [OPTIONS] <PCAP-FILE> <PCI>

-b BITRATE, --bitrate BITRATE
Test bitrates up to BITRATE bits/second.
Expand All @@ -22,8 +23,15 @@ Usage: find-limit [OPTIONS] DEVICE PCAP-FILE
-h, --help
Print usage information.

Apply load on DEVICE by replaying packets from PCAP-FILE, and attempt to
determine the highest bitrate at which a test passes.
Apply load by replaying packets from PCAP-FILE to the corresponding PCI network
adaptors. It will attempt to determin the highest bitrate at which a test passes.
If no script is supplied then find-limit will attempt to see if packets are being
dropped by checking the traffic sent vs traffic recieved.

If you specify only a PCI address and PCAP file it will configure itself to work
the same as specifying a single device as both the recieve and transmit interface.

Examples:
find-limit 01:00.0 cap1.pcap
find-limit cap1.pcap tx tx 01:00.0
find-limit cap1.pcap "NIC 0" "NIC 1" 01:00.0 cap2.pcap "NIC 1" "NIC 0" 01:00.0
198 changes: 121 additions & 77 deletions src/program/loadtest/find-limit/find-limit.lua
Expand Up @@ -15,11 +15,6 @@ local promise = require("program.loadtest.promise")
local WARM_UP_BIT_RATE = 1e9
local WARM_UP_TIME = 5

local function fatal (msg)
print(msg)
main.exit(1)
end

local function show_usage(code)
print(require("program.loadtest.find_limit.README_inc"))
main.exit(code)
Expand All @@ -31,34 +26,31 @@ local function find_limit(tester, max_bitrate, precision, duration, retry_count)
end

-- lo and hi are bitrates, in bits per second.
local function bisect(lo, hi, iter, actual)
local function continue(cur, result, actual)
local function bisect(lo, hi, iter)
local function continue(cur, result)
if result then
print("Success.")
return bisect(cur, hi, 1, actual)
return bisect(cur, hi, 1)
elseif iter <= retry_count then
print("Failed; "..(retry_count - iter).. " retries remaining.")
return bisect(lo, hi, iter + 1, actual)
return bisect(lo, hi, iter + 1)
else
print("Failed.")
return bisect(lo, cur, 1, actual)
return bisect(lo, cur, 1)
end
end
local cur = round((lo + hi) / 2)
if cur == lo or cur == hi then
print(round(actual or lo) * 1e-9)
return lo
print(round(lo) * 1e-9)
return lo
end

-- We need to

return tester.start_load(cur, duration):
and_then(continue, cur)
end
return bisect(0, round(max_bitrate), 1)
end

function parse_args(args)
local function parse_args(args)
local opts = { max_bitrate = 10e9, duration = 1, precision = 0.001e9,
retry_count = 3 }
local function parse_positive_number(prop)
Expand Down Expand Up @@ -90,65 +82,107 @@ function parse_args(args)
{ bitrate="b", duration="D", precision="p",
["retry-count"]="r", help="h", cpu=1,
exec="e"})
if #args ~= 2 then show_usage(1) end
local device, capture_file = unpack(args)

if #args == 2 then
args = {
args[1],
'NIC', 'NIC',
args[2]
}
end
if #args == 0 or #args % 4 ~= 0 then show_usage(1) end
local streams, streams_by_tx_id, pci_devices = {}, {}, {}
for i=1,#args,4 do
local stream = {}
stream.pcap_file = args[i]
stream.tx_name = args[i+1]
stream.rx_name = args[i+2]
stream.tx_id = stream.tx_name:gsub('[^%w]', '_')
stream.rx_id = stream.rx_name:gsub('[^%w]', '_')
stream.tx_device = pci.device_info(args[i+3])
stream.tx_driver = require(stream.tx_device.driver).driver
table.insert(streams, stream)
table.insert(pci_devices, stream.tx_device.pciaddress)
assert(streams_by_tx_id[streams.tx_id] == nil, 'Duplicate: '..stream.tx_name)
streams_by_tx_id[stream.tx_id] = stream
end
for _, stream in ipairs(streams) do
assert(streams_by_tx_id[stream.rx_id], 'Missing stream: '..stream.rx_id)
stream.rx_device = streams_by_tx_id[stream.rx_id].tx_device
end
if opts.cpu then numa.bind_to_cpu(opts.cpu) end
numa.check_affinity_for_pci_addresses({device})
return opts, device, capture_file
numa.check_affinity_for_pci_addresses(pci_devices)
return opts, streams
end

function run(args)
local opts, device, capture_file = parse_args(args)
local device_info = pci.device_info(device)
local driver = require(device_info.driver).driver
local c = config.new()
local opts, streams = parse_args(args)

-- Links are named directionally with respect to NIC apps, but we
-- want to name tx and rx with respect to the whole network
-- function.
local tx_link_name = device_info.rx
local rx_link_name = device_info.tx
local c = config.new()
for _, stream in ipairs(streams) do
stream.pcap_id = 'pcap_'..stream.tx_id
stream.repeater_id = 'repeater'..stream.tx_id
stream.nic_tx_id = 'nic_'..stream.tx_id
stream.nic_rx_id = 'nic_'..stream.rx_id
-- Links are named directionally with respect to NIC apps, but we
-- want to name tx and rx with respect to the whole network
-- function.
stream.nic_tx_link = stream.tx_device.rx
stream.nic_rx_link = stream.rx_device.tx
stream.rx_sink_id = 'rx_sink_'..stream.rx_id

config.app(c, "replay", PcapReader, capture_file)
config.app(c, "repeater", loadgen.RateLimitedRepeater, {})
config.app(c, "nic", driver, { pciaddr = device_info.pciaddress })
config.app(c, "blackhole", basic_apps.Sink)
config.app(c, stream.pcap_id, PcapReader, stream.pcap_file)
config.app(c, stream.repeater_id, loadgen.RateLimitedRepeater)
config.app(c, stream.nic_tx_id, stream.tx_driver, { pciaddr = stream.tx_device.pciaddress})
config.app(c, stream.rx_sink_id, basic_apps.Sink)

config.link(c, "replay.output -> repeater.input")
config.link(c, "repeater.output -> nic."..tx_link_name)
config.link(c, "nic."..rx_link_name.." -> blackhole.input")
config.link(c, stream.pcap_id..".output -> "..stream.repeater_id..".input")
config.link(c, stream.repeater_id..".output -> "..stream.nic_tx_id.."."..stream.nic_tx_link)
config.link(c, stream.nic_rx_id.."."..stream.nic_rx_link.." -> "..stream.rx_sink_id..".input")
end

engine.configure(c)

local nic_app = assert(engine.app_table.nic)
local repeater_app = assert(engine.app_table.repeater)

local function read_counters()
local tx, rx = nic_app.input[tx_link_name], nic_app.output[rx_link_name]
return { txpackets = counter.read(tx.stats.txpackets),
txbytes = counter.read(tx.stats.txbytes),
rxpackets = counter.read(rx.stats.txpackets),
rxbytes = counter.read(rx.stats.txbytes),
rxdrop = nic_app:rxdrop() }
local counters = {}
for _, stream in ipairs(streams) do
local tx_app = assert(engine.app_table[stream.nic_tx_id])
local rx_app = assert(engine.app_table[stream.nic_rx_id])
local tx, rx = tx_app.input[stream.nic_tx_link], rx_app.output[stream.nic_rx_link]
counters[stream.nic_tx_id] = {
txpackets = counter.read(tx.stats.txpackets),
txbytes = counter.read(tx.stats.txbytes),
rxpackets = counter.read(rx.stats.txpackets),
rxbytes = counter.read(rx.stats.txbytes),
rxdrop = rx_app:rxdrop()
}
end
return counters
end

local function print_stats(s)
end

local function check_results(diff)
local tx_bitrate = diff.tx_gbps * 1e9
local function check_results(stats)
if opts.exec then
-- Could pass on some arguments to this string.
return os.execute(opts.exec) == 0, tx_bitrate
else
return diff.rxpackets == diff.txpackets and diff.rxdrop == 0, tx_bitrate
return os.execute(opts.exec) == 0
end

local success = true
for _, stream in ipairs(streams) do
local diff = stats[stream.nic_tx_id]
success = (diff.rxpackets == diff.txpackets and diff.rxdrop == 0) and success
end
return success
end

local tester = {}

function tester.adjust_rates(bit_rate)
repeater_app:set_rate(bit_rate)
for _, stream in ipairs(streams) do
local app = assert(engine.app_table[stream.repeater_id])
app:set_rate(bit_rate)
end
end

function tester.generate_load(bitrate, duration)
Expand All @@ -173,38 +207,48 @@ function run(args)
and_then(promise.Wait, 0.002):
and_then(tester.measure, bitrate, duration)
end

function tester.measure(bitrate, duration)
local gbps_bitrate = bitrate/1e9
local start_counters = read_counters()
local function compute_stats()
local end_counters = read_counters()
local s = {}
for k,v in pairs(start_counters) do
s[k] = tonumber(end_counters[k] - start_counters[k])
local stats = {}
for _, stream in ipairs(streams) do
local s = {}
for k,_ in pairs(start_counters[stream.nic_tx_id]) do
local end_value = end_counters[stream.nic_tx_id][k]
local start_value = start_counters[stream.nic_tx_id][k]
s[k] = tonumber(end_value - start_value)
end
s.applied_gbps = gbps_bitrate
s.tx_mpps = s.txpackets / duration / 1e6
s.tx_gbps = compute_bitrate(s.txpackets, s.txbytes, duration) / 1e9
s.rx_mpps = s.rxpackets / duration / 1e6
s.rx_gbps = compute_bitrate(s.rxpackets, s.rxbytes, duration) / 1e9
s.lost_packets = s.txpackets - s.rxpackets - s.rxdrop
s.lost_percent = s.lost_packets / s.txpackets * 100
print(string.format(' %s:', stream.tx_name))
print(string.format(' TX %d packets (%f MPPS), %d bytes (%f Gbps)',
s.txpackets, s.tx_mpps, s.txbytes, s.tx_gbps))
print(string.format(' RX %d packets (%f MPPS), %d bytes (%f Gbps)',
s.rxpackets, s.rx_mpps, s.rxbytes, s.rx_gbps))
print(string.format(' Loss: %d ingress drop + %d packets lost (%f%%)',
s.rxdrop, s.lost_packets, s.lost_percent))

stats[stream.nic_tx_id] = s
end
s.applied_gbps = gbps_bitrate
s.tx_mpps = s.txpackets / duration / 1e6
s.tx_gbps = compute_bitrate(s.txpackets, s.txbytes, duration) / 1e9
s.rx_mpps = s.rxpackets / duration / 1e6
s.rx_gbps = compute_bitrate(s.rxpackets, s.rxbytes, duration) / 1e9
s.lost_packets = s.txpackets - s.rxpackets - s.rxdrop
s.lost_percent = s.lost_packets / s.txpackets * 100
print(string.format(' TX %d packets (%f MPPS), %d bytes (%f Gbps)',
s.txpackets, s.tx_mpps, s.txbytes, s.tx_gbps))
print(string.format(' RX %d packets (%f MPPS), %d bytes (%f Gbps)',
s.rxpackets, s.rx_mpps, s.rxbytes, s.rx_gbps))
print(string.format(' Loss: %d ingress drop + %d packets lost (%f%%)',
s.rxdrop, s.lost_packets, s.lost_percent))
return s
return stats
end
local function verify_load(s)
if s.tx_gbps < 0.5 * s.applied_gbps then
print("Invalid result.")
return tester.start_load(bitrate, duration)
else
return check_results(s)
end
local function verify_load(stats)
for _, stream in ipairs(streams) do
local s = stats[stream.nic_tx_id]
if s.tx_gbps < 0.5 * s.applied_gbps then
print("Invalid result.")
return tester.start_load(bitrate, duration)
end
end
return check_results(stats)
end
print(string.format('Applying %f Gbps of load.', gbps_bitrate))
return tester.generate_load(bitrate, duration):
Expand Down

0 comments on commit 44bb708

Please sign in to comment.