【问题标题】:What is a good way for writing a function to measure another function in Elixir在 Elixir 中编写函数来测量另一个函数的好方法是什么
【发布时间】:2021-10-16 07:01:28
【问题描述】:

我是 elixir 新手,我正在尝试寻找类似于 Python 的 ContextManager 的东西。

问题:

我有一堆函数,我想在它们周围添加延迟指标。

现在我们有了:

def method_1 do
  ...
end

def method_2 do
  ...
end

... more methods

我想要:


def method_1 do
  start = System.monotonic_time()
  ...
  end = System.monotonic_time()
  emit_metric(end-start)
end

def method_2 do
  start = System.monotonic_time()
  ...
  end = System.monotonic_time()
  emit_metric(end-start)
end

... more methods

现在代码重复是个问题

  start = System.monotonic_time()
  ...
  end = System.monotonic_time()
  emit_metric(end-start)

那么在这种情况下,避免代码重复的更好方法是什么?我喜欢 python 中的context manager 想法。但现在确定如何在 Elixir 中实现类似的功能,提前感谢您的帮助!

【问题讨论】:

    标签: functional-programming erlang elixir


    【解决方案1】:

    在 Erlang/Elixir 中,这是通过高阶函数完成的,请查看 BEAM telemetry。它是用于收集指标和检测代码的 Erlang 和 Elixir 库/标准——它被 Pheonix、Ecto、cowboy 和其他库广泛采用。具体来说,您会对 :telemetry.span/3 函数感兴趣,因为它默认发出开始时间和持续时间测量值:

    def some_function(args) do
      :telemetry.span([:my_app, :my_function], %{metadata: "Some data"}, fn ->
        result = do_some_work(args)
        {result, %{more_metadata: "Some data here"}}
      end)
    end
    
    def do_some_work(args) # actual work goes here
    

    然后,在您的其他代码中,您侦听这些事件并将它们记录/发送到 APM:

    :telemetry.attach_many(
      "test-telemetry", 
      [[:my_app, :my_function, :start], 
       [:my_app, :my_function, :stop], 
       [:my_app, :my_function, :exception]],
      fn event, measurements, metadata, config -> 
        # Handle the actual event.
      end)
      nil
    )
    

    【讨论】:

      【解决方案2】:

      我认为最接近 python 上下文管理器的是使用高阶函数,即以函数作为参数的函数。 所以你可以有类似的东西:

      def measure(fun) do
        start = System.monotonic_time()
        result = fun.()  
        stop = System.monotonic_time()
        emit_metric(stop - start)
        result
      end
      

      你可以像这样使用它:

      measure(fn ->
        do_stuff()
        ...
      end)
      

      注意:还有其他类似的实例,您可以在 python 中使用上下文管理器,这将以类似的方式完成,在我的脑海中:Django 有一个context manager for transactions,但Ecto uses a higher order function 用于同样的事情。

      PS:要测量经过的时间,您可能想使用:timer.tc/1

      def measure(fun) do
        {elapsed, result} = :timer.tc(fun)
        emit_metric(elapsed)
        result
      end
      

      【讨论】:

        【解决方案3】:

        实际上有一个非常漂亮的库,名为 Decorator,其中可以使用宏来“包装”您的函数来执行各种操作。

        在您的情况下,您可以编写一个装饰器模块(感谢@maciej-szlosarczyk 的telemetry 示例):

        defmodule MyApp.Measurements do
          use Decorator.Define, measure: 0
        
          def measure(body, context) do
            meta = Map.take(context, [:name, :module, :arity])
            quote do
              # Pass the metadata information about module/name/arity as metadata to be accessed later
              :telemetry.span([:my_app, :measurements, :function_call], unquote(meta), fn ->
                {unquote(body), %{}}
              end) 
            end
          end
        end
        

        您可以在 Application.start 定义中设置遥测侦听器:

        :telemetry.attach_many(
          "my-app-measurements", 
          [[:my_app, :measurements, :function_call, :start], 
           [:my_app, :measurements, :function_call, :stop], 
           [:my_app, :measurements, :function_call, :exception]],
          &MyApp.MeasurementHandler.handle_telemetry/4)
          nil
        )
        

        然后在任何带有您想要测量的函数调用的模块中,您可以像这样“装饰”这些函数:

        defmodule MyApp.Domain.DoCoolStuff do
          use MyApp.Measurements
          
          @decorate measure()
          def awesome_function(a, b, c) do
            # regular function logic
          end
        end
        

        虽然此示例使用遥测,但您可以在装饰器定义中轻松打印出时差。

        【讨论】:

          猜你喜欢
          • 2018-05-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-04-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多