gmile

gmile

How does one approach gathering runtime statistics about function calls from modules that belong to a specific app?

In my app, using a combination of decorator and telemetry packages, I am able to collect statistics about function calls like this:

defmodule MyApp.FunctionTracing do
  use Decorator.Define, span: 0

  def span(body, context) do
    quote do
      metadata = %{
        function: "#{unquote(context.module)}.#{unquote(context.name)}/#{unquote(length(context.args))}"
      }

      :telemetry.span([:my_app, :function_call], metadata, fn ->
        {unquote(body), metadata}
      end)
    end
  end
end

defmodule MyApp.MyModule do
  use MyApp.FunctionTracing

  @decorate span()
  def create_session(args) do
    # ...
  end
end

This is then exposed via a family of telemetry packages in a form of GET /metrics endpoint for Prometheus collector to come and pick up.

I can do the above manually for functions that I suspect may not be called. Could someone think of a way to scale this approach? E.g. is there a way I could tell Elixir compiler: please, when compiling ALL modules that belong to my own app, do wrap them in such a way?

The end goal here is to highlight & eliminate dead from from a fairly large codebase. Having all functions instrumented this way, I’d deploy the code in staging or production, collect the statistics for a week or two, then use it to base decisions about code clean up.

Or is this too crazy of a thing to want for such a use case? :slight_smile: If it’s too crazy, what would be a good alternative?

I’ve heard one could go a long way with tools already available in Erlang VM / stdlib for tracing and such, - if so, could someone point me in the right direction? At this point all I’m interested in is just counting all function calls coming from modules that belong to my own app.

Ideally, after a week or two of “counting”, I could filter out all function calls with 0 calls, and begin cleaning up the code.

Showing Posts 1 to 7

LostKobrakai

LostKobrakai

I’d start with GitHub - hauleth/mix_unused: Find unused functions in your project · GitHub unless you’re using apply in many places.

gmile

gmile OP

Thanks for bringing this one up - we tried it, and eventually were able to removed some code.

Btw, in our case because the main app is an umbrella type app (working on removing the “umbrela” from it atm…) which made the tool somewhat fail to work. So we ended up using a custom compilation tracing script to locate some of the un-used functions.

Next step currently is runtime analysis, and this is where am I looking for new ideas how to approach it.

al2o3cr

al2o3cr

This article discusses the BEAM’s tracing machinery and some of the Elixir wrappers available.

LostKobrakai

LostKobrakai

And this is a much more practical introduction: Debugging With Tracing in Elixir
For elixir I suggest Extrace to also get elixir syntax for traces.

gmile

gmile OP

E.g. is there a way I could tell Elixir compiler: please, when compiling ALL modules that belong to my own app, do wrap them in such a way?

Btw I guess the following can be considered an answer to my question above:

voltone

voltone

This may be relatively safe in production:

On application startup, call :erlang.trace_pattern({:_, :_, :_}, true, [:call_count]). If code loading is dynamic (i.e. you’re not running as part of a release), also call :erlang.trace_pattern(:on_load, true, [:call_count]).

Then whenever you want to gather call stats for all public functions that have been called at least once:

for {m, _} <- :code.all_loaded(),
    {f, a} <- m.module_info(:functions),
    {:call_count, c} = :erlang.trace_info({m, f, a}, :call_count),
    c > 0,
  do: {{m, f, a}, c}   # or if you prefer: {"#{inspect(m)}.#{f}/#{a}", c}

If you want to include functions that were never called, just drop the c > 0, line.

Or, for your use-case of finding unused functions in a given application:

app = :my_app
for m <-  Application.spec(app)[:modules],
    {f, a} <- m.module_info(:functions),
    {:call_count, c} = :erlang.trace_info({m, f, a}, :call_count),
    c == 0,
  do: {{m, f, a}, c}   # or if you prefer: {"#{inspect(m)}.#{f}/#{a}", c}

In that case you can also selectively enable call count tracing for only the modules of your application, instead of for all modules in the system.

You can reset the counters with :erlang.trace_pattern({:_, :_, :_}, :restart, [:call_count]) or stop tracing with :erlang.trace_pattern({:_, :_, :_}, false, [:call_count]).

gmile

gmile OP

This is pure gold. I’m going to try this, thank you so much for taking the time to write examples!

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
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
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
psy-q
I’m trying to set up Emacs with elixir-ls via lsp-mode and credo via Flycheck. This should mostly be preconfigured as Flycheck picks up c...
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
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
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
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews