3535#include <errno.h>
3636#include <time.h>
3737#include <sys/stat.h>
38+ #include <dirent.h>
3839
3940ZEND_DECLARE_MODULE_GLOBALS (rdump )
4041
@@ -310,6 +311,68 @@ static int64_t rdump_rss_bytes(void)
310311 return (int64_t )(resident * (unsigned long long )page );
311312}
312313
314+ /* Parse a byte quantity like "500", "16K", "256M", "2G" (binary multiples,
315+ * case-insensitive suffix) into bytes. Negatives / garbage -> 0. */
316+ static zend_long rdump_parse_bytes (const char * s )
317+ {
318+ if (s == NULL ) {
319+ return 0 ;
320+ }
321+ while (* s == ' ' || * s == '\t' ) {
322+ s ++ ;
323+ }
324+ char * end = NULL ;
325+ long long v = strtoll (s , & end , 10 );
326+ if (end == s || v < 0 ) {
327+ return 0 ;
328+ }
329+ while (* end == ' ' || * end == '\t' ) {
330+ end ++ ;
331+ }
332+ switch (* end ) {
333+ case 'k' : case 'K' : v *= 1024LL ; break ;
334+ case 'm' : case 'M' : v *= 1024LL * 1024 ; break ;
335+ case 'g' : case 'G' : v *= 1024LL * 1024 * 1024 ; break ;
336+ case 't' : case 'T' : v *= 1024LL * 1024 * 1024 * 1024 ; break ;
337+ default : break ;
338+ }
339+ return v < 0 ? 0 : (zend_long )v ;
340+ }
341+
342+ /* Sum the sizes of every *.rdump file in dir, skipping except_base (the file
343+ * an overwrite would replace, so it must not count toward the new total).
344+ * Best-effort: unreadable entries are ignored. Used to enforce a disk budget
345+ * shared across all workers writing OOM dumps into the same directory. */
346+ static uint64_t rdump_dir_rdump_total (const char * dir , const char * except_base )
347+ {
348+ DIR * d = opendir (dir );
349+ if (!d ) {
350+ return 0 ;
351+ }
352+ uint64_t total = 0 ;
353+ struct dirent * ent ;
354+ while ((ent = readdir (d )) != NULL ) {
355+ const char * n = ent -> d_name ;
356+ size_t ln = strlen (n );
357+ if (ln < 6 || strcmp (n + ln - 6 , ".rdump" ) != 0 ) {
358+ continue ;
359+ }
360+ if (except_base != NULL && strcmp (n , except_base ) == 0 ) {
361+ continue ;
362+ }
363+ char full [4096 ];
364+ if ((size_t )snprintf (full , sizeof (full ), "%s/%s" , dir , n ) >= sizeof (full )) {
365+ continue ;
366+ }
367+ struct stat st ;
368+ if (stat (full , & st ) == 0 && S_ISREG (st .st_mode )) {
369+ total += (uint64_t )st .st_size ;
370+ }
371+ }
372+ closedir (d );
373+ return total ;
374+ }
375+
313376/* ------------------------------------------------------------------ */
314377/* Dump writer. */
315378/* ------------------------------------------------------------------ */
@@ -588,22 +651,48 @@ static void rdump_zend_error_cb(RDUMP_ERROR_CB_PARAMS)
588651 * every request, so cap the auto-dump over the worker's lifetime
589652 * by count and/or by minimum spacing. Both gates apply together;
590653 * the default max of 1 keeps it to a single dump per worker. */
654+ char expanded [4096 ];
655+ const char * out_path = path ;
656+ if (strchr (path , '%' ) != NULL
657+ && rdump_expand_path (path , expanded , sizeof (expanded )) == 0
658+ ) {
659+ out_path = expanded ;
660+ }
661+
591662 zend_long max = RDUMP_G (oom_dump_max );
592663 zend_long interval = RDUMP_G (oom_dump_min_interval );
664+ zend_long budget = RDUMP_G (oom_dump_max_total );
593665 zend_long now = (zend_long )time (NULL );
594666 int capped = (max > 0 && RDUMP_G (oom_dump_count ) >= max );
595667 int too_soon = (interval > 0
596668 && RDUMP_G (oom_dump_last_ts ) != 0
597669 && now - RDUMP_G (oom_dump_last_ts ) < interval );
598670
599- if (!capped && !too_soon ) {
600- char expanded [4096 ];
601- const char * out_path = path ;
602- if (strchr (path , '%' ) != NULL
603- && rdump_expand_path (path , expanded , sizeof (expanded )) == 0
604- ) {
605- out_path = expanded ;
671+ /* Disk budget: if the *.rdump files already in the target's
672+ * directory (excluding the one we'd overwrite) total at or above
673+ * the budget, skip -- this is the only gate that bounds the
674+ * combined footprint of many workers, not just one worker's rate.
675+ * Best-effort: concurrent workers can each pass and overshoot. */
676+ int over_budget = 0 ;
677+ if (!capped && !too_soon && budget > 0 ) {
678+ char pathcopy [4096 ];
679+ const char * dir = "." ;
680+ const char * base = out_path ;
681+ if (snprintf (pathcopy , sizeof (pathcopy ), "%s" , out_path )
682+ < (int )sizeof (pathcopy )) {
683+ char * slash = strrchr (pathcopy , '/' );
684+ if (slash != NULL ) {
685+ * slash = '\0' ;
686+ dir = pathcopy [0 ] != '\0' ? pathcopy : "/" ;
687+ base = slash + 1 ;
688+ }
689+ }
690+ if (rdump_dir_rdump_total (dir , base ) >= (uint64_t )budget ) {
691+ over_budget = 1 ;
606692 }
693+ }
694+
695+ if (!capped && !too_soon && !over_budget ) {
607696 /* Count the attempt (success or failure) and stamp the time
608697 * before writing, so a broken path or full disk cannot retry
609698 * without bound either. */
@@ -677,6 +766,14 @@ PHP_FUNCTION(rdump_set_oom_dump)
677766/* Module plumbing. */
678767/* ------------------------------------------------------------------ */
679768
769+ /* Accept memory_limit-style sizes ("256M", "2G", ...) for the dump budget. */
770+ static ZEND_INI_MH (OnUpdateRdumpBytes )
771+ {
772+ zend_long * p = (zend_long * )ZEND_INI_GET_ADDR ();
773+ * p = rdump_parse_bytes (ZSTR_VAL (new_value ));
774+ return SUCCESS ;
775+ }
776+
680777PHP_INI_BEGIN ()
681778 STD_PHP_INI_ENTRY (
682779 "rdump.oom_dump" , "" , PHP_INI_SYSTEM , OnUpdateString ,
@@ -694,6 +791,10 @@ PHP_INI_BEGIN()
694791 "rdump.oom_dump_min_interval" , "0" , PHP_INI_SYSTEM , OnUpdateLong ,
695792 oom_dump_min_interval , zend_rdump_globals , rdump_globals
696793 )
794+ STD_PHP_INI_ENTRY (
795+ "rdump.oom_dump_max_total" , "0" , PHP_INI_SYSTEM , OnUpdateRdumpBytes ,
796+ oom_dump_max_total , zend_rdump_globals , rdump_globals
797+ )
697798PHP_INI_END ()
698799
699800static const zend_function_entry rdump_functions [] = {
@@ -711,6 +812,7 @@ static PHP_GINIT_FUNCTION(rdump)
711812 rdump_globals -> oom_dump_full = 0 ;
712813 rdump_globals -> oom_dump_max = 1 ;
713814 rdump_globals -> oom_dump_min_interval = 0 ;
815+ rdump_globals -> oom_dump_max_total = 0 ;
714816 rdump_globals -> oom_dump_count = 0 ;
715817 rdump_globals -> oom_dump_last_ts = 0 ;
716818 rdump_globals -> oom_dump_runtime = NULL ;
0 commit comments