Недавно мне потребовалось определить load average системы из Erlang. Готового решения найти не удалось. В результате была написана небольшая NIF библиотека для решения этой задачи. NIF (Native Implemented Function) — это функция, написанная на Си, которую можно вызывать из Erlang’а. Эта функция должна находится в динамической библиотеке, которая, соответственно, называется NIF-библиотекой (NIF library).

Как выяснилось, пишутся NIF’ки довольно просто. Заводим новый модуль:

module ( load_average ) .
export ( [ get / 0 ] ) .
on_load ( init / 0 ) .

ifdef ( TEST ) .
include_lib ( «eunit/include/eunit.hrl» ) .
endif .

init ( ) ->
case code : which ( load_average ) of
Filename when is_list ( Filename ) ->
erlang : load_nif ( filename : join ( [ filename : dirname ( Filename ) ,
«..» , «priv» ,
«load_average» ] ) , [ ] ) ;
Reason when is_atom ( Reason ) ->
{ error , Reason }
end .

spec get ( ) -> { ok , { float ( ) , float ( ) , float ( ) } } | { error , atom ( ) } .
% @doc Returns system load average
get ( ) ->
erlang : nif_error ( nif_library_not_loaded ) .

ifdef ( TEST ) .
get _ test ( ) ->
{ ok , { X , Y , Z } } = ? MODULE : get ( ) ,
true = is_float ( X ) ,
true = is_float ( Y ) ,
true = is_float ( Z ) .
endif .

Главное здесь — это вызов функции erlang : load_nif / 2 . Первым аргументом ей должен быть передан путь к NIF-библиотеке. Путь этот может немного меняться в зависимости от всякого, поэтому используется прием с определением пути к beam-файлу текущего модуля.

Код NIF-библиотеки размещаем в c_src/load_average.c:

#include «erl_nif.h»

static ERL_NIF_TERM get (
ErlNifEnv * env , int argc , const ERL_NIF_TERM argv [ ] ) {
double load_avg [ 3 ] ;
int loads ;

loads = getloadavg ( load_avg , 3 ) ;
if ( loads != 3 ) {
return enif_make_tuple (
env , 2 ,
enif_make_atom ( env , «error» ) ,
enif_make_atom ( env , «invalid_result» )
) ;
}

return enif_make_tuple (
env , 2 ,
enif_make_atom ( env , «ok» ) ,
enif_make_tuple (
env , 3 ,
enif_make_double ( env , load_avg [ 0 ] ) ,
enif_make_double ( env , load_avg [ 1 ] ) ,
enif_make_double ( env , load_avg [ 2 ] )
)
) ;
}

static ErlNifFunc nif_funcs [ ] = {
{ «get» , 0 , get }
} ;

ERL_NIF_INIT ( load_average , nif_funcs , NULL , NULL , NULL , NULL )

Как видите, вся работа сводится к преобразованию сишных типов в Erlang’овские, а также в вызове функции getloadavg . При написании этого кода мне здорово помог вот этот мануал .

Финальным аккордом дописываем в rebar.config:

{ port_specs , [
{ «priv/load_average.so» , [ «c_src/load_average.c» ] }
] } .

Компилируем:

rebar compile

… и NIF-библиотека готова! Полную версию исходного кода вы найдете в этом архиве . В целом от открытия мануала до получения готовой библиотеки у меня ушло менее часа. Вообще, в Erlang’е все очень просто и логично устроено, и ко всему есть хорошая документация. Меня, как новичка в данном языке, это ужасно радует.

Как заверил меня более опытный коллега , NIF’ы — они очень быстрые, но когда падают, тянут за собой всю виртуальную машину Erlang’а. И еще они как бы не очень хорошо переносимы. И есть у меня подозрения, что с многопоточностью там не все просто. В данном конкретном случае, например, вместо написания NIF можно было вызвать утилиту uptime с помощью os : cmd / 1 и пропарсить ее вывод. Тут есть свои минусы, но в целом подход нормальный.

Дополнение: Особую опасность представляют собой NIF, которые выполняются долго (условно, больше 1-2 мс). Такие NIF останавливают выполнение всех акторов, живущих с ними на одном треде. Возможные решения этой проблемы описаны в секции Long-running NIFs документации к библиотеке erl_nif.

Дополнение: См также мою заметку о трассировке в Erlang .

EnglishRussianUkrainian