Skip to content

Commit 3da73a7

Browse files
committed
rabbit_disk_monitor: Use disksup to determine available bytes
`disksup` now exposes the calculation for available disk space for a given path using the same `df` mechanism on Unix. We can use this directly and drop the custom code which reimplements that.
1 parent 7f9d9d1 commit 3da73a7

File tree

2 files changed

+18
-181
lines changed

2 files changed

+18
-181
lines changed

deps/rabbit/src/rabbit_disk_monitor.erl

Lines changed: 17 additions & 178 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@
1515
%% watermark (configurable either as an absolute value or
1616
%% relative to the memory limit).
1717
%%
18-
%% Disk monitoring is done by shelling out to /usr/bin/df
19-
%% instead of related built-in OTP functions because currently
20-
%% this is the most reliable way of determining free disk space
21-
%% for the partition our internal database is on.
22-
%%
2318
%% Update interval is dynamically calculated assuming disk
2419
%% space is being filled at FAST_RATE.
2520

@@ -65,11 +60,7 @@
6560
%% on start-up
6661
retries,
6762
%% Interval between retries
68-
interval,
69-
%% Operating system in use
70-
os,
71-
%% Port running sh to execute df commands
72-
port
63+
interval
7364
}).
7465

7566
%%----------------------------------------------------------------------------
@@ -134,18 +125,9 @@ init([Limit]) ->
134125
State1 = set_min_check_interval(?DEFAULT_MIN_DISK_CHECK_INTERVAL, State0),
135126
State2 = set_max_check_interval(?DEFAULT_MAX_DISK_CHECK_INTERVAL, State1),
136127

137-
OS = os:type(),
138-
Port = case OS of
139-
{unix, _} ->
140-
start_portprogram();
141-
{win32, _OSname} ->
142-
not_used
143-
end,
144-
State3 = State2#state{port=Port, os=OS},
145-
146-
State4 = enable(State3),
128+
State3 = enable(State2),
147129

148-
{ok, State4}.
130+
{ok, State3}.
149131

