在 Elixir 中编写函数来测量另一个函数的好方法是啥
Posted
技术标签:
【中文标题】在 Elixir 中编写函数来测量另一个函数的好方法是啥【英文标题】: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 中实现类似的功能,提前感谢您的帮助!
【问题讨论】:
【参考方案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
虽然此示例使用遥测,但您可以在装饰器定义中轻松打印出时差。
【讨论】:
以上是关于在 Elixir 中编写函数来测量另一个函数的好方法是啥的主要内容,如果未能解决你的问题,请参考以下文章
Elixir/Erlang 中的命名函数是不是有等效于 __MODULE__ 的方法?