Let's say you are writing a lib which performs some work in a loop quickly (up to several million times per second). You need to allow your users to monitor this loop, but the requirements vary: One user just wants to count the total number of cycles, another wants to measure the performance and write some metrics to a file regularly, etc. The only requirement that everybody has is maximum performance.
Maybe you also have an item on your wishlist: To allow your less techie users to configure the monitoring without coding. Sometimes you even dream of reconfiguring the monitoring while the app is running...
The package is registered, so simply:
julia> using Pkg; Pkg.add("Plugins"). (repo)
The first one simply counts the calls to the
using Plugins, Printf, Test mutable struct CounterPlugin <: Plugin count::UInt CounterPlugin() = new(0) end Plugins.symbol(::CounterPlugin) = :counter # For access from the outside / from other plugins Plugins.register(CounterPlugin) function tick(me::CounterPlugin, app) me.count += 1 end;
The second will measure the frequency of
mutable struct PerfPlugin <: Plugin last_call_ts::UInt64 avg_elapsed::Float32 PerfPlugin() = new(time_ns(), 0) end Plugins.symbol(::PerfPlugin) = :perf Plugins.register(PerfPlugin) tickfreq(me::PerfPlugin) = 1e9 / me.avg_elapsed;
tick() hook is called, it calculates the time difference to the stored timestamp of the last call, and updates the exponential moving average:
const alpha = 1.0f-3 function tick(me::PerfPlugin, app) ts = time_ns() diff = ts - me.last_call_ts me.last_call_ts = ts me.avg_elapsed = alpha * Float32(diff) + (1.0f0 - alpha) * me.avg_elapsed end;
Now let's create the base system. Its state holds the plugins and a counter (just to cross-check the
mutable struct App plugins::PluginStack tick_counter::UInt App(plugins, hookfns) = new(PluginStack(plugins, hookfns), 0) end
There is a single operation on the app, which increments the
tick_counter in a cycle and calls the
function tickerop(app::App) tickhook = hooks(app).tick tickerop_kern(app, tickhook) # Using a function barrier to get ~5ns per hook activation end function tickerop_kern(app::App, tickhook) for i = 1:1e6 app.tick_counter += 1 tickhook(app) # We can pass shared state to plugins on the hook. Here, for simplicity, the whole app. end end;
The last step is to initialize the app, call the operation, and read out the performance measurement from the plugins:
app = App([CounterPlugin, PerfPlugin], [tick]) tickerop(app) @test app.plugins[:counter].count == app.tick_counter println("Tick count: $(app.plugins[:counter].count)") println("Average cycle time: $(@sprintf("%.2f", app.plugins[:perf].avg_elapsed)) nanoseconds, frequency: $(@sprintf("%.2f", tickfreq(app.plugins[:perf]) / 1e6)) MHz")
Tick count: 1000000 Average cycle time: 36.79 nanoseconds, frequency: 27.18 MHz
That was on the CI. On an i7 7700K I typically get around 19.95ns / 50.14 MHz. There is no overhead compared to a direct call of the manually merged
You can find this example under
docs/examples/gettingstarted.jl if you check out the repo.
This page was generated using Literate.jl.