Is there an easy way to find instances where a function gets called in code?

I need this for statistical reasons and “all” is a very loose term. It’s OK if I don’t find absolutely all places, though I’d like it if I find places where functions get called via :erlang.apply/3. Also, where it’s possible (where available at compile time), I’d like to get the variables with which the function is called.

Is this doable without major hackery, or am I asking for too much?

2 Likes

If you have access to the original function that is being called, then it is rather simple: Change the function to be a macro.

You then have access to the scope in which the macro is called, and, if you want, you can inspect and evaluate the arguments it was called with. If you want more info on (unhygienic) macros, I’d suggest @sasajuric’s wonderful article series on macros.

Without doing this, it’s going to be hard; your best bet in that case is probably to run a string search algorithm on the code in your codebase. But this will only find occurrences like Foo.bar(), and possibly not the import Foo ... some other code... bar() cases. And cases where apply is used or where the function is called after the module name has been passed around as a variable will be impossible to find this way.

1 Like

You can’t call macros via apply/3 so you’ll run into a bit of an issue there. apply/3 in general is difficult because it resists static analysis of the program. I’m not sure that it’s even theoretically possible to account for all such uses since every instance of apply/3 that takes dynamic input could in theory call your function. Proving that it would be very very hard.

2 Likes

Looks like mix xref is something that might help you. If it doesn’t give you everything you need, you could use its source code for inspiration, or perhaps even extend it.

I don’t understand what do you mean by this?

2 Likes

I mean that if the fun gets called in the code like my_fun("/api/dashboard/home") I want to get “/api/dashboard/home”

1 Like

Can you elaborate on the overall project you’re undertaking?

1 Like

I want to have a convenient way of knowing all the URLs I am hitting, so that I can generate statistics and admin interfaces listing them. I don’t want to have to maintain a list separately (which is tedious and error prone). I also don’t want to have to register them in ets at runtime as it’ll be a performance hit.

I will probably follow @Qqwy’s advice. So far it seems quite doable by refactoring my code.

1 Like

You could also look at having a macro & an extraction task, simmilar to what Gettext is doing with the translation strings.

But that’s only a solution, if you control all the uses of the function, you’d like to track.

1 Like

I do control all the uses. I will have a look, thank you.

1 Like

Are all of the URLs in your code static? If they’re all static or mostly static (ie, “foo/bar/#{id}”) I would recommend simply inverting the problem. Instead of having the strings scattered about your application and then trying to collect them, just put them all in one place:

defmodule MyApp.SomeService do
  def foo_path(), do: "/foo"
  def foo_path(id), do: "/foo/#{id}"
  # etc
end

Now you’ve got a nice easy point of reference for all the values.

If it isn’t static and instead depends on values that exist outside the code there’s simply no way to do this statically. I wouldn’t be concerned about the performance overhead of :ets, it’s adding like 6 microseconds onto an HTTP request that is 1000s of microseconds. You do have to worry about memory though in case the inputs are truly unbounded.

1 Like

Doesn’t really work, because I will still need the context in which I call the URL, for example, which module calls it.

1 Like

As has been noticed, if you make them all macros then they’ll have access to the __CALLER__ and can log that information.

1 Like

And if they aren’t static, you can’t do static analysis, there just isn’t much else to note there.

1 Like

They are static, though they are called from several different places and I want to know statistics for each.
Also, ETS is not an option because often I need the list before I have called the URLs or at least, before I have called ALL of the URLs.

Actually, the route we are descending into is “dry run” all of our functions when I need the list. “Dry run” means “execute all the functions without actually calling the URLs via httpoison”

1 Like