bamorim

bamorim

More Telemetry Events for LiveView

Story behind

Recently, I gave a talk on a meetup about improving performance of Phoenix applications and the example app was a LiveView app. One of the problems I created was a N+1 where the solution was to use the new update_many/1 callback on LiveComponents as an example. The demo showed the “discovery” phase of performance issues by looking at OpenTelemetry traces (I was using OpentelemetryLiveVIew, but now OpentelemetryPhoenix also have some LiveView spans in the new unreleased version).

The problem I noticed was that the N+1 was only clear on full renders where OpentelemetryPhoenix would create a http server span around. Navigating through live patches, didn’t made it clear that there was a problem because there were a bunch of “rogue” Ecto spans in an isolated trace.

What this have to do with Phoenix LiveView itself? Well, these OTEL libraries base their OTEL spans on top of :telemetry spans. Looking at the Phoenix LIveView code base I realised a few more :telemetry spans would be helpful, so the proposal here is to list a few ideas and where to put these.

More callback spans

I first want to start with more callback events

  • [:phoenix, :live_view | :live_component, :update | :update_many]
    • To help find where a query is coming from and be able to solve a N+1 problem, maybe it would be good to wrap calls to update / update_many.
    • It should be easy since the only place that calls it are on Phoenix.LiveView.Utils.maybe_call_update!/3
  • [:phoenix, :live_view | :live_component, :render]
    • A normal N+1 problem is caused by rendering many live components that do a query in their update. Having the “wrapper” component render span be a “parent” of a “update” span would allow us to spot the N+1
    • For LIveView, this would require wrapping both Utils.to_rendered and Diff.render calls. We could wrap the whole render_diff function in Phoenix.LiveView.Channel or at least the true branch on the if (when force == true or when it changed). This would allow it to encapsulate all “nested update/render spans”.
    • For LiveView components, it happens internally to Diff.render, in the Diff.component_to_rendered function, so it should be easy to wrap that in a span as well

Maybe it is also worth adding handle_call, handle_cast and handle_info for the cases where we forward that to the live view (socket.view.handle_call(...)), but also including the handle_result subsequent call.

Channel Lifecycle Spans

Now is where I don’t have a clear idea whether it is a good idea or not, but maybe worth exploring. To be able to wrap a “full cycle” on the channel process for each type of “message”. The challenge here is that this could be leaking internals that should not be treated as the public. The benefit is that we would be able to treat it on OpenTelemetry as “wrapping messaging spans”.

In general the idea would be to wrap the outermost callbacks on Phoenix.LiveView.Channel to be able to have a higher level view on what’s happening.

Some examples:

  • [:phoenix, :live_view_channel, :mount]
    • This would be the “join” call where the live view is mounted. Not sure where to put it, if it should encompass the whole handle_info call with the join data or if it should be internally on the mount or verified_mount functions. I guess it depends on how much data we want to include in the :start event? Also there are questions on what we should send when we reply with an error. This is because the return value can be {:stop, :shutdown, :no_state} so there is no reason on what caused it (we only have a slight hint on the GenServer.reply call) to stop. So maybe we should add some data on the return? but that would require the whole tree to be changed: there are many places that do that, so it might be a challenge to organize this in any sane way.
  • [:phoenix, :live_view_channel, :handle_event]
    • Wrapping the whole handle_info case for the %Message{topic: topic, event: "event"} message
  • [:phoenix, :live_view_channel, :live_patch]
    • Wrapping live patch calls. However that can be challenging since it can happen on the %Message{topic: topic, event: "live_patch"} but also internally on the functions mount_handle_params_result and handle_redirect.

Also, it might be worth having a span for each message it receives, from redirect messages, to async results.

Contribution

If this is desirable, I can open a PR on phoenix_live_view repo with some of these. I think the extra telemetry events for the live view / live component callbacks are almost certainly worth it while others probably need more discussion.

First Post!

josevalim

josevalim

Creator of Elixir

Hi @bamorim, very nice thoughts here.

I think we should definitely consider instrumenting either [:phoenix, :live_view, :render] or [:phoenix, :live_view, :update_many] (invoke once per component type). It depends on how much granularity you want. A LiveComponent doesn’t really execute those actions - it does render but it is very fast, none of what LiveView is doing - so I wouldn’t include it.

About channel events, I am not sure I understand. We already capture the user handle_event/handle_params/mount. I am not sure we are doing internally something meaningful to be worth capturing more. I guess for mount we do a lot of work, but we don’t know what we are mounting until the expensive work is done, so the span would be vague. :frowning:

Where Next?

Popular in Proposals: Ideas Top

tubedude
Hey, Earlier i posted a question, but after some research I think a proposal is due. Working on a Phoenix Live View app, I needed clien...
New
andreamancuso
Hey folks, This might sound niche, but I think it’s worth bringing up - especially given Phoenix’s reputation for being lightweight, por...
New
mythicalprogrammer
Hello, since the 1.8rc0 was out, DaisyUI was noted to have the benefit of light and dark mode. From the elixir subreddit, it seem a few ...
New
pelopo
Hi, Oficial docs got this wonderful feature to download the ePub version that we can chuck to our kindles and read it. Now we are in the...
New
mortenlund
Hi! I would like to suggest a new callback in the lifecycle of the Live Component which is unmount. Sometimes it is nice to be able to ...
New
azyzz228
The slow network is known to be an Achilles heel of LiveView’s architecture. Recently, I was working on creating a fast rendering map wi...
New
victor23k
Hi all! I come with a proposal to create LiveViews inside the shadow DOM. At Doofinder, we have experience building embeddable LiveView a...
New
ffloyd
The Problem Currently, if I define a struct in the following way: defmodule MyStruct do # Both x and y will have the FIXED values unti...
New
calebjosue
I am reading (Once more) the Phoenix web framework documentation, in the From endpoint to views section contained in the Request life-cyc...
New
lemaster
Currently we have stream_insert and stream that can either add new items to a stream or update existing items if they are already present...
New

Other popular topics Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 41454 115
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30840 112
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New

We're in Beta

About us Mission Statement