arjan

arjan

Hi,

I am writing the Elixir integration for AppSignal, an application metrics solution. As of such, I am looking for a way to decorate functions in a developer-friendly way, so that these functions are automagically wrapped with library calls to measure the time it took to execute them (and send that info off to the backend).

As instrumenting functions is a common task while analyzing an application’s performance I want to minimize the amount of work the developer has to do to add instrumentation. As of such I have found two ways to do the instrumentation, both of which have its drawbacks:

1 - an instrumented do .... end block, in which you define the functions example here. Drawback of this method is that inside the block, everything is indented one level deeper, causing all the code to change while you have in fact just added 2 lines of code;making merging code changes harder;

2 - replace def foo() by def_instrument foo() (or similar) and use Kernel.def (like suggested here); the downside of this method is that it just “feels” weird to not read def, plus editor syntax highlighting breaks.

Are there any alternatives to tackle this? Ideally my solution would be a (Python / Java) decorator kind of syntax like this:

@instrumented
def foo(bar) do
  ...

But I am not sure that this is technically possible. I would love some input from the community on this!

cheers,
Arjan

Showing Posts 1 to 10

OvermindDL1

OvermindDL1

I’d probably just make something that the user could use just via:

defmodule SomeUserModule do
  use Instrumented,
    funs [
      (:hello, 0)
      ]

  def hello, do: "world"

  # .. lots of more functions
end

In your use you can hook all the functions that are listed by the tuple of the funName and funArity and use a macro to hook the def’s and instrument the ones that match the list. That seems like the easiest way to hook that. Could do an :all or so for the funs to say to do everything

Or you could do something like this if you want to mark ‘at’ the function site:

defmodule SomeUserModule do
  use Instrumented

  @instrumented
  def hello, do: "world"

  # .. lots of more functions
end

Where that would basically do the same thing but you’d walk the ast looking for that attribute than an immediately following function declaration or so.

It would be entirely possible for someone to make a generic ‘function decorator’ macro module that works like that, could do things like @decorate SomeModule.functionWrapper or so before the functions. Unsure if anyone’s made one yet, but eh?

arjan

arjan OP

Yes this latest idea was how I envisioned implementing it, walking the AST looking for module attributes next to a def. However, I dont know where I should hook in to, @before_compile does not allow you to alter the AST, iirc.. and __using__ only allows you to add code to the end of the module.

OvermindDL1

OvermindDL1

What about the @on_definition hook?

You can always hook def itself within the module with a macro and have it either call to the normal def or do special things based on information too (that would work well with the first style especially), or at least using it for the second style and renaming the function (adding a NONINSTRUMENTED_ or so to everything) then setting up trampolines with instrumenting or not at the end based on the annotations.

arjan

arjan OP

Yes, overriding def with a macro seems possible however I still don’t know how to get the @ attributes preceding the def, because I’m not sure there is a way to get the entire AST tree in some kind of hook and alter it.

I’m starting to lean to your first suggestion:

use InstrumentedDefs, foo: 2, bar: 3
NobbZ

NobbZ

Use a generic callback for def, when it gets called, check if @instrumented is set as a module-attribute, if so, do your stuff and unset it. If it is not set just do nothing.

There are some handy functions in the Module-module to work with module attributes ((get|put|delete|register)_attribute/*)

arjan

arjan OP

Thanks. But I guess I also need to check its position in the AST - basically misuse module attributes to check whether an attribute occurs just before a def.

Maybe if I put the decorators inside the function as the first thing, I can get away with just overriding def:

def some_function(bar) do
  @instrumented
  # ... do stuff
end
NobbZ

NobbZ

Why do you need it to be the very last thing before the function or the very first in it? If you are too strict, it will be impossible to compose such annotations.

Also, why should it matter if one writes variant A or B?

A:

@instrumented
@doc "documented!"
def foo(bar), do: bar

B:

@doc "documented!"
@instrumented
def foo(bar), do: bar

Of course you can exchange @doc with @spec or any other function level attribute or combinations thereof.


PS: AFAIK you cant do just an empty attribute you need to assign something to it.

arjan

arjan OP

It doesnt need to be strictly the last attribute before the def but the attribute should be scoped to the function somehow, e.g. some defs need instrumentation, others dont:

def foo(bar) do
  # not instrumented

@instrumented
def i_am_instrumented(bar) do
  # ...

def foo2(bar) do
  # not instrumented, again

I know about attribute definitions needing definition but inside the __using__ the attribute can get defined.

NobbZ

NobbZ

You have missed some of my points.

When you define @instrumented in the using, it will get replaced by that value in the sourcecode on every occurence, this can make your sourcecode invalid.

My proposal was to explicitely set it before functions, maybe specifying some extra stuff (similar to ExUnits @tags). And unset it in the callback.

arjan

arjan OP

Ah! That makes sense suddenly.
So @instrumented “blablah” just before a def (which is an overridden macro); and then the macro does its AST magic, and unsets the attribute. Neat! I’ll try that.

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews