From 28a3de151368a90ffcef1a9c1d93de83037a38b5 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Thu, 21 Jul 2016 14:43:54 +0200 Subject: [PATCH 1/2] =?UTF-8?q?statistics:=20introduce=20shm=20frame=20abs?= =?UTF-8?q?traction=20to=20eliminate=20boilerplate=20code...=20=20-=20remo?= =?UTF-8?q?ved=20=E2=80=9Calias=E2=80=9D=20and=20=E2=80=9Cpath=E2=80=9D=20?= =?UTF-8?q?from=20core.shm=20=20-=20added=20=E2=80=9Cregister=E2=80=9D=20a?= =?UTF-8?q?nd=20=E2=80=9C{create,open,delete}=5Fframe=E2=80=9D=20to=20core?= =?UTF-8?q?.shm=20=20-=20made=20core.counter=20API=20match=20create/open/d?= =?UTF-8?q?elete=20interface=20=20-=20registered=20core.counter=20and=20co?= =?UTF-8?q?re.histogram=20with=20core.shm=20=20-=20moved=20inline=20docume?= =?UTF-8?q?ntation=20of=20core.{shm,counter,histogram}=20to=20README.md=20?= =?UTF-8?q?=20-=20update=20modules=20to=20use=20shm=20frame=20abstraction?= =?UTF-8?q?=20where=20sensible?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/README.md | 215 ++++++++++++++++++++++++- src/apps/intel/intel_app.lua | 55 +++---- src/apps/ipsec/esp.lua | 16 +- src/apps/ipv6/nd_light.lua | 61 +++---- src/apps/keyed_ipv6_tunnel/tunnel.lua | 37 ++--- src/apps/packet_filter/pcap_filter.lua | 23 +-- src/apps/rate_limiter/rate_limiter.lua | 19 +-- src/apps/socket/raw.lua | 39 ++--- src/apps/tap/tap.lua | 41 +++-- src/apps/vhost/vhost_user.lua | 26 ++- src/core/app.lua | 64 ++------ src/core/counter.lua | 44 +++-- src/core/histogram.lua | 47 +----- src/core/link.lua | 8 +- src/core/main.lua | 2 +- src/core/shm.lua | 153 ++++++++---------- src/lib/virtio/net_device.lua | 6 +- src/program/top/README | 7 +- src/program/top/top.lua | 68 ++++---- 19 files changed, 486 insertions(+), 445 deletions(-) diff --git a/src/README.md b/src/README.md index 31b281c466..b7d5da48b7 100644 --- a/src/README.md +++ b/src/README.md @@ -104,6 +104,13 @@ the engine for use in processing and are *read-only*. Name of the app. *Read-only*. +— Field **myapp.shm** + +Can be set to a specification for `core.shm.create_frame` during `new`. When +set, this field will be initialized to a frame of shared memory objects by the +engine. + + — Method **myapp:link** *Optional*. Called any time the app’s links may have been changed (including on @@ -401,7 +408,7 @@ can be accessed directly by network cards. The important characteristic of DMA memory is being located in contiguous physical memory at a stable address. -— Function **memory.dma_alloc** *bytes*, *[alignment]* +— Function **memory.dma_alloc** *bytes*, [*alignment*] Returns a pointer to *bytes* of new DMA memory. @@ -417,6 +424,210 @@ Returns the physical address (`uint64_t`) the DMA memory at *pointer*. Size of a single huge page in bytes. Read-only. +## Shared Memory (core.shm) + +This module facilitates creation and management of named shared memory objects. +Objects can be created using `shm.create` similar to `ffi.new`, except that +separate calls to `shm.open` for the same name will each return a new mapping +of the same shared memory. Different processes can share memory by mapping an +object with the same name (and type). Each process can map any object any +number of times. + +Mappings are deleted on process termination or with an explicit `shm.unmap`. +Names are unlinked from objects that are no longer needed using `shm.unlink`. +Object memory is freed when the name is unlinked and all mappings have been +deleted. + +Names can be fully qualified or abbreviated to be within the current process. +Here are examples of names and how they are resolved where `` is the PID +of this process: + +- Local: `foo/bar` ⇒ `/var/run/snabb//foo/bar` +- Fully qualified: `/1234/foo/bar` ⇒ `/var/run/snabb/1234/foo/bar` + +Behind the scenes the objects are backed by files on ram disk +(`/var/run/snabb/`) and accessed with the equivalent of POSIX shared +memory (`shm_overview(7)`). + +The practical limit on the number of objects that can be mapped will depend on +the operating system limit for memory mappings. On Linux the default limit is +65,530 mappings: + +``` +$ sysctl vm.max_map_count vm.max_map_count = 65530 +``` + +— Function **shm.create** *name*, *type* + +Creates and maps a shared object of *type* into memory via a hierarchical +*name*. Returns a pointer to the mapped object. + +— Function **shm.open** *name*, *type*, [*readonly*] + +Maps an existing shared object of *type* into memory via a hierarchical *name*. +If *readonly* is non-nil the shared object is mapped in read-only mode. +*Readonly* defaults to nil. Fails if the shared object does not already exist. +Returns a pointer to the mapped object. + +— Function **shm.unmap** *pointer* + +Deletes the memory mapping for *pointer*. + +— Function **shm.unlink** *path* + +Unlinks the subtree of objects designated by *path* from the filesystem. + +— Function **shm.children** *path* + +Returns an array of objects in the directory designated by *path*. + +— Function **shm.register** *type*, *module* + +Registers an abstract shared memory object *type* implemented by *module* in +`shm.types`. *Module* must provide the following functions: + + - **create** *name*, ... + - **open**, *name* + +and can optionally provide the function: + + - **delete**, *name* + +The *module*’s `type` variable must be bound to *type*. To register a new type +a module might invoke `shm.register` like so: + +``` +type = shm.register('mytype', getfenv()) +-- Now the following holds true: +-- shm.types[type] == getfenv() +``` + +— Variable **shm.types** + +A table that maps types to modules. See `shm.register`. + +— Function **shm.create_frame** *path*, *specification* + +Creates and returns a shared memory frame by *specification* under *path*. A +frame is a table of mapped—possibly abstract‑shared memory objects. +*Specification* must be of the form: + +``` +{ = {, ...}, + ... } +``` + +*Module* must implement an abstract type registered with `shm.register`, and is +followed by additional initialization arguments to its `create` function. +Example usage: + +``` +local counter = require("core.counter") +-- Create counters foo/bar/{dtime,rxpackets,txpackets}.counter +local f = shm.create_frame( + "foo/bar", + {dtime = {counter, C.get_unix_time()}, + rxpackets = {counter}, + txpackets = {counter}}) +counter.add(f.rxpackets) +counter.read(f.dtime) +``` + +— Function **shm.open_frame** *path* + +Opens and returns the shared memory frame under *path* for reading. + +— Function **shm.delete_frame** *frame* + +Deletes/unmaps a shared memory *frame*. The *frame* directory is unlinked if +*frame* was created by `shm.create_frame`. + + +### Counter (core.counter) + +Double-buffered shared memory counters. Counters are 64-bit unsigned values. +Registered with `core.shm` as type `counter`. + +— Function **counter.create** *name*, [*initval*] + +Creates and returns a `counter` by *name*, initialized to *initval*. *Initval* +defaults to 0. + +— Function **counter.open** *name* + +Opens and returns the counter by *name* for reading. + +— Function **counter.delete** *name* + +Deletes and unmaps the counter by *name*. + +— Function **counter.commit** + +Commits buffered counter values to public shared memory. + +— Function **counter.set** *counter*, *value* + +Sets *counter* to *value*. + +— Function **counter.add** *counter*, [*value*] + +Increments *counter* by *value*. *Value* defaults to 1. + +— Function **counter.read** *counter* + +Returns the value of *counter*. + + +### Histogram (core.histogram) + +Shared memory histogram with logarithmic buckets. Registered with `core.shm` as +type `histogram`. + +— Function **histogram.new** *min*, *max* + +Returns a new `histogram`, with buckets covering the range from *min* to *max*. +The range between *min* and *max* will be divided logarithmically. + +— Function **histogram.create** *name*, *min*, *max* + +Creates and returns a `histogram` as in `histogram.new` by *name*. If the file +exists already, it will be cleared. + +— Function **histogram.open** *name* + +Opens and returns `histogram` by *name* for reading. + +— Method **histogram:add** *measurement* + +Adds *measurement* to *histogram*. + +— Method **histogram:iterate** *prev* + +When used as `for count, lo, hi in histogram:iterate()`, visits all buckets in +*histogram* in order from lowest to highest. *Count* is the number of samples +recorded in that bucket, and *lo* and *hi* are the lower and upper bounds of +the bucket. Note that *count* is an unsigned 64-bit integer; to get it as a Lua +number, use `tonumber`. + +If *prev* is given, it should be a snapshot of the previous version of the +histogram. In that case, the *count* values will be returned as a difference +between their values in *histogram* and their values in *prev*. + +— Method **histogram:snapshot** [*dest*] + +Copies out the contents of *histogram* into the `histogram` *dest* and returns +*dest*. If *dest* is not given, the result will be a fresh `histogram`. + +— Method **histogram:clear** + +Clears the buckets of *histogram*. + +— Method **histogram:wrap_thunk* *thunk*, *now* + +Returns a closure that wraps *thunk*, measuring and recording the difference +between calls to *now* before and after *thunk* into *histogram*. + + ## Lib (core.lib) The `core.lib` module contains miscellaneous utilities. @@ -674,4 +885,4 @@ A list of command-line arguments to the running script. Read-only. — Function **main.exit** *status* -Cleanly exists the process with *status*. +Cleanly exits the process with *status*. diff --git a/src/apps/intel/intel_app.lua b/src/apps/intel/intel_app.lua index c85c2e5530..4eee563127 100644 --- a/src/apps/intel/intel_app.lua +++ b/src/apps/intel/intel_app.lua @@ -34,12 +34,6 @@ 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) @@ -63,21 +57,31 @@ function Intel82599:new (arg) 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.."/" + if not self.stats.shm then + self.stats.shm = shm.create_frame( + "pci/"..conf.pciaddr, + {dtime = {counter, C.get_unix_time()}, + mtu = {counter, self.dev.mtu}, + speed = {counter, 10000000000}, -- 10 Gbits + status = {counter, 2}, -- Link down + promisc = {counter}, + macaddr = {counter}, + rxbytes = {counter}, + rxpackets = {counter}, + rxmcast = {counter}, + rxbcast = {counter}, + rxdrop = {counter}, + rxerrors = {counter}, + txbytes = {counter}, + txpackets = {counter}, + txmcast = {counter}, + txbcast = {counter}, + txdrop = {counter}, + txerrors = {counter}}) 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) + counter.set(self.stats.shm.macaddr, macaddress:new(conf.macaddr).bits) end end return setmetatable(self, Intel82599) @@ -102,10 +106,7 @@ function Intel82599:stop() 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) + shm.delete_frame(self.stats.shm) end end @@ -117,7 +118,7 @@ function Intel82599:reconfig(arg) self.dev:reconfig(conf) if not self.dev.pf and conf.macaddr then - counter.set(self.stats.counters.macaddr, + counter.set(self.stats.shm.macaddr, macaddress:new(conf.macaddr).bits) end end @@ -153,11 +154,11 @@ function Intel82599:add_receive_buffers () end end --- Synchronize self.stats.s/r a and self.stats.counters. +-- Synchronize self.stats.s/r a and self.stats.shm. 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 counters = self.stats.shm 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()) @@ -195,7 +196,7 @@ function Intel82599:push () -- 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) + -- counter.add(self.stats.shm.txdrop) -- packet.free(p) -- else do local p = receive(l) diff --git a/src/apps/ipsec/esp.lua b/src/apps/ipsec/esp.lua index f468b883ad..79c4232c42 100644 --- a/src/apps/ipsec/esp.lua +++ b/src/apps/ipsec/esp.lua @@ -28,12 +28,7 @@ function AES128gcm:new (arg) keymat = conf.key:sub(1, 32), salt = conf.key:sub(33, 40), window_size = conf.replay_window} - 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()) + self.shm = { txerrors = {counter}, rxerrors = {counter} } return setmetatable(self, {__index = AES128gcm}) end @@ -47,7 +42,7 @@ function AES128gcm:push () link.transmit(output, p) else packet.free(p) - counter.add(self.counters.txerrors) + counter.add(self.shm.txerrors) end end -- Decapsulation path @@ -59,12 +54,7 @@ function AES128gcm:push () link.transmit(output, p) else packet.free(p) - counter.add(self.counters.rxerrors) + counter.add(self.shm.rxerrors) end end end - -function AES128gcm:stop () - -- delete counters - for name, _ in pairs(self.counters) do counter.delete(name) end -end diff --git a/src/apps/ipv6/nd_light.lua b/src/apps/ipv6/nd_light.lua index 2429f42782..7d00b8ff6d 100644 --- a/src/apps/ipv6/nd_light.lua +++ b/src/apps/ipv6/nd_light.lua @@ -79,20 +79,9 @@ local function check_ip_address(ip, desc) return ip end -local provided_counters = { - 'type', 'dtime', 'status', 'rxerrors', 'txerrors', 'txdrop', - 'ns_checksum_errors', 'ns_target_address_errors', - 'na_duplicate_errors', 'na_target_address_errors', - 'nd_protocol_errors' -} - function nd_light:new (arg) - local arg = arg and config.parse_app_arg(arg) or {} --copy the args to avoid changing the arg table so that it stays reusable. - local conf = {} - for k,v in pairs(arg) do - conf[k] = v - end + local conf = arg and lib.deepcopy(config.parse_app_arg(arg)) or {} local o = nd_light:superClass().new(self) conf.delay = conf.delay or 1000 assert(conf.local_mac, "nd_light: missing local MAC address") @@ -211,13 +200,15 @@ function nd_light:new (arg) o._logger = lib.logger_new({ module = 'nd_light' }) -- Create counters - o.counters = {} - for _, name in ipairs(provided_counters) do - o.counters[name] = counter.open(name) - end - counter.set(o.counters.type, 0x1001) -- Virtual interface - counter.set(o.counters.dtime, C.get_unix_time()) - counter.set(o.counters.status, 2) -- Link down + o.shm = { status = {counter, 2}, -- Link down + rxerrors = {counter}, + txerrors = {counter}, + txdrop = {counter}, + ns_checksum_errors = {counter}, + ns_target_address_errors = {counter}, + na_duplicate_errors = {counter}, + na_target_address_errors = {counter}, + nd_protocol_errors = {counter} } return o end @@ -227,16 +218,16 @@ local function ns (self, dgram, eth, ipv6, icmp) local mem, length = self._cache.mem mem[0], length = dgram:payload() if not icmp:checksum_check(mem[0], length, ipv6) then - counter.add(self.counters.ns_checksum_errors) - counter.add(self.counters.rxerrors) + counter.add(self.shm.ns_checksum_errors) + counter.add(self.shm.rxerrors) return nil end -- Parse the neighbor solicitation and check if it contains our own -- address as target local ns = dgram:parse_match(nil, self._match_ns) if not ns then - counter.add(self.counters.ns_target_address_errors) - counter.add(self.counters.rxerrors) + counter.add(self.shm.ns_target_address_errors) + counter.add(self.shm.rxerrors) return nil end -- Ignore options as long as we don't implement a proper neighbor @@ -257,21 +248,21 @@ end -- Process neighbor advertisement local function na (self, dgram, eth, ipv6, icmp) if self._eth_header then - counter.add(self.counters.na_duplicate_errors) - counter.add(self.counters.rxerrors) + counter.add(self.shm.na_duplicate_errors) + counter.add(self.shm.rxerrors) return nil end local na = dgram:parse_match(nil, self._match_na) if not na then - counter.add(self.counters.na_target_address_errors) - counter.add(self.counters.rxerrors) + counter.add(self.shm.na_target_address_errors) + counter.add(self.shm.rxerrors) return nil end local option = na:options(dgram:payload()) if not (#option == 1 and option[1]:type() == 2) then -- Invalid NS, ignore - counter.add(self.counters.nd_protocol_errors) - counter.add(self.counters.rxerrors) + counter.add(self.shm.nd_protocol_errors) + counter.add(self.shm.rxerrors) return nil end self._eth_header = ethernet:new({ src = self._config.local_mac, @@ -279,7 +270,7 @@ local function na (self, dgram, eth, ipv6, icmp) type = 0x86dd }) self._logger:log(string.format("Resolved next-hop %s to %s", ipv6:ntop(self._config.next_hop), ethernet:ntop(option[1]:option():addr()))) - counter.set(self.counters.status, 1) -- Link up + counter.set(self.shm.status, 1) -- Link up return nil end @@ -293,8 +284,8 @@ local function from_south (self, p) local eth, ipv6, icmp = unpack(dgram:stack()) if ipv6:hop_limit() ~= 255 then -- Avoid off-link spoofing as per RFC - counter.add(self.counters.nd_protocol_errors) - counter.add(self.counters.rxerrors) + counter.add(self.shm.nd_protocol_errors) + counter.add(self.shm.rxerrors) return nil end local result @@ -341,7 +332,7 @@ function nd_light:push () -- Drop packets until ND for the next-hop -- has completed. packet.free(link.receive(l_in)) - counter.add(self.counters.txdrop) + counter.add(self.shm.txdrop) else local p = cache.p p[0] = link.receive(l_in) @@ -350,7 +341,7 @@ function nd_light:push () link.transmit(l_out, p[0]) else packet.free(p[0]) - counter.add(self.counters.txerrors) + counter.add(self.shm.txerrors) end end end @@ -362,8 +353,6 @@ function nd_light:stop () self._next_hop.packet = nil packet.free(self._sna.packet) self._sna.packet = nil - -- delete counters - for name, _ in pairs(self.counters) do counter.delete(name) end end function selftest () diff --git a/src/apps/keyed_ipv6_tunnel/tunnel.lua b/src/apps/keyed_ipv6_tunnel/tunnel.lua index e29951fc10..bf339e3083 100644 --- a/src/apps/keyed_ipv6_tunnel/tunnel.lua +++ b/src/apps/keyed_ipv6_tunnel/tunnel.lua @@ -103,12 +103,6 @@ end SimpleKeyedTunnel = {} -local provided_counters = { - 'type', 'dtime', 'rxerrors', - 'length_errors', 'protocol_errors', 'cookie_errors', - 'remote_address_errors', 'local_address_errors' -} - function SimpleKeyedTunnel:new (arg) local conf = arg and config.parse_app_arg(arg) or {} -- required fields: @@ -170,20 +164,18 @@ function SimpleKeyedTunnel:new (arg) header[HOP_LIMIT_OFFSET] = conf.hop_limit end - 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()) - local o = { header = header, remote_address = remote_address, local_address = local_address, remote_cookie = remote_cookie[0], - counters = counters + shm = { rxerrors = {counter}, + length_errors = {counter}, + protocol_errors = {counter}, + cookie_errors = {counter}, + remote_address_errors = {counter}, + local_address_errors = {counter} } } return setmetatable(o, {__index = SimpleKeyedTunnel}) @@ -213,18 +205,18 @@ function SimpleKeyedTunnel:push() local drop = true repeat if p.length < HEADER_SIZE then - counter.add(self.counters.length_errors) + counter.add(self.shm.length_errors) break end local next_header = ffi.cast(next_header_ctype, p.data + NEXT_HEADER_OFFSET) if next_header[0] ~= L2TPV3_NEXT_HEADER then - counter.add(self.counters.protocol_errors) + counter.add(self.shm.protocol_errors) break end local cookie = ffi.cast(pcookie_ctype, p.data + COOKIE_OFFSET) if cookie[0] ~= self.remote_cookie then - counter.add(self.counters.cookie_errors) + counter.add(self.shm.cookie_errors) break end @@ -232,7 +224,7 @@ function SimpleKeyedTunnel:push() if remote_address[0] ~= self.remote_address[0] or remote_address[1] ~= self.remote_address[1] then - counter.add(self.counters.remote_address_errors) + counter.add(self.shm.remote_address_errors) break end @@ -240,7 +232,7 @@ function SimpleKeyedTunnel:push() if local_address[0] ~= self.local_address[0] or local_address[1] ~= self.local_address[1] then - counter.add(self.counters.local_address_errors) + counter.add(self.shm.local_address_errors) break end @@ -248,7 +240,7 @@ function SimpleKeyedTunnel:push() until true if drop then - counter.add(self.counters.rxerrors) + counter.add(self.shm.rxerrors) -- discard packet packet.free(p) else @@ -258,11 +250,6 @@ function SimpleKeyedTunnel:push() end end -function SimpleKeyedTunnel:stop () - -- delete counters - for name, _ in pairs(self.counters) do counter.delete(name) end -end - -- prepare header template to be used by all apps prepare_header_template() diff --git a/src/apps/packet_filter/pcap_filter.lua b/src/apps/packet_filter/pcap_filter.lua index 43e1a3917d..c832b60945 100644 --- a/src/apps/packet_filter/pcap_filter.lua +++ b/src/apps/packet_filter/pcap_filter.lua @@ -15,10 +15,6 @@ local pf = require("pf") -- pflua PcapFilter = {} -local provided_counters = { - 'dtime', 'type', 'rxerrors', 'sessions_established' -} - -- PcapFilter is an app that drops all packets that don't match a -- specified filter expression. -- @@ -36,16 +32,10 @@ function PcapFilter:new (conf) local o = { -- XXX Investigate the latency impact of filter compilation. accept_fn = pf.compile_filter(conf.filter), - state_table = conf.state_table or false + state_table = conf.state_table or false, + shm = { rxerrors = {counter}, sessions_established = {counter} } } if conf.state_table then conntrack.define(conf.state_table) end - -- Create counters - o.counters = {} - for _, name in ipairs(provided_counters) do - o.counters[name] = counter.open(name) - end - counter.set(o.counters.type, 0x1001) -- Virtual interface - counter.set(o.counters.dtime, C.get_unix_time()) return setmetatable(o, { __index = PcapFilter }) end @@ -62,21 +52,16 @@ function PcapFilter:push () elseif self.accept_fn(p.data, p.length) then if spec then spec:track(self.state_table) - counter.add(self.counters.sessions_established) + counter.add(self.shm.sessions_established) end link.transmit(o, p) else packet.free(p) - counter.add(self.counters.rxerrors) + counter.add(self.shm.rxerrors) end end end -function PcapFilter:stop () - -- delete counters - for name, _ in pairs(self.counters) do counter.delete(name) end -end - -- Testing local pcap = require("apps.pcap.pcap") diff --git a/src/apps/rate_limiter/rate_limiter.lua b/src/apps/rate_limiter/rate_limiter.lua index 421e5ec3e0..34a7684661 100644 --- a/src/apps/rate_limiter/rate_limiter.lua +++ b/src/apps/rate_limiter/rate_limiter.lua @@ -23,10 +23,6 @@ local floor, min = math.floor, math.min RateLimiter = {} -local provided_counters = { - 'type', 'dtime', 'txdrop' -} - -- Source produces synthetic packets of such size local PACKET_SIZE = 60 @@ -35,18 +31,12 @@ function RateLimiter:new (arg) assert(conf.rate) assert(conf.bucket_capacity) conf.initial_capacity = conf.initial_capacity or conf.bucket_capacity - 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()) local o = { rate = conf.rate, bucket_capacity = conf.bucket_capacity, bucket_content = conf.initial_capacity, - counters = counters + shm = { txdrop = {counter} } } return setmetatable(o, {__index=RateLimiter}) end @@ -93,17 +83,12 @@ function RateLimiter:push () link.transmit(o, p) else -- discard packet - counter.add(self.counters.txdrop) + counter.add(self.shm.txdrop) packet.free(p) end end end -function RateLimiter:stop () - -- delete counters - for name, _ in pairs(self.counters) do counter.delete(name) end -end - local function compute_effective_rate (rl, rate, snapshot) local elapsed_time = (tonumber(C.get_time_ns()) - snapshot.time) / 1e9 diff --git a/src/apps/socket/raw.lua b/src/apps/socket/raw.lua index 6cf60c7e33..739a6258e4 100644 --- a/src/apps/socket/raw.lua +++ b/src/apps/socket/raw.lua @@ -23,12 +23,6 @@ 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) @@ -48,15 +42,16 @@ function RawSocket:new (ifname) sock:close() error(err) end - 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}, + shm = { rxbytes = {counter}, + rxpackets = {counter}, + rxmcast = {counter}, + rxbcast = {counter}, + txbytes = {counter}, + txpackets = {counter}, + txmcast = {counter}, + txbcast = {counter} }}, {__index = RawSocket}) end @@ -81,13 +76,13 @@ function RawSocket:receive () 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) + counter.add(self.shm.rxbytes, sz) + counter.add(self.shm.rxpackets) if ethernet:is_mcast(p.data) then - counter.add(self.counters.rxmcast) + counter.add(self.shm.rxmcast) end if ethernet:is_bcast(p.data) then - counter.add(self.counters.rxbcast) + counter.add(self.shm.rxbcast) end return packet.clone(p) end @@ -98,13 +93,13 @@ 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) + counter.add(self.shm.txbytes, p.length) + counter.add(self.shm.txpackets) if ethernet:is_mcast(p.data) then - counter.add(self.counters.txmcast) + counter.add(self.shm.txmcast) end if ethernet:is_bcast(p.data) then - counter.add(self.counters.txbcast) + counter.add(self.shm.txbcast) end packet.free(p) end @@ -128,8 +123,6 @@ 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 9444e3f5a9..56b37fe70f 100644 --- a/src/apps/tap/tap.lua +++ b/src/apps/tap/tap.lua @@ -16,12 +16,6 @@ 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") @@ -35,13 +29,16 @@ function Tap:new (name) sock:close() error("Error opening /dev/net/tun: " .. tostring(err)) end - 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}, + return setmetatable({sock = sock, + name = name, + shm = { rxbytes = {counter}, + rxpackets = {counter}, + rxmcast = {counter}, + rxbcast = {counter}, + txbytes = {counter}, + txpackets = {counter}, + txmcast = {counter}, + txbcast = {counter} }}, {__index = Tap}) end @@ -63,13 +60,13 @@ function Tap:pull () end p.length = len link.transmit(l, p) - counter.add(self.counters.rxbytes, len) - counter.add(self.counters.rxpackets) + counter.add(self.shm.rxbytes, len) + counter.add(self.shm.rxpackets) if ethernet:is_mcast(p.data) then - counter.add(self.counters.rxmcast) + counter.add(self.shm.rxmcast) end if ethernet:is_bcast(p.data) then - counter.add(self.counters.rxbcast) + counter.add(self.shm.rxbcast) end end end @@ -88,13 +85,13 @@ 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) + counter.add(self.shm.txbytes, len) + counter.add(self.shm.txpackets) if ethernet:is_mcast(p.data) then - counter.add(self.counters.txmcast) + counter.add(self.shm.txmcast) end if ethernet:is_bcast(p.data) then - counter.add(self.counters.txbcast) + counter.add(self.shm.txbcast) end -- The write completed so dequeue it from the link and free the packet link.receive(l) @@ -104,8 +101,6 @@ 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 63e972b141..0c97308378 100644 --- a/src/apps/vhost/vhost_user.lua +++ b/src/apps/vhost/vhost_user.lua @@ -28,12 +28,6 @@ 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, @@ -48,7 +42,15 @@ function VhostUser:new (args) function () self:process_qemu_requests() end, 5e8,-- 500 ms 'non-repeating' - ) + ), + -- counters + shm = { rxbytes = {counter}, + rxpackets = {counter}, + rxmcast = {counter}, + rxdrop = {counter}, + txbytes = {counter}, + txpackets = {counter}, + txmcast = {counter} } } self = setmetatable(o, {__index = VhostUser}) self.dev = net_device.VirtioNetDevice:new(self, args.disable_mrg_rxbuf) @@ -59,13 +61,6 @@ 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 @@ -82,9 +77,6 @@ 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 () diff --git a/src/core/app.lua b/src/core/app.lua index 1c01a029e5..30d1c0c7d8 100644 --- a/src/core/app.lua +++ b/src/core/app.lua @@ -7,7 +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 shm = require("core.shm") local histogram = require('core.histogram') local counter = require("core.counter") local zone = require("jit.zone") @@ -30,11 +30,11 @@ link_table, link_array = {}, {} configuration = config.new() -- Counters for statistics. -breaths = counter.open("engine/breaths") -- Total breaths taken -frees = counter.open("engine/frees") -- Total packets freed -freebits = counter.open("engine/freebits") -- Total packet bits freed (for 10GbE) -freebytes = counter.open("engine/freebytes") -- Total packet bytes freed -configs = counter.open("engine/configs") -- Total configurations loaded +breaths = counter.create("engine/breaths.counter") -- Total breaths taken +frees = counter.create("engine/frees.counter") -- Total packets freed +freebits = counter.create("engine/freebits.counter") -- Total packet bits freed (for 10GbE) +freebytes = counter.create("engine/freebytes.counter") -- Total packet bytes freed +configs = counter.create("engine/configs.counter") -- Total configurations loaded -- Breathing regluation to reduce CPU usage when idle by calling usleep(3). -- @@ -70,8 +70,6 @@ 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. @@ -85,7 +83,6 @@ function with_restart (app, method) else status, result = true, method(app) end - shm.path = oldshm return status, result end @@ -165,11 +162,10 @@ function apply_config_actions (actions, conf) local ops = {} function ops.stop (name) 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 + if app_table[name].shm then + shm.delete_frame(app_table[name].shm) end end function ops.keep (name) @@ -180,10 +176,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 = "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))) @@ -192,11 +185,14 @@ 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 app.zone = zone + if app.shm then + app.shm.dtime = {counter, C.get_unix_time()} + app.shm = shm.create_frame("apps/"..name, app.shm) + end end function ops.restart (name) ops.stop(name) @@ -206,10 +202,7 @@ 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 @@ -268,7 +261,7 @@ function main (options) local breathe = breathe if options.measure_latency or options.measure_latency == nil then - local latency = histogram.create('engine/latency', 1e-6, 1e0) + local latency = histogram.create('engine/latency.histogram', 1e-6, 1e0) breathe = latency:wrap_thunk(breathe, now) end @@ -519,35 +512,6 @@ 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 -- XXX add graphviz() function back. diff --git a/src/core/counter.lua b/src/core/counter.lua index c7d17b0eb1..bfe32e2470 100644 --- a/src/core/counter.lua +++ b/src/core/counter.lua @@ -25,10 +25,13 @@ module(..., package.seeall) +local lib = require("core.lib") local shm = require("core.shm") local ffi = require("ffi") require("core.counter_h") +type = shm.register('counter', getfenv()) + local counter_t = ffi.typeof("struct counter") -- Double buffering: @@ -43,25 +46,28 @@ local public = {} local private = {} local numbers = {} -- name -> number -function open (name, readonly) - local qname = shm.resolve(name) - if numbers[qname] then return private[numbers[qname]] end +function create (name, initval) + if numbers[name] then return private[numbers[name]] end local n = #public+1 - if readonly then - public[n] = shm.open(name, counter_t, readonly) - private[n] = public[#public] -- use counter directly - else - public[n] = shm.create(name, counter_t) - private[n] = ffi.new(counter_t) - end - numbers[qname] = n + public[n] = shm.create(name, counter_t) + private[n] = ffi.new(counter_t) + numbers[name] = n + if initval then set(private[n], initval) end + return private[n] +end + +function open (name) + if numbers[name] then return private[numbers[name]] end + local n = #public+1 + public[n] = shm.open(name, counter_t, 'readonly') + private[n] = public[#public] -- use counter directly + numbers[name] = n return private[n] end function delete (name) - local qname = shm.resolve(name) - local number = numbers[qname] - if not number then error("counter not found for deletion: " .. qname) end + local number = numbers[name] + if not number then error("counter not found for deletion: " .. name) end -- Free shm object shm.unmap(public[number]) -- If we "own" the counter for writing then we unlink it too. @@ -69,7 +75,7 @@ function delete (name) shm.unlink(name) end -- Free local state - numbers[qname] = false + numbers[name] = false public[number] = false private[number] = false end @@ -85,10 +91,14 @@ function set (counter, value) counter.c = value end function add (counter, value) counter.c = counter.c + (value or 1) end function read (counter) return counter.c end +ffi.metatype( counter_t, + {__tostring = + function (counter) return lib.comma_value(counter.c) end}) + function selftest () print("selftest: core.counter") - local a = open("core.counter/counter/a") - local b = open("core.counter/counter/b") + local a = create("core.counter/counter/a") + local b = create("core.counter/counter/b") local a2 = shm.create("core.counter/counter/a", counter_t, true) set(a, 42) set(b, 43) diff --git a/src/core/histogram.lua b/src/core/histogram.lua index d865d1850f..6971776451 100644 --- a/src/core/histogram.lua +++ b/src/core/histogram.lua @@ -1,52 +1,15 @@ +-- Use of this source code is governed by the Apache 2.0 license; see COPYING. + -- histogram.lua -- a histogram with logarithmic buckets --- --- API: --- histogram.new(min, max) => histogram --- Make a new histogram, with buckets covering the range from MIN to MAX. --- The range between MIN and MAX will be divided logarithmically. --- --- histogram.create(name, min, max) => histogram --- Create a histogram as in new(), but also map it into --- /var/run/snabb/PID/NAME, exposing it for analysis by other processes. --- If the file exists already, it will be cleared. --- --- histogram.open(pid, name) => histogram --- Open a histogram mapped as /var/run/snabb/PID/NAME. --- --- histogram.add(histogram, measurement) --- Add a measurement to a histogram. --- --- histogram.iterate(histogram, prev) --- When used as "for count, lo, hi in histogram:iterate()", --- visits all buckets in a histogram in order from lowest to --- highest. COUNT is the number of samples recorded in that bucket, --- and LO and HI are the lower and upper bounds of the bucket. Note --- that COUNT is an unsigned 64-bit integer; to get it as a Lua --- number, use tonumber(). --- --- If PREV is given, it should be a snapshot of the previous version --- of the histogram. In that case, the COUNT values will be --- returned as a difference between their values in HISTOGRAM and --- their values in PREV. --- --- histogram.snapshot(a, b) --- Copy out the contents of A into B and return B. If B is not given, --- the result will be a fresh histogram. --- --- histogram.clear(a) --- Clear the counters in A. --- --- histogram.wrap_thunk(histogram, thunk, now) --- Return a closure that wraps THUNK, but which measures the difference --- between calls to NOW before and after the thunk, recording that --- difference into HISTOGRAM. --- + module(...,package.seeall) local ffi = require("ffi") local shm = require("core.shm") local log, floor, max, min = math.log, math.floor, math.max, math.min +type = shm.register('histogram', getfenv()) + -- Fill a 4096-byte page with buckets. 4096/8 = 512, minus the three -- header words means 509 buckets. The first and last buckets are catch-alls. local bucket_count = 509 diff --git a/src/core/link.lua b/src/core/link.lua index aacff1e741..6fcc9fb58c 100644 --- a/src/core/link.lua +++ b/src/core/link.lua @@ -15,6 +15,7 @@ local counter = require("core.counter") require("core.counter_h") require("core.link_h") +local link_t = ffi.typeof("struct link") local band = require("bit").band @@ -26,9 +27,9 @@ local provided_counters = { } function new (name) - local r = shm.create("links/"..name, "struct link") + local r = ffi.new(link_t) for _, c in ipairs(provided_counters) do - r.stats[c] = counter.open("counters/"..name.."/"..c) + r.stats[c] = counter.create("links/"..name.."/"..c..".counter") end counter.set(r.stats.dtime, C.get_unix_time()) return r @@ -36,9 +37,8 @@ end function free (r, name) for _, c in ipairs(provided_counters) do - counter.delete("counters/"..name.."/"..c) + counter.delete("links/"..name.."/"..c..".counter") end - shm.unmap(r) shm.unlink("links/"..name) end diff --git a/src/core/main.lua b/src/core/main.lua index 112cdd5491..da22c06f9e 100644 --- a/src/core/main.lua +++ b/src/core/main.lua @@ -135,7 +135,7 @@ end -- Cleanup after Snabb process. function shutdown (pid) if not _G.developer_debug then - shm.unlink("//"..pid) + shm.unlink("/"..pid) end end diff --git a/src/core/shm.lua b/src/core/shm.lua index 77b5a93917..46298a7b29 100644 --- a/src/core/shm.lua +++ b/src/core/shm.lua @@ -2,66 +2,6 @@ -- shm.lua -- shared memory alternative to ffi.new() --- API: --- shm.create(name, type) => ptr --- Map a shared object into memory via a hierarchical name, creating it --- if needed. --- shm.open(name, type[, readonly]) => ptr --- Map a shared object into memory via a hierarchical name. Fail if --- the shared object does not already exist. --- shm.unmap(ptr) --- Delete a memory mapping. --- shm.unlink(path) --- Unlink a subtree of objects from the filesystem. --- --- (See NAME SYNTAX below for recognized name formats.) --- --- Example: --- local freelist = shm.map("engine/freelist/packet", "struct freelist") --- --- This is like ffi.new() except that separate calls to map() for the --- same name will each return a new mapping of the same shared --- memory. Different processes can share memory by mapping an object --- with the same name (and type). Each process can map any object any --- number of times. --- --- Mappings are deleted on process termination or with an explicit unmap: --- shm.unmap(freelist) --- --- Names are unlinked from objects that are no longer needed: --- shm.unlink("engine/freelist/packet") --- shm.unlink("engine") --- --- Object memory is freed when the name is unlinked and all mappings --- have been deleted. --- --- Behind the scenes the objects are backed by files on ram disk: --- /var/run/snabb/$pid/engine/freelist/packet --- --- and accessed with the equivalent of POSIX shared memory (shm_overview(7)). --- --- The practical limit on the number of objects that can be mapped --- will depend on the operating system limit for memory mappings. --- On Linux the default limit is 65,530 mappings: --- $ sysctl vm.max_map_count --- vm.max_map_count = 65530 - --- NAME SYNTAX: --- --- Names can be fully qualified, abbreviated to be within the current --- process, or further abbreviated to be relative to the current value --- of the 'path' variable. Here are examples of names and how they are --- resolved: --- Fully qualified: --- //1234/foo/bar => /var/run/snabb/1234/foo/bar --- Path qualified: --- /foo/bar => /var/run/snabb/$pid/foo/bar --- Local: --- bar => /var/run/snabb/$pid/$path/bar --- .. where $pid is the PID of this process and $path is the current --- value of the 'path' variable in this module. - - module(..., package.seeall) local ffi = require("ffi") @@ -71,7 +11,6 @@ local const = require("syscall.linux.constants") -- Root directory where the object tree is created. root = "/var/run/snabb" -path = "" -- Table (address->size) of all currently mapped objects. mappings = {} @@ -117,10 +56,9 @@ function open (name, type, readonly) end function resolve (name) - local q, p = name:match("^(/*)(.*)") -- split qualifier (/ or //) + local q, p = name:match("^(/*)(.*)") -- split qualifier (/) local result = p - if q == '' and path ~= '' then result = path.."/"..result end - if q ~= '//' then result = tostring(S.getpid()).."/"..result end + if q ~= '/' then result = tostring(S.getpid()).."/"..result end return result end @@ -170,53 +108,102 @@ function children (name) return S.util.dirtable(root.."/"..resolve(name), true) or {} end --- Create an additional name for an existing object. -function alias (toname, fromname) - assert(S.symlink(root.."/"..resolve(toname), root.."/"..resolve(fromname)), - "alias symlink failed") +-- Type registry for modules that implement abstract shm objects. +types = {} +function register (type, module) + assert(module, "Must supply module") + assert(not types[type], "Duplicate shm type: "..type) + types[type] = module + return type +end + +-- Create a directory of shm objects defined by specs under path. +function create_frame (path, specs) + local frame = {} + frame.specs = specs + frame.path = path.."/" + for name, spec in pairs(specs) do + assert(frame[name] == nil, "shm: duplicate name: "..name) + local module = spec[1] + local initargs = lib.array_copy(spec) + table.remove(initargs, 1) -- strip type name from spec + frame[name] = module.create(frame.path..name.."."..module.type, + unpack(initargs)) + end + return frame +end + +-- Open a directory of shm objects for reading, determine their types by file +-- extension. +function open_frame (path) + local frame = {} + frame.specs = {} + frame.path = path.."/" + frame.readonly = true + for _, file in ipairs(children(path)) do + local name, type = file:match("(.*)[.](.*)$") + local module = types[type] + if module then + assert(frame[name] == nil, "shm: duplicate name: "..name) + frame[name] = module.open(frame.path..file) + frame.specs[name] = {module} + end + end + return frame end +-- Delete/unmap a frame of shm objects. The frame's directory is unlinked if +-- the frame was created by create_frame. +function delete_frame (frame) + for name, spec in pairs(frame.specs) do + local module = spec[1] + if rawget(module, 'delete') then + module.delete(frame.path..name.."."..module.type) + else + unmap(frame[name]) + end + end + if not frame.readonly then + unlink(frame.path) + end +end + + function selftest () print("selftest: shm") - print("checking paths..") - path = 'foo/bar' + + print("checking resolve..") pid = tostring(S.getpid()) - local p1 = resolve("//"..pid.."/foo/bar/baz/beer") - local p2 = resolve("/foo/bar/baz/beer") - local p3 = resolve("baz/beer") + local p1 = resolve("/"..pid.."/foo/bar/baz/beer") + local p2 = resolve("foo/bar/baz/beer") assert(p1 == p2, p1.." ~= "..p2) - assert(p1 == p3, p1.." ~= "..p3) print("checking shared memory..") - path = 'shm/selftest' - local name = "obj" + local name = "shm/selftest/obj" print("create "..name) local p1 = create(name, "struct { int x, y, z; }") local p2 = create(name, "struct { int x, y, z; }") - alias(name, name..".alias") - local p3 = create(name..".alias", "struct { int x, y, z; }") assert(p1 ~= p2) assert(p1.x == p2.x) p1.x = 42 assert(p1.x == p2.x) - assert(p1.x == p3.x) assert(unlink(name)) unmap(p1) unmap(p2) -- Test that we can open and cleanup many objects print("checking many objects..") - path = 'shm/selftest/manyobj' + local path = 'shm/selftest/manyobj' local n = 10000 local objs = {} for i = 1, n do - table.insert(objs, create("obj/"..i, "uint64_t[1]")) + table.insert(objs, create(path.."/"..i, "uint64_t[1]")) end print(n.." objects created") for i = 1, n do unmap(objs[i]) end print(n.." objects unmapped") - assert((#children("/shm/selftest/manyobj/obj")) == n, "child count mismatch") - assert(unlink("/")) + assert((#children(path)) == n, "child count mismatch") + assert(unlink("shm")) print("selftest ok") end diff --git a/src/lib/virtio/net_device.lua b/src/lib/virtio/net_device.lua index 14883b1e1a..3676d49fc3 100644 --- a/src/lib/virtio/net_device.lua +++ b/src/lib/virtio/net_device.lua @@ -153,7 +153,7 @@ end function VirtioNetDevice:rx_packet_end(header_id, total_size, rx_p) local l = self.owner.output.tx - local counters = self.owner.counters + local counters = self.owner.shm if l then if band(self.rx_hdr_flags, C.VIO_NET_HDR_F_NEEDS_CSUM) ~= 0 and -- Bounds-check the checksum area @@ -264,7 +264,7 @@ function VirtioNetDevice:tx_buffer_add(tx_p, addr, len) end function VirtioNetDevice:tx_packet_end(header_id, total_size, tx_p) - local counters = self.owner.counters + local counters = self.owner.shm counter.add(counters.txbytes, tx_p.length) counter.add(counters.txpackets) if ethernet:is_mcast(tx_p.data) then @@ -340,7 +340,7 @@ function VirtioNetDevice:tx_buffer_add_mrg_rxbuf(tx_p, addr, len) end function VirtioNetDevice:tx_packet_end_mrg_rxbuf(header_id, total_size, tx_p) - local counters = self.owner.counters + local counters = self.owner.shm -- free the packet only when all its data is processed if self.tx.finished then counter.add(counters.txbytes, tx_p.length) diff --git a/src/program/top/README b/src/program/top/README index ce815d6f89..fc7d0aa34d 100644 --- a/src/program/top/README +++ b/src/program/top/README @@ -3,8 +3,11 @@ Usage: -h, --help Print usage information. - -c, --counters - Print counters of object by and exit. + -l, --list + List shared memory objects in and exit. + Examples: snabb top -l engine + snabb top -l apps/foo + snabb top -l "links/foo.tx -> bar.rx" 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 8334d76be3..0bf9322d91 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", counters = "c" + help = "h", list = "l" } function clearterm () io.write('\027[2J') end @@ -21,19 +21,19 @@ function run (args) local opt = {} local object = nil 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.l (arg) object = arg end + args = lib.dogetopt(args, opt, "hl:", 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) + if object then list_shm(target_pid, object) else top(target_pid) end ordered_exit(0) end function select_snabb_instance (pid) - local instances = shm.children("//") + local instances = shm.children("/") if pid then -- Try to use given pid for _, instance in ipairs(instances) do @@ -51,33 +51,30 @@ function select_snabb_instance (pid) end function ordered_exit (value) - shm.unlink("//"..S.getpid()) -- Unlink own shm tree to avoid clutter + 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))}) +function list_shm (pid, object) + local frame = shm.open_frame("/"..pid.."/"..object) + local sorted = {} + for name, _ in pairs(frame) do table.insert(sorted, name) end + table.sort(sorted) + for _, name in ipairs(sorted) do + if name ~= 'path' and name ~= 'specs' and name ~= 'readonly' then + print_row({30, 30}, {name, tostring(frame[name])}) + end end + shm.delete_frame(frame) end function top (instance_pid) - local instance_tree = "//"..instance_pid + local instance_tree = "/"..instance_pid local counters = open_counters(instance_tree) local configs = 0 local last_stats = nil while (true) do - if configs < counter.read(counters.configs) then + if configs < counter.read(counters.engine.configs) then -- If a (new) config is loaded we (re)open the link counters. open_link_counters(counters, instance_tree) end @@ -97,41 +94,31 @@ end function open_counters (tree) local counters = {} - for _, name in ipairs({"configs", "breaths", "frees", "freebytes"}) do - counters[name] = counter.open(tree.."/engine/"..name, 'readonly') - end - local success, latency = pcall(histogram.open, tree..'/engine/latency') - if success then counters.latency = latency end + counters.engine = shm.open_frame(tree.."/engine") counters.links = {} -- These will be populated on demand. return counters end function open_link_counters (counters, tree) -- Unmap and clear existing link counters. - for linkspec, _ in pairs(counters.links) do - for _, name - in ipairs({"rxpackets", "txpackets", "rxbytes", "txbytes", "txdrop"}) do - counter.delete(tree.."/counters/"..linkspec.."/"..name) - end + for _, link_frame in pairs(counters.links) do + shm.delete_frame(link_frame) end counters.links = {} -- Open current link counters. for _, linkspec in ipairs(shm.children(tree.."/links")) do - counters.links[linkspec] = {} - for _, name - in ipairs({"rxpackets", "txpackets", "rxbytes", "txbytes", "txdrop"}) do - counters.links[linkspec][name] = - counter.open(tree.."/counters/"..linkspec.."/"..name, 'readonly') - end + counters.links[linkspec] = shm.open_frame(tree.."/links/"..linkspec) end end function get_stats (counters) local new_stats = {} for _, name in ipairs({"configs", "breaths", "frees", "freebytes"}) do - new_stats[name] = counter.read(counters[name]) + new_stats[name] = counter.read(counters.engine[name]) + end + if counters.engine.latency then + new_stats.latency = counters.engine.latency:snapshot() end - if counters.latency then new_stats.latency = counters.latency:snapshot() end new_stats.links = {} for linkspec, link in pairs(counters.links) do new_stats.links[linkspec] = {} @@ -153,7 +140,7 @@ function print_global_metrics (new_stats, last_stats) {float_s(frees / 1000), float_s(bytes / (1000^3)), tostring(breaths)}) end -function summarize_latency(histogram, prev) +function summarize_latency (histogram, prev) local total = histogram.total if prev then total = total - prev.total end if total == 0 then return 0, 0, 0 end @@ -174,7 +161,6 @@ function print_latency_metrics (new_stats, last_stats) local min, avg, max = summarize_latency(cur, prev) print_row(global_metrics_row, {"Min breath (us)", "Average", "Maximum"}) - print_row(global_metrics_row, {float_s(min*1e6), float_s(avg*1e6), float_s(max*1e6)}) print("\n") From 70ad231533666f4e09c7d0213f7e0812104a464a Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Thu, 21 Jul 2016 14:50:18 +0200 Subject: [PATCH 2/2] snabb top: remove obsolete unlinking of runtime files (supervisor). --- src/program/top/top.lua | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/program/top/top.lua b/src/program/top/top.lua index 0bf9322d91..250af23892 100644 --- a/src/program/top/top.lua +++ b/src/program/top/top.lua @@ -29,7 +29,6 @@ function run (args) if object then list_shm(target_pid, object) else top(target_pid) end - ordered_exit(0) end function select_snabb_instance (pid) @@ -47,12 +46,7 @@ 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 - ordered_exit(1) -end - -function ordered_exit (value) - shm.unlink("/"..S.getpid()) -- Unlink own shm tree to avoid clutter - os.exit(value) + os.exit(1) end function list_shm (pid, object)