%% @hidden -module(ra_file_handle). -include("ra.hrl"). -behaviour(gen_server). -export([start_link/0]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -export([open/2, close/1, sync/1, write/2, read/2, position/2]). -export([pwrite/2, pwrite/3, pread/2, pread/3]). -define(SERVER, ?MODULE). -define(TABLE, ra_io_metrics). -define(COUNT_TIME, [io_sync, io_seek, io_file_handle_open_attempt]). -define(COUNT_TIME_BYTES, [io_read, io_write]). -record(state, {monitors = #{} :: #{pid() => reference()}}). open(File, Modes) -> gen_server:cast(?MODULE, {open, self()}), update(io_file_handle_open_attempt, fun() -> file:open(File, Modes) end). close(Fd) -> gen_server:cast(?MODULE, {close, self()}), file:close(Fd). sync(Fd) -> update(io_sync, fun() -> file:sync(Fd) end). write(Fd, Bytes) -> update(io_write, iolist_size(Bytes), fun() -> file:write(Fd, Bytes) end). read(Fd, Bytes) -> update(io_read, Bytes, fun() -> file:read(Fd, Bytes) end). position(Fd, Location) -> update(io_seek, fun() -> file:position(Fd, Location) end). pwrite(Fd, LocBytes) -> update(io_write, fun() -> file:pwrite(Fd, LocBytes) end). pwrite(Fd, Location, Bytes) -> update(io_write, iolist_size(Bytes), fun() -> file:pwrite(Fd, Location, Bytes) end). pread(Fd, LocBytes) -> update(io_read, fun() -> file:pread(Fd, LocBytes) end). pread(Fd, Location, Number) -> update(io_read, Number, fun() -> file:pread(Fd, Location, Number) end). update(Op, Bytes, Thunk) -> {Time, Res} = timer_tc(Thunk), _ = ets:update_counter(?TABLE, {Op, count}, 1), _ = ets:update_counter(?TABLE, {Op, bytes}, Bytes), _ = ets:update_counter(?TABLE, {Op, time}, Time), Res. update(Op, Thunk) -> {Time, Res} = timer_tc(Thunk), _ = ets:update_counter(?TABLE, {Op, count}, 1), _ = ets:update_counter(?TABLE, {Op, time}, Time), Res. timer_tc(Thunk) -> T1 = erlang:monotonic_time(), Res = Thunk(), T2 = erlang:monotonic_time(), Diff = erlang:convert_time_unit(T2 - T1, native, micro_seconds), {Diff, Res}. start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). init([]) -> process_flag(trap_exit, true), [ets:insert(?TABLE, {{Op, Counter}, 0}) || Op <- ?COUNT_TIME_BYTES, Counter <- [count, bytes, time]], [ets:insert(?TABLE, {{Op, Counter}, 0}) || Op <- ?COUNT_TIME, Counter <- [count, time]], {ok, #state{}}. handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. handle_cast({open, Pid}, #state{monitors = Monitors} = State) -> ets:update_counter(ra_open_file_metrics, Pid, 1, {Pid, 0}), case Monitors of #{Pid := _MRef} -> {noreply, State}; _ -> MRef = erlang:monitor(process, Pid), {noreply, State#state{monitors = Monitors#{Pid => MRef}}} end; handle_cast({close, Pid}, State) -> ets:update_counter(ra_open_file_metrics, Pid, -1, {Pid, 0}), {noreply, State}; handle_cast(_Msg, State) -> {noreply, State}. handle_info({'DOWN', MRef, process, Pid, _}, #state{monitors = Monitors0} = State) -> case maps:take(Pid, Monitors0) of {MRef, Monitors} -> ets:delete(ra_open_file_metrics, Pid), {noreply, State#state{monitors = Monitors}}; error -> {noreply, State} end; handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}.