150132
handle_call({set_disk_free_limit, _}, _From, #state{enabled = false} = State) ->
151133
?LOG_INFO("Cannot set disk free limit: "
@@ -210,43 +192,6 @@ code_change(_OldVsn, State, _Extra) ->
210192
%% Internal functions
211193
%%----------------------------------------------------------------------------
212194

213-
start_portprogram() ->
214-
Args = ["-s", "rabbit_disk_monitor"],
215-
Opts = [stream, stderr_to_stdout, {args, Args}],
216-
erlang:open_port({spawn_executable, "/bin/sh"}, Opts).
217-
218-
run_port_cmd(Cmd0, Port) ->
219-
%% Insert a carriage return, ^M or ASCII 13, after the command,
220-
%% to indicate end of output
221-
Cmd1 = io_lib:format("~ts < /dev/null; echo \"\^M\"~n", [Cmd0]),
222-
Cmd2 = rabbit_data_coercion:to_utf8_binary(Cmd1),
223-
Port ! {self(), {command, [Cmd2, 10]}}, % The 10 at the end is a newline
224-
get_reply(Port, []).
225-
226-
get_reply(Port, O) ->
227-
receive
228-
{Port, {data, N}} ->
229-
case newline(N, O) of
230-
{ok, Str} ->
231-
Str;
232-
{more, Acc} ->
233-
get_reply(Port, Acc)
234-
end;
235-
{'EXIT', Port, Reason} ->
236-
exit({port_died, Reason})
237-
end.
238-
239-
% Character 13 is ^M or carriage return
240-
newline([13|_], B) ->
241-
{ok, lists:reverse(B)};
242-
newline([H|T], B) ->
243-
newline(T, [H|B]);
244-
newline([], B) ->
245-
{more, B}.
246-
247-
find_cmd(Cmd) ->
248-
os:find_executable(Cmd).
249-
250195
safe_ets_lookup(Key, Default) ->
251196
try
252197
case ets:lookup(?ETS_NAME, Key) of
@@ -281,10 +226,8 @@ set_disk_limits(State, Limit0) ->
281226

282227
internal_update(State = #state{limit = Limit,
283228
dir = Dir,
284-
alarmed = Alarmed,
285-
os = OS,
286-
port = Port}) ->
287-
CurrentFree = get_disk_free(Dir, OS, Port),
229+
alarmed = Alarmed}) ->
230+
CurrentFree = get_disk_free(Dir),
288231
%% note: 'NaN' is considered to be less than a number
289232
NewAlarmed = CurrentFree < Limit,
290233
case {Alarmed, NewAlarmed} of
@@ -300,103 +243,16 @@ internal_update(State = #state{limit = Limit,
300243
ets:insert(?ETS_NAME, {disk_free, CurrentFree}),
301244
State#state{alarmed = NewAlarmed, actual = CurrentFree}.
302245

303-
get_disk_free(Dir, {unix, Sun}, Port)
304-
when Sun =:= sunos; Sun =:= sunos4; Sun =:= solaris ->
305-
Df = find_cmd("df"),
306-
parse_free_unix(run_port_cmd(Df ++ " -k '" ++ Dir ++ "'", Port));
307-
get_disk_free(Dir, {unix, _}, Port) ->
308-
Df = find_cmd("df"),
309-
parse_free_unix(run_port_cmd(Df ++ " -kP '" ++ Dir ++ "'", Port));
310-
get_disk_free(Dir, {win32, _}, not_used) ->
311-
% Dir:
312-
% "c:/Users/username/AppData/Roaming/RabbitMQ/db/rabbit2@username-z01-mnesia"
313-
case win32_get_drive_letter(Dir) of
314-
error ->
315-
?LOG_WARNING("Expected the mnesia directory absolute "
316-
"path to start with a drive letter like "
317-
"'C:'. The path is: '~tp'", [Dir]),
318-
{ok, Free} = win32_get_disk_free_dir(Dir),
319-
Free;
320-
DriveLetter ->
321-
% Note: yes, "$\s" is the $char sequence for an ASCII space
322-
F = fun([D, $:, $\\, $\s | _]) when D =:= DriveLetter ->
323-
true;
324-
(_) -> false
325-
end,
326-
% Note: we can use os_mon_sysinfo:get_disk_info/1 after the following is fixed:
327-
% https://github.com/erlang/otp/issues/6156
328-
try
329-
% Note: DriveInfoStr is in this format
330-
% "C:\\ DRIVE_FIXED 720441434112 1013310287872 720441434112\n"
331-
Lines = os_mon_sysinfo:get_disk_info(),
332-
[DriveInfoStr] = lists:filter(F, Lines),
333-
[DriveLetter, $:, $\\, $\s | DriveInfo] = DriveInfoStr,
334-
335-
% https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdiskfreespaceexa
336-
% lib/os_mon/c_src/win32sysinfo.c:
337-
% if (fpGetDiskFreeSpaceEx(drive,&availbytes,&totbytes,&totbytesfree)){
338-
% sprintf(answer,"%s DRIVE_FIXED %I64u %I64u %I64u\n",drive,availbytes,totbytes,totbytesfree);
339-
["DRIVE_FIXED", FreeBytesAvailableToCallerStr,
340-
_TotalNumberOfBytesStr, _TotalNumberOfFreeBytesStr] = string:tokens(DriveInfo, " "),
341-
list_to_integer(FreeBytesAvailableToCallerStr)
342-
catch _:{timeout, _}:_ ->
343-
%% could not compute the result
344-
'NaN';
345-
_:Reason:_ ->
346-
?LOG_WARNING("Free disk space monitoring failed to retrieve the amount of available space: ~p", [Reason]),
347-
%% could not compute the result
348-
'NaN'
349-
end
350-
end.
351-
352-
parse_free_unix(Str) ->
353-
case string:tokens(Str, "\n") of
354-
[_, S | _] -> case string:tokens(S, " \t") of
355-
[_, _, _, Free | _] -> list_to_integer(Free) * 1024;
356-
_ -> exit({unparseable, Str})
357-
end;
358-
_ -> exit({unparseable, Str})
359-
end.
360-
361-
win32_get_drive_letter([DriveLetter, $:, $/ | _]) when (DriveLetter >= $a andalso DriveLetter =< $z) ->
362-
% Note: os_mon_sysinfo returns drives with uppercase letters, so uppercase it here
363-
DriveLetter - 32;
364-
win32_get_drive_letter([DriveLetter, $:, $/ | _]) when (DriveLetter >= $A andalso DriveLetter =< $Z) ->
365-
DriveLetter;
366-
win32_get_drive_letter(_) ->
367-
error.
368-
369-
win32_get_disk_free_dir(Dir) ->
370-
%% On Windows, the Win32 API enforces a limit of 260 characters
371-
%% (MAX_PATH). If we call `dir` with a path longer than that, it
372-
%% fails with "File not found". Starting with Windows 10 version
373-
%% 1607, this limit was removed, but the administrator has to
374-
%% configure that.
375-
%%
376-
%% NTFS supports paths up to 32767 characters. Therefore, paths
377-
%% longer than 260 characters exist but they are "inaccessible" to
378-
%% `dir`.
379-
%%
380-
%% A workaround is to tell the Win32 API to not parse a path and
381-
%% just pass it raw to the underlying filesystem. To do this, the
382-
%% path must be prepended with "\\?\". That's what we do here.
383-
%%
384-
%% However, the underlying filesystem may not support forward
385-
%% slashes transparently, as the Win32 API does. Therefore, we
386-
%% convert all forward slashes to backslashes.
387-
%%
388-
%% See the following page to learn more about this:
389-
%% https://ss64.com/nt/syntax-filenames.html
390-
RawDir = "\\\\?\\" ++ string:replace(Dir, "/", "\\", all),
391-
case run_os_cmd("dir /-C /W \"" ++ RawDir ++ "\"") of
392-
{error, Error} ->
393-
exit({unparseable, Error});
394-
CommandResult ->
395-
LastLine0 = lists:last(string:tokens(CommandResult, "\r\n")),
396-
LastLine1 = lists:reverse(LastLine0),
397-
{match, [Free]} = re:run(LastLine1, "(\\d+)",
398-
[{capture, all_but_first, list}]),
399-
{ok, list_to_integer(lists:reverse(Free))}
246+
-spec get_disk_free(file:filename_all()) ->
247+
AvailableBytes :: non_neg_integer() | 'NaN'.
248+
get_disk_free(Dir) ->
249+
case disksup:get_disk_info(Dir) of
250+
[{D, 0, 0, 0, 0}] when D =:= Dir orelse D =:= "none" ->
251+
'NaN';
252+
[{_MountPoint, _TotalKiB, AvailableKiB, _Capacity}] ->
253+
AvailableKiB * 1024;
254+
_DiskInfo ->
255+
'NaN'
400256
end.
401257

402258
interpret_limit({mem_relative, Relative})
@@ -437,8 +293,8 @@ interval(#state{limit = Limit,
437293
enable(#state{retries = 0} = State) ->
438294
?LOG_ERROR("Free disk space monitor failed to start!"),
439295
State;
440-
enable(#state{dir = Dir, os = OS, port = Port} = State) ->
441-
enable_handle_disk_free(catch get_disk_free(Dir, OS, Port), State).
296+
enable(#state{dir = Dir} = State) ->
297+
enable_handle_disk_free(get_disk_free(Dir), State).
442298

443299
enable_handle_disk_free(DiskFree, State) when is_integer(DiskFree) ->
444300
enable_handle_total_memory(catch vm_memory_monitor:get_total_memory(), DiskFree, State);
@@ -461,20 +317,3 @@ enable_handle_total_memory(Error, _DiskFree, #state{interval = Interval, retries
461317
[Retries, Error]),
462318
erlang:send_after(Interval, self(), try_enable),
463319
State#state{enabled = false}.
464-
465-
run_os_cmd(Cmd) ->
466-
Pid = self(),
467-
Ref = make_ref(),
468-
CmdFun = fun() ->
469-
CmdResult = rabbit_misc:os_cmd(Cmd),
470-
Pid ! {Pid, Ref, CmdResult}
471-
end,
472-
CmdPid = spawn(CmdFun),
473-
receive
474-
{Pid, Ref, CmdResult} ->
475-
CmdResult
476-
after 5000 ->
477-
exit(CmdPid, kill),
478-
?LOG_ERROR("Command timed out: '~ts'", [Cmd]),
479-
{error, timeout}
480-
end.

deps/rabbit/test/unit_disk_monitor_SUITE.erl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99

1010
-include_lib("eunit/include/eunit.hrl").
1111

12-
-compile(export_all).
13-
14-
-define(TIMEOUT, 30000).
12+
-compile([nowarn_export_all, export_all]).
1513

1614
all() ->
1715
[

0 commit comments

Comments
 (0)