erlang 在Elixir中,编写函数来测量另一个函数的好方法是什么

yrdbyhpb  于 2022-12-08  发布在  Erlang
关注(0)|答案(3)|浏览(146)

我是elixel的新手,我试图找到类似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中的上下文管理器的想法。但是现在我确定了如何在Elixir中实现类似的功能,提前感谢你的帮助!

zour9fqk

zour9fqk1#

在Erlang/Elixir中,这是通过高阶函数来完成的,看看BEAM telemetry。它是一个Erlang和Elixir库/标准,用于收集度量和测试代码a-它被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
)
ntjbwcob

ntjbwcob2#

我认为最接近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有一个用于事务的上下文管理器,但Ecto使用了一个更高阶的函数来处理同样的事情。
PS:要测量经过的时间,您可能需要使用:timer.tc/1

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

pgccezyw3#

实际上有一个非常漂亮的库,叫做Decorator,在这个库中,宏可以用来" Package "你的函数来做各种各样的事情。
在本例中,您可以编写一个装饰器模块(感谢telemetry示例中的@maciej-szlosarczyk):

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

虽然这个例子使用了遥测技术,但是您可以在装饰器定义中轻松地打印出时间差。

相关问题