Mix xref graph

I’m having a hard time finding out why changes in some of my modules trigger recompilations in 100+ other modules in my project. I’m trying to use mix xref graph --sink [some_path], but the few modules I tried all list a module at the top (and a big tree beneith), which I’m not even sure why it would have a compile time dependency to any of the them. Is there any way to use xref to explain why one module does have a dependency on another?

2 Likes

I’ve been bitten before by remote types triggering more modules to be compiled than expected. I think that might be fixed in 1.6 though iirc. There was also a good article at http://milhouseonsoftware.com/2016/08/11/understanding-elixir-recompilation/ but that seems to have disappeared. Another symptom I’ve seen is circular dependencies (even if passing through several other seemingly unrelated modules first) - which might fit your symptoms.

I’ve read the article you linked (it’s still here), but even being on 1.6 I don’t really feel confident in my ability to parse the list of modules returned by xref. E.g. for one specific file I get ~120 recompiled modules on changes, but a xref (sink) list of over 500 lines of module paths with ~170 distinct ones in it. I don’t feel like I have a good idea about how to proceed from that stage and what I need to look for. Do I need to start at the leafes of the xref tree or from the inner branches? Are there any common signs/code snippets to look out for?

I’ve had a quick look in the compile.elixir file, which seems to be the source of information for xref, but while it’s certainly more verbose in “how files are dependent on each other” it still couldn’t really answer why the files I mentioned in my initial post would be dependent in any way.

So I noticed that xref does now return runtime dependencies as well, so here are the struct/compile dependencies, which explains my initial confusion about some returned modules. But still changing that module does recompile a lot more modules than the ones listed here:

$ mix xref graph --sink lib/connect/scheduling/potentials/potential.ex --label compile
lib/connect_web/channels/team_channel.ex
└── lib/connect_web/protocols/full_name_protocol.ex (compile)
    └── lib/connect/scheduling/potentials/potential.ex (compile)
lib/connect_web/controllers/app/tenant_settings/team_controller.ex
└── lib/connect_web/protocols/full_name_protocol.ex (compile)
lib/connect_web/protocols/full_name_protocol.ex
lib/connect_web/views/api/manage/unassigned_activities_view.ex
└── lib/connect_web/protocols/full_name_protocol.ex (compile)
lib/connect_web/views/app/dashboard_view.ex
└── lib/connect_web/protocols/full_name_protocol.ex (compile)
lib/connect_web/views/app/manage/unassigned_activities_view.ex
└── lib/connect_web/protocols/full_name_protocol.ex (compile)
lib/connect_web/views/app/tenant_settings/members_view.ex
└── lib/connect_web/protocols/full_name_protocol.ex (compile)
lib/connect_web/views/app/tenant_user_view.ex
└── lib/connect_web/protocols/full_name_protocol.ex (compile)
lib/connect_web/views/layout_view.ex
└── lib/connect_web/protocols/full_name_protocol.ex (compile)
lib/connect_web/views/meta/breadcrumbs.ex
└── lib/connect_web/protocols/full_name_protocol.ex (compile)
$ mix xref graph --sink lib/connect/scheduling/potentials/potential.ex --label struct
lib/connect/scheduling/potentials/potentials.ex
└── lib/connect/scheduling/potentials/potential.ex (struct)
lib/connect/scheduling/scheduling.ex
└── lib/connect/scheduling/potentials/potential.ex (struct)

That’s because if you have a compile time dependency on a module changed at runtime, that compile time dependency is recompiled too.

But it does seem you already have a large compile time dependency on “lib/connect_web/protocols/full_name_protocol.ex” and therefore on potential and that is going to indeed cause frequent recompiles.

Note mix xref graph --format stats can also help you find hotspots.

1 Like

What exactly do you mean by “a module changed at runtime”? fswatch does record file updates even for my endpoint/router, which doesn’t feel right, which is why I was looking for a way to find dependencies between specific files/modules. It seems xref is currently more suited for exporative searches for critical areas.

If file X has a runtime dependency on lib/connect/scheduling/potentials/potential.ex and a file Y has a compile time dependency on X, then Y will also be recompiled when potential.ex changes.

1 Like