diff --git a/lib/luajit/.gitignore b/lib/luajit/.gitignore index 1a07bf75bf..a0e6e84e23 100644 --- a/lib/luajit/.gitignore +++ b/lib/luajit/.gitignore @@ -9,3 +9,12 @@ *.dmp *.swp .tags +*.dwo +/src/lj_bcdef.h +/src/lj_ffdef.h +/src/lj_folddef.h +/src/lj_libdef.h +/src/lj_recdef.h +/src/lj_vm.S +/src/raptorjit +/src/host/buildvm_arch.h diff --git a/src/README.md b/src/README.md index e02bddb988..62ac156f16 100644 --- a/src/README.md +++ b/src/README.md @@ -55,7 +55,7 @@ these in a desired way using *links* and finally pass the resulting app network on to the Snabb engine. The engine's job is to: * Pump traffic through the app network - * Keep the app network running (e.g. restart failed apps) + * Apply, and inform apps of configuration and link changes * Report on the network status @@ -117,11 +117,24 @@ will be used to validate the app’s arg when it is configured using `config.app`. -— Method **myapp:link** +— Method **myapp:link** *dir* *name* -*Optional*. Called any time the app’s links may have been changed (including on -start-up). Guaranteed to be called before `pull` and `push` are called with new -links. +*Optional*. Called during `engine.configure()` when a link of the app is +added. Unless `unlink` is specified this method is also called when a link +is removed. +Guaranteed to be called before `pull` and `push` are called with new links. + +*Dir* is either `'input'` or `'output'`, and *name* is the string name +of the link. I.e., the added link can be accessed at `self[dir][name]`. + + +— Method **myapp:unlink** *dir* *name* + +*Optional*. Called during `engine.configure()` when a link of the app is +removed. + +*Dir* is either `'input'` or `'output'`, and *name* is the string name +of the link. — Method **myapp:pull** @@ -139,6 +152,53 @@ transmitting them to output ports. For example: Move packets from input ports to output ports or to a network adapter. +— Field **myapp.push_link** + +*Optional*. When specified must be a table of per-link `push()` methods +that take an input link as an argument. For example an app could specify +a **push_link** method for its input link *foo*: + +``` +Myapp = { push_link={} } +function Myapp.push_link:foo (input) + while not link.empty(input) do something() end +end +``` + +**Push_link** methods are copied to a fresh table when the app is started, +and it is valid to create **push_link** methods dynamically during `link()`, +for example like so: + +``` +Myapp = { push_link={} } +function Myapp:link (dir, name) + -- NB: Myapp.push_link ~= self.push_link + if dir == 'input' then + self.push_link[name] = function (self, input) + while not link.empty(input) do something() end + end + end +end +function Myapp:unlink (dir, name) + if dir == 'input' then + self.push_link[name] = nil + end +end +``` + +**Push** is not called when an app has **push_link** methods +for *all* of its input links. If, however, an app at least one input link +without an associated **push_link** method then **push** is called +in addition to the **push_link** methods. + + +— Method **myapp:tick** + +*Optional*. Called periodically at **engine.tick_Hz** frequency. + +For example: Move packets from input ports to output ports or to a +network adapter. + — Method **myapp:reconfig** *arg* @@ -285,6 +345,13 @@ how many times per second to poll. This setting is not used when engine.busywait is true. +— Variable **engine.tick_Hz** + +Frequency at which to call **app:tick** methods. The default value is +1000 (call `tick()`s every millisecond). + +A value of 0 effectively disables `tick()` methods. + ## Link (core.link) A *link* is a [ring buffer](http://en.wikipedia.org/wiki/Circular_buffer) diff --git a/src/apps/lwaftr/V4V6.lua b/src/apps/lwaftr/V4V6.lua index 1bf52b5233..14b5f739e5 100644 --- a/src/apps/lwaftr/V4V6.lua +++ b/src/apps/lwaftr/V4V6.lua @@ -193,17 +193,19 @@ local function test_join () config.app(c, 'v4v6', V4V6) config.app(c, 'sink', basic_apps.Sink) - config.link(c, 'source.output -> v4v6.v4') - config.link(c, 'source.output -> v4v6.v6') + config.link(c, 'source.v4 -> v4v6.v4') + config.link(c, 'source.v6 -> v4v6.v6') config.link(c, 'v4v6.output -> sink.input') engine.configure(c) - link.transmit(engine.app_table.source.output.output, arp_pkt()) - link.transmit(engine.app_table.source.output.output, ipv4_pkt()) - link.transmit(engine.app_table.source.output.output, ipv6_pkt()) + for _, output in ipairs{'v4', 'v6'} do + link.transmit(engine.app_table.source.output[output], arp_pkt()) + link.transmit(engine.app_table.source.output[output], ipv4_pkt()) + link.transmit(engine.app_table.source.output[output], ipv6_pkt()) + end engine.main({duration = 0.1, noreport = true}) - assert(link.stats(engine.app_table.sink.input.input).rxpackets == 3) + assert(link.stats(engine.app_table.sink.input.input).rxpackets == 3*2) end function selftest () diff --git a/src/apps/packet_filter/pcap_filter.lua b/src/apps/packet_filter/pcap_filter.lua index 612192587a..4e1160f1c6 100644 --- a/src/apps/packet_filter/pcap_filter.lua +++ b/src/apps/packet_filter/pcap_filter.lua @@ -135,8 +135,7 @@ function selftest_run (stateful, expected, tolerance, native) print(("Run for 1 second (stateful = %s)..."):format(stateful)) - local deadline = lib.timeout(1.0) - repeat app.breathe() until deadline() + app.main{duration=1} app.report({showlinks=true}) local sent = link.stats(app.app_table.pcap_filter.input.input).rxpackets diff --git a/src/apps/pcap/tap.lua b/src/apps/pcap/tap.lua index f18171136b..db720cf4bc 100644 --- a/src/apps/pcap/tap.lua +++ b/src/apps/pcap/tap.lua @@ -67,7 +67,7 @@ function selftest () config.link(c, "source.output -> tap.input") config.link(c, "tap.output -> sink.input") app.configure(c) - while not app.app_table.source.done do app.breathe() end + app.main{done=function () return app.app_table.source.done end} local n = 0 for packet, record in pcap.records(tmp) do n = n + 1 end diff --git a/src/apps/rate_limiter/rate_limiter.lua b/src/apps/rate_limiter/rate_limiter.lua index 3b68b65c62..4b02918cc2 100644 --- a/src/apps/rate_limiter/rate_limiter.lua +++ b/src/apps/rate_limiter/rate_limiter.lua @@ -148,11 +148,7 @@ function selftest () local snapshot = rl:get_stat_snapshot() -- push some packets through it - while seconds_to_run > 0 do - app.breathe() - timer.run() - C.usleep(10) -- avoid busy loop - end + app.main{duration=seconds_to_run} -- print final report app.report() @@ -194,10 +190,7 @@ function selftest () rl:reset(rate_busy_loop, bucket_size) local snapshot = rl:get_stat_snapshot() - for i = 1, 100000 do - app.breathe() - timer.run() - end + app.main{duration=0.1} local elapsed_time = (tonumber(C.get_time_ns()) - snapshot.time) / 1e9 print("elapsed time ", elapsed_time, "seconds") diff --git a/src/apps/test/match.lua b/src/apps/test/match.lua index 57d48f7453..c4697ddf51 100644 --- a/src/apps/test/match.lua +++ b/src/apps/test/match.lua @@ -84,14 +84,16 @@ function selftest() engine.main({duration=0.0001}) assert(#engine.app_table.sink:errors() > 0) - engine.configure(config.new()) + local c = config.new() config.app(c, "sink", Match, {fuzzy=true}) + config.app(c, "src", basic_apps.Source, 8) config.app(c, "comparator", basic_apps.Source, 8) config.app(c, "garbage", basic_apps.Source, 12) config.app(c, "join", basic_apps.Join) config.link(c, "src.output -> join.src") config.link(c, "garbage.output -> join.garbage") config.link(c, "join.output -> sink.rx") + config.link(c, "comparator.output -> sink.comparator") engine.configure(c) engine.main({duration=0.0001}) assert(#engine.app_table.sink:errors() == 0) diff --git a/src/core/app.lua b/src/core/app.lua index f4059d6d30..d189269c95 100644 --- a/src/core/app.lua +++ b/src/core/app.lua @@ -21,7 +21,6 @@ pull_npackets = math.floor(link.max / 10) -- Set to true to enable logging log = false -local use_restart = false test_skipped_code = 43 @@ -74,6 +73,22 @@ maxsleep = 100 -- loop (100% CPU) instead of sleeping according to the Hz setting. busywait = false +-- tick_Hz: Frequency at which to execute tick() methods ( per second) +tick_Hz = 1000 + +local tick, tick_current_freq +function enable_tick (freq) + freq = freq or tick_Hz + if freq == tick_current_freq then + return + end + if freq > 0 then + tick = lib.throttle(1/freq) + else + tick = function () return false end + end +end + -- Profiling with vmprofile -------------------------------- vmprofile_enabled = true @@ -121,56 +136,6 @@ function now () return (running and monotonic_now) or C.get_monotonic_time() 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 status, result - setvmprofile(app.zone) - if use_restart then - -- Run fn in protected mode using pcall. - status, result = pcall(method, app) - - -- If pcall caught an error mark app as "dead" (record time and cause - -- of death). - if not status then - app.dead = { error = result, time = now() } - end - else - status, result = true, method(app) - end - setvmprofile("engine") - return status, result -end - --- Restart dead apps. -function restart_dead_apps () - if not use_restart then return end - local restart_delay = 2 -- seconds - local actions = {} - - for name, app in pairs(app_table) do - if app.dead and (now() - app.dead.time) >= restart_delay then - io.stderr:write(("Restarting %s (died at %f: %s)\n") - :format(name, app.dead.time, app.dead.error)) - local info = configuration.apps[name] - table.insert(actions, {'stop_app', {name}}) - table.insert(actions, {'start_app', {name, info.class, info.arg}}) - for linkspec in pairs(configuration.links) do - local fa, fl, ta, tl = config.parse_link(linkspec) - if fa == name then - table.insert(actions, {'link_output', {fa, fl, linkspec}}) - end - if ta == name then - table.insert(actions, {'link_input', {ta, tl, linkspec}}) - end - end - end - end - - -- Restart dead apps if necessary. - if #actions > 0 then apply_config_actions(actions) end -end - -- Configure the running app network to match new_configuration. -- -- Successive calls to configure() will migrate from the old to the @@ -349,14 +314,16 @@ function apply_config_actions (actions) local link = app.output[linkname] app.output[linkname] = nil remove_link_from_array(app.output, link) - if app.link then app:link() end + if app.unlink then app:unlink('output', linkname) + elseif app.link then app:link('output', linkname) end end function ops.unlink_input (appname, linkname) local app = app_table[appname] local link = app.input[linkname] app.input[linkname] = nil remove_link_from_array(app.input, link) - if app.link then app:link() end + if app.unlink then app:unlink('input', linkname) + elseif app.link then app:link('input', linkname) end end function ops.free_link (linkspec) link.free(link_table[linkspec], linkspec) @@ -370,16 +337,22 @@ function apply_config_actions (actions) function ops.link_output (appname, linkname, linkspec) local app = app_table[appname] local link = assert(link_table[linkspec]) + assert(not app.output[linkname], + appname..": duplicate output link "..linkname) app.output[linkname] = link table.insert(app.output, link) - if app.link then app:link() end + if app.link then app:link('output', linkname) end end function ops.link_input (appname, linkname, linkspec) local app = app_table[appname] local link = assert(link_table[linkspec]) + assert(not app.input[linkname], + appname..": duplicate input link "..linkname) app.input[linkname] = link table.insert(app.input, link) - if app.link then app:link() end + if app.link then + app:link('input', linkname) + end end function ops.stop_app (name) local app = app_table[name] @@ -404,6 +377,16 @@ function apply_config_actions (actions) app.shm.dtime = {counter, C.get_unix_time()} app.shm = shm.create_frame("apps/"..name, app.shm) end + if class.push_link then + if type(class.push_link) ~= 'table' then + error(("bad push_link value for app '%s' (must be a table)") + :format(name)) + end + app.push_link = {} + for name, method in pairs(class.push_link) do + app.push_link[name] = method + end + end configuration.apps[name] = { class = class, arg = arg } end function ops.reconfig_app (name, class, arg) @@ -445,13 +428,17 @@ function tsort (nodes, entries, successors) return ret end -breathe_pull_order = {} -breathe_push_order = {} +local breathe_pull_order = {} +local breathe_push_order = {} +local breathe_ticks = {} -- Sort the links in the app graph, and arrange to run push() on the -- apps on the receiving ends of those links. This will run app:push() -- once for each link, which for apps with multiple links may cause the -- app's push function to run multiple times in a breath. +-- +-- Also collect tick methods that need to be run on tick breaths in +-- deterministic order. function compute_breathe_order () breathe_pull_order, breathe_push_order = {}, {} local pull_links, inputs, successors = {}, {}, {} @@ -468,11 +455,17 @@ function compute_breathe_order () end end for linkname,link in pairs(app.input) do - linknames[link] = appname..'.'..linkname - inputs[link] = app + -- NB: each link is indexed by number and by name. + if type(linkname) == 'string' then + linknames[link] = appname..'.'..linkname + local push_link = app.push_link and app.push_link[linkname] + local push = push_link or app.push + inputs[link] = { app = app, push = push, link = link } + end end end - for link,app in pairs(inputs) do + for link,spec in pairs(inputs) do + local app = spec.app successors[link] = {} if not app.pull then for _,succ in pairs(app.output) do @@ -500,12 +493,22 @@ function compute_breathe_order () table.sort(successors[link], cmp_links) end local link_order = tsort(nodes, entry_nodes, successors) - local i = 1 for _,link in ipairs(link_order) do - if breathe_push_order[#breathe_push_order] ~= inputs[link] then - table.insert(breathe_push_order, inputs[link]) + local spec = inputs[link] + local prev = breathe_push_order[#breathe_push_order] + if spec.push then + if not prev or prev.app ~= spec.app or prev.push ~= spec.push then + table.insert(breathe_push_order, spec) + end end end + breathe_ticks = {} + for _,app in pairs(app_table) do + if app.tick then + table.insert(breathe_ticks, app) + end + end + table.sort(breathe_ticks, cmp_apps) end -- Call this to "run snabb switch". @@ -532,6 +535,9 @@ function main (options) breathe = latency:wrap_thunk(breathe, now) end + -- Enable tick + enable_tick() + monotonic_now = C.get_monotonic_time() repeat breathe() @@ -575,16 +581,14 @@ end function breathe () running = true monotonic_now = C.get_monotonic_time() - -- Restart: restart dead apps - restart_dead_apps() -- Inhale: pull work into the app network local i = 1 ::PULL_LOOP:: do - if i > #breathe_pull_order then goto PULL_EXIT end - local app = breathe_pull_order[i] - if app.pull and not app.dead then - with_restart(app, app.pull) + if i > #breathe_pull_order then goto PULL_EXIT else + local app = breathe_pull_order[i] + setvmprofile(app.zone) + app:pull() end i = i+1 goto PULL_LOOP @@ -594,15 +598,23 @@ function breathe () i = 1 ::PUSH_LOOP:: do - if i > #breathe_push_order then goto PUSH_EXIT end - local app = breathe_push_order[i] - if app.push and not app.dead then - with_restart(app, app.push) + if i > #breathe_push_order then goto PUSH_EXIT else + local spec = breathe_push_order[i] + local app, push, link = spec.app, spec.push, spec.link + setvmprofile(app.zone) + push(app, link) end i = i+1 goto PUSH_LOOP end ::PUSH_EXIT:: + -- Tick: call tick() methods at tick_Hz frequency + if tick() then + for _, app in ipairs(breathe_ticks) do + app:tick() + end + end + setvmprofile("engine") counter.add(breaths) -- Commit counters and rebalance freelists at a reasonable frequency if counter.read(breaths) % 100 == 0 then @@ -686,35 +698,28 @@ end function report_apps () print ("apps report:") for name, app in pairs(app_table) do - if app.dead then - print(name, ("[dead: %s]"):format(app.dead.error)) - elseif app.report then + if app.report then + setvmprofile(app.zone) print(name) - if use_restart then - with_restart(app, app.report) - else - -- Restarts are disabled, still we want to not die on - -- errors during app reports, thus this workaround: - local status, err = pcall(app.report, app) - if not status then - print("Warning: "..name.." threw an error during report: "..err) - end - end + app:report() end end + setvmprofile("engine") end function selftest () print("selftest: app") - local App = { push = true } + local App = {} function App:new () return setmetatable({}, {__index = App}) end + function App:pull () end + function App:push () end local c1 = config.new() config.app(c1, "app1", App) config.app(c1, "app2", App) config.link(c1, "app1.x -> app2.x") print("empty -> c1") configure(c1) - assert(#breathe_pull_order == 0) + assert(#breathe_pull_order == 2) assert(#breathe_push_order == 1) assert(app_table.app1 and app_table.app2) local orig_app1 = app_table.app1 @@ -732,7 +737,7 @@ function selftest () config.link(c2, "app2.x -> app1.x") print("c1 -> c2") configure(c2) - assert(#breathe_pull_order == 0) + assert(#breathe_pull_order == 2) assert(#breathe_push_order == 2) assert(app_table.app1 ~= orig_app1) -- should be restarted assert(app_table.app2 == orig_app2) -- should be the same @@ -742,7 +747,7 @@ function selftest () configure(c1) -- c2 -> c1 assert(app_table.app1 ~= orig_app1) -- should be restarted assert(app_table.app2 == orig_app2) -- should be the same - assert(#breathe_pull_order == 0) + assert(#breathe_pull_order == 2) assert(#breathe_push_order == 1) print("c1 -> empty") configure(config.new()) @@ -759,45 +764,106 @@ function selftest () assert(not pcall(config.app, c3, "app_invalid", AppC)) assert(not pcall(config.app, c3, "app_invalid", AppC, {b="bar"})) assert(not pcall(config.app, c3, "app_invalid", AppC, {a="bar", c="foo"})) --- Test app restarts on failure. - use_restart = true - print("c_fail") - local App1 = {zone="test"} - function App1:new () return setmetatable({}, {__index = App1}) end - function App1:pull () error("Pull error.") end - function App1:push () return true end - function App1:report () return true end - local App2 = {zone="test"} - function App2:new () return setmetatable({}, {__index = App2}) end - function App2:pull () return true end - function App2:push () error("Push error.") end - function App2:report () return true end - local App3 = {zone="test"} - function App3:new () return setmetatable({}, {__index = App3}) end - function App3:pull () return true end - function App3:push () return true end - function App3:report () error("Report error.") end - local c_fail = config.new() - config.app(c_fail, "app1", App1) - config.app(c_fail, "app2", App2) - config.app(c_fail, "app3", App3) - config.link(c_fail, "app1.x -> app2.x") - configure(c_fail) - local orig_app1 = app_table.app1 - local orig_app2 = app_table.app2 - local orig_app3 = app_table.app3 - main({duration = 4, report = {showapps = true}}) - assert(app_table.app1 ~= orig_app1) -- should be restarted - assert(app_table.app2 ~= orig_app2) -- should be restarted - 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 -- Check engine stop + local c4 = config.new() + config.app(c4, "app1", App) + engine.configure(c4) assert(not lib.equal(app_table, {})) engine.stop() assert(lib.equal(app_table, {})) + -- Test tick() + local TickApp = {} + function TickApp:new () return setmetatable({ticks=0}, {__index = TickApp}) end + function TickApp:tick () self.ticks = self.ticks + 1 end + local c5 = config.new() + config.app(c5, "app_tick", TickApp) + engine.configure(c5) + local t = 0.1 + engine.main{duration=t} + local expected_ticks = t * tick_Hz + local ratio = app_table.app_tick.ticks / expected_ticks + assert(ratio >= 0.9 and ratio <= 1.1) + print("ticks: actual/expected = "..ratio) + + -- Test link() 3.0 + local LinkApp = {push_link={}} + function LinkApp:new () + local self = {linked={input={}, output={}}, called={}, pushed=false} + return setmetatable(self, {__index = LinkApp}) + end + function LinkApp:link (dir, name) + print('link', dir, name) + self.linked[dir][name] = assert(self[dir][name]) + if dir == 'input' then + self.push_link[name] = function (self, input) + print('push_link', name, input) + self.called[name] = true + end + end + end + function LinkApp:unlink (dir, name) + print('unlink', dir, name) + assert(not self[dir][name]) + self.linked[dir][name] = nil + end + function LinkApp:push () + self.pushed = true + end + local c6 = config.new() + config.app(c6, "app_pull", App) + config.app(c6, "link_app", LinkApp) + config.link(c6, "app_pull.output -> link_app.input") + engine.configure(c6) + assert(#breathe_pull_order == 1) + assert(#breathe_push_order == 1) + engine.main{done=function () return true end} + assert(app_table.link_app.linked.input.input) + assert(app_table.link_app.called.input) + assert(not app_table.link_app.pushed) + local c7 = config.new() + config.app(c7, "app_pull", App) + config.app(c7, "link_app", LinkApp) + engine.configure(c7) + assert(not app_table.link_app.linked.input.input) + -- Backwards compatible? + local LegacyApp = {push_link={}} + function LegacyApp:new () + local self = {linked={input={}, output={}}, called={}, pushed=false} + return setmetatable(self, {__index = LegacyApp}) + end + function LegacyApp:link (dir, name) + print('link', dir, name) + self.linked[dir][name] = self[dir][name] + end + function LegacyApp.push_link:newstyle (input) + print('push_link', 'newstyle', input) + self.called.newstyle = true + end + function LegacyApp:push () + self.pushed = true + end + local c8 = config.new() + config.app(c8, "app_pull", App) + config.app(c8, "link_app", LegacyApp) + config.link(c8, "app_pull.output -> link_app.input") + config.link(c8, "app_pull.output2 -> link_app.newstyle") + engine.configure(c8) + assert(#breathe_pull_order == 1) + assert(#breathe_push_order == 2) + engine.main{done=function () return true end} + assert(app_table.link_app.linked.input.input) + assert(app_table.link_app.linked.input.newstyle) + assert(app_table.link_app.called.newstyle) + assert(app_table.link_app.pushed) + local c9 = config.new() + config.app(c9, "app_pull", App) + config.app(c9, "link_app", LegacyApp) + engine.configure(c9) + assert(not app_table.link_app.linked.input.input) + assert(not app_table.link_app.linked.input.newstyle) + -- Check one can't unclaim a name if no name is claimed. assert(not pcall(unclaim_name)) diff --git a/src/lib/ptree/worker.lua b/src/lib/ptree/worker.lua index f188807802..0717e15044 100644 --- a/src/lib/ptree/worker.lua +++ b/src/lib/ptree/worker.lua @@ -105,6 +105,8 @@ function Worker:main () if not engine.auditlog_enabled then engine.enable_auditlog() end + engine.enable_tick() + engine.setvmprofile("engine") repeat self.breathe() diff --git a/src/lib/timers/ingress_drop_monitor.lua b/src/lib/timers/ingress_drop_monitor.lua index b8b82e6e53..3fe54a2d26 100644 --- a/src/lib/timers/ingress_drop_monitor.lua +++ b/src/lib/timers/ingress_drop_monitor.lua @@ -54,12 +54,10 @@ function new(args) end function IngressDropMonitor:sample () - local app_array = engine.breathe_push_order local sum = self.current_value sum[0] = 0 - for i = 1, #app_array do - local app = app_array[i] - if app.get_rxstats and not app.dead then + for _, app in pairs(engine.app_table) do + if app.get_rxstats then sum[0] = sum[0] + app:get_rxstats().dropped end end diff --git a/src/program/snabbmark/README b/src/program/snabbmark/README index e31f5fb6dd..d400f309a4 100644 --- a/src/program/snabbmark/README +++ b/src/program/snabbmark/README @@ -1,7 +1,14 @@ Usage: - snabbmark basic1 + snabbmark basic1 + snabbmark basic1_events + snabbmark basic1_tick + snabbmark basic1_push_link Benchmark basic app network packet flow. + The 'events' and 'tick' variants exercise 10 concurrent apps waiting + on a lib.throttle(), with the latter variant using the tick() app method. + The 'push_link' variant exercises dynamic push methods created on link(). + snabbmark nfvconfig Benchmark loading and transitioning from to times. diff --git a/src/program/snabbmark/snabbmark.lua b/src/program/snabbmark/snabbmark.lua index 085ae941d2..0609ef64a1 100644 --- a/src/program/snabbmark/snabbmark.lua +++ b/src/program/snabbmark/snabbmark.lua @@ -22,7 +22,13 @@ end function run (args) local command = table.remove(args, 1) if command == 'basic1' and #args == 1 then - basic1(unpack(args)) + basic1(unpack(args), {timer=true}) + elseif command == 'basic1_events' and #args == 1 then + basic1(unpack(args), {events=true, nevents=10}) + elseif command == 'basic1_tick' and #args == 1 then + basic1(unpack(args), {events=true, nevents=10, use_tick=true}) + elseif command == 'basic1_push_link' and #args == 1 then + basic1(unpack(args), {push_link=true}) elseif command == 'nfvconfig' and #args == 3 then nfvconfig(unpack(args)) elseif command == 'solarflare' and #args >= 2 and #args <= 3 then @@ -47,7 +53,7 @@ function gbits (bps) return (bps * 8) / (1024^3) end -function basic1 (npackets) +function basic1 (npackets, opt) npackets = tonumber(npackets) or error("Invalid number of packets: " .. npackets) local c = config.new() -- Simple topology: @@ -62,12 +68,12 @@ function basic1 (npackets) config.link(c, "Source.tx -> Tee.rx") config.link(c, "Tee.tx1 -> Sink.rx1") config.link(c, "Tee.tx2 -> Sink.rx2") + basic1_opts(c, opt) engine.configure(c) local start = C.get_monotonic_time() - timer.activate(timer.new("null", function () end, 1e6, 'repeating')) - while link.stats(engine.app_table.Source.output.tx).txpackets < npackets do - engine.main({duration = 0.01, no_report = true}) - end + engine.main{no_report = true, done = function () + return link.stats(engine.app_table.Source.output.tx).txpackets >= npackets + end} local finish = C.get_monotonic_time() local runtime = finish - start local packets = link.stats(engine.app_table.Source.output.tx).txpackets @@ -76,6 +82,46 @@ function basic1 (npackets) print(("Processed %.1f million packets in %.2f seconds (rate: %.1f Mpps)."):format(packets / 1e6, runtime, packets / runtime / 1e6)) end +function basic1_opts (c, opt) + if opt.timer then + timer.activate(timer.new("null", function () end, 1e6, 'repeating')) + end + if opt.events then + local EventApp = {} + function EventApp:new () + return setmetatable({n=0, t=lib.throttle(1)}, {__index = EventApp}) + end + EventApp[(opt.use_tick and 'tick') or 'pull'] = function (self) + if self.t() then self.n = self.n+1 end + end + for i = 1, opt.nevents do + config.app(c, "Event"..i, EventApp) + end + end + if opt.push_link then + local PushLinkSink = {push_link={}} + function PushLinkSink:new () + return setmetatable({}, {__index = PushLinkSink}) + end + function PushLinkSink:discard (input) + while not link.empty(input) do + packet.free(link.receive(input)) + end + end + function PushLinkSink:link (dir, name) + if dir == 'input' then + local input = self[dir][name] -- capture in closure + self.push_link[name] = function (self) + -- use input from closure instead of argument + -- to exercise upvalue access performance + self:discard(input) + end + end + end + config.app(c, "Sink", PushLinkSink) + end +end + function nfvconfig (confpath_x, confpath_y, nloads) local nfvconfig = require("program.snabbnfv.nfvconfig") nloads = tonumber(nloads) diff --git a/src/program/snabbvmx/lwaftr/setup.lua b/src/program/snabbvmx/lwaftr/setup.lua index 5623d63dd8..a91782185f 100644 --- a/src/program/snabbvmx/lwaftr/setup.lua +++ b/src/program/snabbvmx/lwaftr/setup.lua @@ -434,10 +434,12 @@ local function lwaftr_app_check (c, conf, lwconf, sources, sinks) config.app(c, "vm_v4v6", V4V6, { description = "vm_v4v6", mirror = false }) + config.app(c, "nh_fwd6_join", basic_apps.Join) config.link(c, "nh_fwd6.vm -> vm_v4v6.v6") - config.link(c, "vm_v4v6.v6 -> nh_fwd6.vm") + config.link(c, "vm_v4v6.v6 -> nh_fwd6_join.vm1") config.link(c, "nh_fwd4.vm -> vm_v4v6.v4") - config.link(c, "vm_v4v6.v4 -> nh_fwd6.vm") + config.link(c, "vm_v4v6.v4 -> nh_fwd6_join.vm2") + config.link(c, "nh_fwd6_join.output -> nh_fwd6.vm") config.app(c, "DummyVhost", basic_apps.Sink) config.link(c, "DummyVhost.tx -> vm_v4v6.input")