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

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
water
I’m aware of 2 such projects, are there others? git: GitHub - team-alembic/realworld: A fullstack Phoenix LiveView application with back...
New
itzmidinesh
Hi all, I’m working on a Phoenix project where I need to make external HTTP requests and I’m trying to choose between Req and HTTPoison....
New
pragdave
How do I tell Hologram not to escape HTML when substituting into templates? {Textile.render(@product.long_description)} escapes the HTM...
New
rlipscombe
I’d like to create a file with 600 permissions (rw-------), but there’s a race condition if I create it and then change the permissions. ...
New
leoschweizer
Hi, we run different LLM models across a cluster, with each node loading a configurable subset. Model-specific queues let nodes consume ...
New
rlipscombe
When I use Mix.install with a git dependency, using git@github.com:... how do I change the SSH identity used? I’ve got two Github accoun...
New

Other Trending Topics Top

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
raa-ziq
Hi everyone :waving_hand: I wanted to share something I’ve been building — NowBlind, a random chat app designed to help people discover ...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New
kip
Localize is the next generation localisation library for Elixir. Think of it as ex_cldr version 3.0. The first version will be released ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews