kip

kip

ex_cldr Core Team

Runtime “warn once” strategy

I’m after ideas and suggestions on how to emit a message when a certain condition is met at runtime - but only once, the first time the condition is met.

My use case is this: in ex_cldr_numbers a number format like #,###.## is compiled into meta data at compile time for all known formats (there are a lot of them in CLDR).

Since formats can be arbitrary they can also be compiled at runtime and then processed. That’s expensive (doubles the execution time for formatting a number in typical cases). And since number formatting is often in a tight loop its something to be avoided.

Therefore I want to emit a message (probably via Logger.debug) when a format is going to be compiled. But only the first time that format is being compiled. Otherwise the risk is that the log output becomes far too noisy to be useful. Additional formats can be defined in configuration and hence pre-compiled, therefore knowing which ones aren’t pre-compiled helps decide if they should be configured.

I can imagine some scaffolding involving an ets table and a counter based upon the format (which is a binary). And in development mode that’s probably ok, the overhead won’t be much. But I’m not sure that’s a good production solution.

This warn_once idea has cropped up as an idea for me in a few other cases so I figure someone may already have a solid approach.

Most Liked

kip

kip

ex_cldr Core Team

In case anyone stumbles across this question in their own question, the following is suitable for me needs:

  defmacro warn_once(key, message, level \\ :warn) do
    caller = __CALLER__.module

    quote do
      require Logger

      if :persistent_term.get({unquote(caller), unquote(key)}, true) do
        Logger.unquote(level)(unquote(message))
        :persistent_term.put({unquote(caller), unquote(key)}, nil)
      end
    end
  end

I wrap this at the calling site with a macro to make the inclusion of this generated code dependent on configuration, and whether :persistent_term exists.

dimitarvp

dimitarvp

Good solution. :+1:

Changing persistent_term value is, I hear, expensive, but since it’s done very rarely in this scenario, the added peace of mind is very worth it.

kip

kip

ex_cldr Core Team

@dimitarvp thanks for chiming in. I try hard to do as much work at compile time. Here is the module in question (edited for brevity). Its perfectly acceptable to provide an arbitrary format string at runtime, the only issue being the potential of this being a performance bottleneck. It might not be in some use cases, but it will in others.

I could:

  1. Emit a log event for each time its used. Easy, but potentially noisy
  2. Emit a telemetry event. But its not really a metric so probably a perversion
  3. Encapsulate in runtime generated modules (but it doesn’t feel right to do so just for a single function call). I think this is what you meant?
  4. Find another low cost way to “warn once” at runtime. Very comfortable to make this a configurable option - and thereby emit no code and therefore no performance impact, if not required,
defmodule Cldr.Number.Backend.Decimal.Formatter do
  @moduledoc false

  def define_number_module(config) do
    alias Cldr.Number.Formatter.Decimal
    backend = config.backend

    quote location: :keep do
      defmodule Number.Formatter.Decimal do
        .....
        def to_string(number, format, options \\ [])

        def to_string(number, format, options) when is_binary(format) and is_list(options) do
          with {:ok, options} <- Options.validate_options(number, unquote(backend), options) do
            to_string(number, format, options)
          end
        end

        # Precompile the known formats and build the formatting pipeline
        # specific to this format thereby optimizing the performance.
        unquote(Decimal.define_to_string(backend))

        ....

        # For formats not precompiled we need to compile first
        # and then process. This will be slower than a compiled
        # format since we have to (a) compile the format and (b)
        # execute the full formatting pipeline.
        def to_string(number, format, %Options{} = options) when is_binary(format) do
          case Compiler.format_to_metadata(format) do
            {:ok, meta} ->
              --> warn_once("This format isn't precompiled and will be compiled in each formatting request"*
              meta = Decimal.update_meta(meta, number, unquote(backend), options)
              Decimal.do_to_string(number, meta, unquote(backend), options)

            {:error, message} ->
              {:error, {Cldr.FormatCompileError, message}}
          end
        end
      end
    end
  end
end

Where Next?

Popular in Questions Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
nobody
How to bind a phoenix app to a specific ip address? could not find anything about that, nowhere, unfortunately, but for me this is quite...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; somethi...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
dotdotdotPaul
Okay, I’m having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I’m sure I’...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Other popular topics Top

Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31142 143
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New

We're in Beta

About us Mission Statement