MadsBoydMadsen
Macro to provide timing for a function call - handle any signature
I’m working on a macro to support automatic timing for functions.
If I have this - say:
def double(x) do
x + x
end
I would like to be able to change it to this on a needs basis:
def_timed double(x) do
x + x
end
I’ve got the basics of a macro written that can handle this:
defmodule Cw.Utilities.Timing.DefTimed do
defmacro def_timed({name, _meta, args} = _ast, do: body) do
arg_vars = Enum.map(args, fn {arg_name, _, _} -> arg_name end)
quote do
def unquote(name)(unquote_splicing(args)) do
Timer.wrap_timed(
fn unquote_splicing(arg_vars) -> unquote(body) end,
[unquote_splicing(arg_vars)],
unquote(name)
)
end
end
end
end
defmodule Timer do
def wrap_timed(fun, args, name) do
start_time = System.monotonic_time(:millisecond)
result = apply(fun, args)
end_time = System.monotonic_time(:millisecond)
IO.puts("""
[TIMED] Function: #{name}
[TIMED] Arguments: #{inspect(args)}
[TIMED] Execution time: #{end_time - start_time} ms
""")
result
end
end
This works fine for functions with simple signatures as above.
But it doesn’t work when the signatures includes keyword-list, optionals, guards or pattern-matching.
I sense, that this must be a solved problem, but I cannot find anything online to demonstrate how to do it.
I’m looking for help to work it out.
NOTE: One of my colleagues have just pointed out, that macros should only be used sparingly (Meta-programming anti-patterns — Elixir v1.20.2).
My use-case strikes me as being pretty perfect for a macro, as adding and removing instrumentation would be a breeze (good luck to me
)
Is there a better - and equally straight forward - way for me to achieve what I’m trying to do ?
Most Liked
benwilson512
Checkout Elixir function decorators — decorator v1.4.0 which implements a module attribute based decorator pattern.
The main code for defining decorators is here: decorator/lib/decorator/decorate.ex at master · arjan/decorator · GitHub
billylanchantin
I’m honestly don’t think this is a solved problem largely for the reasons you note: the forms the AST can take are quite varied. While Elixir does give you the ability to manipulate AST directly, it doesn’t give you many nice helpers like Macro.find_the_do_block_in_this_ast/1.
(I’m speculating here, but I imagine it’s because the AST is an implementation detail subject to change.)
My personal experience with writing macros is that you’re on your own somewhat. The Meta-Programming chapters of the docs are quite good. But for specific applications, you often need to dig in and see what the expressions you plan to work with happen to come out as in AST form.
Note: another snag you’ll hit is that there are other blocks that come after the do block:
def reciprocal(x) when is_integer(x) do
1 / x
rescue
ArithmeticError -> :infinity
end
al2o3cr
The tricky part is that a function doesn’t even need to name its arguments if it pattern-matches on them, for instance:
def foo([a | _], %{wat: b}) do
...
end
Replacing def with def_timed:
def_timed foo([a | _], %{wat: b}) do
...
end
could expand to something like:
def foo(arg1, arg2) do
Timer.wrap_timed(
&untimed_foo/2,
[arg1, arg2],
:foo
)
end
def untimed_foo([a | _], %{wat: b}) do
...
end
untimed_foo would use the original args AST unaltered, while the code generated in foo only cares about length(args).
Supporting default args directly in def_timed would be a lot of extra hassle, but using a do-less def would let you avoid that:
def timed_thing_with_defaults(x, y \\ 1, z \\ 2)
def_timed timed_thing_with_defaults(x, y, z) do
...
end
Last Post!
dimitarvp
Then here’s one more homework I’m afraid: you’ll need to integrate OpenTelemetry in your project.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #hex









