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/bridge/flooding.lua b/src/apps/bridge/flooding.lua index 6bac5f9196..7001ad7c83 100644 --- a/src/apps/bridge/flooding.lua +++ b/src/apps/bridge/flooding.lua @@ -21,20 +21,18 @@ function bridge:new (arg) end function bridge:push() - local src_ports = self._src_ports + local ports = self._ports local dst_ports = self._dst_ports - local output = self.output local i = 1 - while src_ports[i] do - local src_port = src_ports[i] - local l_in = self.input[src_port] + while ports[i] do + local l_in = ports[i].l_in while not empty(l_in) do - local ports = dst_ports[src_port] + local dst = dst_ports[i] local p = receive(l_in) - transmit(output[ports[1]], p) + transmit(ports[dst[1]].l_out, p) local j = 2 - while ports[j] do - transmit(output[ports[j]], clone(p)) + while dst[j] do + transmit(ports[dst[j]].l_out, clone(p)) j = j + 1 end end diff --git a/src/apps/intel/intel1g.lua b/src/apps/intel/intel1g.lua index e1c125e14c..8253d28fc2 100644 --- a/src/apps/intel/intel1g.lua +++ b/src/apps/intel/intel1g.lua @@ -632,7 +632,7 @@ end -- function Intel1g:new() function selftest () print("selftest: Intel1g") - local pciaddr = os.getenv("SNABB_PCI_INTEL1G0") + local pciaddr = lib.getenv("SNABB_PCI_INTEL1G0") if not pciaddr then print("SNABB_PCI_INTEL1G0 not set") os.exit(engine.test_skipped_code) 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..578b22f10c 100644 --- a/src/apps/vhost/vhost_user.lua +++ b/src/apps/vhost/vhost_user.lua @@ -13,7 +13,6 @@ 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 net_device= require("lib.virtio.net_device") local timer = require("core.timer") @@ -28,12 +27,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, @@ -51,7 +44,9 @@ function VhostUser:new (args) ) } self = setmetatable(o, {__index = VhostUser}) - self.dev = net_device.VirtioNetDevice:new(self, args.disable_mrg_rxbuf) + self.dev = net_device.VirtioNetDevice:new(self, + args.disable_mrg_rxbuf, + args.disable_indirect_desc) if args.is_server then self.listen_socket = C.vhost_user_listen(self.socket_path) assert(self.listen_socket >= 0) @@ -59,13 +54,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 +70,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/bench/snabbnfv-iperf-1500-crypto b/src/bench/snabbnfv-iperf-1500-crypto index 7cbfb2fa24..64d37ad639 100755 --- a/src/bench/snabbnfv-iperf-1500-crypto +++ b/src/bench/snabbnfv-iperf-1500-crypto @@ -1,6 +1,6 @@ #!/usr/bin/env bash set -e -out=$(program/snabbnfv/selftest.sh bench 1500 \ - program/snabbnfv/test_fixtures/nfvconfig/test_functions/crypto.ports) +export SNABB_IPERF_BENCH_CONF=program/snabbnfv/test_fixtures/nfvconfig/test_functions/crypto.ports +out=$(program/snabbnfv/selftest.sh bench 1500) # Extract floating point Gbits number from output. echo "$out" | grep IPERF-1500 | cut -d " " -f 2 diff --git a/src/bench/snabbnfv-iperf-1500-tunnel+crypto b/src/bench/snabbnfv-iperf-1500-tunnel+crypto index 7967c28d26..bfda596eee 100755 --- a/src/bench/snabbnfv-iperf-1500-tunnel+crypto +++ b/src/bench/snabbnfv-iperf-1500-tunnel+crypto @@ -1,6 +1,6 @@ #!/usr/bin/env bash set -e -out=$(program/snabbnfv/selftest.sh bench 1500 \ - program/snabbnfv/test_fixtures/nfvconfig/test_functions/crypto-tunnel.ports) +export SNABB_IPERF_BENCH_CONF=program/snabbnfv/test_fixtures/nfvconfig/test_functions/crypto-tunnel.ports +out=$(program/snabbnfv/selftest.sh bench 1500) # Extract floating point Gbits number from output. echo "$out" | grep IPERF-1500 | cut -d " " -f 2 diff --git a/src/bench/snabbnfv-loadgen-dpdk b/src/bench/snabbnfv-loadgen-dpdk index 3ce491d208..08c8eac489 100755 --- a/src/bench/snabbnfv-loadgen-dpdk +++ b/src/bench/snabbnfv-loadgen-dpdk @@ -1,5 +1,5 @@ #!/usr/bin/env bash set -e -out=$(timeout 120 program/snabbnfv/packetblaster_bench.sh) +out=$(timeout 120 program/snabbnfv/dpdk_bench.sh) # Extract floating point Mpps number from output. echo "$out" | tail -n 1 | cut -f 2 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..5930142313 100644 --- a/src/core/main.lua +++ b/src/core/main.lua @@ -134,8 +134,8 @@ end -- Cleanup after Snabb process. function shutdown (pid) - if not _G.developer_debug then - shm.unlink("//"..pid) + if not _G.developer_debug and not lib.getenv("SNABB_SHM_KEEP") then + shm.unlink("/"..pid) end end diff --git a/src/core/packet.lua b/src/core/packet.lua index 143bfdf920..1f8fc579a8 100644 --- a/src/core/packet.lua +++ b/src/core/packet.lua @@ -129,12 +129,6 @@ function free (p) free_internal(p) end --- Return pointer to packet data. -function data (p) return p.data end - --- Return packet data length. -function length (p) return p.length end - -- Set packet data length. function resize (p, len) assert(len <= max_payload, "packet payload overflow") diff --git a/src/core/shm.lua b/src/core/shm.lua index 77b5a93917..929eebedec 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") @@ -70,8 +10,7 @@ local S = require("syscall") local const = require("syscall.linux.constants") -- Root directory where the object tree is created. -root = "/var/run/snabb" -path = "" +root = os.getenv("SNABB_SHM_ROOT") or "/var/run/snabb" -- 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/doc/testing.md b/src/doc/testing.md index b2b7beb761..839f0f9b21 100644 --- a/src/doc/testing.md +++ b/src/doc/testing.md @@ -85,12 +85,16 @@ the tests: * `SNABB_TELNET0`, `SNABB_TELNET1`—Optional telnet ports to use in tests that require them. The default is 5000 and 5001. -* `SNABB_PCAP`—Optional PCAP file for use in tests that require one. The - default depends on the individual test. - * `SNABB_PERF_SAMPLESIZE`—Optional sample size for `scripts/bench.sh`. The default is 1. +* `SNABB_PACKET_SIZES`, `SNABB_PACKET_SRC`, `SNABB_PACKET_DST`—Optional + `--sizes`, `--src`, and `--dst` arguments for tests using `packetblaster + synth`. + +* `SNABB_IPERF_BENCH_CONF`, `SNABB_DPDK_BENCH_CONF`—Optional NFV configurations + for `program/snabbnfv/selftest.sh bench` and `program/snabbnfv/dpdk_bench.sh`. + ## Running a SnabbBot CI Instance diff --git a/src/lib/io/virtual_ether_mux.lua b/src/lib/io/virtual_ether_mux.lua new file mode 100644 index 0000000000..5fe69438d5 --- /dev/null +++ b/src/lib/io/virtual_ether_mux.lua @@ -0,0 +1,97 @@ +-- Use of this source code is governed by the Apache 2.0 license; see COPYING. + +module(..., package.seeall) +local pci = require("lib.hardware.pci") +local RawSocket = require("apps.socket.raw").RawSocket +local LearningBridge = require("apps.bridge.learning").bridge +local FloodingBridge = require("apps.bridge.flooding").bridge +local vlan = require("apps.vlan.vlan") +local basic_apps = require("apps.basic.basic_apps") +local Synth = require("apps.test.synth").Synth + +function configure (c, ports, io) + local links + if io and io.pci then + local device = pci.device_info(io.pci) + if device and (device.driver == 'apps.intel.intel_app' + or device.driver == 'apps.solarflare.solarflare') then + links = configureVMDq(c, device, ports) + else + error("Unknown device: "..io.pci) + end + else + local Switch = "Switch" + local switch_ports = {} + for i, port in ipairs(ports) do + switch_ports[i] = port_name(port) + end + local Trunk + if io and io.iface then + config.app(c, "TrunkIface", RawSocket, io.iface) + Trunk = {port = "TrunkIface", + input = "TrunkIface.rx", + output = "TrunkIface.tx"} + end + if io and io.bench then + config.app(c, "BenchSource", Synth, io.bench) + config.app(c, "BenchSink", basic_apps.Sink) + Trunk = {port = "TrunkBench", + input = "BenchSink.rx", + output = "BenchSource.tx"} + end + if Trunk then switch_ports[#switch_ports+1] = Trunk.port end + if #ports <= 2 then + config.app(c, Switch, FloodingBridge, {ports = switch_ports}) + else + config.app(c, Switch, LearningBridge, {ports = switch_ports}) + end + if Trunk then + config.link(c, Trunk.output.." -> "..Switch.."."..Trunk.port) + config.link(c, Switch.."."..Trunk.port.." -> "..Trunk.input) + end + links = {} + for i, port in ipairs(ports) do + local name = port_name(port) + local Switch_link = Switch.."."..name + local Port_tx, Port_rx = Switch_link, Switch_link + if port.vlan then + local VlanTag, VlanUntag = name.."_VlanTag", name.."_VlanUntag" + config.app(c, VlanTag, vlan.Tagger, {tag = port.vlan}) + config.link(c, VlanTag..".output -> "..Port_rx) + Port_rx = VlanTag..".input" + config.app(c, VlanUntag, vlan.Untagger, {tag = port.vlan}) + config.link(c, Port_tx.." -> "..VlanUntag..".input") + Port_tx = VlanUntag..".output" + end + links[i] = {input = Port_rx, output = Port_tx} + end + end + return links +end + +-- Return name of port in . +function port_name (port_config) + return port_config.port_id:gsub("-", "_") +end + +function configureVMDq (c, device, ports) + local links = {} + for i, port in ipairs(ports) do + local name = port_name(port) + local NIC = name.."_NIC" + local vmdq = true + if not port.mac_address then + if #ports ~= 1 then + error("multiple ports defined but promiscuous mode requested for port: "..name) + end + vmdq = false + end + config.app(c, NIC, require(device.driver).driver, + {pciaddr = device.pciaddress, + vmdq = vmdq, + macaddr = port.mac_address, + vlan = port.vlan}) + links[i] = {input = NIC..".rx", output = NIC..".tx"} + end + return links +end diff --git a/src/lib/ipsec/esp.lua b/src/lib/ipsec/esp.lua index e8409acf39..5df0fdc6db 100644 --- a/src/lib/ipsec/esp.lua +++ b/src/lib/ipsec/esp.lua @@ -199,17 +199,17 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ local p_min = packet.from_string("012345678901234567890123456789012345678901234567890123") p_min.data[18] = 0 -- Set IPv6 payload length to zero p_min.data[19] = 0 -- ... - assert(packet.length(p_min) == PAYLOAD_OFFSET) - print("original", lib.hexdump(ffi.string(packet.data(p_min), packet.length(p_min)))) + assert(p_min.length == PAYLOAD_OFFSET) + print("original", lib.hexdump(ffi.string(p_min.data, p_min.length))) local e_min = packet.clone(p_min) assert(enc:encapsulate(e_min)) - print("encrypted", lib.hexdump(ffi.string(packet.data(e_min), packet.length(e_min)))) - assert(packet.length(e_min) == dec.MIN_SIZE+PAYLOAD_OFFSET) + print("encrypted", lib.hexdump(ffi.string(e_min.data, e_min.length))) + assert(e_min.length == dec.MIN_SIZE+PAYLOAD_OFFSET) assert(dec:decapsulate(e_min)) - print("decrypted", lib.hexdump(ffi.string(packet.data(e_min), packet.length(e_min)))) - assert(packet.length(e_min) == PAYLOAD_OFFSET) - assert(packet.length(p_min) == packet.length(e_min) - and C.memcmp(p_min, e_min, packet.length(p_min)) == 0, + print("decrypted", lib.hexdump(ffi.string(e_min.data, e_min.length))) + assert(e_min.length == PAYLOAD_OFFSET) + assert(p_min.length == e_min.length + and C.memcmp(p_min, e_min, p_min.length) == 0, "integrity check failed") -- Check transmitted Sequence Number wrap around enc.seq:low(0) diff --git a/src/lib/virtio/net_device.lua b/src/lib/virtio/net_device.lua index 14883b1e1a..fe694721f4 100644 --- a/src/lib/virtio/net_device.lua +++ b/src/lib/virtio/net_device.lua @@ -10,8 +10,6 @@ local link = require("core.link") local memory = require("core.memory") local packet = require("core.packet") local timer = require("core.timer") -local counter = require("core.counter") -local ethernet = require("lib.protocol.ethernet") local vq = require("lib.virtio.virtq_device") local checksum = require("lib.checksum") local ffi = require("ffi") @@ -55,7 +53,6 @@ local invalid_header_id = 0xffff --]] local supported_features = C.VIRTIO_F_ANY_LAYOUT + - C.VIRTIO_RING_F_INDIRECT_DESC + C.VIRTIO_NET_F_CTRL_VQ + C.VIRTIO_NET_F_MQ + C.VIRTIO_NET_F_CSUM @@ -71,7 +68,7 @@ local max_virtq_pairs = 16 VirtioNetDevice = {} -function VirtioNetDevice:new(owner, disable_mrg_rxbuf) +function VirtioNetDevice:new(owner, disable_mrg_rxbuf, disable_indirect_desc) assert(owner) local o = { owner = owner, @@ -102,10 +99,15 @@ function VirtioNetDevice:new(owner, disable_mrg_rxbuf) self.hdr_type = virtio_net_hdr_type self.hdr_size = virtio_net_hdr_size - if disable_mrg_rxbuf then - self.supported_features = supported_features - else - self.supported_features = supported_features + C.VIRTIO_NET_F_MRG_RXBUF + self.supported_features = supported_features + + if not disable_mrg_rxbuf then + self.supported_features = self.supported_features + + C.VIRTIO_NET_F_MRG_RXBUF + end + if not disable_indirect_desc then + self.supported_features = self.supported_features + + C.VIRTIO_RING_F_INDIRECT_DESC end return o @@ -153,7 +155,6 @@ end function VirtioNetDevice:rx_packet_end(header_id, total_size, rx_p) local l = self.owner.output.tx - local counters = self.owner.counters if l then if band(self.rx_hdr_flags, C.VIO_NET_HDR_F_NEEDS_CSUM) ~= 0 and -- Bounds-check the checksum area @@ -165,18 +166,9 @@ 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 - counter.add(counters.rxbytes, rx_p.length) - counter.add(counters.rxpackets) - if ethernet:is_mcast(rx_p.data) then - counter.add(counters.rxmcast) - end - if ethernet:is_bcast(rx_p.data) then - counter.add(counters.rxbcast) - end link.transmit(l, rx_p) else debug("droprx", "len", rx_p.length) - counter.add(counters.rxdrop) packet.free(rx_p) end self.virtq[self.ring_id]:put_buffer(header_id, total_size) @@ -264,15 +256,6 @@ 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 - counter.add(counters.txbytes, tx_p.length) - counter.add(counters.txpackets) - if ethernet:is_mcast(tx_p.data) then - counter.add(counters.txmcast) - end - if ethernet:is_bcast(tx_p.data) then - counter.add(counters.txbcast) - end packet.free(tx_p) self.virtq[self.ring_id]:put_buffer(header_id, total_size) end @@ -340,17 +323,8 @@ 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 -- free the packet only when all its data is processed if self.tx.finished then - counter.add(counters.txbytes, tx_p.length) - counter.add(counters.txpackets) - if ethernet:is_mcast(tx_p.data) then - counter.add(counters.txmcast) - end - if ethernet:is_bcast(tx_p.data) then - counter.add(counters.txbcast) - end packet.free(tx_p) self.tx.p = nil self.tx.data_sent = nil diff --git a/src/program/pci_bind/README b/src/program/pci_bind/README new file mode 100644 index 0000000000..7c6dc6565a --- /dev/null +++ b/src/program/pci_bind/README @@ -0,0 +1,48 @@ +Usage: + pci_bind [-ubir] + pci_bind [-ah] + + -a, --all + Restore all PCI addresses to kernel management + -b, --bind + Bind the PCI address to the kernel + -h, --help + Print usage information + -i, --info + Print out the associated Linux interface name + -r, --remove + Remove the specified PCI address + -u, --unbind + Unbind the PCI address, so Snabb can use it + +Manage PCI interfaces, readying them for Snabb or returning them to kernel +management. + +Bind the specified PCI address, giving management to the Linux kernel with -b. +With -u, instead unbind the specified PCI address, readying it for use by Snabb. + +Note that after using -r, a full PCI rescan is needed to use the device again, +which by default will bring all of the unbound network interfaces back under +kernel management. If you would like to avoid this, use -u and -b instead. + +Both short-form and long-form PCI addresses are fine: 01:1f.3 or 0000:01:1f.3. + + +Examples: + +Unbind PCI adress 01:00.0 from the kernel, readying it for Snabb. +pci_bind -u 01:00.0 + +Bind only 01:00.0 to the kernel, readying it for non-Snabb use. +pci_bind -b 01:00.0 + +Have the kernel take over all cards again: +pci_bind -a + +Find the interface name of a Linux-managed card from the PCI address. +pci_bind -i 01:00.0 + +Make the kernel entirely forget about a PCI address. +This can be run before pci_bind -a; it does not make a card usable by Snabb. +Occasionally a card cannot be re-bound before doing this. +pci_bind -r 01:00.0 diff --git a/src/program/pci_bind/README.inc b/src/program/pci_bind/README.inc new file mode 120000 index 0000000000..100b93820a --- /dev/null +++ b/src/program/pci_bind/README.inc @@ -0,0 +1 @@ +README \ No newline at end of file diff --git a/src/program/pci_bind/pci_bind.lua b/src/program/pci_bind/pci_bind.lua new file mode 100644 index 0000000000..6118d39873 --- /dev/null +++ b/src/program/pci_bind/pci_bind.lua @@ -0,0 +1,90 @@ +-- Use of this source code is governed by the Apache 2.0 license; see COPYING. + +module(..., package.seeall) + +local lib = require("core.lib") +local pci = require("lib.hardware.pci") +local S = require("syscall") + +local usage = require("program.pci_bind.README_inc") + +local long_opts = { + all = "a", + bind = "b", + info = "i", + help = "h", + remove = "r", + unbind = "u" +} + +local function verify_and_normalize_pci_path(pci_addr) + local p = pci.path(pci_addr) + local msg = "No such device: %s, checked %s. \n\z + If it was removed, restore with -a" + if not S.stat(p) then + print(msg:format(pci_addr, p)) + main.exit(1) + end + return p +end + +local function write_to_file(filename, content) + if not lib.writefile(filename, content) then + print(("Writing to %s failed, quitting"):format(filename)) + main.exit(1) + end +end + +local function print_info(pci_path, pci_addr) + local eth = lib.firstfile(pci_path .. '/net') + if not eth then + print(("Unable to find interface name for %s, quitting."):format(pci_addr)) + print(("If it should have an interface name, run pci_bind -r %s, \n\z + then pci_bind -a. \z + Warning: -a rescans all interfaces, not just one."):format(pci_addr)) + main.exit(1) + else + print(("%s is known as %s"):format(pci_addr, eth)) + end +end + +function run(args) + local handlers = {} + local opts = {} + local pci_addr + local pci_path + function handlers.h (arg) print(usage) main.exit(0) end + function handlers.u (arg) opts.unbind_driv = true pci_addr = arg end + function handlers.b (arg) opts.bind_driv = true pci_addr = arg end + function handlers.i (arg) opts.info = true pci_addr = arg end + function handlers.r (arg) opts.remove = true pci_addr = arg end + function handlers.a (arg) opts.rescan_all = true end + args = lib.dogetopt(args, handlers, "hab:i:r:u:", long_opts) + if #args > 0 then print(usage) main.exit(1) end + if pci_addr then + pci_path = verify_and_normalize_pci_path(pci_addr) + end + if opts.info then print_info(pci_path, pci_addr) end + if opts.bind_driv then + write_to_file(pci_path .. '/driver/bind', pci.qualified(pci_addr)) + print(("Bound %s back to the kernel."):format(pci_addr)) + print_info(pci_path, pci_addr) + end + if opts.unbind_driv then + write_to_file(pci_path .. '/driver/unbind', pci.qualified(pci_addr)) + print(("Unbound %s, ready for Snabb."):format(pci_addr)) + end + if opts.remove then + write_to_file(pci_path .. '/remove', "1") + local msg = "Successfully removed %s. \z + Note that this does not let Snabb use it. \n\z + To restore kernel management, use pci_bind -a. \n\z + To ready a card for Snabb, use pci_bind -u . \n\z + Example: pci_bind -u ixgbe 00:02.0" + print(msg:format(pci_addr)) + end + if opts.rescan_all then + write_to_file('/sys/bus/pci/rescan', "1") + print("Rescanned all PCI devices. Run ifconfig to list kernel-managed devices.") + end +end diff --git a/src/program/snabbnfv/dpdk_bench.sh b/src/program/snabbnfv/dpdk_bench.sh new file mode 100755 index 0000000000..423095ad87 --- /dev/null +++ b/src/program/snabbnfv/dpdk_bench.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +export SKIPPED_CODE=43 + +if [ -z "$SNABB_PCI_INTEL0" -o -z "$SNABB_PCI_INTEL1" ]; then + export SNABB_PCI_INTEL0=soft + export SNABB_PCI_INTEL1=soft +fi + +if [ -z "$SNABB_TELNET0" ]; then + export SNABB_TELNET0=5000 + echo "Defaulting to SNABB_TELNET0=$SNABB_TELNET0" +fi + +if [ -z "$PACKETS" ]; then + export PACKETS=100e6 + echo "Defaulting to PACKETS=$PACKETS" +fi + +if [ -z "$SNABB_PACKET_SIZES" ]; then + export SNABB_PACKET_SIZES=60 + echo "Defaulting to SNABB_PACKET_SIZES=$SNABB_PACKET_SIZES" +fi + +if [ -z "$SNABB_PACKET_SRC" ]; then + export SNABB_PACKET_SRC="52:54:00:00:00:02" + echo "Defaulting to SNABB_PACKET_SRC=$SNABB_PACKET_SRC" +fi + +if [ -z "$SNABB_PACKET_DST" ]; then + export SNABB_PACKET_DST="52:54:00:00:00:01" + echo "Defaulting to SNABB_PACKET_DST=$SNABB_PACKET_DST" +fi + +if [ -z "$SNABB_DPDK_BENCH_CONF" ]; then + export SNABB_DPDK_BENCH_CONF="program/snabbnfv/test_fixtures/nfvconfig/test_functions/snabbnfv-bench.port" + echo "Defaulting to SNABB_DPDK_BENCH_CONF=$SNABB_DPDK_BENCH_CONF" +fi + +source program/snabbnfv/test_env/test_env.sh + +if [ "$SNABB_PCI_INTEL0" != "soft" ]; then + snabb $SNABB_PCI_INTEL0 "packetblaster synth \ +--sizes $SNABB_PACKET_SIZES \ +--src $SNABB_PACKET_SRC \ +--dst $SNABB_PACKET_DST \ +$SNABB_PCI_INTEL0" +fi +qemu_dpdk $SNABB_PCI_INTEL1 vhost_B.sock $SNABB_TELNET0 +snabbnfv_bench $SNABB_PCI_INTEL1 $PACKETS $SNABB_DPDK_BENCH_CONF diff --git a/src/program/snabbnfv/nfvconfig.lua b/src/program/snabbnfv/nfvconfig.lua index 8977b6477d..60fe17e443 100644 --- a/src/program/snabbnfv/nfvconfig.lua +++ b/src/program/snabbnfv/nfvconfig.lua @@ -8,6 +8,7 @@ local RateLimiter = require("apps.rate_limiter.rate_limiter").RateLimiter local nd_light = require("apps.ipv6.nd_light").nd_light local L2TPv3 = require("apps.keyed_ipv6_tunnel.tunnel").SimpleKeyedTunnel local AES128gcm = require("apps.ipsec.esp").AES128gcm +local virtual_ether_mux = require("lib.io.virtual_ether_mux") local pci = require("lib.hardware.pci") local ffi = require("ffi") local C = ffi.C @@ -18,35 +19,24 @@ function port_name (port_config) return port_config.port_id:gsub("-", "_") end --- Compile app configuration from for and vhost_user --- . Returns configuration. -function load (file, pciaddr, sockpath) - local device_info = pci.device_info(pciaddr) - if not device_info then - print(format("could not find device information for PCI address %s", pciaddr)) - main.exit(1) - end - +-- Compile app configuration from for and vhost_user . +-- Optionally install source and sink. Returns configuration. +function load (file, pciaddr, sockpath, soft_bench) local ports = lib.load_conf(file) local c = config.new() - for _,t in ipairs(ports) do - local vlan, mac_address = t.vlan, t.mac_address + local io_links + if pciaddr then + io_links = virtual_ether_mux.configure(c, ports, {pci = pciaddr}) + else + io_links = virtual_ether_mux.configure(c, ports, {bench = soft_bench}) + end + for i,t in ipairs(ports) do local name = port_name(t) - local NIC = name.."_NIC" local Virtio = name.."_Virtio" - local vmdq = true - if not t.mac_address then - if #ports ~= 1 then - error("multiple ports defined but promiscuous mode requested for port: "..name) - end - vmdq = false - end - config.app(c, NIC, require(device_info.driver).driver, - {pciaddr = pciaddr, - vmdq = vmdq, - macaddr = mac_address, - vlan = vlan}) - config.app(c, Virtio, VhostUser, {socket_path=sockpath:format(t.port_id)}) + config.app(c, Virtio, VhostUser, + {socket_path=sockpath:format(t.port_id), + disable_mrg_rxbuf=t.disable_mrg_rxbuf, + disable_indirect_desc=t.disable_indirect_desc}) local VM_rx, VM_tx = Virtio..".rx", Virtio..".tx" if t.tx_police_gbps then local TxLimit = name.."_TxLimit" @@ -84,7 +74,7 @@ function load (file, pciaddr, sockpath) -- This will talk to our local gateway. local ND = name.."_ND" config.app(c, ND, nd_light, - {local_mac = mac_address, + {local_mac = t.mac_address, local_ip = t.tunnel.local_ip, next_hop = t.tunnel.next_hop}) -- VM -> Tunnel -> ND <-> Network @@ -109,8 +99,8 @@ function load (file, pciaddr, sockpath) config.link(c, RxLimit..".output -> "..VM_rx) VM_rx = RxLimit..".input" end - config.link(c, NIC..".tx -> "..VM_rx) - config.link(c, VM_tx.." -> "..NIC..".rx") + config.link(c, io_links[i].output.." -> "..VM_rx) + config.link(c, VM_tx.." -> "..io_links[i].input) end -- Return configuration c. diff --git a/src/program/snabbnfv/packetblaster_bench.sh b/src/program/snabbnfv/packetblaster_bench.sh deleted file mode 100755 index 5d264463b7..0000000000 --- a/src/program/snabbnfv/packetblaster_bench.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -export SKIPPED_CODE=43 - -if [ -z "$SNABB_PCI_INTEL0" ]; then echo "Need SNABB_PCI_INTEL0"; exit $SKIPPED_CODE; fi -if [ -z "$SNABB_PCI_INTEL1" ]; then echo "Need SNABB_PCI_INTEL1"; exit $SKIPPED_CODE; fi - -if [ -z "$SNABB_TELNET0" ]; then - export SNABB_TELNET0=5000 - echo "Defaulting to SNABB_TELNET0=$SNABB_TELNET0" -fi - -if [ -z "$PACKETS" ]; then - echo "Defaulting to PACKETS=100e6" - export PACKETS=100e6 -fi - -if [ -z "$CAPFILE" ]; then - echo "Defaulting to CAPFILE=64" - export CAPFILE=64 -fi - -source program/snabbnfv/test_env/test_env.sh - -packetblaster $SNABB_PCI_INTEL0 $CAPFILE -qemu_dpdk $SNABB_PCI_INTEL1 vhost_B.sock $SNABB_TELNET0 -snabbnfv_bench $SNABB_PCI_INTEL1 $PACKETS diff --git a/src/program/snabbnfv/packetblaster_bench.sh b/src/program/snabbnfv/packetblaster_bench.sh new file mode 120000 index 0000000000..3bd1d83787 --- /dev/null +++ b/src/program/snabbnfv/packetblaster_bench.sh @@ -0,0 +1 @@ +dpdk_bench.sh \ No newline at end of file diff --git a/src/program/snabbnfv/selftest.sh b/src/program/snabbnfv/selftest.sh index 795f0d907d..0d4d5c8b89 100755 --- a/src/program/snabbnfv/selftest.sh +++ b/src/program/snabbnfv/selftest.sh @@ -2,7 +2,9 @@ SKIPPED_CODE=43 -if [ -z "$SNABB_PCI0" ]; then echo "Need SNABB_PCI0"; exit $SKIPPED_CODE; fi +if [ -z "$SNABB_PCI0" ]; then + export SNABB_PCI0=soft +fi if [ -z "$SNABB_TELNET0" ]; then export SNABB_TELNET0=5000 echo "Defaulting to SNABB_TELNET0=$SNABB_TELNET0" @@ -11,6 +13,10 @@ if [ -z "$SNABB_TELNET1" ]; then export SNABB_TELNET1=5001 echo "Defaulting to SNABB_TELNET1=$SNABB_TELNET1" fi +if [ -z "$SNABB_IPERF_BENCH_CONF" ]; then + export SNABB_IPERF_BENCH_CONF=program/snabbnfv/test_fixtures/nfvconfig/test_functions/same_vlan.ports + echo "Defaulting to SNABB_IPERF_BENCH_CONF=$SNABB_IPERF_BENCH_CONF" +fi TESTCONFPATH="/tmp/snabb_nfv_selftest_ports.$$" FUZZCONFPATH="/tmp/snabb_nfv_selftest_fuzz$$.ports" @@ -302,11 +308,7 @@ function crypto_tests { # Run iperf benchmark. If is "jumbo", jumboframes will be enabled. # defaults to same_vlan.ports. function iperf_bench { - if [ -z "$2" ]; then - load_config program/snabbnfv/test_fixtures/nfvconfig/test_functions/same_vlan.ports - else - load_config "$2" - fi + load_config "$SNABB_IPERF_BENCH_CONF" if [ "$1" = "jumbo" ]; then test_jumboping $SNABB_TELNET0 $SNABB_TELNET1 "$(ip 1)%eth0" \ diff --git a/src/program/snabbnfv/test_env/test_env.sh b/src/program/snabbnfv/test_env/test_env.sh index 56bd6b7914..e895d48ad6 100644 --- a/src/program/snabbnfv/test_env/test_env.sh +++ b/src/program/snabbnfv/test_env/test_env.sh @@ -67,7 +67,10 @@ function pci_node { numactl -H | grep "cpus: $cpu" | cut -d " " -f 2 ;; *) - echo $1 + if [ "$1" = "soft" ] + then echo 0 + else echo $1 + fi ;; esac } @@ -134,19 +137,13 @@ function qemu { launch_qemu $1 $2 $3 bzImage qemu } -function packetblaster { - snabb $1 "packetblaster replay program/snabbnfv/test_fixtures/pcap/$2.pcap $1" -} - function qemu_dpdk { launch_qemu $1 $2 $3 bzImage qemu-dpdk } function snabbnfv_bench { numactl --cpunodebind=$(pci_node $1) --membind=$(pci_node $1) \ - ./snabb snabbnfv traffic -B $2 $1 \ - program/snabbnfv/test_fixtures/nfvconfig/test_functions/snabbnfv-bench1.port \ - vhost_%s.sock + ./snabb snabbnfv traffic -B $2 $1 $3 vhost_%s.sock } function on_exit { diff --git a/src/program/snabbnfv/test_fixtures/nfvconfig/test_functions/snabbnfv-bench-no-indirect_desc.port b/src/program/snabbnfv/test_fixtures/nfvconfig/test_functions/snabbnfv-bench-no-indirect_desc.port new file mode 100644 index 0000000000..4105d96ab8 --- /dev/null +++ b/src/program/snabbnfv/test_fixtures/nfvconfig/test_functions/snabbnfv-bench-no-indirect_desc.port @@ -0,0 +1,10 @@ +return { + { vlan = nil, + mac_address = "52:54:00:00:00:01", + port_id = "B", + ingress_filter = nil, + gbps = nil, + tunnel = nil, + disable_indirect_desc = true + }, +} diff --git a/src/program/snabbnfv/test_fixtures/nfvconfig/test_functions/snabbnfv-bench1.port b/src/program/snabbnfv/test_fixtures/nfvconfig/test_functions/snabbnfv-bench-no-mrg_rxbuf.port similarity index 73% rename from src/program/snabbnfv/test_fixtures/nfvconfig/test_functions/snabbnfv-bench1.port rename to src/program/snabbnfv/test_fixtures/nfvconfig/test_functions/snabbnfv-bench-no-mrg_rxbuf.port index 762e0a036d..7f959f8ce4 100644 --- a/src/program/snabbnfv/test_fixtures/nfvconfig/test_functions/snabbnfv-bench1.port +++ b/src/program/snabbnfv/test_fixtures/nfvconfig/test_functions/snabbnfv-bench-no-mrg_rxbuf.port @@ -4,6 +4,7 @@ return { port_id = "B", ingress_filter = nil, gbps = nil, - tunnel = nil + tunnel = nil, + disable_mrg_rxbuf = true }, } diff --git a/src/program/snabbnfv/test_fixtures/nfvconfig/test_functions/snabbnfv-bench.port b/src/program/snabbnfv/test_fixtures/nfvconfig/test_functions/snabbnfv-bench.port index bf919c5ff8..762e0a036d 100644 --- a/src/program/snabbnfv/test_fixtures/nfvconfig/test_functions/snabbnfv-bench.port +++ b/src/program/snabbnfv/test_fixtures/nfvconfig/test_functions/snabbnfv-bench.port @@ -1,7 +1,7 @@ return { { vlan = nil, - mac_address = "52:54:00:00:00:00", - port_id = "a", + mac_address = "52:54:00:00:00:01", + port_id = "B", ingress_filter = nil, gbps = nil, tunnel = nil diff --git a/src/program/snabbnfv/traffic/traffic.lua b/src/program/snabbnfv/traffic/traffic.lua index b944673150..4b5739d439 100644 --- a/src/program/snabbnfv/traffic/traffic.lua +++ b/src/program/snabbnfv/traffic/traffic.lua @@ -37,14 +37,17 @@ function run (args) args = lib.dogetopt(args, opt, "hHB:k:l:D:b", long_opts) if #args == 3 then local pciaddr, confpath, sockpath = unpack(args) - local ok, info = pcall(pci.device_info, pciaddr) - if not ok then - print("Error: device not found " .. pciaddr) - os.exit(1) - end - if not info.driver then - print("Error: no driver for device " .. pciaddr) - os.exit(1) + if pciaddr == "soft" then pciaddr = nil end + if pciaddr then + local ok, info = pcall(pci.device_info, pciaddr) + if not ok then + print("Error: device not found " .. pciaddr) + os.exit(1) + end + if not info.driver then + print("Error: no driver for device " .. pciaddr) + os.exit(1) + end end if loadreportinterval > 0 then local t = timer.new("nfvloadreport", engine.report_load, loadreportinterval*1e9, 'repeating') @@ -100,12 +103,18 @@ end function bench (pciaddr, confpath, sockpath, npackets) npackets = tonumber(npackets) local ports = dofile(confpath) - local nic = (nfvconfig.port_name(ports[1])).."_NIC" + local nic, bench + if pciaddr then + nic = (nfvconfig.port_name(ports[1])).."_NIC" + else + nic = "BenchSink" + bench = { src="52:54:00:00:00:02", dst="52:54:00:00:00:01", sizes = {60}} + end engine.log = true engine.Hz = false print("Loading " .. confpath) - engine.configure(nfvconfig.load(confpath, pciaddr, sockpath)) + engine.configure(nfvconfig.load(confpath, pciaddr, sockpath, bench)) -- From designs/nfv local start, packets, bytes = 0, 0, 0 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..250af23892 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,18 @@ 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 @@ -47,37 +46,29 @@ 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) + os.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))}) +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 +88,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 +134,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 +155,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") diff --git a/src/scripts/dock.sh b/src/scripts/dock.sh index d39e98ffb1..c825972cf2 100755 --- a/src/scripts/dock.sh +++ b/src/scripts/dock.sh @@ -15,7 +15,11 @@ docker run --rm --privileged -i -v $(dirname $PWD):/snabb $DOCKERFLAGS \ -e SNABB_PCI_SOLARFLARE1=$SNABB_PCI_SOLARFLARE1 \ -e SNABB_TELNET0=$SNABB_TELNET0 \ -e SNABB_TELNET1=$SNABB_TELNET1 \ - -e SNABB_PCAP=$SNABB_PCAP \ + -e SNABB_PACKET_SIZES=$SNABB_PACKET_SIZES \ + -e SNABB_PACKET_SRC=$SNABB_PACKET_SRC \ + -e SNABB_PACKET_DST=$SNABB_PACKET_DST \ + -e SNABB_IPERF_BENCH_CONF=$SNABB_IPERF_BENCH_CONF \ + -e SNABB_DPDK_BENCH_CONF=$SNABB_DPDK_BENCH_CONF \ -e SNABB_PERF_SAMPLESIZE=$SNABB_PERF_SAMPLESIZE \ $SNABB_TEST_IMAGE \ bash -c "mount -t hugetlbfs none /hugetlbfs && (cd snabb/src; $*)"