From b34c3eee2bf5cb05a6f47c0ed9e318632c8dc534 Mon Sep 17 00:00:00 2001 From: Luke Gorrie Date: Mon, 22 Feb 2016 13:06:18 +0000 Subject: [PATCH 01/19] engine: Set shm path to "app/$name" Set the shared memory path (shm.path) to a private namespace for each app with prefix "app/$name". This means that apps can create shm objects such as counters and by default these will appear in a local namespace for that app. --- src/core/app.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/core/app.lua b/src/core/app.lua index 35a5305631..099fc3125a 100644 --- a/src/core/app.lua +++ b/src/core/app.lua @@ -5,6 +5,7 @@ local lib = require("core.lib") local link = require("core.link") local config = require("core.config") local timer = require("core.timer") +local shm = require("core.shm") local counter = require("core.counter") local zone = require("jit.zone") local ffi = require("ffi") @@ -65,6 +66,8 @@ end -- Run app:methodname() in protected mode (pcall). If it throws an -- error app will be marked as dead and restarted eventually. local function with_restart (app, method) + local oldshm = shm.path + shm.path = app.shmpath if use_restart then -- Run fn in protected mode using pcall. local status, err = pcall(method, app) @@ -75,6 +78,7 @@ local function with_restart (app, method) else method(app) end + shm.path = oldshm end -- Restart dead apps. @@ -162,7 +166,11 @@ function apply_config_actions (actions, conf) function ops.start (name) local class = conf.apps[name].class local arg = conf.apps[name].arg + local shmpath, shmorig = "app/"..name, shm.path + shm.path = shmpath local app = class:new(arg) + shm.path = shmorig + local shmpath = "app/"..name if type(app) ~= 'table' then error(("bad return value from app '%s' start() method: %s"):format( name, tostring(app))) @@ -171,6 +179,7 @@ function apply_config_actions (actions, conf) app.appname = name app.output = {} app.input = {} + app.shmpath = shmpath new_app_table[name] = app table.insert(new_app_array, app) app_name_to_index[name] = #new_app_array From 94ff2347175cc2e56153f0af3d16f9af86c2a367 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Tue, 12 Apr 2016 19:41:12 +0200 Subject: [PATCH 02/19] Amendments to #766: - Use "apps/" instead of "app/" for uniformity - Set shm path to "apps/$name" when calling `app:stop' too - Unlink "apps/$name" after `app:stop' using `shm.unlink' - Add a test case to core.app selftest --- src/core/app.lua | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/core/app.lua b/src/core/app.lua index 099fc3125a..5ff7d476eb 100644 --- a/src/core/app.lua +++ b/src/core/app.lua @@ -156,7 +156,13 @@ function apply_config_actions (actions, conf) -- Table of functions that execute config actions local ops = {} function ops.stop (name) - if app_table[name].stop then app_table[name]:stop() end + if app_table[name].stop then + local shmorig = shm.path + shm.path = app_table[name].shmpath + app_table[name]:stop() + shm.path = shmorig + shm.unlink(app_table[name].shmpath) + end end function ops.keep (name) new_app_table[name] = app_table[name] @@ -166,11 +172,10 @@ function apply_config_actions (actions, conf) function ops.start (name) local class = conf.apps[name].class local arg = conf.apps[name].arg - local shmpath, shmorig = "app/"..name, shm.path + local shmpath, shmorig = "apps/"..name, shm.path shm.path = shmpath local app = class:new(arg) shm.path = shmorig - local shmpath = "app/"..name if type(app) ~= 'table' then error(("bad return value from app '%s' start() method: %s"):format( name, tostring(app))) @@ -492,6 +497,34 @@ function selftest () assert(app_table.app3 == orig_app3) -- should be the same main({duration = 4, report = {showapps = true}}) assert(app_table.app3 ~= orig_app3) -- should be restarted + -- Test shm.path management + print("shm.path management") + local S = require("syscall") + local App4 = {zone="test"} + function App4:new () + local c = counter.open('test') + counter.set(c, 42) + counter.commit() + return setmetatable({test_counter = c}, + {__index = App4}) + end + function App4:pull () + assert(counter.read(self.test_counter) == 42, "Invalid counter value") + counter.add(self.test_counter) + end + function App4:stop () + assert(counter.read(self.test_counter) == 43, "Invalid counter value") + counter.delete('test') + end + local c_counter = config.new() + config.app(c_counter, "App4", App4) + configure(c_counter) + main({done = function () return app_table.App4.test_counter end}) + assert(S.stat(shm.root.."/"..shm.resolve("apps/App4/test")), + "Missing : apps/App4/test") + configure(config.new()) + assert(not S.stat(shm.root.."/"..shm.resolve("apps/App4")), + "Failed to unlink apps/App4") print("OK") end From fad0f43921d8f226f199f1cbaed02e5e6efc6d30 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Wed, 13 Apr 2016 16:44:14 +0200 Subject: [PATCH 03/19] core.counter: Qualify counter names using `shm.resolve'. --- src/core/counter.lua | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/core/counter.lua b/src/core/counter.lua index a479df4040..881cea5717 100644 --- a/src/core/counter.lua +++ b/src/core/counter.lua @@ -44,7 +44,8 @@ local private = {} local numbers = {} -- name -> number function open (name, readonly) - if numbers[name] then error("counter already opened: " .. name) end + local qname = shm.resolve(name) + if numbers[qname] then error("counter already opened: " .. qname) end local n = #public+1 if readonly then public[n] = shm.open(name, counter_t, readonly) @@ -53,13 +54,14 @@ function open (name, readonly) public[n] = shm.create(name, counter_t) private[n] = ffi.new(counter_t) end - numbers[name] = n + numbers[qname] = n return private[n] end function delete (name) - local number = numbers[name] - if not number then error("counter not found for deletion: " .. name) end + local qname = shm.resolve(name) + local number = numbers[qname] + if not number then error("counter not found for deletion: " .. qname) end -- Free shm object shm.unmap(public[number]) -- If we "own" the counter for writing then we unlink it too. @@ -67,7 +69,7 @@ function delete (name) shm.unlink(name) end -- Free local state - numbers[name] = false + numbers[qname] = false public[number] = false private[number] = false end From 7ed4ed0383bb24fd780fb6ecda3197f5c1106165 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Tue, 12 Apr 2016 20:21:33 +0200 Subject: [PATCH 04/19] snabb top: add `--app' option to print app counters. --- src/program/top/README | 8 ++++-- src/program/top/top.lua | 61 +++++++++++++++++++++++++++-------------- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/src/program/top/README b/src/program/top/README index 365c357326..66471f390c 100644 --- a/src/program/top/README +++ b/src/program/top/README @@ -3,10 +3,12 @@ Usage: -h, --help Print usage information. + -a, --app + Print counters of app by and exit. -Display realtime performance statistics for a running Snabb -instance with . If is not supplied and there is only one Snabb -Switch instance, top will connect to that instance. +Display realtime performance statistics for a running Snabb instance with +. If is not supplied and there is only one Snabb instance, top will +connect to that instance. The following global metrics will be displayed: diff --git a/src/program/top/top.lua b/src/program/top/top.lua index 6617027b1b..f66af6df0d 100644 --- a/src/program/top/top.lua +++ b/src/program/top/top.lua @@ -12,20 +12,57 @@ local histogram = require("core.histogram") local usage = require("program.top.README_inc") local long_opts = { - help = "h" + help = "h", app = "a" } function clearterm () io.write('\027[2J') end function run (args) local opt = {} + local app_name = nil function opt.h (arg) print(usage) main.exit(1) end - args = lib.dogetopt(args, opt, "h", long_opts) + function opt.a (arg) app_name = arg end + args = lib.dogetopt(args, opt, "ha:", long_opts) if #args > 1 then print(usage) main.exit(1) end local target_pid = args[1] - local instance_tree = "//"..(select_snabb_instance(target_pid)) + if app_name then app(select_snabb_instance(target_pid), app_name) + else top(select_snabb_instance(target_pid)) end +end + +function select_snabb_instance (pid) + local instances = shm.children("//") + if pid then + -- Try to use given pid + for _, instance in ipairs(instances) do + if instance == pid then return pid end + end + print("No such Snabb instance: "..pid) + elseif #instances == 2 then + -- Two means one is us, so we pick the other. + local own_pid = tostring(S.getpid()) + if instances[1] == own_pid then return instances[2] + else return instances[1] end + elseif #instances == 1 then print("No Snabb instance found.") + else print("Multple Snabb instances found. Select one.") end + os.exit(1) +end + +function app (instance_pid, app_name) + local app_tree = "//"..instance_pid.."/apps/"..app_name + local cnames = shm.children(app_tree) + table.sort(cnames, function (a, b) return a < b end) + for _, cname in ipairs(cnames) do + local cpath = app_tree.."/"..cname + local value = counter.read(counter.open(cpath, 'readonly')) + print_row({30, 30}, {cname, lib.comma_value(value)}) + counter.delete(cpath) + end +end + +function top (instance_pid) + local instance_tree = "//"..instance_pid local counters = open_counters(instance_tree) local configs = 0 local last_stats = nil @@ -48,24 +85,6 @@ function run (args) end end -function select_snabb_instance (pid) - local instances = shm.children("//") - if pid then - -- Try to use given pid - for _, instance in ipairs(instances) do - if instance == pid then return pid end - end - print("No such Snabb instance: "..pid) - elseif #instances == 2 then - -- Two means one is us, so we pick the other. - local own_pid = tostring(S.getpid()) - if instances[1] == own_pid then return instances[2] - else return instances[1] end - elseif #instances == 1 then print("No Snabb instance found.") - else print("Multple Snabb instances found. Select one.") end - os.exit(1) -end - function open_counters (tree) local counters = {} for _, name in ipairs({"configs", "breaths", "frees", "freebytes"}) do From eb9005b5c5ec4aa637b8a327f556029f8ef4cf29 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Wed, 13 Apr 2016 17:24:44 +0200 Subject: [PATCH 05/19] snabb top: unlink own shm tree to avoid clutter. --- src/program/top/top.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/program/top/top.lua b/src/program/top/top.lua index f66af6df0d..54308c63f3 100644 --- a/src/program/top/top.lua +++ b/src/program/top/top.lua @@ -29,6 +29,7 @@ function run (args) if app_name then app(select_snabb_instance(target_pid), app_name) else top(select_snabb_instance(target_pid)) end + ordered_exit(0) end function select_snabb_instance (pid) @@ -46,7 +47,12 @@ function select_snabb_instance (pid) else return instances[1] end elseif #instances == 1 then print("No Snabb instance found.") else print("Multple Snabb instances found. Select one.") end - os.exit(1) + ordered_exit(1) +end + +function ordered_exit (value) + shm.unlink("//"..S.getpid()) -- Unlink own shm tree to avoid clutter + os.exit(value) end function app (instance_pid, app_name) From 5fbe0d6fba0a1c0fd785102bb6da0a184f95dfd5 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Thu, 14 Apr 2016 16:39:55 +0200 Subject: [PATCH 06/19] vhost_user: Add RFC 7223 app counters. --- src/apps/vhost/vhost_user.lua | 36 +++++++++++++++++++++++++++++++++++ src/lib/virtio/net_device.lua | 4 ++++ 2 files changed, 40 insertions(+) diff --git a/src/apps/vhost/vhost_user.lua b/src/apps/vhost/vhost_user.lua index 7890b495f5..96e82780eb 100644 --- a/src/apps/vhost/vhost_user.lua +++ b/src/apps/vhost/vhost_user.lua @@ -13,7 +13,9 @@ local lib = require("core.lib") local link = require("core.link") local main = require("core.main") local memory = require("core.memory") +local counter = require("core.counter") local pci = require("lib.hardware.pci") +local ethernet = require("lib.protocol.ethernet") local net_device= require("lib.virtio.net_device") local timer = require("core.timer") local ffi = require("ffi") @@ -52,6 +54,15 @@ function VhostUser:new (args) else self.qemu_connect = self.client_connect end + -- initialize counters + self.counters = {} + for _, name in ipairs({'type', 'discontinuity-time', + 'in-octets', 'in-unicast', 'in-multicast', 'in-discards', + 'out-octets', 'out-unicast', 'out-multicast'}) do + self.counters[name] = counter.open(name) + end + counter.set(self.counters['type'], 0x1001) -- Virtual interface + counter.set(self.counters['discontinuity-time'], C.get_unix_time()) return self end @@ -68,6 +79,9 @@ function VhostUser:stop() self:free_mem_table() if self.link_down_proc then self.link_down_proc() end + + -- delete counters + for name, _ in pairs(self.counters) do counter.delete(name) end end function VhostUser:pull () @@ -86,6 +100,28 @@ function VhostUser:push () end end +function VhostUser:tx_callback (p) + counter.add(self.counters['out-octets'], packet.length(p)) + if ethernet:is_mcast(packet.data(p)) then + counter.add(self.counters['out-multicast']) + else + counter.add(self.counters['out-unicast']) + end +end + +function VhostUser:rx_callback (p) + counter.add(self.counters['in-octets'], packet.length(p)) + if ethernet:is_mcast(packet.data(p)) then + counter.add(self.counters['in-multicast']) + else + counter.add(self.counters['in-unicast']) + end +end + +function VhostUser:rxdrop_callback (p) + counter.add(self.counters['in-discards']) +end + -- Try to connect to QEMU. function VhostUser:client_connect () return C.vhost_user_connect(self.socket_path) diff --git a/src/lib/virtio/net_device.lua b/src/lib/virtio/net_device.lua index 1f9f321191..0cbdcd13c3 100644 --- a/src/lib/virtio/net_device.lua +++ b/src/lib/virtio/net_device.lua @@ -163,9 +163,11 @@ function VirtioNetDevice:rx_packet_end(header_id, total_size, rx_p) rx_p.length - self.rx_hdr_csum_start, self.rx_hdr_csum_offset) end + self.owner:rx_callback(rx_p) link.transmit(l, rx_p) else debug("droprx", "len", rx_p.length) + self.owner:rxdrop_callback(rx_p) packet.free(rx_p) end self.virtq[self.ring_id]:put_buffer(header_id, total_size) @@ -253,6 +255,7 @@ function VirtioNetDevice:tx_buffer_add(tx_p, addr, len) end function VirtioNetDevice:tx_packet_end(header_id, total_size, tx_p) + self.owner:tx_callback(tx_p) packet.free(tx_p) self.virtq[self.ring_id]:put_buffer(header_id, total_size) end @@ -313,6 +316,7 @@ end function VirtioNetDevice:tx_packet_end_mrg_rxbuf(header_id, total_size, tx_p) -- free the packet only when all its data is processed if self.tx.finished then + self.owner:tx_callback(tx_p) packet.free(tx_p) self.tx.p = nil self.tx.data_sent = nil From 8bb3215bf759f6c53af07c5c13afd2ea60003334 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Thu, 14 Apr 2016 17:57:09 +0200 Subject: [PATCH 07/19] Intel_app: Add RFC 7223 app counters. --- src/apps/intel/intel_app.lua | 45 ++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/apps/intel/intel_app.lua b/src/apps/intel/intel_app.lua index 80cace3d9f..f104fad553 100644 --- a/src/apps/intel/intel_app.lua +++ b/src/apps/intel/intel_app.lua @@ -6,8 +6,10 @@ local zone = require("jit.zone") local basic_apps = require("apps.basic.basic_apps") local ffi = require("ffi") local lib = require("core.lib") +local counter = require("core.counter") local pci = require("lib.hardware.pci") local register = require("lib.hardware.register") +local macaddress = require("lib.macaddress") local intel10g = require("apps.intel.intel10g") local freelist = require("core.freelist") local receive, transmit, full, empty = link.receive, link.transmit, link.full, link.empty @@ -36,6 +38,7 @@ end -- Create an Intel82599 App for the device with 'pciaddress'. function Intel82599:new (arg) local conf = config.parse_app_arg(arg) + local self = setmetatable({counters = {}}, Intel82599) if conf.vmdq then if devices[conf.pciaddr] == nil then @@ -45,12 +48,26 @@ function Intel82599:new (arg) local poolnum = firsthole(dev.vflist)-1 local vf = dev.pf:new_vf(poolnum) dev.vflist[poolnum+1] = vf - return setmetatable({dev=vf:open(conf)}, Intel82599) + self.dev = vf:open(conf) else - local dev = intel10g.new_sf(conf):open() - if not dev then return null end - return setmetatable({dev=dev, zone="intel"}, Intel82599) + self.dev = assert(intel10g.new_sf(conf):open(), "Can not open device.") + self.zone = "intel" end + + self.counters['type'] = counter.open('type') + self.counters['discontinuity-time'] = counter.open('discontinuity-time') + self.counters['in-octets'] = counter.open('in-octets') + self.counters['in-discards'] = counter.open('in-discards') + self.counters['out-octets'] = counter.open('out-octets') + counter.set(self.counters['type'], 0x1000) -- Hardware interface + counter.set(self.counters['discontinuity-time'], C.get_unix_time()) + if conf.vmdq then + self.counters['phys-address'] = counter.open('phys-address') + counter.set(self.counters['phys-address'], + macaddress:new(conf.macaddr).bits) + end + + return self end function Intel82599:stop() @@ -71,6 +88,9 @@ function Intel82599:stop() if close_pf then close_pf:close() end + + -- delete counters + for name, _ in pairs(self.counters) do counter.delete(name) end end @@ -79,6 +99,11 @@ function Intel82599:reconfig(arg) assert((not not self.dev.pf) == (not not conf.vmdq), "Can't reconfig from VMDQ to single-port or viceversa") self.dev:reconfig(conf) + + if conf.vmdq then + counter.set(self.counters['phys-address'], + macaddress:new(conf.macaddr).bits) + end end -- Allocate receive buffers from the given freelist. @@ -96,6 +121,13 @@ function Intel82599:pull () transmit(l, self.dev:receive()) end self:add_receive_buffers() + if self.dev.rxstats then + counter.set(self.counters['in-octets'], + bit.lshift(self.dev.pf.qs.QBRC_H[self.dev.rxstats]()+0LL, 32) + + self.dev.pf.qs.QBRC_L[self.dev.rxstats]()) + counter.set(self.counters['in-discards'], + self.dev.pf.qs.QPRDC[self.dev.rxstats]()) + end end function Intel82599:add_receive_buffers () @@ -116,6 +148,11 @@ function Intel82599:push () end end self.dev:sync_transmit() + if self.dev.txstats then + counter.set(self.counters['out-octets'], + bit.lshift(self.dev.pf.qs.QBTC_H[self.dev.txstats]()+0LL, 32) + + self.dev.pf.qs.QBTC_L[self.dev.txstats]()) + end end -- Report on relevant status and statistics. From 7a5547859a92ba935ed93ef1d144f1a9d8a1ab39 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Thu, 14 Apr 2016 18:13:51 +0200 Subject: [PATCH 08/19] snabb top: Add --link parameter to list link counters. --- src/program/top/README | 3 ++- src/program/top/top.lua | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/program/top/README b/src/program/top/README index 66471f390c..ae91f6e4c4 100644 --- a/src/program/top/README +++ b/src/program/top/README @@ -4,7 +4,8 @@ Usage: -h, --help Print usage information. -a, --app - Print counters of app by and exit. + -l, --link + Print counters of app or link by and exit. Display realtime performance statistics for a running Snabb instance with . If is not supplied and there is only one Snabb instance, top will diff --git a/src/program/top/top.lua b/src/program/top/top.lua index 54308c63f3..3b1f57a600 100644 --- a/src/program/top/top.lua +++ b/src/program/top/top.lua @@ -12,7 +12,7 @@ local histogram = require("core.histogram") local usage = require("program.top.README_inc") local long_opts = { - help = "h", app = "a" + help = "h", app = "a", link = "l" } function clearterm () io.write('\027[2J') end @@ -20,15 +20,18 @@ function clearterm () io.write('\027[2J') end function run (args) local opt = {} local app_name = nil + local link_name = nil function opt.h (arg) print(usage) main.exit(1) end function opt.a (arg) app_name = arg end - args = lib.dogetopt(args, opt, "ha:", long_opts) + function opt.l (arg) link_name = arg end + args = lib.dogetopt(args, opt, "ha:l:", long_opts) if #args > 1 then print(usage) main.exit(1) end - local target_pid = args[1] + local target_pid = select_snabb_instance(args[1]) - if app_name then app(select_snabb_instance(target_pid), app_name) - else top(select_snabb_instance(target_pid)) end + if app_name then list_counters("//"..target_pid.."/apps/"..app_name) + elseif link_name then list_counters("//"..target_pid.."/counters/"..link_name) + else top(target_pid) end ordered_exit(0) end @@ -55,12 +58,11 @@ function ordered_exit (value) os.exit(value) end -function app (instance_pid, app_name) - local app_tree = "//"..instance_pid.."/apps/"..app_name - local cnames = shm.children(app_tree) +function list_counters (path) + local cnames = shm.children(path) table.sort(cnames, function (a, b) return a < b end) for _, cname in ipairs(cnames) do - local cpath = app_tree.."/"..cname + local cpath = path.."/"..cname local value = counter.read(counter.open(cpath, 'readonly')) print_row({30, 30}, {cname, lib.comma_value(value)}) counter.delete(cpath) From dde5da2ac77cda91ed806b68399d7680c92004e3 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Thu, 14 Apr 2016 19:17:20 +0200 Subject: [PATCH 09/19] core.app: Put app counters under "counters/", update snabb top. --- src/core/app.lua | 10 +++++----- src/program/top/README | 5 ++--- src/program/top/top.lua | 18 ++++++++---------- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/core/app.lua b/src/core/app.lua index 352f5999f3..fa251390bb 100644 --- a/src/core/app.lua +++ b/src/core/app.lua @@ -175,7 +175,7 @@ function apply_config_actions (actions, conf) function ops.start (name) local class = conf.apps[name].class local arg = conf.apps[name].arg - local shmpath, shmorig = "apps/"..name, shm.path + local shmpath, shmorig = "counters/"..name, shm.path shm.path = shmpath local app = class:new(arg) shm.path = shmorig @@ -530,11 +530,11 @@ function selftest () config.app(c_counter, "App4", App4) configure(c_counter) main({done = function () return app_table.App4.test_counter end}) - assert(S.stat(shm.root.."/"..shm.resolve("apps/App4/test")), - "Missing : apps/App4/test") + assert(S.stat(shm.root.."/"..shm.resolve("counters/App4/test")), + "Missing : counters/App4/test") configure(config.new()) - assert(not S.stat(shm.root.."/"..shm.resolve("apps/App4")), - "Failed to unlink apps/App4") + assert(not S.stat(shm.root.."/"..shm.resolve("counters/App4")), + "Failed to unlink counters/App4") print("OK") end diff --git a/src/program/top/README b/src/program/top/README index ae91f6e4c4..ce815d6f89 100644 --- a/src/program/top/README +++ b/src/program/top/README @@ -3,9 +3,8 @@ Usage: -h, --help Print usage information. - -a, --app - -l, --link - Print counters of app or link by and exit. + -c, --counters + Print counters of object by and exit. Display realtime performance statistics for a running Snabb instance with . If is not supplied and there is only one Snabb instance, top will diff --git a/src/program/top/top.lua b/src/program/top/top.lua index 3b1f57a600..d69a999695 100644 --- a/src/program/top/top.lua +++ b/src/program/top/top.lua @@ -12,26 +12,23 @@ local histogram = require("core.histogram") local usage = require("program.top.README_inc") local long_opts = { - help = "h", app = "a", link = "l" + help = "h", counters = "c" } function clearterm () io.write('\027[2J') end function run (args) local opt = {} - local app_name = nil - local link_name = nil + local object = nil function opt.h (arg) print(usage) main.exit(1) end - function opt.a (arg) app_name = arg end - function opt.l (arg) link_name = arg end - args = lib.dogetopt(args, opt, "ha:l:", long_opts) + function opt.c (arg) object = arg end + args = lib.dogetopt(args, opt, "hc:", long_opts) if #args > 1 then print(usage) main.exit(1) end local target_pid = select_snabb_instance(args[1]) - if app_name then list_counters("//"..target_pid.."/apps/"..app_name) - elseif link_name then list_counters("//"..target_pid.."/counters/"..link_name) - else top(target_pid) end + if object then list_counters(target_pid, object) + else top(target_pid) end ordered_exit(0) end @@ -58,7 +55,8 @@ function ordered_exit (value) os.exit(value) end -function list_counters (path) +function list_counters (pid, object) + local path = "//"..pid.."/counters/"..object local cnames = shm.children(path) table.sort(cnames, function (a, b) return a < b end) for _, cname in ipairs(cnames) do From 924ff4e6e82880d296d6be9ddae4dda3aef4dff0 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Mon, 18 Apr 2016 19:30:15 +0200 Subject: [PATCH 10/19] lib.json: Import JSON4Lua 1.0.0, include encode functionality. --- src/lib/json.lua | 658 ++++++++++++++++++++++++++++++----------------- 1 file changed, 417 insertions(+), 241 deletions(-) diff --git a/src/lib/json.lua b/src/lib/json.lua index 86cbf85e43..9ab4abd38e 100644 --- a/src/lib/json.lua +++ b/src/lib/json.lua @@ -1,241 +1,417 @@ --- JSON4Lua: JSON encoding / decoding support for the Lua language. --- json Module. --- Author: Craig Mason-Jones --- Homepage: http://json.luaforge.net/ --- Version: 0.9.40 --- This module is released under the MIT License (MIT). --- --- NOTE: This is only the decode functionality ripped out from JSON4Lua. --- See: https://github.com/craigmj/json4lua - -module(..., package.seeall) - -local math = require('math') -local string = require("string") -local table = require("table") - -local base = _G - --- Private functions -local decode_scanArray -local decode_scanComment -local decode_scanConstant -local decode_scanNumber -local decode_scanObject -local decode_scanString -local decode_scanWhitespace - ---- Decodes a JSON string and returns the decoded value as a Lua data structure / value. --- @param s The string to scan. --- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1. --- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil, --- and the position of the first character after --- the scanned JSON object. -function decode(s, startPos) - startPos = startPos and startPos or 1 - startPos = decode_scanWhitespace(s,startPos) - base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']') - local curChar = string.sub(s,startPos,startPos) - -- Object - if curChar=='{' then - return decode_scanObject(s,startPos) - end - -- Array - if curChar=='[' then - return decode_scanArray(s,startPos) - end - -- Number - if string.find("+-0123456789.e", curChar, 1, true) then - return decode_scanNumber(s,startPos) - end - -- String - if curChar==[["]] or curChar==[[']] then - return decode_scanString(s,startPos) - end - if string.sub(s,startPos,startPos+1)=='/*' then - return decode(s, decode_scanComment(s,startPos)) - end - -- Otherwise, it must be a constant - return decode_scanConstant(s,startPos) -end - ---- The null function allows one to specify a null value in an associative array (which is otherwise --- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null } -function null() - return null -- so json.null() will also return null ;-) -end ------------------------------------------------------------------------------ --- Internal, PRIVATE functions. --- Following a Python-like convention, I have prefixed all these 'PRIVATE' --- functions with an underscore. ------------------------------------------------------------------------------ - ---- Scans an array from JSON into a Lua object --- startPos begins at the start of the array. --- Returns the array and the next starting position --- @param s The string being scanned. --- @param startPos The starting position for the scan. --- @return table, int The scanned array as a table, and the position of the next character to scan. -function decode_scanArray(s,startPos) - local array = {} -- The return value - local stringLen = string.len(s) - base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s ) - startPos = startPos + 1 - -- Infinite loop for array elements - repeat - startPos = decode_scanWhitespace(s,startPos) - base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.') - local curChar = string.sub(s,startPos,startPos) - if (curChar==']') then - return array, startPos+1 - end - if (curChar==',') then - startPos = decode_scanWhitespace(s,startPos+1) - end - base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.') - object, startPos = decode(s,startPos) - table.insert(array,object) - until false -end - ---- Scans a comment and discards the comment. --- Returns the position of the next character following the comment. --- @param string s The JSON string to scan. --- @param int startPos The starting position of the comment -function decode_scanComment(s, startPos) - base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos) - local endPos = string.find(s,'*/',startPos+2) - base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos) - return endPos+2 -end - ---- Scans for given constants: true, false or null --- Returns the appropriate Lua type, and the position of the next character to read. --- @param s The string being scanned. --- @param startPos The position in the string at which to start scanning. --- @return object, int The object (true, false or nil) and the position at which the next character should be --- scanned. -function decode_scanConstant(s, startPos) - local consts = { ["true"] = true, ["false"] = false, ["null"] = nil } - local constNames = {"true","false","null"} - - for i,k in base.pairs(constNames) do - --print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k) - if string.sub(s,startPos, startPos + string.len(k) -1 )==k then - return consts[k], startPos + string.len(k) - end - end - base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos) -end - ---- Scans a number from the JSON encoded string. --- (in fact, also is able to scan numeric +- eqns, which is not --- in the JSON spec.) --- Returns the number, and the position of the next character --- after the number. --- @param s The string being scanned. --- @param startPos The position at which to start scanning. --- @return number, int The extracted number and the position of the next character to scan. -function decode_scanNumber(s,startPos) - local endPos = startPos+1 - local stringLen = string.len(s) - local acceptableChars = "+-0123456789.e" - while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true) - and endPos<=stringLen - ) do - endPos = endPos + 1 - end - local stringValue = 'return ' .. string.sub(s,startPos, endPos-1) - local stringEval = base.loadstring(stringValue) - base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos) - return stringEval(), endPos -end - ---- Scans a JSON object into a Lua object. --- startPos begins at the start of the object. --- Returns the object and the next starting position. --- @param s The string being scanned. --- @param startPos The starting position of the scan. --- @return table, int The scanned object as a table and the position of the next character to scan. -function decode_scanObject(s,startPos) - local object = {} - local stringLen = string.len(s) - local key, value - base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s) - startPos = startPos + 1 - repeat - startPos = decode_scanWhitespace(s,startPos) - base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.') - local curChar = string.sub(s,startPos,startPos) - if (curChar=='}') then - return object,startPos+1 - end - if (curChar==',') then - startPos = decode_scanWhitespace(s,startPos+1) - end - base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.') - -- Scan the key - key, startPos = decode(s,startPos) - base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) - startPos = decode_scanWhitespace(s,startPos) - base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) - base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos) - startPos = decode_scanWhitespace(s,startPos+1) - base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) - value, startPos = decode(s,startPos) - object[key]=value - until false -- infinite loop while key-value pairs are found -end - ---- Scans a JSON string from the opening inverted comma or single quote to the --- end of the string. --- Returns the string extracted as a Lua string, --- and the position of the next non-string character --- (after the closing inverted comma or single quote). --- @param s The string being scanned. --- @param startPos The starting position of the scan. --- @return string, int The extracted string as a Lua string, and the next character to parse. -function decode_scanString(s,startPos) - base.assert(startPos, 'decode_scanString(..) called without start position') - local startChar = string.sub(s,startPos,startPos) - base.assert(startChar==[[']] or startChar==[["]],'decode_scanString called for a non-string') - local escaped = false - local endPos = startPos + 1 - local bEnded = false - local stringLen = string.len(s) - repeat - local curChar = string.sub(s,endPos,endPos) - -- Character escaping is only used to escape the string delimiters - if not escaped then - if curChar==[[\]] then - escaped = true - else - bEnded = curChar==startChar - end - else - -- If we're escaped, we accept the current character come what may - escaped = false - end - endPos = endPos + 1 - base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos) - until bEnded - local stringValue = 'return ' .. string.sub(s, startPos, endPos-1) - local stringEval = base.loadstring(stringValue) - base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos) - return stringEval(), endPos -end - ---- Scans a JSON string skipping all whitespace from the current start position. --- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached. --- @param s The string being scanned --- @param startPos The starting position where we should begin removing whitespace. --- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string --- was reached. -function decode_scanWhitespace(s,startPos) - local whitespace=" \n\r\t" - local stringLen = string.len(s) - while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do - startPos = startPos + 1 - end - return startPos -end +----------------------------------------------------------------------------- +-- JSON4Lua: JSON encoding / decoding support for the Lua language. +-- json Module. +-- Author: Craig Mason-Jones +-- Homepage: http://github.com/craigmj/json4lua/ +-- Version: 1.0.0 +-- This module is released under the MIT License (MIT). +-- Please see LICENCE.txt for details. +-- +-- USAGE: +-- This module exposes two functions: +-- json.encode(o) +-- Returns the table / string / boolean / number / nil / json.null value as a JSON-encoded string. +-- json.decode(json_string) +-- Returns a Lua object populated with the data encoded in the JSON string json_string. +-- +-- REQUIREMENTS: +-- compat-5.1 if using Lua 5.0 +-- +-- CHANGELOG +-- 0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix). +-- Fixed Lua 5.1 compatibility issues. +-- Introduced json.null to have null values in associative arrays. +-- json.encode() performance improvement (more than 50%) through table.concat rather than .. +-- Introduced decode ability to ignore /**/ comments in the JSON string. +-- 0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays. +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Imports and dependencies +----------------------------------------------------------------------------- +local math = require('math') +local string = require("string") +local table = require("table") + +----------------------------------------------------------------------------- +-- Module declaration +----------------------------------------------------------------------------- +local json = {} -- Public namespace +local json_private = {} -- Private namespace + +-- Public functions + +-- Private functions +local decode_scanArray +local decode_scanComment +local decode_scanConstant +local decode_scanNumber +local decode_scanObject +local decode_scanString +local decode_scanWhitespace +local encodeString +local isArray +local isEncodable + +----------------------------------------------------------------------------- +-- PUBLIC FUNCTIONS +----------------------------------------------------------------------------- +--- Encodes an arbitrary Lua object / variable. +-- @param v The Lua object / variable to be JSON encoded. +-- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode) +function json.encode (v) + -- Handle nil values + if v==nil then + return "null" + end + + local vtype = type(v) + + -- Handle strings + if vtype=='string' then + return '"' .. json_private.encodeString(v) .. '"' -- Need to handle encoding in string + end + + -- Handle booleans + if vtype=='number' or vtype=='boolean' then + return tostring(v) + end + + -- Handle tables + if vtype=='table' then + local rval = {} + -- Consider arrays separately + local bArray, maxCount = isArray(v) + if bArray then + for i = 1,maxCount do + table.insert(rval, json.encode(v[i])) + end + else -- An object, not an array + for i,j in pairs(v) do + if isEncodable(i) and isEncodable(j) then + table.insert(rval, '"' .. json_private.encodeString(i) .. '":' .. json.encode(j)) + end + end + end + if bArray then + return '[' .. table.concat(rval,',') ..']' + else + return '{' .. table.concat(rval,',') .. '}' + end + end + + -- Handle null values + if vtype=='function' and v==null then + return 'null' + end + + assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. tostring(v)) +end + + +--- Decodes a JSON string and returns the decoded value as a Lua data structure / value. +-- @param s The string to scan. +-- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1. +-- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil, +-- and the position of the first character after +-- the scanned JSON object. +function json.decode(s, startPos) + startPos = startPos and startPos or 1 + startPos = decode_scanWhitespace(s,startPos) + assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']') + local curChar = string.sub(s,startPos,startPos) + -- Object + if curChar=='{' then + return decode_scanObject(s,startPos) + end + -- Array + if curChar=='[' then + return decode_scanArray(s,startPos) + end + -- Number + if string.find("+-0123456789.e", curChar, 1, true) then + return decode_scanNumber(s,startPos) + end + -- String + if curChar==[["]] or curChar==[[']] then + return decode_scanString(s,startPos) + end + if string.sub(s,startPos,startPos+1)=='/*' then + return decode(s, decode_scanComment(s,startPos)) + end + -- Otherwise, it must be a constant + return decode_scanConstant(s,startPos) +end + +--- The null function allows one to specify a null value in an associative array (which is otherwise +-- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null } +function null() + return null -- so json.null() will also return null ;-) +end +----------------------------------------------------------------------------- +-- Internal, PRIVATE functions. +-- Following a Python-like convention, I have prefixed all these 'PRIVATE' +-- functions with an underscore. +----------------------------------------------------------------------------- + +--- Scans an array from JSON into a Lua object +-- startPos begins at the start of the array. +-- Returns the array and the next starting position +-- @param s The string being scanned. +-- @param startPos The starting position for the scan. +-- @return table, int The scanned array as a table, and the position of the next character to scan. +function decode_scanArray(s,startPos) + local array = {} -- The return value + local stringLen = string.len(s) + assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s ) + startPos = startPos + 1 + -- Infinite loop for array elements + repeat + startPos = decode_scanWhitespace(s,startPos) + assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.') + local curChar = string.sub(s,startPos,startPos) + if (curChar==']') then + return array, startPos+1 + end + if (curChar==',') then + startPos = decode_scanWhitespace(s,startPos+1) + end + assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.') + object, startPos = json.decode(s,startPos) + table.insert(array,object) + until false +end + +--- Scans a comment and discards the comment. +-- Returns the position of the next character following the comment. +-- @param string s The JSON string to scan. +-- @param int startPos The starting position of the comment +function decode_scanComment(s, startPos) + assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos) + local endPos = string.find(s,'*/',startPos+2) + assert(endPos~=nil, "Unterminated comment in string at " .. startPos) + return endPos+2 +end + +--- Scans for given constants: true, false or null +-- Returns the appropriate Lua type, and the position of the next character to read. +-- @param s The string being scanned. +-- @param startPos The position in the string at which to start scanning. +-- @return object, int The object (true, false or nil) and the position at which the next character should be +-- scanned. +function decode_scanConstant(s, startPos) + local consts = { ["true"] = true, ["false"] = false, ["null"] = nil } + local constNames = {"true","false","null"} + + for i,k in pairs(constNames) do + if string.sub(s,startPos, startPos + string.len(k) -1 )==k then + return consts[k], startPos + string.len(k) + end + end + assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos) +end + +--- Scans a number from the JSON encoded string. +-- (in fact, also is able to scan numeric +- eqns, which is not +-- in the JSON spec.) +-- Returns the number, and the position of the next character +-- after the number. +-- @param s The string being scanned. +-- @param startPos The position at which to start scanning. +-- @return number, int The extracted number and the position of the next character to scan. +function decode_scanNumber(s,startPos) + local endPos = startPos+1 + local stringLen = string.len(s) + local acceptableChars = "+-0123456789.e" + while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true) + and endPos<=stringLen + ) do + endPos = endPos + 1 + end + local stringValue = 'return ' .. string.sub(s,startPos, endPos-1) + local stringEval = loadstring(stringValue) + assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos) + return stringEval(), endPos +end + +--- Scans a JSON object into a Lua object. +-- startPos begins at the start of the object. +-- Returns the object and the next starting position. +-- @param s The string being scanned. +-- @param startPos The starting position of the scan. +-- @return table, int The scanned object as a table and the position of the next character to scan. +function decode_scanObject(s,startPos) + local object = {} + local stringLen = string.len(s) + local key, value + assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s) + startPos = startPos + 1 + repeat + startPos = decode_scanWhitespace(s,startPos) + assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.') + local curChar = string.sub(s,startPos,startPos) + if (curChar=='}') then + return object,startPos+1 + end + if (curChar==',') then + startPos = decode_scanWhitespace(s,startPos+1) + end + assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.') + -- Scan the key + key, startPos = json.decode(s,startPos) + assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + startPos = decode_scanWhitespace(s,startPos) + assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos) + startPos = decode_scanWhitespace(s,startPos+1) + assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + value, startPos = json.decode(s,startPos) + object[key]=value + until false -- infinite loop while key-value pairs are found +end + +-- START SoniEx2 +-- Initialize some things used by decode_scanString +-- You know, for efficiency +local escapeSequences = { + ["\\t"] = "\t", + ["\\f"] = "\f", + ["\\r"] = "\r", + ["\\n"] = "\n", + ["\\b"] = "\b" +} +setmetatable(escapeSequences, {__index = function(t,k) + -- skip "\" aka strip escape + return string.sub(k,2) +end}) +-- END SoniEx2 + +--- Scans a JSON string from the opening inverted comma or single quote to the +-- end of the string. +-- Returns the string extracted as a Lua string, +-- and the position of the next non-string character +-- (after the closing inverted comma or single quote). +-- @param s The string being scanned. +-- @param startPos The starting position of the scan. +-- @return string, int The extracted string as a Lua string, and the next character to parse. +function decode_scanString(s,startPos) + assert(startPos, 'decode_scanString(..) called without start position') + local startChar = string.sub(s,startPos,startPos) + -- START SoniEx2 + -- PS: I don't think single quotes are valid JSON + assert(startChar == [["]] or startChar == [[']],'decode_scanString called for a non-string') + --assert(startPos, "String decoding failed: missing closing " .. startChar .. " for string at position " .. oldStart) + local t = {} + local i,j = startPos,startPos + while string.find(s, startChar, j+1) ~= j+1 do + local oldj = j + i,j = string.find(s, "\\.", j+1) + local x,y = string.find(s, startChar, oldj+1) + if not i or x < i then + i,j = x,y-1 + end + table.insert(t, string.sub(s, oldj+1, i-1)) + if string.sub(s, i, j) == "\\u" then + local a = string.sub(s,j+1,j+4) + j = j + 4 + local n = tonumber(a, 16) + assert(n, "String decoding failed: bad Unicode escape " .. a .. " at position " .. i .. " : " .. j) + -- math.floor(x/2^y) == lazy right shift + -- a % 2^b == bitwise_and(a, (2^b)-1) + -- 64 = 2^6 + -- 4096 = 2^12 (or 2^6 * 2^6) + local x + if n < 0x80 then + x = string.char(n % 0x80) + elseif n < 0x800 then + -- [110x xxxx] [10xx xxxx] + x = string.char(0xC0 + (math.floor(n/64) % 0x20), 0x80 + (n % 0x40)) + else + -- [1110 xxxx] [10xx xxxx] [10xx xxxx] + x = string.char(0xE0 + (math.floor(n/4096) % 0x10), 0x80 + (math.floor(n/64) % 0x40), 0x80 + (n % 0x40)) + end + table.insert(t, x) + else + table.insert(t, escapeSequences[string.sub(s, i, j)]) + end + end + table.insert(t,string.sub(j, j+1)) + assert(string.find(s, startChar, j+1), "String decoding failed: missing closing " .. startChar .. " at position " .. j .. "(for string at position " .. startPos .. ")") + return table.concat(t,""), j+2 + -- END SoniEx2 +end + +--- Scans a JSON string skipping all whitespace from the current start position. +-- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached. +-- @param s The string being scanned +-- @param startPos The starting position where we should begin removing whitespace. +-- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string +-- was reached. +function decode_scanWhitespace(s,startPos) + local whitespace=" \n\r\t" + local stringLen = string.len(s) + while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do + startPos = startPos + 1 + end + return startPos +end + +--- Encodes a string to be JSON-compatible. +-- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-) +-- @param s The string to return as a JSON encoded (i.e. backquoted string) +-- @return The string appropriately escaped. + +local escapeList = { + ['"'] = '\\"', + ['\\'] = '\\\\', + ['/'] = '\\/', + ['\b'] = '\\b', + ['\f'] = '\\f', + ['\n'] = '\\n', + ['\r'] = '\\r', + ['\t'] = '\\t' +} + +function json_private.encodeString(s) + local s = tostring(s) + return s:gsub(".", function(c) return escapeList[c] end) -- SoniEx2: 5.0 compat +end + +-- Determines whether the given Lua type is an array or a table / dictionary. +-- We consider any table an array if it has indexes 1..n for its n items, and no +-- other data in the table. +-- I think this method is currently a little 'flaky', but can't think of a good way around it yet... +-- @param t The table to evaluate as an array +-- @return boolean, number True if the table can be represented as an array, false otherwise. If true, +-- the second returned value is the maximum +-- number of indexed elements in the array. +function isArray(t) + -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable + -- (with the possible exception of 'n') + local maxIndex = 0 + for k,v in pairs(t) do + if (type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair + if (not isEncodable(v)) then return false end -- All array elements must be encodable + maxIndex = math.max(maxIndex,k) + else + if (k=='n') then + if v ~= table.getn(t) then return false end -- False if n does not hold the number of elements + else -- Else of (k=='n') + if isEncodable(v) then return false end + end -- End of (k~='n') + end -- End of k,v not an indexed pair + end -- End of loop across all pairs + return true, maxIndex +end + +--- Determines whether the given Lua object / table / variable can be JSON encoded. The only +-- types that are JSON encodable are: string, boolean, number, nil, table and json.null. +-- In this implementation, all other types are ignored. +-- @param o The object to examine. +-- @return boolean True if the object should be JSON encoded, false if it should be ignored. +function isEncodable(o) + local t = type(o) + return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null) +end + +return json \ No newline at end of file From 8e34093991bb1c4579c84fd0569b54cafc762889 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Mon, 18 Apr 2016 21:05:00 +0200 Subject: [PATCH 11/19] lib.macaddress: Support numeric initialization; add method to get numeric representation. --- src/apps/intel/intel_app.lua | 4 ++-- src/lib/macaddress.lua | 26 +++++++++++++++++--------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/apps/intel/intel_app.lua b/src/apps/intel/intel_app.lua index f104fad553..a4224cf0d0 100644 --- a/src/apps/intel/intel_app.lua +++ b/src/apps/intel/intel_app.lua @@ -64,7 +64,7 @@ function Intel82599:new (arg) if conf.vmdq then self.counters['phys-address'] = counter.open('phys-address') counter.set(self.counters['phys-address'], - macaddress:new(conf.macaddr).bits) + macaddress:new(conf.macaddr):int()) end return self @@ -102,7 +102,7 @@ function Intel82599:reconfig(arg) if conf.vmdq then counter.set(self.counters['phys-address'], - macaddress:new(conf.macaddr).bits) + macaddress:new(conf.macaddr):int()) end end diff --git a/src/lib/macaddress.lua b/src/lib/macaddress.lua index e003fa1cc7..e55bdcfb3c 100644 --- a/src/lib/macaddress.lua +++ b/src/lib/macaddress.lua @@ -13,17 +13,21 @@ function mac_mt:new (m) return m end local macobj = mac_t() - local i = 0; - for b in m:gmatch('%x%x') do - if i == 6 then - -- avoid out of bound array index + if type(m) == 'string' then + local i = 0; + for b in m:gmatch('%x%x') do + if i == 6 then + -- avoid out of bound array index + return nil, "malformed MAC address: " .. m + end + macobj.bytes[i] = tonumber(b, 16) + i = i + 1 + end + if i < 6 then return nil, "malformed MAC address: " .. m end - macobj.bytes[i] = tonumber(b, 16) - i = i + 1 - end - if i < 6 then - return nil, "malformed MAC address: " .. m + else + macobj.bits = m end return macobj end @@ -38,6 +42,10 @@ function mac_mt.__eq (a, b) return a.bits == b.bits end +function mac_mt:int () + return self.bits +end + function mac_mt:subbits (i,j) local b = bit.rshift(self.bits, i) local mask = bit.bnot(bit.lshift(0xffffffffffffLL, j-i)) From 5f9efd280f683e394c677e6d606b2d4624831d5f Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Mon, 18 Apr 2016 21:05:34 +0200 Subject: [PATCH 12/19] =?UTF-8?q?core.link:=20Create=20=E2=80=9Cdiscontinu?= =?UTF-8?q?ity-time=E2=80=9D=20counters.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/link.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/link.lua b/src/core/link.lua index ed825e2606..34e2ffc942 100644 --- a/src/core/link.lua +++ b/src/core/link.lua @@ -28,6 +28,8 @@ function new (name) for _, c in ipairs(counternames) do r.stats[c] = counter.open("counters/"..name.."/"..c) end + counter.set(counter.open("counters/"..name.."/discontinuity-time"), + C.get_unix_time()) return r end @@ -35,6 +37,7 @@ function free (r, name) for _, c in ipairs(counternames) do counter.delete("counters/"..name.."/"..c) end + counter.delete("counters/"..name.."/discontinuity-time") shm.unmap(r) shm.unlink("links/"..name) end From 7b391482949cf10caac34871bb5890af80ab2f6b Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Tue, 19 Apr 2016 17:44:30 +0200 Subject: [PATCH 13/19] snabb top: add `--yang' option to print YANG model as JSON. --- src/program/top/README | 2 + src/program/top/top.lua | 109 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 103 insertions(+), 8 deletions(-) diff --git a/src/program/top/README b/src/program/top/README index ce815d6f89..9fa3500804 100644 --- a/src/program/top/README +++ b/src/program/top/README @@ -5,6 +5,8 @@ Usage: Print usage information. -c, --counters Print counters of object by and exit. + -y, --yang + Print YANG model encoded in JSON and exit. Display realtime performance statistics for a running Snabb instance with . If is not supplied and there is only one Snabb instance, top will diff --git a/src/program/top/top.lua b/src/program/top/top.lua index d69a999695..34ae80be6e 100644 --- a/src/program/top/top.lua +++ b/src/program/top/top.lua @@ -9,10 +9,12 @@ local shm = require("core.shm") local counter = require("core.counter") local S = require("syscall") local histogram = require("core.histogram") +local json = require("lib.json") +local macaddress = require("lib.macaddress") local usage = require("program.top.README_inc") local long_opts = { - help = "h", counters = "c" + help = "h", counters = "c", yang = "y" } function clearterm () io.write('\027[2J') end @@ -20,15 +22,18 @@ function clearterm () io.write('\027[2J') end function run (args) local opt = {} local object = nil + local yang = false function opt.h (arg) print(usage) main.exit(1) end function opt.c (arg) object = arg end - args = lib.dogetopt(args, opt, "hc:", long_opts) + function opt.y () yang = true end + args = lib.dogetopt(args, opt, "hc:y", long_opts) if #args > 1 then print(usage) main.exit(1) end local target_pid = select_snabb_instance(args[1]) - if object then list_counters(target_pid, object) - else top(target_pid) end + if object then list_counters(target_pid, object) + elseif yang then dump_yang(target_pid) + else top(target_pid) end ordered_exit(0) end @@ -55,18 +60,106 @@ function ordered_exit (value) os.exit(value) end +function read_counter (name, path) + if path then name = path.."/"..name end + local value = counter.read(counter.open(name, 'readonly')) + counter.delete(name) + return value +end + function list_counters (pid, object) local path = "//"..pid.."/counters/"..object local cnames = shm.children(path) table.sort(cnames, function (a, b) return a < b end) for _, cname in ipairs(cnames) do - local cpath = path.."/"..cname - local value = counter.read(counter.open(cpath, 'readonly')) - print_row({30, 30}, {cname, lib.comma_value(value)}) - counter.delete(cpath) + print_row({30, 30}, {cname, lib.comma_value(read_counter(cname, path))}) end end +function dump_yang (instance_pid) + local instance_tree = "//"..instance_pid + local interface_state = {} + local types = { [0x1000] = 'hardware', + [0x1001] = 'virtual', + [0x1002] = 'link' } + + for _, link in ipairs(shm.children(instance_tree.."/links")) do + local counters = instance_tree.."/counters/"..link + local statistics = {} + statistics['discontinuity-time'] = + totime(read_counter('discontinuity-time', counters)) + statistics['in-octets'] = + tohex64(read_counter('rxbytes', counters)) + statistics['out-octets'] = + tohex64(read_counter('txbytes', counters)) + statistics['out-discards'] = + truncate32(tonumber(read_counter('txdrop', counters))) + table.insert(interface_state, { name = link, + type = types[0x1002], + statistics = statistics }) + end + + for _, name in ipairs(shm.children(instance_tree.."/counters")) do + local counters = instance_tree.."/counters/"..name + local exists = {} + for _, c in ipairs(shm.children(counters)) do exists[c] = true end + local type = nil + if exists['type'] then + type = types[tonumber(read_counter('type', counters))] + end + if type then + local statistics = {} + statistics['discontinuity-time'] = + totime(read_counter('discontinuity-time', counters)) + for _, c in ipairs({'in-octets', 'in-unicast', + 'in-broadcast', 'in-multicast', + 'out-octets', 'out-unicast', + 'out-broadcast', 'out-multicast'}) do + if exists[c] then + statistics[c] = tohex64(read_counter(c, counters)) + end + end + for _, c in ipairs({'in-discards', 'out-discards'}) do + if exists[c] then + statistics[c] = truncate32(read_counter(c, counters)) + end + end + local interface = { name = name, type = type, statistics = statistics} + if exists['phys-address'] then + interface['phys-address'] = + tomac(read_counter('phys-address', counters)) + end + table.insert(interface_state, interface) + end + end + + print(json.encode({['interface-state'] = interface_state})) +end + +function tomac (n) + return tostring(macaddress:new(n)) +end + +function totime (n) + return os.date("!%FT%TZ", tonumber(n)) +end + +function truncate32 (n) + local box = ffi.new("union { uint64_t i64; uint32_t i32[2]; }") + box.i64 = n + if ffi.abi("le") then return tonumber(box.i32[0]) + elseif ffi.abi("be") then return tonumber(box.i32[1]) end +end + +function tohex64 (n) + local box = ffi.new("uint64_t[1]") + box[0] = n + local s = ffi.string(box, 8) + if ffi.abi("le") then s = s:reverse() end + return string.format("0x%02X%02X%02X%02X%02X%02X%02X%02X", s:byte(1, 8)) +end + + function top (instance_pid) local instance_tree = "//"..instance_pid local counters = open_counters(instance_tree) From 898474106031398f46b7e90dfe7cfd53403c661b Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Wed, 20 Apr 2016 13:43:36 +0200 Subject: [PATCH 14/19] snabb top --yang: Represent uint64_t as decimal string. --- src/program/top/top.lua | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/program/top/top.lua b/src/program/top/top.lua index 34ae80be6e..317c0a2a77 100644 --- a/src/program/top/top.lua +++ b/src/program/top/top.lua @@ -89,9 +89,9 @@ function dump_yang (instance_pid) statistics['discontinuity-time'] = totime(read_counter('discontinuity-time', counters)) statistics['in-octets'] = - tohex64(read_counter('rxbytes', counters)) + tostring64(read_counter('rxbytes', counters)) statistics['out-octets'] = - tohex64(read_counter('txbytes', counters)) + tostring64(read_counter('txbytes', counters)) statistics['out-discards'] = truncate32(tonumber(read_counter('txdrop', counters))) table.insert(interface_state, { name = link, @@ -116,7 +116,7 @@ function dump_yang (instance_pid) 'out-octets', 'out-unicast', 'out-broadcast', 'out-multicast'}) do if exists[c] then - statistics[c] = tohex64(read_counter(c, counters)) + statistics[c] = tostring64(read_counter(c, counters)) end end for _, c in ipairs({'in-discards', 'out-discards'}) do @@ -151,12 +151,10 @@ function truncate32 (n) elseif ffi.abi("be") then return tonumber(box.i32[1]) end end -function tohex64 (n) - local box = ffi.new("uint64_t[1]") - box[0] = n - local s = ffi.string(box, 8) - if ffi.abi("le") then s = s:reverse() end - return string.format("0x%02X%02X%02X%02X%02X%02X%02X%02X", s:byte(1, 8)) +function tostring64 (n) + assert(ffi.istype('uint64_t', n)) + local s = tostring(n) + return s:sub(1, #s - 3) end From ee00d167b2aa437861f5ee04804e80755053ef36 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Thu, 28 Apr 2016 18:26:55 +0200 Subject: [PATCH 15/19] [core.lib] Generalize `timer' to optionally accept 'repeating' option and support injecting a function to determine the current time. --- src/README.md | 14 +++++++++++--- src/core/lib.lua | 21 +++++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/README.md b/src/README.md index b406eeabd9..4cfbb73dd7 100644 --- a/src/README.md +++ b/src/README.md @@ -543,10 +543,18 @@ Returns a table that acts as a bounds checked wrapper around a C array of ctype and the caller must ensure that the allocated memory region at *base*/*offset* is at least `sizeof(type)*size` bytes long. -— Function **lib.timer** *s* +— Function **lib.timer** *duration*, *mode*, *timefun* + +Returns a closure that will return `false` until *duration* has elapsed. If +*mode* is `'repeating'` the timer will reset itself after returning `true`, +thus implementing an interval timer. *Timefun* is used to get a monotonic time. +*Timefun* defaults to `C.get_time_ns`. + +The “deadline” for a given *duration* is computed by adding *duration* to the +result of calling *timefun*, and is saved in the resulting closure. A +*duration* has elapsed when its deadline is less than or equal the value +obtained using *timefun* when calling the closure. -Returns a function that accepts no parameters and acts as a predicate to -test if *ns* nanoseconds have elapsed. — Function **lib.waitfor** *condition* diff --git a/src/core/lib.lua b/src/core/lib.lua index b6223cc8e6..6e37a90964 100644 --- a/src/core/lib.lua +++ b/src/core/lib.lua @@ -246,10 +246,23 @@ function bounds_checked (type, base, offset, size) return wrap(ffi.cast(tptr, ffi.cast("uint8_t *", base) + offset)) end --- Return a function that will return false until NS nanoseconds have elapsed. -function timer (ns) - local deadline = C.get_time_ns() + ns - return function () return C.get_time_ns() >= deadline end +-- Return a function that will return false until duration has elapsed. +-- If mode is 'repeating' the timer will reset itself after returning true, +-- thus implementing an interval timer. Timefun defaults to `C.get_time_ns'. +function timer (duration, mode, timefun) + timefun = timefun or C.get_time_ns + local deadline = timefun() + duration + local function oneshot () + return timefun() >= deadline + end + local function repeating () + if timefun() >= deadline then + deadline = deadline + duration + return true + else return false end + end + if mode == 'repeating' then return repeating + else return oneshot end end -- Loop until the function `condition` returns true. From 45490b8e162c96e16f739672c951d9b92f896fd5 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Thu, 28 Apr 2016 19:30:55 +0200 Subject: [PATCH 16/19] Revert "Intel_app: Add RFC 7223 app counters." This reverts commit 8bb3215bf759f6c53af07c5c13afd2ea60003334. --- src/apps/intel/intel_app.lua | 45 ++++-------------------------------- 1 file changed, 4 insertions(+), 41 deletions(-) diff --git a/src/apps/intel/intel_app.lua b/src/apps/intel/intel_app.lua index a4224cf0d0..80cace3d9f 100644 --- a/src/apps/intel/intel_app.lua +++ b/src/apps/intel/intel_app.lua @@ -6,10 +6,8 @@ local zone = require("jit.zone") local basic_apps = require("apps.basic.basic_apps") local ffi = require("ffi") local lib = require("core.lib") -local counter = require("core.counter") local pci = require("lib.hardware.pci") local register = require("lib.hardware.register") -local macaddress = require("lib.macaddress") local intel10g = require("apps.intel.intel10g") local freelist = require("core.freelist") local receive, transmit, full, empty = link.receive, link.transmit, link.full, link.empty @@ -38,7 +36,6 @@ end -- Create an Intel82599 App for the device with 'pciaddress'. function Intel82599:new (arg) local conf = config.parse_app_arg(arg) - local self = setmetatable({counters = {}}, Intel82599) if conf.vmdq then if devices[conf.pciaddr] == nil then @@ -48,26 +45,12 @@ function Intel82599:new (arg) local poolnum = firsthole(dev.vflist)-1 local vf = dev.pf:new_vf(poolnum) dev.vflist[poolnum+1] = vf - self.dev = vf:open(conf) + return setmetatable({dev=vf:open(conf)}, Intel82599) else - self.dev = assert(intel10g.new_sf(conf):open(), "Can not open device.") - self.zone = "intel" + local dev = intel10g.new_sf(conf):open() + if not dev then return null end + return setmetatable({dev=dev, zone="intel"}, Intel82599) end - - self.counters['type'] = counter.open('type') - self.counters['discontinuity-time'] = counter.open('discontinuity-time') - self.counters['in-octets'] = counter.open('in-octets') - self.counters['in-discards'] = counter.open('in-discards') - self.counters['out-octets'] = counter.open('out-octets') - counter.set(self.counters['type'], 0x1000) -- Hardware interface - counter.set(self.counters['discontinuity-time'], C.get_unix_time()) - if conf.vmdq then - self.counters['phys-address'] = counter.open('phys-address') - counter.set(self.counters['phys-address'], - macaddress:new(conf.macaddr):int()) - end - - return self end function Intel82599:stop() @@ -88,9 +71,6 @@ function Intel82599:stop() if close_pf then close_pf:close() end - - -- delete counters - for name, _ in pairs(self.counters) do counter.delete(name) end end @@ -99,11 +79,6 @@ function Intel82599:reconfig(arg) assert((not not self.dev.pf) == (not not conf.vmdq), "Can't reconfig from VMDQ to single-port or viceversa") self.dev:reconfig(conf) - - if conf.vmdq then - counter.set(self.counters['phys-address'], - macaddress:new(conf.macaddr):int()) - end end -- Allocate receive buffers from the given freelist. @@ -121,13 +96,6 @@ function Intel82599:pull () transmit(l, self.dev:receive()) end self:add_receive_buffers() - if self.dev.rxstats then - counter.set(self.counters['in-octets'], - bit.lshift(self.dev.pf.qs.QBRC_H[self.dev.rxstats]()+0LL, 32) - + self.dev.pf.qs.QBRC_L[self.dev.rxstats]()) - counter.set(self.counters['in-discards'], - self.dev.pf.qs.QPRDC[self.dev.rxstats]()) - end end function Intel82599:add_receive_buffers () @@ -148,11 +116,6 @@ function Intel82599:push () end end self.dev:sync_transmit() - if self.dev.txstats then - counter.set(self.counters['out-octets'], - bit.lshift(self.dev.pf.qs.QBTC_H[self.dev.txstats]()+0LL, 32) - + self.dev.pf.qs.QBTC_L[self.dev.txstats]()) - end end -- Report on relevant status and statistics. From f0ed10b70781505e38809acd189744067993f516 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Thu, 28 Apr 2016 20:34:23 +0200 Subject: [PATCH 17/19] intel_app: expose per-pciaddress statistics in `counters/'. --- src/apps/intel/intel_app.lua | 69 +++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/src/apps/intel/intel_app.lua b/src/apps/intel/intel_app.lua index 80cace3d9f..0fff2dfc5e 100644 --- a/src/apps/intel/intel_app.lua +++ b/src/apps/intel/intel_app.lua @@ -6,6 +6,8 @@ local zone = require("jit.zone") local basic_apps = require("apps.basic.basic_apps") local ffi = require("ffi") local lib = require("core.lib") +local shm = require("core.shm") +local counter = require("core.counter") local pci = require("lib.hardware.pci") local register = require("lib.hardware.register") local intel10g = require("apps.intel.intel10g") @@ -36,21 +38,49 @@ end -- Create an Intel82599 App for the device with 'pciaddress'. function Intel82599:new (arg) local conf = config.parse_app_arg(arg) + local self = {} if conf.vmdq then if devices[conf.pciaddr] == nil then - devices[conf.pciaddr] = {pf=intel10g.new_pf(conf):open(), vflist={}} + local pf = intel10g.new_pf(conf):open() + devices[conf.pciaddr] = {pf=pf, vflist={}, stats={register=pf.s}} end local dev = devices[conf.pciaddr] local poolnum = firsthole(dev.vflist)-1 local vf = dev.pf:new_vf(poolnum) dev.vflist[poolnum+1] = vf - return setmetatable({dev=vf:open(conf)}, Intel82599) + self.dev = vf:open(conf) + self.stats = devices[conf.pciaddr].stats else - local dev = intel10g.new_sf(conf):open() - if not dev then return null end - return setmetatable({dev=dev, zone="intel"}, Intel82599) + self.dev = assert(intel10g.new_sf(conf):open(), "Can not open device.") + self.stats = { register = self.dev.s } + self.zone = "intel" end + if not self.stats.counters then + local counters = {} + local path = "/counters/"..conf.pciaddr.."/" + counters['type'] = counter.open(path..'type') + counters['discontinuity-time'] = counter.open(path..'discontinuity-time') + counters['in-octets'] = counter.open(path..'in-octets') + counters['in-multicast'] = counter.open(path..'in-multicast') + counters['in-broadcast'] = counter.open(path..'in-broadcast') + counters['in-discards'] = counter.open(path..'in-discards') + counters['out-octets'] = counter.open(path..'out-octets') + counters['out-multicast'] = counter.open(path..'out-multicast') + counters['out-broadcast'] = counter.open(path..'out-broadcast') + counters['out-discards'] = counter.open(path..'out-discards') + counter.set(counters['type'], 0x1000) -- Hardware interface + counter.set(counters['discontinuity-time'], C.get_unix_time()) + if not conf.vmdq and conf.macaddr then + counters['phys-address'] = counter.open(path..'phys-address') + counter.set(counters['phys-address'], + macaddress:new(conf.macaddr):int()) + end + self.stats.counters = counters + self.stats.path = path + self.stats.sync_timer = lib.timer(0.001, 'repeating', engine.now) + end + return setmetatable(self, Intel82599) end function Intel82599:stop() @@ -71,6 +101,12 @@ function Intel82599:stop() if close_pf then close_pf:close() end + if not self.dev.pf or close_pf then + for name, _ in pairs(self.stats.counters) do + counter.delete(self.stats.path..name) + end + shm.unlink(self.stats.path) + end end @@ -79,6 +115,11 @@ function Intel82599:reconfig(arg) assert((not not self.dev.pf) == (not not conf.vmdq), "Can't reconfig from VMDQ to single-port or viceversa") self.dev:reconfig(conf) + + if not self.dev.pf and conf.macaddr then + counter.set(self.stats.counters['phys-address'], + macaddress:new(conf.macaddr):int()) + end end -- Allocate receive buffers from the given freelist. @@ -96,6 +137,9 @@ function Intel82599:pull () transmit(l, self.dev:receive()) end self:add_receive_buffers() + if self.stats.sync_timer() then + self:sync_stats() + end end function Intel82599:add_receive_buffers () @@ -105,6 +149,21 @@ function Intel82599:add_receive_buffers () end end +-- Synchronize self.stats.register a and self.stats.counters. +function Intel82599:sync_stats () + --- XXX - it is questionable if I choose the right register to counter + --- mapping + local counters, register = self.stats.counters, self.stats.register + counter.set(counters['in-octets'], register.GORC64()) + counter.set(counters['in-multicast'], register.MPRC()) + counter.set(counters['in-broadcast'], register.BPRC()) + counter.set(counters['in-discards'], register.TPR() - register.GPRC()) + counter.set(counters['out-octets'], register.GOTC64()) + counter.set(counters['out-multicast'], register.MPTC()) + counter.set(counters['out-broadcast'], register.BPTC()) + counter.set(counters['out-discards'], register.TPT() - register.GPTC()) +end + -- Push packets from our 'rx' link onto the network. function Intel82599:push () local l = self.input.rx From c186591e312fb90c4c2d847be01255fb7dba0c8b Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Mon, 25 Apr 2016 16:25:06 +0200 Subject: [PATCH 18/19] lib.protocol.ethernet: Add n_mcast, branch-free Multicast predicate. --- src/apps/vhost/vhost_user.lua | 16 ++++++---------- src/lib/protocol/README.md | 9 +++++++++ src/lib/protocol/ethernet.lua | 7 ++++++- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/apps/vhost/vhost_user.lua b/src/apps/vhost/vhost_user.lua index 96e82780eb..16936709bf 100644 --- a/src/apps/vhost/vhost_user.lua +++ b/src/apps/vhost/vhost_user.lua @@ -102,20 +102,16 @@ end function VhostUser:tx_callback (p) counter.add(self.counters['out-octets'], packet.length(p)) - if ethernet:is_mcast(packet.data(p)) then - counter.add(self.counters['out-multicast']) - else - counter.add(self.counters['out-unicast']) - end + local mcast = ethernet:n_mcast(packet.data(p)) + counter.add(self.counters['out-multicast'], mcast) + counter.add(self.counters['out-unicast'], 1 - mcast) end function VhostUser:rx_callback (p) counter.add(self.counters['in-octets'], packet.length(p)) - if ethernet:is_mcast(packet.data(p)) then - counter.add(self.counters['in-multicast']) - else - counter.add(self.counters['in-unicast']) - end + local mcast = ethernet:n_mcast(packet.data(p)) + counter.add(self.counters['in-multicast'], mcast) + counter.add(self.counters['in-unicast'], 1 - mcast) end function VhostUser:rxdrop_callback (p) diff --git a/src/lib/protocol/README.md b/src/lib/protocol/README.md index 6f239687d7..4a13483a4b 100644 --- a/src/lib/protocol/README.md +++ b/src/lib/protocol/README.md @@ -98,6 +98,15 @@ Returns the binary representation of MAC address denoted by *string*. Returns the string representation of *mac* address. +— Function **ethernet:is_mcast** *mac* + +Returns a true value if *mac* address denotes a [Multicast address](https://en.wikipedia.org/wiki/Multicast_address#Ethernet). + +— Function **ethernet:n_mcast** *mac* + +Returns 1 if *mac* address denotes a [Multicast address](https://en.wikipedia.org/wiki/Multicast_address#Ethernet) +and 0 otherwise. + — Function **ethernet:ipv6_mcast** *ip* Returns the MAC address for IPv6 multicast *ip* as defined by RFC2464, diff --git a/src/lib/protocol/ethernet.lua b/src/lib/protocol/ethernet.lua index 0a55c20f1b..b5abed8d31 100644 --- a/src/lib/protocol/ethernet.lua +++ b/src/lib/protocol/ethernet.lua @@ -76,9 +76,14 @@ function ethernet:ipv6_mcast(ip) return result end +-- Return 1 if MAC address has its group bit set and 0 otherwise +function ethernet:n_mcast (addr) + return band(addr[0], 0x01) +end + -- Check whether a MAC address has its group bit set function ethernet:is_mcast (addr) - return band(addr[0], 0x01) ~= 0 + return ethernet:n_mcast(addr) ~= 0 end -- Instance methods From b09e843465686fd1afad4c6c48a248f33de6b07e Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Fri, 27 May 2016 15:24:11 +0200 Subject: [PATCH 19/19] Fix for f0ed10b: require macaddress module. --- src/apps/intel/intel_app.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/src/apps/intel/intel_app.lua b/src/apps/intel/intel_app.lua index f5773d66f3..2645e2d22c 100644 --- a/src/apps/intel/intel_app.lua +++ b/src/apps/intel/intel_app.lua @@ -10,6 +10,7 @@ local shm = require("core.shm") local counter = require("core.counter") local pci = require("lib.hardware.pci") local register = require("lib.hardware.register") +local macaddress = require("lib.macaddress") local intel10g = require("apps.intel.intel10g") local receive, transmit, full, empty = link.receive, link.transmit, link.full, link.empty Intel82599 = {}