@@ -52,6 +52,40 @@ def set_logger(context):
5252
5353 return logger
5454
55+
56+
57+ def _read_proc_kb (field , path = '/proc/self/status' ):
58+ """Read a '<field>: <n> kB' line from /proc. Returns None when unavailable."""
59+ try :
60+ with open (path ) as f :
61+ for line in f :
62+ if line .startswith (field + ':' ):
63+ return int (line .split ()[1 ])
64+ except (OSError , ValueError , IndexError ):
65+ pass
66+ return None
67+
68+
69+ def _reset_peak_rss ():
70+ """Reset the kernel peak-memory counter (VmHWM) to the current RSS. Linux-only."""
71+ try :
72+ with open ('/proc/self/clear_refs' , 'w' ) as f :
73+ f .write ('5' )
74+ return True
75+ except OSError :
76+ return False
77+
78+
79+ def _feature_mem_txt (context ):
80+ """Peak memory for this feature: the high-water mark since before_feature,
81+ and how much that is above the memory at feature start. Empty if /proc is unusable."""
82+ peak_kb = _read_proc_kb ('VmHWM' )
83+ start_kb = getattr (context , 'feature_rss_start_kb' , None )
84+ if peak_kb and start_kb and getattr (context , 'feature_peak_reset' , False ):
85+ delta_mb = (peak_kb - start_kb ) / 1024
86+ return f" Peak RSS: { peak_kb / 1024 :.0f} MB (delta { delta_mb :+.0f} MB)."
87+ return ""
88+
5589def before_feature (context , feature ):
5690 #@todo incorporate into gherkin error handling
5791 # assert protocol.enforce(context, feature), 'failed'
@@ -62,7 +96,12 @@ def before_feature(context, feature):
6296 except KeyError : # run via console, task_id not provided
6397 context .validation_task_id = None
6498 Scenario .continue_after_failed_step = False
65-
99+
100+ # note current RSS and reset the kernel peak counter so that VmHWM in
101+ # after_feature reflects the peak of *this* feature only.
102+ context .feature_rss_start_kb = _read_proc_kb ('VmRSS' )
103+ context .feature_peak_reset = _reset_peak_rss ()
104+
66105 execution_mode = context .config .userdata .get ('execution_mode' )
67106 if execution_mode and execution_mode == 'ExecutionMode.PRODUCTION' :
68107 context .feature_start_time = time .process_time ()
@@ -177,7 +216,7 @@ def reduce_db_outcomes(feature_outcomes):
177216 end_time = time .process_time ()
178217 elapsed_time = end_time - context .feature_start_time
179218 logger = set_logger (context )
180- logger .info (f"Feature '{ feature .name } ' completed. Elapsed process time: { elapsed_time :.2f} seconds." )
219+ logger .info (f"Feature '{ feature .name } ' completed. Elapsed process time: { elapsed_time :.2f} seconds.{ _feature_mem_txt ( context ) } " )
181220
182221 else : # invoked via console or CI/CD pipeline
183222 outcomes = [outcome .to_dict () for outcome in context .gherkin_outcomes ]
0 commit comments