Недавно мне потребовалось определить load average системы из Erlang. Готового решения найти не удалось. В результате была написана небольшая NIF библиотека для решения этой задачи. NIF (Native Implemented Function) — это функция, написанная на Си, которую можно вызывать из Erlang’а. Эта функция должна находится в динамической библиотеке, которая, соответственно, называется NIF-библиотекой (NIF library).
Как выяснилось, пишутся NIF’ки довольно просто. Заводим новый модуль:
— 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:
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:
{ «priv/load_average.so» , [ «c_src/load_average.c» ] }
] } .
Компилируем:
… и NIF-библиотека готова! Полную версию исходного кода вы найдете в этом архиве . В целом от открытия мануала до получения готовой библиотеки у меня ушло менее часа. Вообще, в Erlang’е все очень просто и логично устроено, и ко всему есть хорошая документация. Меня, как новичка в данном языке, это ужасно радует.
Как заверил меня более опытный коллега , NIF’ы — они очень быстрые, но когда падают, тянут за собой всю виртуальную машину Erlang’а. И еще они как бы не очень хорошо переносимы. И есть у меня подозрения, что с многопоточностью там не все просто. В данном конкретном случае, например, вместо написания NIF можно было вызвать утилиту uptime с помощью os : cmd / 1
и пропарсить ее вывод. Тут есть свои минусы, но в целом подход нормальный.
Дополнение: Особую опасность представляют собой NIF, которые выполняются долго (условно, больше 1-2 мс). Такие NIF останавливают выполнение всех акторов, живущих с ними на одном треде. Возможные решения этой проблемы описаны в секции Long-running NIFs документации к библиотеке erl_nif.
Дополнение: См также мою заметку о трассировке в Erlang .