diff --git a/src/README.md b/src/README.md index 911ebf501f..1a5eb2a993 100644 --- a/src/README.md +++ b/src/README.md @@ -316,6 +316,10 @@ not been transmitted or freed. The number of allocatable packets is limited by the size of the underlying "freelist", e.g. a pool of unused packet objects from and to which packets are allocated and freed. +— Variable **packet.max_payload** + +Maximum payload length for packets. Read-only. + — Function **packet.allocate** Returns a new empty packet. An an error is raised if there are no packets @@ -549,10 +553,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/apps/intel/intel10g.lua b/src/apps/intel/intel10g.lua index ba8a3c14b4..5e2ffc564c 100644 --- a/src/apps/intel/intel10g.lua +++ b/src/apps/intel/intel10g.lua @@ -6,7 +6,8 @@ --- affordable (~$400) network cards made by Intel and others. --- --- You will need to familiarize yourself with the excellent [data ---- sheet]() to understand this module. +--- sheet](http://www.intel.com/content/dam/www/public/us/en/documents/datasheets/82599-10-gbe-controller-datasheet.pdf) +--- to understand this module. module(...,package.seeall) @@ -17,7 +18,6 @@ local pci = require("lib.hardware.pci") local register = require("lib.hardware.register") local index_set = require("lib.index_set") local macaddress = require("lib.macaddress") -local mib = require("lib.ipc.shmem.mib") local timer = require("core.timer") local bits, bitset = lib.bits, lib.bitset @@ -49,10 +49,6 @@ local default = { -- The default MTU allows for an IP packet of a total size of 9000 -- bytes without VLAN tagging. mtu = 9014, - - snmp = { - status_timer = 5, -- Interval for IF status check and MIB update - } } local function pass (...) return ... end @@ -79,7 +75,6 @@ function new_sf (conf) rdh = 0, -- Cache of receive head (RDH) register rdt = 0, -- Cache of receive tail (RDT) register rxnext = 0, -- Index of next buffer to receive - snmp = conf.snmp, } return setmetatable(dev, M_sf) end @@ -118,9 +113,6 @@ end --- See data sheet section 4.6.3 "Initialization Sequence." function M_sf:init () - if self.snmp then - self:init_snmp() - end self:init_dma_memory() self.redos = 0 @@ -190,156 +182,6 @@ function M_sf:ingress_packet_drops () return self.qs.QPRDC[0]() end -function M_sf:init_snmp () - -- Rudimentary population of a row in the ifTable MIB. Allocation - -- of the ifIndex is delegated to the SNMP agent via the name of - -- the interface in ifDescr (currently the PCI address). - local ifTable = mib:new({ directory = self.snmp.directory or nil, - filename = self.pciaddress }) - self.snmp.ifTable = ifTable - -- ifTable - ifTable:register('ifDescr', 'OctetStr', self.pciaddress) - ifTable:register('ifType', 'Integer32', 6) -- ethernetCsmacd - ifTable:register('ifMtu', 'Integer32', self.mtu) - ifTable:register('ifSpeed', 'Gauge32', 10000000) - - -- After a reset of the NIC, the "native" MAC address is copied to - -- the receive address register #0 from the EEPROM - local ral, rah = self.r.RAL[0](), self.r.RAH[0]() - assert(bit.band(rah, bits({ AV = 31 })) == bits({ AV = 31 }), - "MAC address on "..self.pciaddress.." is not valid ") - local mac = ffi.new("struct { uint32_t lo; uint16_t hi; }") - mac.lo = ral - mac.hi = bit.band(rah, 0xFFFF) - ifTable:register('ifPhysAddress', { type = 'OctetStr', length = 6 }, - ffi.string(mac, 6)) - ifTable:register('ifAdminStatus', 'Integer32', 1) -- up - ifTable:register('ifOperStatus', 'Integer32', 2) -- down - ifTable:register('ifLastChange', 'TimeTicks', 0) - ifTable:register('_X_ifLastChange_TicksBase', 'Counter64', C.get_unix_time()) - ifTable:register('ifInOctets', 'Counter32', 0) - ifTable:register('ifInUcastPkts', 'Counter32', 0) - ifTable:register('ifInDiscards', 'Counter32', 0) - ifTable:register('ifInErrors', 'Counter32', 0) -- TBD - ifTable:register('ifInUnknownProtos', 'Counter32', 0) -- TBD - ifTable:register('ifOutOctets', 'Counter32', 0) - ifTable:register('ifOutUcastPkts', 'Counter32', 0) - ifTable:register('ifOutDiscards', 'Counter32', 0) - ifTable:register('ifOutErrors', 'Counter32', 0) -- TBD - -- ifXTable - ifTable:register('ifName', { type = 'OctetStr', length = 255 }, - self.pciaddress) - ifTable:register('ifInMulticastPkts', 'Counter32', 0) - ifTable:register('ifInBroadcastPkts', 'Counter32', 0) - ifTable:register('ifOutMulticastPkts', 'Counter32', 0) - ifTable:register('ifOutBroadcastPkts', 'Counter32', 0) - ifTable:register('ifHCInOctets', 'Counter64', 0) - ifTable:register('ifHCInUcastPkts', 'Counter64', 0) - ifTable:register('ifHCInMulticastPkts', 'Counter64', 0) - ifTable:register('ifHCInBroadcastPkts', 'Counter64', 0) - ifTable:register('ifHCOutOctets', 'Counter64', 0) - ifTable:register('ifHCOutUcastPkts', 'Counter64', 0) - ifTable:register('ifHCOutMulticastPkts', 'Counter64', 0) - ifTable:register('ifHCOutBroadcastPkts', 'Counter64', 0) - ifTable:register('ifLinkUpDownTrapEnable', 'Integer32', 2) -- disabled - ifTable:register('ifHighSpeed', 'Gauge32', 10000) - ifTable:register('ifPromiscuousMode', 'Integer32', 2) -- false - ifTable:register('ifConnectorPresent', 'Integer32', 1) -- true - ifTable:register('ifAlias', { type = 'OctetStr', length = 64 }, - self.pciaddress) -- TBD add description - ifTable:register('ifCounterDiscontinuityTime', 'TimeTicks', 0) -- TBD - ifTable:register('_X_ifCounterDiscontinuityTime', 'Counter64', 0) -- TBD - - --- Create a timer to periodically check the interface status. - --- Static variables are used in the timer function to avoid - --- garbage. - local status = { [1] = 'up', [2] = 'down' } - local mask = bits{Link_up=30} - local promisc = bits({UPE = 9}) - -- Pre-allocate storage for the results of register-reads - local r = { - in_mcast_pkts = { r = self.s.MPRC }, - in_bcast_pkts = { r = self.s.BPRC }, - in_pkts = { r = self.s.GPRC }, - in_octets64 = { r = self.s.GORC64 }, - - out_mcast_pkts = { r = self.s.MPTC }, - out_bcast_pkts = { r = self.s.BPTC }, - out_pkts = { r = self.s.GPTC }, - out_octets64 = { r = self.s.GOTC64 }, - } - local r_keys = {} - for k, _ in pairs(r) do - table.insert(r_keys, k) - r[k].v = ffi.new("uint64_t [1]") - end - local function read_registers() - for _, k in ipairs(r_keys) do - r[k].v[0] = r[k].r() - end - end - self.logger = lib.logger_new({ module = 'intel10g' }) - local t = timer.new("Interface "..self.pciaddress.." status checker", - function(t) - local old = ifTable:get('ifOperStatus') - local new = 1 - if band(self.r.LINKS(), mask) ~= mask then - new = 2 - end - if old ~= new then - self.logger:log("Interface "..self.pciaddress.. - " status change: "..status[old].. - " => "..status[new]) - ifTable:set('ifOperStatus', new) - ifTable:set('ifLastChange', 0) - ifTable:set('_X_ifLastChange_TicksBase', - C.get_unix_time()) - end - - ifTable:set('ifPromiscuousMode', - (bit.band(self.r.FCTRL(), promisc) ~= 0ULL - and 1) or 2) - -- Update counters - read_registers() - ifTable:set('ifHCInMulticastPkts', r.in_mcast_pkts.v[0]) - ifTable:set('ifInMulticastPkts', r.in_mcast_pkts.v[0]) - ifTable:set('ifHCInBroadcastPkts', r.in_bcast_pkts.v[0]) - ifTable:set('ifInBroadcastPkts', r.in_bcast_pkts.v[0]) - local in_ucast_pkts = r.in_pkts.v[0] - r.in_bcast_pkts.v[0] - - r.in_mcast_pkts.v[0] - ifTable:set('ifHCInUcastPkts', in_ucast_pkts) - ifTable:set('ifInUcastPkts', in_ucast_pkts) - ifTable:set('ifHCInOctets', r.in_octets64.v[0]) - ifTable:set('ifInOctets', r.in_octets64.v[0]) - - ifTable:set('ifHCOutMulticastPkts', r.out_mcast_pkts.v[0]) - ifTable:set('ifOutMulticastPkts', r.out_mcast_pkts.v[0]) - ifTable:set('ifHCOutBroadcastPkts', r.out_bcast_pkts.v[0]) - ifTable:set('ifOutBroadcastPkts', r.out_bcast_pkts.v[0]) - local out_ucast_pkts = r.out_pkts.v[0] - r.out_bcast_pkts.v[0] - - r.out_mcast_pkts.v[0] - ifTable:set('ifHCOutUcastPkts', out_ucast_pkts) - ifTable:set('ifOutUcastPkts', out_ucast_pkts) - ifTable:set('ifHCOutOctets', r.out_octets64.v[0]) - ifTable:set('ifOutOctets', r.out_octets64.v[0]) - - -- The RX receive drop counts are only - -- available through the RX stats register. - -- We only read stats register #0 here. See comment - -- in init_statistics() - ifTable:set('ifInDiscards', self.qs.QPRDC[0]()) - - ifTable:set('ifInErrors', self.s.CRCERRS() + - self.s.ILLERRC() + self.s.ERRBC() + - self.s.RUC() + self.s.RFC() + - self.s.ROC() + self.s.RJC()) - end, - 1e9 * (self.snmp.status_timer or - default.snmp.status_timer), 'repeating') - timer.activate(t) - return self -end - function M_sf:global_reset () local reset = bits{LinkReset=3, DeviceReset=26} self.r.CTRL(reset) @@ -452,22 +294,10 @@ end local txdesc_flags = bits{ifcs=25, dext=29, dtyp0=20, dtyp1=21, eop=24} function M_sf:transmit (p) - -- We must not send packets that are bigger than the MTU. This - -- check is currently disabled to satisfy some selftests until - -- agreement on this strategy is reached. - -- if p.length > self.mtu then - -- if self.snmp then - -- local errors = self.snmp.ifTable:ptr('ifOutDiscards') - -- errors[0] = errors[0] + 1 - -- end - -- packet.free(p) - -- else - do - self.txdesc[self.tdt].address = memory.virtual_to_physical(p.data) - self.txdesc[self.tdt].options = bor(p.length, txdesc_flags, lshift(p.length+0ULL, 46)) - self.txpackets[self.tdt] = p - self.tdt = band(self.tdt + 1, num_descriptors - 1) - end + self.txdesc[self.tdt].address = memory.virtual_to_physical(p.data) + self.txdesc[self.tdt].options = bor(p.length, txdesc_flags, lshift(p.length+0ULL, 46)) + self.txpackets[self.tdt] = p + self.tdt = band(self.tdt + 1, num_descriptors - 1) end function M_sf:sync_transmit () @@ -653,7 +483,6 @@ function new_pf (conf) mac_set = index_set:new(127, "MAC address table"), vlan_set = index_set:new(64, "VLAN Filter table"), mirror_set = index_set:new(4, "Mirror pool table"), - snmp = conf.snmp, } return setmetatable(dev, M_pf) end @@ -679,9 +508,6 @@ function M_pf:close() end function M_pf:init () - if self.snmp then - self:init_snmp() - end self.redos = 0 local mask = bits{Link_up=30} for i = 1, 100 do @@ -707,7 +533,6 @@ function M_pf:init () return self end -M_pf.init_snmp = M_sf.init_snmp M_pf.global_reset = M_sf.global_reset M_pf.disable_interrupts = M_sf.disable_interrupts M_pf.set_receive_descriptors = pass @@ -788,7 +613,6 @@ function M_pf:new_vf (poolnum) base = self.base, -- mmap()ed register file s = self.s, -- Statistics registers mtu = self.mtu, - snmp = self.snmp, -- and others are our own r = {}, -- Configuration registers poolnum = poolnum, diff --git a/src/apps/intel/intel_app.lua b/src/apps/intel/intel_app.lua index 2490a09798..c85c2e5530 100644 --- a/src/apps/intel/intel_app.lua +++ b/src/apps/intel/intel_app.lua @@ -6,8 +6,11 @@ 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 macaddress = require("lib.macaddress") local intel10g = require("apps.intel.intel10g") local receive, transmit, full, empty = link.receive, link.transmit, link.full, link.empty Intel82599 = {} @@ -31,25 +34,53 @@ local function firsthole(t) end end +local provided_counters = { + 'type', 'dtime', 'mtu', 'speed', 'status', 'promisc', 'macaddr', + 'rxbytes', 'rxpackets', 'rxmcast', 'rxbcast', 'rxdrop', 'rxerrors', + 'txbytes', 'txpackets', 'txmcast', 'txbcast', 'txdrop', 'txerrors' +} -- 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 = { s = pf.s, r = pf.r, qs = pf.qs } } 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 = { s = self.dev.s, r = self.dev.r, qs = self.dev.qs } + self.zone = "intel" end + if not self.stats.counters then + self.stats.path = "/counters/"..conf.pciaddr.."/" + self.stats.sync_timer = lib.timer(0.001, 'repeating', engine.now) + self.stats.counters = {} + for _, name in ipairs(provided_counters) do + self.stats.counters[name] = counter.open(self.stats.path..name) + end + counter.set(self.stats.counters.type, 0x1000) -- Hardware interface + counter.set(self.stats.counters.dtime, C.get_unix_time()) + counter.set(self.stats.counters.mtu, self.dev.mtu) + counter.set(self.stats.counters.speed, 10000000000) -- 10 Gbits + counter.set(self.stats.counters.status, 2) -- down + if not conf.vmdq and conf.macaddr then + counter.set(self.stats.counters.macaddr, + macaddress:new(conf.macaddr).bits) + end + end + return setmetatable(self, Intel82599) end function Intel82599:stop() @@ -70,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 @@ -78,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.macaddr, + macaddress:new(conf.macaddr).bits) + end end -- Allocate receive buffers from the given freelist. @@ -95,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:ingress_packet_drops () @@ -108,11 +153,51 @@ function Intel82599:add_receive_buffers () end end +-- Synchronize self.stats.s/r a and self.stats.counters. +local link_up_mask = lib.bits{Link_up=30} +local promisc_mask = lib.bits{UPE=9} +function Intel82599:sync_stats () + local counters = self.stats.counters + local s, r, qs = self.stats.s, self.stats.r, self.stats.qs + counter.set(counters.rxbytes, s.GORC64()) + counter.set(counters.rxpackets, s.GPRC()) + local mprc, bprc = s.MPRC(), s.BPRC() + counter.set(counters.rxmcast, mprc + bprc) + counter.set(counters.rxbcast, bprc) + -- The RX receive drop counts are only available through the RX stats + -- register. We only read stats register #0 here. + counter.set(counters.rxdrop, qs.QPRDC[0]()) + counter.set(counters.rxerrors, s.CRCERRS() + s.ILLERRC() + s.ERRBC() + + s.RUC() + s.RFC() + s.ROC() + s.RJC()) + counter.set(counters.txbytes, s.GOTC64()) + counter.set(counters.txpackets, s.GPTC()) + local mptc, bptc = s.MPTC(), s.BPTC() + counter.set(counters.txmcast, mptc + bptc) + counter.set(counters.txbcast, bptc) + if bit.band(r.LINKS(), link_up_mask) == link_up_mask then + counter.set(counters.status, 1) -- Up + else + counter.set(counters.status, 2) -- Down + end + if bit.band(r.FCTRL(), promisc_mask) ~= 0ULL then + counter.set(counters.promisc, 1) -- True + else + counter.set(counters.promisc, 2) -- False + end +end + -- Push packets from our 'rx' link onto the network. function Intel82599:push () local l = self.input.rx if l == nil then return end while not empty(l) and self.dev:can_transmit() do + -- We must not send packets that are bigger than the MTU. This + -- check is currently disabled to satisfy some selftests until + -- agreement on this strategy is reached. + -- if p.length > self.dev.mtu then + -- counter.add(self.stats.counters.txdrop) + -- packet.free(p) + -- else do local p = receive(l) self.dev:transmit(p) --packet.deref(p) diff --git a/src/apps/socket/raw.lua b/src/apps/socket/raw.lua index 24ad578d5d..6cf60c7e33 100644 --- a/src/apps/socket/raw.lua +++ b/src/apps/socket/raw.lua @@ -7,6 +7,8 @@ local h = require("syscall.helpers") local bit = require("bit") local link = require("core.link") local packet = require("core.packet") +local counter = require("core.counter") +local ethernet = require("lib.protocol.ethernet") local ffi = require("ffi") local C = ffi.C @@ -21,6 +23,12 @@ local c, t = S.c, S.types.t RawSocket = {} +local provided_counters = { + 'type', 'dtime', + 'rxbytes', 'rxpackets', 'rxmcast', 'rxbcast', + 'txbytes', 'txpackets', 'txmcast', 'txbcast' +} + function RawSocket:new (ifname) assert(ifname) local index, err = S.util.if_nametoindex(ifname) @@ -40,7 +48,16 @@ function RawSocket:new (ifname) sock:close() error(err) end - return setmetatable({sock = sock}, {__index = RawSocket}) + local counters = {} + for _, name in ipairs(provided_counters) do + counters[name] = counter.open(name) + end + counter.set(counters.type, 0x1001) -- Virtual interface + counter.set(counters.dtime, C.get_unix_time()) + return setmetatable({sock = sock, + rx_p = packet.allocate(), + counters = counters}, + {__index = RawSocket}) end function RawSocket:pull () @@ -61,10 +78,18 @@ function RawSocket:can_receive () end function RawSocket:receive () - local buffer = ffi.new("uint8_t[?]", C.PACKET_PAYLOAD_SIZE) - local sz, err = S.read(self.sock, buffer, C.PACKET_PAYLOAD_SIZE) - assert(sz, err) - return packet.from_pointer(buffer, sz) + local p = self.rx_p + local sz = assert(S.read(self.sock, p.data, packet.max_payload)) + p.length = sz + counter.add(self.counters.rxbytes, sz) + counter.add(self.counters.rxpackets) + if ethernet:is_mcast(p.data) then + counter.add(self.counters.rxmcast) + end + if ethernet:is_bcast(p.data) then + counter.add(self.counters.rxbcast) + end + return packet.clone(p) end function RawSocket:push () @@ -73,6 +98,14 @@ function RawSocket:push () while not link.empty(l) and self:can_transmit() do local p = link.receive(l) self:transmit(p) + counter.add(self.counters.txbytes, p.length) + counter.add(self.counters.txpackets) + if ethernet:is_mcast(p.data) then + counter.add(self.counters.txmcast) + end + if ethernet:is_bcast(p.data) then + counter.add(self.counters.txbcast) + end packet.free(p) end end @@ -94,6 +127,9 @@ end function RawSocket:stop() self.sock:close() + packet.free(self.rx_p) + -- delete counters + for name, _ in pairs(self.counters) do counter.delete(name) end end function selftest () diff --git a/src/apps/tap/tap.lua b/src/apps/tap/tap.lua index 497399d11a..9444e3f5a9 100644 --- a/src/apps/tap/tap.lua +++ b/src/apps/tap/tap.lua @@ -5,6 +5,8 @@ module(..., package.seeall) local S = require("syscall") local link = require("core.link") local packet = require("core.packet") +local counter = require("core.counter") +local ethernet = require("lib.protocol.ethernet") local ffi = require("ffi") local C = ffi.C local const = require("syscall.linux.constants") @@ -14,6 +16,12 @@ local t = S.types.t Tap = { } +local provided_counters = { + 'type', 'dtime', + 'rxbytes', 'rxpackets', 'rxmcast', 'rxbcast', + 'txbytes', 'txpackets', 'txmcast', 'txbcast' +} + function Tap:new (name) assert(name, "missing tap interface name") @@ -27,8 +35,14 @@ function Tap:new (name) sock:close() error("Error opening /dev/net/tun: " .. tostring(err)) end - - return setmetatable({sock = sock, name = name}, {__index = Tap}) + local counters = {} + for _, name in ipairs(provided_counters) do + counters[name] = counter.open(name) + end + counter.set(counters.type, 0x1001) -- Virtual interface + counter.set(counters.dtime, C.get_unix_time()) + return setmetatable({sock = sock, name = name, counters = counters}, + {__index = Tap}) end function Tap:pull () @@ -49,6 +63,14 @@ function Tap:pull () end p.length = len link.transmit(l, p) + counter.add(self.counters.rxbytes, len) + counter.add(self.counters.rxpackets) + if ethernet:is_mcast(p.data) then + counter.add(self.counters.rxmcast) + end + if ethernet:is_bcast(p.data) then + counter.add(self.counters.rxbcast) + end end end @@ -66,6 +88,14 @@ function Tap:push () if len ~= p.length and err.errno == const.E.AGAIN then return end + counter.add(self.counters.txbytes, len) + counter.add(self.counters.txpackets) + if ethernet:is_mcast(p.data) then + counter.add(self.counters.txmcast) + end + if ethernet:is_bcast(p.data) then + counter.add(self.counters.txbcast) + end -- The write completed so dequeue it from the link and free the packet link.receive(l) packet.free(p) @@ -74,6 +104,8 @@ end function Tap:stop() self.sock:close() + -- delete counters + for name, _ in pairs(self.counters) do counter.delete(name) end end function selftest() diff --git a/src/apps/vhost/vhost_user.lua b/src/apps/vhost/vhost_user.lua index 7890b495f5..a8238a5ab7 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") @@ -27,6 +29,12 @@ assert(ffi.sizeof("struct vhost_user_msg") == 276, "ABI error") VhostUser = {} +local provided_counters = { + 'type', 'dtime', + 'rxbytes', 'rxpackets', 'rxmcast', 'rxbcast', 'rxdrop', + 'txbytes', 'txpackets', 'txmcast', 'txbcast' +} + function VhostUser:new (args) local o = { state = 'init', dev = nil, @@ -52,6 +60,13 @@ function VhostUser:new (args) else self.qemu_connect = self.client_connect end + -- initialize counters + self.counters = {} + for _, name in ipairs(provided_counters) do + self.counters[name] = counter.open(name) + end + counter.set(self.counters.type, 0x1001) -- Virtual interface + counter.set(self.counters.dtime, C.get_unix_time()) return self end @@ -68,6 +83,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 +104,32 @@ function VhostUser:push () end end +function VhostUser:tx_callback (p) + counter.add(self.counters.txbytes, packet.length(p)) + counter.add(self.counters.txpackets) + if ethernet:is_mcast(packet.data(p)) then + counter.add(self.counters.txmcast) + end + if ethernet:is_bcast(packet.data(p)) then + counter.add(self.counters.txbcast) + end +end + +function VhostUser:rx_callback (p) + counter.add(self.counters.rxbytes, packet.length(p)) + counter.add(self.counters.rxpackets) + if ethernet:is_mcast(packet.data(p)) then + counter.add(self.counters.rxmcast) + end + if ethernet:is_bcast(packet.data(p)) then + counter.add(self.counters.rxbcast) + end +end + +function VhostUser:rxdrop_callback (p) + counter.add(self.counters.rxdrop) +end + -- Try to connect to QEMU. function VhostUser:client_connect () return C.vhost_user_connect(self.socket_path) diff --git a/src/core/app.lua b/src/core/app.lua index c4fb94836c..5d7fa0e8fd 100644 --- a/src/core/app.lua +++ b/src/core/app.lua @@ -7,6 +7,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 histogram = require('core.histogram') local counter = require("core.counter") local zone = require("jit.zone") @@ -69,19 +70,23 @@ end -- Run app:methodname() in protected mode (pcall). If it throws an -- error app will be marked as dead and restarted eventually. function with_restart (app, method) + local oldshm = shm.path + shm.path = app.shmpath + local status, result if use_restart then -- Run fn in protected mode using pcall. - local status, result_or_error = pcall(method, app) + status, result = pcall(method, app) -- If pcall caught an error mark app as "dead" (record time and cause -- of death). if not status then - app.dead = { error = result_or_error, time = now() } + app.dead = { error = result, time = now() } end - return status, result_or_error else - return true, method(app) + status, result = true, method(app) end + shm.path = oldshm + return status, result end -- Restart dead apps. @@ -159,7 +164,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] @@ -169,7 +180,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 = "counters/"..name, shm.path + shm.path = shmpath local app = class:new(arg) + shm.path = shmorig if type(app) ~= 'table' then error(("bad return value from app '%s' start() method: %s"):format( name, tostring(app))) @@ -178,6 +192,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 @@ -191,7 +206,10 @@ function apply_config_actions (actions, conf) if app_table[name].reconfig then local arg = conf.apps[name].arg local app = app_table[name] + local shmorig = shm.path + shm.path = app.shmpath app:reconfig(arg) + shm.path = shmorig new_app_table[name] = app table.insert(new_app_array, app) app_name_to_index[name] = #new_app_array @@ -497,6 +515,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("counters/App4/test")), + "Missing : counters/App4/test") + configure(config.new()) + assert(not S.stat(shm.root.."/"..shm.resolve("counters/App4")), + "Failed to unlink counters/App4") print("OK") end diff --git a/src/core/counter.lua b/src/core/counter.lua index f0afc0748b..c7d17b0eb1 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 return private[numbers[name]] end + local qname = shm.resolve(name) + if numbers[qname] then return private[numbers[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 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. diff --git a/src/core/link.h b/src/core/link.h index c4ca2b45a5..f4caadaf91 100644 --- a/src/core/link.h +++ b/src/core/link.h @@ -9,7 +9,7 @@ struct link { // http://en.wikipedia.org/wiki/Circular_buffer struct packet *packets[LINK_RING_SIZE]; struct { - struct counter *txbytes, *rxbytes, *txpackets, *rxpackets, *txdrop; + struct counter *dtime, *txbytes, *rxbytes, *txpackets, *rxpackets, *txdrop; } stats; // Two cursors: // read: the next element to be read diff --git a/src/core/link.lua b/src/core/link.lua index ed825e2606..aacff1e741 100644 --- a/src/core/link.lua +++ b/src/core/link.lua @@ -21,18 +21,21 @@ local band = require("bit").band local size = C.LINK_RING_SIZE -- NB: Huge slow-down if this is not local max = C.LINK_MAX_PACKETS -local counternames = {"rxpackets", "txpackets", "rxbytes", "txbytes", "txdrop"} +local provided_counters = { + "dtime", "rxpackets", "rxbytes", "txpackets", "txbytes", "txdrop" +} function new (name) local r = shm.create("links/"..name, "struct link") - for _, c in ipairs(counternames) do + for _, c in ipairs(provided_counters) do r.stats[c] = counter.open("counters/"..name.."/"..c) end + counter.set(r.stats.dtime, C.get_unix_time()) return r end function free (r, name) - for _, c in ipairs(counternames) do + for _, c in ipairs(provided_counters) do counter.delete("counters/"..name.."/"..c) end shm.unmap(r) @@ -92,8 +95,7 @@ end function stats (r) local stats = {} - for _, c - in ipairs(counternames) do + for _, c in ipairs(provided_counters) do stats[c] = tonumber(counter.read(r.stats[c])) end return stats diff --git a/src/core/packet.lua b/src/core/packet.lua index 6c4ed5311b..143bfdf920 100644 --- a/src/core/packet.lua +++ b/src/core/packet.lua @@ -17,7 +17,7 @@ local packet_t = ffi.typeof("struct packet") local packet_ptr_t = ffi.typeof("struct packet *") local packet_size = ffi.sizeof(packet_t) local header_size = 8 -local max_payload = tonumber(C.PACKET_PAYLOAD_SIZE) +max_payload = tonumber(C.PACKET_PAYLOAD_SIZE) -- Freelist containing empty packets ready for use. diff --git a/src/doc/statistics.md b/src/doc/statistics.md new file mode 100644 index 0000000000..852d77829f --- /dev/null +++ b/src/doc/statistics.md @@ -0,0 +1,54 @@ +### Statistics counters in Snabb + +Below is list of statistics counters defined by Snabb, and their relation to +RFC 7223 and ifTable MIB. All counters are unsigned 64bit integers. Each Snabb +app can optionally implement any number of these counters. + +| Snabb | RFC 7223 | ifTable MIB +| ----- | -------- | ----------- +| | name | +| | description? | ifDescr +| type (~= identity) | type | ifType +| | enabled? | +| | link-up-down-trap-enable? | +| | admin-status | +| status (= enum) | oper-status | ifOperStatus +| | last-change? | ifLastChange +| | if-index | +| macaddr (uint64) | phys-address? | ifPhysAddress +| | higher-layer-if* | +| | lower-layer-if* | +| speed | speed? | ifSpeed +| dtime (seconds since epoch) | discontinuity-time | ifCounterDiscontinuityTime +| rxbytes | in-octets? | ifInOctets +| rxpackets | | +| | in-unicast-pkts? | +| rxbcast | in-broadcast-pkts? | ifInBroadcastPkts +| rxmcast | | +| | in-multicast-pkts? | ifInMulticastPkts +| rxdrop | in-discards? | ifInDiscards +| rxerrors | in-errors? | ifInErrors +| | in-unknown-protos? | +| txbytes | out-octets? | ifOutOctets +| txpackets | | +| | out-unicast-pkts? | +| txbcast | out-broadcast-pkts? | ifOutBroadcastPkts +| txmcast | | +| | out-multicast-pkts? | ifOutMulticastPkts +| txdrop | out-discards? | ifOutDiscards +| txerrors | out-errors? | ifOutErrors +| mtu | | ifMtu +| promisc | | ifPromiscuousMode + + +#### type + + - `0x1000` Hardware interface + - `0x1001` Virtual interface + +#### rxbcast, rxmcast, rxpackets, txbcast, txmcast, txpackets + +Snabb defines total packet counts, multicast packet counts (packets with group +bit set) and broadcast packet counts for both input and output. + + E.g.: (tx/rx)bcast ⊆ (tx/rx)mcast ⊆ (tx/rx)packets diff --git a/src/lib/ipc/shmem/iftable_mib.lua b/src/lib/ipc/shmem/iftable_mib.lua new file mode 100644 index 0000000000..8d96d29e67 --- /dev/null +++ b/src/lib/ipc/shmem/iftable_mib.lua @@ -0,0 +1,154 @@ +-- Use of this source code is governed by the Apache 2.0 license; see COPYING. + +module(...,package.seeall) +local mib = require("lib.ipc.shmem.mib") +local counter = require("core.counter") +local macaddress = require("lib.macaddress") +local ffi = require("ffi") + +local iftypes = { + [0x1000] = 6, -- ethernetCsmacd + [0x1001] = 53, -- propVirtual +} + +function init_snmp (name, counters, directory, interval) + -- Rudimentary population of a row in the ifTable MIB. Allocation + -- of the ifIndex is delegated to the SNMP agent via the name of + -- the interface in ifDescr. + local ifTable = mib:new({ directory = directory or nil, + filename = name }) + -- ifTable + ifTable:register('ifDescr', 'OctetStr', name) + ifTable:register('ifType', 'Integer32') + if counters.type then + ifTable:set('ifType', iftypes[counter.read(counters.type)] or 1) -- other + end + ifTable:register('ifMtu', 'Integer32') + if counters.mtu then + ifTable:set('ifMtu', counter.read(counters.mtu)) + end + ifTable:register('ifSpeed', 'Gauge32') + ifTable:register('ifHighSpeed', 'Gauge32') + if counters.speed then + ifTable:set('ifSpeed', counter.read(counters.speed)) + ifTable:set('ifHighSpeed', counter.read(counters.speed) / 1000) + end + ifTable:register('ifPhysAddress', { type = 'OctetStr', length = 6 }) + if counters.macaddr then + local mac = macaddress:new(counter.read(counters.macaddr)) + ifTable:set('ifPhysAddress', ffi.string(mac.bytes, 6)) + end + ifTable:register('ifAdminStatus', 'Integer32', 1) -- up + ifTable:register('ifOperStatus', 'Integer32', 2) -- down + ifTable:register('ifLastChange', 'TimeTicks', 0) + ifTable:register('_X_ifLastChange_TicksBase', 'Counter64', + C.get_unix_time()) + ifTable:register('ifInOctets', 'Counter32', 0) + ifTable:register('ifInUcastPkts', 'Counter32', 0) + ifTable:register('ifInDiscards', 'Counter32', 0) + ifTable:register('ifInErrors', 'Counter32', 0) -- TBD + ifTable:register('ifInUnknownProtos', 'Counter32', 0) -- TBD + ifTable:register('ifOutOctets', 'Counter32', 0) + ifTable:register('ifOutUcastPkts', 'Counter32', 0) + ifTable:register('ifOutDiscards', 'Counter32', 0) + ifTable:register('ifOutErrors', 'Counter32', 0) -- TBD + -- ifXTable + ifTable:register('ifName', { type = 'OctetStr', length = 255 }, name) + ifTable:register('ifInMulticastPkts', 'Counter32', 0) + ifTable:register('ifInBroadcastPkts', 'Counter32', 0) + ifTable:register('ifOutMulticastPkts', 'Counter32', 0) + ifTable:register('ifOutBroadcastPkts', 'Counter32', 0) + ifTable:register('ifHCInOctets', 'Counter64', 0) + ifTable:register('ifHCInUcastPkts', 'Counter64', 0) + ifTable:register('ifHCInMulticastPkts', 'Counter64', 0) + ifTable:register('ifHCInBroadcastPkts', 'Counter64', 0) + ifTable:register('ifHCOutOctets', 'Counter64', 0) + ifTable:register('ifHCOutUcastPkts', 'Counter64', 0) + ifTable:register('ifHCOutMulticastPkts', 'Counter64', 0) + ifTable:register('ifHCOutBroadcastPkts', 'Counter64', 0) + ifTable:register('ifLinkUpDownTrapEnable', 'Integer32', 2) -- disabled + ifTable:register('ifPromiscuousMode', 'Integer32', 2) -- false + ifTable:register('ifConnectorPresent', 'Integer32', 1) -- true + ifTable:register('ifAlias', { type = 'OctetStr', length = 64 }, + name) -- TBD add description + ifTable:register('ifCounterDiscontinuityTime', 'TimeTicks', 0) + ifTable:register('_X_ifCounterDiscontinuityTime', 'Counter64') + if counters.dtime then + ifTable:set('_X_ifCounterDiscontinuityTime', counter.read(counters.dtime)) + end + + local logger = lib.logger_new({ module = 'iftable_mib' }) + local function t () + local old, new + if counters.status then + old = ifTable:get('ifOperStatus') + new = counter.read(counters.status) + else + new = 1 + end + if old ~= new then + logger:log("Interface "..name.. + " status change: "..status[old].." => "..status[new]) + ifTable:set('ifOperStatus', new) + ifTable:set('ifLastChange', 0) + ifTable:set('_X_ifLastChange_TicksBase', C.get_unix_time()) + end + + if counters.promisc then + ifTable:set('ifPromiscuousMode', counter.read(counters.promisc)) + end + -- Update counters + if counters.rxpackets and counters.rxmcast and counters.rxbcast then + local rxbcast = counter.read(counters.rxbcast) + local rxmcast = counter.read(counters.rxmcast) + local rxpackets = counter.read(counters.rxpackets) + local inMcast = rxmcast - rxbcast + local inUcast = rxpackets - rxmcast + ifTable:set('ifHCInMulticastPkts', inMcast) + ifTable:set('ifInMulticastPkts', inMcast) + ifTable:set('ifHCInBroadcastPkts', rxbcast) + ifTable:set('ifInBroadcastPkts', rxbcast) + ifTable:set('ifHCInUcastPkts', inUcast) + ifTable:set('ifInUcastPkts', inUcast) + end + if counters.rxbytes then + local rxbytes = counter.read(counters.rxbytes) + ifTable:set('ifHCInOctets', rxbytes) + ifTable:set('ifInOctets', rxbytes) + end + if counters.rxdrop then + ifTable:set('ifInDiscards', counter.read(counters.rxdrop)) + end + if counters.rxerrors then + ifTable:set('ifInErrors', counter.read(counters.rxerrors)) + end + if counters.txpackets and counters.txmcast and counters.txbcast then + local txbcast = counter.read(counters.txbcast) + local txmcast = counter.read(counters.txmcast) + local txpackets = counter.read(counters.txpackets) + local outMcast = txmcast - txbcast + local outUcast = txpackets - txmcast + ifTable:set('ifHCOutMulticastPkts', outMcast) + ifTable:set('ifOutMulticastPkts', outMcast) + ifTable:set('ifHCOutBroadcastPkts', txbcast) + ifTable:set('ifOutBroadcastPkts', txbcast) + ifTable:set('ifHCOutUcastPkts', outUcast) + ifTable:set('ifOutUcastPkts', outUcast) + end + if counters.txbytes then + local txbytes = counter.read(counters.txbytes) + ifTable:set('ifHCOutOctets', txbytes) + ifTable:set('ifOutOctets', txbytes) + end + if counters.txdrop then + ifTable:set('ifOutDiscards', counter.read(counters.txdrop)) + end + if counters.txerrors then + ifTable:set('ifOutErrors', counter.read(counters.txerrors)) + end + end + local t = timer.new("Interface "..name.." status checker", + t, 1e9 * (interval or 5), 'repeating') + timer.activate(t) + return t +end diff --git a/src/lib/macaddress.lua b/src/lib/macaddress.lua index e003fa1cc7..a973b1d7b3 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 diff --git a/src/lib/protocol/README.md b/src/lib/protocol/README.md index 6f239687d7..8ff44fba97 100644 --- a/src/lib/protocol/README.md +++ b/src/lib/protocol/README.md @@ -98,6 +98,14 @@ 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:is_bcast** *mac* + +Returns a true value if *mac* address denotes a [Broadcast address](https://en.wikipedia.org/wiki/Broadcast_address#Ethernet). + — 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..c874f35acb 100644 --- a/src/lib/protocol/ethernet.lua +++ b/src/lib/protocol/ethernet.lua @@ -81,6 +81,12 @@ function ethernet:is_mcast (addr) return band(addr[0], 0x01) ~= 0 end +local bcast_address = ethernet:pton("FF:FF:FF:FF:FF:FF") +-- Check whether a MAC address is the broadcast address +function ethernet:is_bcast (addr) + return C.memcmp(addr, bcast_address, 6) == 0 +end + -- Instance methods function ethernet:src (a) diff --git a/src/lib/virtio/net_device.lua b/src/lib/virtio/net_device.lua index 9c93e66fbb..e01ac7f86b 100644 --- a/src/lib/virtio/net_device.lua +++ b/src/lib/virtio/net_device.lua @@ -162,9 +162,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) @@ -252,6 +254,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 @@ -312,6 +315,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 diff --git a/src/program/top/README b/src/program/top/README index 365c357326..ce815d6f89 100644 --- a/src/program/top/README +++ b/src/program/top/README @@ -3,10 +3,12 @@ Usage: -h, --help Print usage information. + -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 -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..8334d76be3 100644 --- a/src/program/top/top.lua +++ b/src/program/top/top.lua @@ -12,20 +12,67 @@ local histogram = require("core.histogram") local usage = require("program.top.README_inc") local long_opts = { - help = "h" + help = "h", counters = "c" } function clearterm () io.write('\027[2J') end function run (args) local opt = {} + local object = nil function opt.h (arg) print(usage) main.exit(1) end - args = lib.dogetopt(args, opt, "h", 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 = args[1] + local target_pid = select_snabb_instance(args[1]) - local instance_tree = "//"..(select_snabb_instance(target_pid)) + if object then list_counters(target_pid, object) + else top(target_pid) end + ordered_exit(0) +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 + ordered_exit(1) +end + +function ordered_exit (value) + shm.unlink("//"..S.getpid()) -- Unlink own shm tree to avoid clutter + 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 + print_row({30, 30}, {cname, lib.comma_value(read_counter(cname, path))}) + 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 +95,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