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
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
150132handle_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-
250195safe_ets_lookup (Key , Default ) ->
251196 try
252197 case ets :lookup (? ETS_NAME , Key ) of
@@ -281,10 +226,8 @@ set_disk_limits(State, Limit0) ->
281226
282227internal_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
402258interpret_limit ({mem_relative , Relative })
@@ -437,8 +293,8 @@ interval(#state{limit = Limit,
437293enable (# 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
443299enable_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 .
0 commit comments