MoonGen
 All Files Functions Variables Pages
stats.lua
Go to the documentation of this file.
1 ---------------------------------
2 --- @file stats.lua
3 --- @brief Stats ...
4 --- @todo TODO docu
5 ---------------------------------
6 
7 local mod = {}
8 
9 local dpdk = require "dpdk"
10 local device = require "device"
11 
12 function mod.average(data)
13  local sum = 0
14  for i, v in ipairs(data) do
15  sum = sum + v
16  end
17  return sum / #data
18 end
19 
20 function mod.median(data)
21  return mod.percentile(data, 50)
22 end
23 
24 function mod.percentile(data, p)
25  local sortedData = { }
26  for k, v in ipairs(data) do
27  sortedData[k] = v
28  end
29  table.sort(sortedData)
30  return data[math.ceil(#data * p / 100)]
31 end
32 
33 function mod.stdDev(data)
34  local avg = mod.average(data)
35  local sum = 0
36  for i, v in ipairs(data) do
37  sum = sum + (v - avg) ^ 2
38  end
39  return (sum / (#data - 1)) ^ 0.5
40 end
41 
42 function mod.sum(data)
43  local sum = 0
44  for i, v in ipairs(data) do
45  sum = sum + v
46  end
47  return sum
48 end
49 
50 function mod.addStats(data, ignoreFirstAndLast)
51  local copy = { }
52  if ignoreFirstAndLast then
53  for i = 2, #data - 1 do
54  copy[i - 1] = data[i]
55  end
56  else
57  for i = 1, #data do
58  copy[i] = data[i]
59  end
60  end
61  data.avg = mod.average(copy)
62  data.stdDev = mod.stdDev(copy)
63  data.median = mod.median(copy)
64  data.sum = mod.sum(copy)
65 end
66 
67 local function getPlainUpdate(direction)
68  return function(stats, file, total, mpps, mbit, wireMbit)
69  file:write(("[%s] %s %d packets, current rate %.2f Mpps, %.2f MBit/s, %.2f MBit/s wire rate.\n"):format(stats.name, direction, total, mpps, mbit, wireMbit))
70  file:flush()
71  end
72 end
73 
74 local function getPlainFinal(direction)
75  return function(stats, file)
76  file:write(("[%s] %s %d packets with %d bytes payload (including CRC).\n"):format(stats.name, direction, stats.total, stats.totalBytes))
77  file:write(("[%s] %s %f (StdDev %f) Mpps, %f (StdDev %f) MBit/s, %f (StdDev %f) MBit/s wire rate on average.\n"):format(
78  stats.name, direction,
79  stats.mpps.avg, stats.mpps.stdDev,
80  stats.mbit.avg, stats.mbit.stdDev,
81  stats.wireMbit.avg, stats.wireMbit.stdDev
82  ))
83  file:flush()
84  end
85 end
86 
87 local function getIniUpdate(direction)
88  return function(stats, file, total, mpps, mbit, wireMbit)
89  file:write(("%s%s,packets=%d,rate=%.2f,rate_mbits=%.2f,rate_wire=%.2f\n"):format(stats.name, direction, total, mpps, mbit, wireMbit))
90  file:flush()
91  end
92 end
93 
94 local function getIniFinal(direction)
95  return function(stats, file)
96  file:write(("%sTotal%s,packets=%d,bytes=%d,rate=%f,rate_dev=%f,rate_mbits=%f,rate_mbits_dev=%f,rate_wire=%f,rate_wire_dev=%f.\n"):format(
97  stats.name, direction,
98  stats.total, stats.totalBytes,
99  stats.mpps.avg, stats.mpps.stdDev,
100  stats.mbit.avg, stats.mbit.stdDev,
101  stats.wireMbit.avg, stats.wireMbit.stdDev
102  ))
103  file:flush()
104  end
105 end
106 local formatters = {}
107 formatters["plain"] = {
108  rxStatsInit = function() end, -- nothing for plain, machine-readable formats can print a header here
109  rxStatsUpdate = getPlainUpdate("Received"),
110  rxStatsFinal = getPlainFinal("Received"),
111 
112  txStatsInit = function() end,
113  txStatsUpdate = getPlainUpdate("Sent"),
114  txStatsFinal = getPlainFinal("Sent"),
115 }
116 
117 -- Formatter that does nothing
118 formatters["nil"] = {
119  rxStatsInit = function() end,
120  rxStatsUpdate = function() end,
121  rxStatsFinal = function() end,
122 
123  txStatsInit = function() end,
124  txStatsUpdate = function() end,
125  txStatsFinal = function () end,
126 }
127 
128 -- Ini-Like Formatting
129 formatters["ini"] = {
130  rxStatsInit = function() end,
131  rxStatsUpdate = getIniUpdate("Received"),
132  rxStatsFinal = getIniFinal("Received"),
133 
134  txStatsInit = function() end,
135  txStatsUpdate = getIniUpdate("Sent"),
136  txStatsFinal = getIniFinal("Sent"),
137 }
138 
139 formatters["CSV"] = formatters["plain"] -- TODO
140 
141 --- base constructor for rx and tx counters
142 local function newCounter(ctrType, name, dev, format, file)
143  format = format or "CSV"
144  file = file or io.stdout
145  local closeFile = false
146  if type(file) == "string" then
147  file = io.open("w+")
148  closeFile = true
149  end
150  if not formatters[format] then
151  error("unsupported output format " .. format)
152  end
153  return {
154  name = name,
155  dev = dev,
156  format = format,
157  file = file,
158  closeFile = closeFile,
159  total = 0,
160  totalBytes = 0,
161  current = 0,
162  currentBytes = 0,
163  mpps = {},
164  mbit = {},
165  wireMbit = {},
166  }
167 end
168 
169 -- base class for rx and tx counters
170 
171 local function printStats(self, statsType, event, ...)
172  local func = formatters[self.format][statsType .. event]
173  if func then
174  func(self, self.file, ...)
175  else
176  print("[Missing formatter for " .. self.format .. "]", self.name, statsType, event, ...)
177  end
178 end
179 
180 local function updateCounter(self, time, pkts, bytes, dontPrint)
181  if not self.lastUpdate then
182  -- first call, save current stats but do not print anything
183  self.total, self.totalBytes = pkts, bytes
184  self.lastTotal = self.total
185  self.lastTotalBytes = self.totalBytes
186  self.lastUpdate = time
187  self:print("Init")
188  return
189  end
190  local elapsed = time - self.lastUpdate
191  self.lastUpdate = time
192  self.total = self.total + pkts
193  self.totalBytes = self.totalBytes + bytes
194  local mpps = (self.total - self.lastTotal) / elapsed / 10^6
195  local mbit = (self.totalBytes - self.lastTotalBytes) / elapsed / 10^6 * 8
196  local wireRate = mbit + (mpps * 20 * 8)
197  if not dontPrint then
198  self:print("Update", self.total, mpps, mbit, wireRate)
199  end
200  table.insert(self.mpps, mpps)
201  table.insert(self.mbit, mbit)
202  table.insert(self.wireMbit, wireRate)
203  self.lastTotal = self.total
204  self.lastTotalBytes = self.totalBytes
205 end
206 
207 local function getStats(self)
208  mod.addStats(self.mpps, true)
209  mod.addStats(self.mbit, true)
210  mod.addStats(self.wireMbit, true)
211  return self.mpps, self.mbit, self.wireMbit, self.total, self.totalBytes
212 end
213 
214 local function finalizeCounter(self, sleep)
215  -- wait for any remaining packets to arrive/be sent if necessary
216  dpdk.sleepMillis(sleep)
217  -- last stats are probably complete nonsense, especially if sleep ~= 0
218  -- we just do this to get the correct totals
219  local pkts, bytes = self:getThroughput()
220  updateCounter(self, dpdk.getTime(), pkts, bytes, true)
221  mod.addStats(self.mpps, true)
222  mod.addStats(self.mbit, true)
223  mod.addStats(self.wireMbit, true)
224  self:print("Final")
225  if self.closeFile then
226  self.file:close()
227  end
228 end
229 
230 
231 local rxCounter = {} -- base class
232 local devRxCounter = setmetatable({}, rxCounter)
233 local pktRxCounter = setmetatable({}, rxCounter)
234 local manualRxCounter = setmetatable({}, rxCounter)
235 rxCounter.__index = rxCounter
236 devRxCounter.__index = devRxCounter
237 pktRxCounter.__index = pktRxCounter
238 manualRxCounter.__index = manualRxCounter
239 
240 --- Create a new rx counter using device statistics registers.
241 --- @param name the name of the counter, included in the output. defaults to the device name
242 --- @param dev the device to track
243 --- @param format the output format, "CSV" (default) and "plain" are currently supported
244 --- @param file the output file, defaults to standard out
245 function mod:newDevRxCounter(name, dev, format, file)
246  if type(name) == "table" then
247  return self:newDevRxCounter(nil, name, dev, format)
248  end
249  -- use device if queue objects are passed
250  dev = dev and dev.dev or dev
251  if type(dev) ~= "table" then
252  error("bad device")
253  end
254  name = name or tostring(dev):sub(2, -2) -- strip brackets as they are added by the 'plain' output again
255  local obj = newCounter("dev", name, dev, format, file)
256  obj.sleep = 100
257  return setmetatable(obj, devRxCounter)
258 end
259 
260 --- Create a new rx counter that can be updated by passing packet buffers to it.
261 --- @param name the name of the counter, included in the output
262 --- @param format the output format, "CSV" (default) and "plain" are currently supported
263 --- @param file the output file, defaults to standard out
264 function mod:newPktRxCounter(name, format, file)
265  local obj = newCounter("pkt", name, nil, format, file)
266  return setmetatable(obj, pktRxCounter)
267 end
268 
269 --- Create a new rx counter that has to be updated manually.
270 --- @param name the name of the counter, included in the output
271 --- @param format the output format, "CSV" (default) and "plain" are currently supported
272 --- @param file the output file, defaults to standard out
273 function mod:newManualRxCounter(name, format, file)
274  local obj = newCounter("manual", name, nil, format, file)
275  return setmetatable(obj, manualRxCounter)
276 end
277 
278 --- Base class
279 function rxCounter:finalize(sleep)
280  finalizeCounter(self, self.sleep or 0)
281 end
282 
283 function rxCounter:print(event, ...)
284  printStats(self, "rxStats", event, ...)
285 end
286 
287 function rxCounter:update()
288  local time = dpdk.getTime()
289  if self.lastUpdate and time <= self.lastUpdate + 1 then
290  return
291  end
292  local pkts, bytes = self:getThroughput()
293  updateCounter(self, time, pkts, bytes)
294 end
295 
296 function rxCounter:getStats()
297  -- force an update
298  local pkts, bytes = self:getThroughput()
299  updateCounter(self, dpdk.getTime(), pkts, bytes, true)
300  return getStats(self)
301 end
302 
303 --- Device-based counter
304 function devRxCounter:getThroughput()
305  return self.dev:getRxStats()
306 end
307 
308 --- Packet-based counter
309 function pktRxCounter:countPacket(buf)
310  self.current = self.current + 1
311  self.currentBytes = self.currentBytes + buf.pkt.pkt_len + 4 -- include CRC
312 end
313 
314 function pktRxCounter:getThroughput()
315  local pkts, bytes = self.current, self.currentBytes
316  self.current, self.currentBytes = 0, 0
317  return pkts, bytes
318 end
319 
320 
321 --- Manual rx counter
322 function manualRxCounter:update(pkts, bytes)
323  self.current = self.current + pkts
324  self.currentBytes = self.currentBytes + bytes
325  local time = dpdk.getTime()
326  if self.lastUpdate and time <= self.lastUpdate + 1 then
327  return
328  end
329  local pkts, bytes = self:getThroughput()
330  updateCounter(self, time, pkts, bytes)
331 end
332 
333 function manualRxCounter:getThroughput()
334  local pkts, bytes = self.current, self.currentBytes
335  self.current, self.currentBytes = 0, 0
336  return pkts, bytes
337 end
338 
339 function manualRxCounter:updateWithSize(pkts, size)
340  self.current = self.current + pkts
341  self.currentBytes = self.currentBytes + pkts * (size + 4)
342  local time = dpdk.getTime()
343  if self.lastUpdate and time <= self.lastUpdate + 1 then
344  return
345  end
346  local pkts, bytes = self:getThroughput()
347  updateCounter(self, time, pkts, bytes)
348 end
349 
350 
351 local txCounter = {} -- base class for tx counters
352 local devTxCounter = setmetatable({}, txCounter)
353 local pktTxCounter = setmetatable({}, txCounter)
354 local manualTxCounter = setmetatable({}, txCounter)
355 txCounter.__index = txCounter
356 devTxCounter.__index = devTxCounter
357 pktTxCounter.__index = pktTxCounter
358 manualTxCounter.__index = manualTxCounter
359 
360 --- Create a new tx counter using device statistics registers.
361 --- @param name the name of the counter, included in the output. defaults to the device name
362 --- @param dev the device to track
363 --- @param format the output format, "CSV" (default) and "plain" are currently supported
364 --- @param file the output file, defaults to standard out
365 function mod:newDevTxCounter(name, dev, format, file)
366  if type(name) == "table" then
367  return self:newDevTxCounter(nil, name, dev, format)
368  end
369  -- use device if queue objects are passed
370  dev = dev and dev.dev or dev
371  if type(dev) ~= "table" then
372  error("bad device")
373  end
374  name = name or tostring(dev):sub(2, -2) -- strip brackets as they are added by the 'plain' output again
375  local obj = newCounter("dev", name, dev, format, file)
376  obj.sleep = 50
377  return setmetatable(obj, devTxCounter)
378 end
379 
380 --- Create a new tx counter that can be updated by passing packet buffers to it.
381 --- @param name the name of the counter, included in the output
382 --- @param format the output format, "CSV" (default) and "plain" are currently supported
383 --- @param file the output file, defaults to standard out
384 function mod:newPktTxCounter(name, format, file)
385  local obj = newCounter("pkt", name, nil, format, file)
386  return setmetatable(obj, pktTxCounter)
387 end
388 
389 --- Create a new tx counter that has to be updated manually.
390 --- @param name the name of the counter, included in the output
391 --- @param format the output format, "CSV" (default) and "plain" are currently supported
392 --- @param file the output file, defaults to standard out
393 function mod:newManualTxCounter(name, format, file)
394  local obj = newCounter("manual", name, nil, format, file)
395  return setmetatable(obj, manualTxCounter)
396 end
397 
398 --- Base class
399 function txCounter:finalize(sleep)
400  finalizeCounter(self, self.sleep or 0)
401 end
402 
403 function txCounter:print(event, ...)
404  printStats(self, "txStats", event, ...)
405 end
406 
407 --- Device-based counter
408 function txCounter:update()
409  local time = dpdk.getTime()
410  if self.lastUpdate and time <= self.lastUpdate + 1 then
411  return
412  end
413  local pkts, bytes = self:getThroughput()
414  updateCounter(self, time, pkts, bytes)
415 end
416 
417 --- Get accumulated statistics.
418 --- Calculat the average throughput.
419 function txCounter:getStats()
420  -- force an update
421  local pkts, bytes = self:getThroughput()
422  updateCounter(self, dpdk.getTime(), pkts, bytes, true)
423  return getStats(self)
424 end
425 
426 function devTxCounter:getThroughput()
427  return self.dev:getTxStats()
428 end
429 
430 --- Packet-based counter
431 function pktTxCounter:countPacket(buf)
432  self.current = self.current + 1
433  self.currentBytes = self.currentBytes + buf.pkt.pkt_len + 4 -- include CRC
434 end
435 
436 function pktTxCounter:getThroughput()
437  local pkts, bytes = self.current, self.currentBytes
438  self.current, self.currentBytes = 0, 0
439  return pkts, bytes
440 end
441 
442 --- Manual rx counter
443 function manualTxCounter:update(pkts, bytes)
444  self.current = self.current + pkts
445  self.currentBytes = self.currentBytes + bytes
446  local time = dpdk.getTime()
447  if self.lastUpdate and time <= self.lastUpdate + 1 then
448  return
449  end
450  local pkts, bytes = self:getThroughput()
451  updateCounter(self, time, pkts, bytes)
452 end
453 
454 function manualTxCounter:updateWithSize(pkts, size)
455  self.current = self.current + pkts
456  self.currentBytes = self.currentBytes + pkts * (size + 4)
457  local time = dpdk.getTime()
458  if self.lastUpdate and time <= self.lastUpdate + 1 then
459  return
460  end
461  local pkts, bytes = self:getThroughput()
462  updateCounter(self, time, pkts, bytes)
463 end
464 
465 function manualTxCounter:getThroughput()
466  local pkts, bytes = self.current, self.currentBytes
467  self.current, self.currentBytes = 0, 0
468  return pkts, bytes
469 end
470 
471 return mod
472 
function mod sleepMillis(t)
Delay by t milliseconds.
function mod newDevTxCounter(name, dev, format, file)
Create a new tx counter using device statistics registers.
function mod newPktRxCounter(name, format, file)
Create a new rx counter that can be updated by passing packet buffers to it.
function pktRxCounter countPacket(buf)
Packet-based counter.
function rxCounter update()
Device-based counter.
function mod new(n)
function rxCounter finalize(sleep)
Base class.
function mod newManualRxCounter(name, format, file)
Create a new rx counter that has to be updated manually.
function mod newDevRxCounter(name, dev, format, file)
Create a new rx counter using device statistics registers.
local mod
high-level dpdk wrapper
Definition: dpdk.lua:6
local function newCounter(ctrType, name, dev, format, file)
base constructor for rx and tx counters
function mod newManualTxCounter(name, format, file)
Create a new tx counter that has to be updated manually.
function devRxCounter getThroughput()
Device-based counter.
function mod getTime()
gets the time in seconds
local pkt
Module for packets (rte_mbuf)
Definition: packet.lua:20
n
Create a new array of memory buffers (initialized to nil).
Definition: memory.lua:76
function rxCounter getStats()
Get accumulated statistics.
function dev getRxStats()
get the number of packets received since the last call to this function
function mod newPktTxCounter(name, format, file)
Create a new tx counter that can be updated by passing packet buffers to it.