Permalink
Cannot retrieve contributors at this time
415 lines (354 sloc)
14.7 KB
| --package.path = package.path .. "rfc2544/?.lua" | |
| package.path = package.path .. ";/opt/dpdk/MoonGen/rfc2544/?.lua" | |
| local standalone = false | |
| if master == nil then | |
| standalone = true | |
| master = "dummy" | |
| end | |
| local dpdk = require "dpdk" | |
| local memory = require "memory" | |
| local device = require "device" | |
| local filter = require "filter" | |
| local ffi = require "ffi" | |
| local barrier = require "barrier" | |
| local timer = require "timer" | |
| local utils = require "utils.utils" | |
| local arp = require "proto.arp" | |
| local tikz = require "utils.tikz" | |
| local UDP_PORT = 42 | |
| local benchmark = {} | |
| benchmark.__index = benchmark | |
| function benchmark.create() | |
| local self = setmetatable({}, benchmark) | |
| self.initialized = false | |
| return self | |
| end | |
| setmetatable(benchmark, {__call = benchmark.create}) | |
| function benchmark:init(arg) | |
| self.duration = arg.duration or 10 | |
| self.rateThreshold = arg.rateThreshold or 10 | |
| self.maxLossRate = arg.maxLossRate or 0.001 | |
| self.rxQueues = arg.rxQueues | |
| self.txQueues = arg.txQueues | |
| self.numIterations = arg.numIterations or 1 | |
| self.skipConf = arg.skipConf | |
| self.dut = arg.dut | |
| self.initialized = true | |
| end | |
| function benchmark:config() | |
| self.undoStack = {} | |
| utils.addInterfaceIP(self.dut.ifIn, "198.18.1.1", 24) | |
| table.insert(self.undoStack, {foo = utils.delInterfaceIP, args = {self.dut.ifIn, "198.18.1.1", 24}}) | |
| utils.addInterfaceIP(self.dut.ifOut, "198.19.1.1", 24) | |
| table.insert(self.undoStack, {foo = utils.delInterfaceIP, args = {self.dut.ifOut, "198.19.1.1", 24}}) | |
| end | |
| function benchmark:undoConfig() | |
| local len = #self.undoStack | |
| for k, v in ipairs(self.undoStack) do | |
| --work in stack order | |
| local elem = self.undoStack[len - k + 1] | |
| elem.foo(unpack(elem.args)) | |
| end | |
| --clear stack | |
| self.undoStack = {} | |
| end | |
| function benchmark:getCSVHeader() | |
| local str = "frame size(byte),duration(s),max loss rate(%),rate threshold(packets)" | |
| for i=1, self.numIterations do | |
| str = str .. "," .. "rate(mpps) iter" .. i .. ",spkts(byte) iter" .. i .. ",rpkts(byte) iter" .. i | |
| end | |
| return str | |
| end | |
| function benchmark:resultToCSV(result) | |
| local str = "" | |
| for i=1, self.numIterations do | |
| str = str .. result[i].frameSize .. "," .. self.duration .. "," .. self.maxLossRate * 100 .. "," .. self.rateThreshold .. "," .. result[i].mpps .. "," .. result[i].spkts .. "," .. result[i].rpkts | |
| if i < self.numIterations then | |
| str = str .. "\n" | |
| end | |
| end | |
| return str | |
| end | |
| function benchmark:toTikz(filename, ...) | |
| local values = {} | |
| local numResults = select("#", ...) | |
| for i=1, numResults do | |
| local result = select(i, ...) | |
| local avg = 0 | |
| local numVals = 0 | |
| local frameSize | |
| for _, v in ipairs(result) do | |
| frameSize = v.frameSize | |
| avg = avg + v.mpps | |
| numVals = numVals + 1 | |
| end | |
| avg = avg / numVals | |
| table.insert(values, {k = frameSize, v = avg}) | |
| end | |
| table.sort(values, function(e1, e2) return e1.k < e2.k end) | |
| local xtick = "" | |
| local t64 = false | |
| local last = -math.huge | |
| for k, p in ipairs(values) do | |
| if (p.k - last) >= 128 then | |
| xtick = xtick .. p.k | |
| if values[k + 1] then | |
| xtick = xtick .. "," | |
| end | |
| last = p.k | |
| end | |
| end | |
| local imgMpps = tikz.new(filename .. "_mpps" .. ".tikz", [[ xlabel={packet size [byte]}, ylabel={rate [Mpps]}, grid=both, ymin=0, xmin=0, xtick={]] .. xtick .. [[},scaled ticks=false, width=9cm, height=4cm, cycle list name=exotic]]) | |
| local imgMbps = tikz.new(filename .. "_mbps" .. ".tikz", [[ xlabel={packet size [byte]}, ylabel={rate [Gbit/s]}, grid=both, ymin=0, xmin=0, xtick={]] .. xtick .. [[},scaled ticks=false, width=9cm, height=4cm, cycle list name=exotic,legend style={at={(0.99,0.02)},anchor=south east}]]) | |
| imgMpps:startPlot() | |
| imgMbps:startPlot() | |
| for _, p in ipairs(values) do | |
| imgMpps:addPoint(p.k, p.v) | |
| imgMbps:addPoint(p.k, p.v * (p.k + 20) * 8 / 1000) | |
| end | |
| local legend = "throughput at max " .. self.maxLossRate * 100 .. " \\% packet loss" | |
| imgMpps:endPlot(legend) | |
| imgMbps:endPlot(legend) | |
| imgMpps:startPlot() | |
| imgMbps:startPlot() | |
| for _, p in ipairs(values) do | |
| local linkRate = self.txQueues[1].dev:getLinkStatus().speed | |
| imgMpps:addPoint(p.k, linkRate / (p.k + 20) / 8) | |
| imgMbps:addPoint(p.k, linkRate / 1000) | |
| end | |
| imgMpps:finalize("link rate") | |
| imgMbps:finalize("link rate") | |
| end | |
| function benchmark:bench(frameSize) | |
| if not self.initialized then | |
| return print("benchmark not initialized"); | |
| elseif frameSize == nil then | |
| return error("benchmark got invalid frameSize"); | |
| end | |
| if not self.skipConf then | |
| self:config() | |
| end | |
| local binSearch = utils.binarySearch() | |
| local pktLost = true | |
| local maxLinkRate = self.txQueues[1].dev:getLinkStatus().speed | |
| local rate, lastRate | |
| local bar = barrier.new(0,2) | |
| local results = {} | |
| local rateSum = 0 | |
| local finished = false | |
| --repeat the test for statistical purpose | |
| for iteration=1,self.numIterations do | |
| local port = UDP_PORT | |
| binSearch:init(0, maxLinkRate) | |
| rate = maxLinkRate -- start at maximum, so theres a chance at reaching maximum (otherwise only maximum - threshold can be reached) | |
| lastRate = rate | |
| printf("starting iteration %d for frameSize %d", iteration, frameSize) | |
| --init maximal transfer rate without packetloss of this iteration to zero | |
| results[iteration] = {spkts = 0, rpkts = 0, mpps = 0, frameSize = frameSize} | |
| -- loop until no packetloss | |
| while dpdk.running() do | |
| -- workaround for rate bug | |
| local numQueues = rate > (64 * 64) / (84 * 84) * maxLinkRate and rate < maxLinkRate and 3 or 1 | |
| bar:reinit(numQueues + 1) | |
| if rate < maxLinkRate then | |
| -- not maxLinkRate | |
| -- eventual multiple slaves | |
| -- set rate is payload rate not wire rate | |
| for i=1, numQueues do | |
| printf("set queue %i to rate %d", i, rate * frameSize / (frameSize + 20) / numQueues) | |
| self.txQueues[i]:setRate(rate * frameSize / (frameSize + 20) / numQueues) | |
| end | |
| else | |
| -- maxLinkRate | |
| self.txQueues[1]:setRate(rate) | |
| end | |
| local loadTasks = {} | |
| -- traffic generator | |
| for i=1, numQueues do | |
| table.insert(loadTasks, dpdk.launchLua("throughputLoadSlave", self.txQueues[i], port, frameSize, self.duration, mod, bar)) | |
| end | |
| -- count the incoming packets | |
| local ctrTask = dpdk.launchLua("throughputCounterSlave", self.rxQueues[1], port, frameSize, self.duration, bar) | |
| -- wait until all slaves are finished | |
| local spkts = 0 | |
| for _, loadTask in pairs(loadTasks) do | |
| spkts = spkts + loadTask:wait() | |
| end | |
| local rpkts = ctrTask:wait() | |
| local lossRate = (spkts - rpkts) / spkts | |
| local validRun = lossRate <= self.maxLossRate | |
| if validRun then | |
| -- theres a minimal gap between self.duration and the real measured duration, but that | |
| -- doesnt matter | |
| results[iteration] = { spkts = spkts, rpkts = rpkts, mpps = spkts / 10^6 / self.duration, frameSize = frameSize} | |
| end | |
| printf("sent %d packets, received %d", spkts, rpkts) | |
| printf("rate %f and packetloss %f => %d", rate, lossRate, validRun and 1 or 0) | |
| lastRate = rate | |
| rate, finished = binSearch:next(rate, validRun, self.rateThreshold) | |
| if finished then | |
| -- not setting rate in table as it is not guaranteed that last round all | |
| -- packets were received properly | |
| local mpps = results[iteration].mpps | |
| printf("maximal rate for packetsize %d: %0.2f Mpps, %0.2f MBit/s, %0.2f MBit/s wire rate", frameSize, mpps, mpps * frameSize * 8, mpps * (frameSize + 20) * 8) | |
| rateSum = rateSum + results[iteration].mpps | |
| break | |
| end | |
| printf("changing rate from %d MBit/s to %d MBit/s", lastRate, rate) | |
| -- TODO: maybe wait for resettlement of DUT (RFC2544) | |
| port = port + 1 | |
| dpdk.sleepMillis(100) | |
| --device.reclaimTxBuffers() | |
| end | |
| end | |
| if not self.skipConf then | |
| self:undoConfig() | |
| end | |
| return results, rateSum / self.numIterations | |
| end | |
| function throughputLoadSlave(queue, port, frameSize, duration, modifier, bar) | |
| local ethDst = arp.blockingLookup("198.18.1.1", 10) | |
| --TODO: error on timeout | |
| --wait for counter slave | |
| bar:wait() | |
| -- gen payload template suggested by RFC2544 | |
| local udpPayloadLen = frameSize - 46 | |
| local udpPayload = ffi.new("uint8_t[?]", udpPayloadLen) | |
| for i = 0, udpPayloadLen - 1 do | |
| udpPayload[i] = bit.band(i, 0xf) | |
| end | |
| local mem = memory.createMemPool(function(buf) | |
| local pkt = buf:getUdpPacket() | |
| pkt:fill{ | |
| pktLength = frameSize - 4, -- self sets all length headers fields in all used protocols, -4 for FCS | |
| ethSrc = queue, -- get the src mac from the device | |
| ethDst = ethDst, | |
| -- TODO: too slow with conditional -- eventual launch a second slave for self | |
| -- ethDst SHOULD be in 1% of the frames the hardware broadcast address | |
| -- for switches ethDst also SHOULD be randomized | |
| -- if ipDest is dynamical created it is overwritten | |
| -- does not affect performance, as self fill is done before any packet is sent | |
| ip4Src = "198.18.1.2", | |
| ip4Dst = "198.19.1.2", | |
| udpSrc = UDP_PORT, | |
| -- udpSrc will be set later as it varies | |
| } | |
| -- fill udp payload with prepared udp payload | |
| ffi.copy(pkt.payload, udpPayload, udpPayloadLen) | |
| end) | |
| local bufs = mem:bufArray() | |
| --local modifierFoo = utils.getPktModifierFunction(modifier, baseIp, wrapIp, baseEth, wrapEth) | |
| -- TODO: RFC2544 routing updates if router | |
| -- send learning frames: | |
| -- ARP for IP | |
| local sendBufs = function(bufs, port) | |
| -- allocate buffers from the mem pool and store them in self array | |
| bufs:alloc(frameSize - 4) | |
| for _, buf in ipairs(bufs) do | |
| local pkt = buf:getUdpPacket() | |
| -- set packet udp port | |
| pkt.udp:setDstPort(port) | |
| -- apply modifier like ip or mac randomisation to packet | |
| -- modifierFoo(pkt) | |
| end | |
| -- send packets | |
| bufs:offloadUdpChecksums() | |
| return queue:send(bufs) | |
| end | |
| -- warmup phase to wake up card | |
| local timer = timer:new(0.1) | |
| while timer:running() do | |
| sendBufs(bufs, port - 1) | |
| end | |
| -- benchmark phase | |
| timer:reset(duration) | |
| local totalSent = 0 | |
| while timer:running() do | |
| totalSent = totalSent + sendBufs(bufs, port) | |
| end | |
| return totalSent | |
| end | |
| function throughputCounterSlave(queue, port, frameSize, duration, bar) | |
| local bufs = memory.bufArray() | |
| local stats = {} | |
| bar:wait() | |
| local timer = timer:new(duration + 3) | |
| while timer:running() do | |
| local rx = queue:tryRecv(bufs, 1000) | |
| for i = 1, rx do | |
| local buf = bufs[i] | |
| local pkt = buf:getUdpPacket() | |
| local port = pkt.udp:getDstPort() | |
| stats[port] = (stats[port] or 0) + 1 | |
| end | |
| bufs:freeAll() | |
| end | |
| return stats[port] or 0 | |
| end | |
| function configure(parser) | |
| parser:description("Generates bidirectional CBR traffic with hardware rate control and measure latencies.") -- FIXME:Is this an accurate description? | |
| parser:argument("txport", "Device ID to transmit from."):convert(tonumber) | |
| parser:argument("rxport", "Device ID to receive on."):convert(tonumber) | |
| parser:option("-d --duration", "Test duration in seconds. Default: " .. 10):default(10):convert(tonumber) | |
| parser:option("-r --rate", "Transmit rate in Mbit/s. Default: " .. 10000):default(10000):convert(tonumber) | |
| parser:option("-n --numiterations", "Number test iterations. Default: " .. 1):default(1):convert(tonumber) | |
| end | |
| --for standalone benchmark | |
| function master(args) | |
| local txPort, rxPort = args.txport, args.rxport | |
| local rxDev, txDev | |
| if txPort == rxPort then | |
| -- sending and receiving from the same port | |
| txDev = device.config({port = txPort, rxQueues = 2, txQueues = 4}) | |
| rxDev = txDev | |
| else | |
| -- two different ports, different configuration | |
| txDev = device.config({port = txPort, rxQueues = 2, txQueues = 4}) | |
| rxDev = device.config({port = rxPort, rxQueues = 2, txQueues = 3}) | |
| end | |
| device.waitForLinks() | |
| if txPort == rxPort then | |
| dpdk.launchLua(arp.arpTask, { | |
| { | |
| txQueue = txDev:getTxQueue(0), | |
| rxQueue = txDev:getRxQueue(1), | |
| ips = {"198.18.1.2", "198.19.1.2"} | |
| } | |
| }) | |
| else | |
| dpdk.launchLua(arp.arpTask, { | |
| { | |
| txQueue = txDev:getTxQueue(0), | |
| rxQueue = txDev:getRxQueue(1), | |
| ips = {"198.18.1.2"} | |
| }, | |
| { | |
| txQueue = rxDev:getTxQueue(0), | |
| rxQueue = rxDev:getRxQueue(1), | |
| ips = {"198.19.1.2", "198.18.1.1"} | |
| } | |
| }) | |
| end | |
| local bench = benchmark() | |
| bench:init({ | |
| txQueues = {txDev:getTxQueue(1), txDev:getTxQueue(2), txDev:getTxQueue(3)}, | |
| rxQueues = {rxDev:getRxQueue(0)}, | |
| duration = args.duration, | |
| numIterations = args.numiterations, | |
| skipConf = true, | |
| }) | |
| print(bench:getCSVHeader()) | |
| local results = {} | |
| local FRAME_SIZES = {64, 128, 256, 512, 1024, 1280, 1518} | |
| for _, frameSize in ipairs(FRAME_SIZES) do | |
| local result = bench:bench(frameSize) | |
| -- save and report results | |
| table.insert(results, result) | |
| print(bench:resultToCSV(result)) | |
| end | |
| bench:toTikz("throughput", unpack(results)) | |
| end | |
| local mod = {} | |
| mod.__index = mod | |
| mod.benchmark = benchmark | |
| return mod |