mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	
		
			
	
	
		
			82 lines
		
	
	
	
		
			2.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			82 lines
		
	
	
	
		
			2.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								import collections
							 | 
						||
| 
								 | 
							
								import marshal
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from .collector import Collector
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class PstatsCollector(Collector):
							 | 
						||
| 
								 | 
							
								    def __init__(self, sample_interval_usec):
							 | 
						||
| 
								 | 
							
								        self.result = collections.defaultdict(
							 | 
						||
| 
								 | 
							
								            lambda: dict(total_rec_calls=0, direct_calls=0, cumulative_calls=0)
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        self.stats = {}
							 | 
						||
| 
								 | 
							
								        self.sample_interval_usec = sample_interval_usec
							 | 
						||
| 
								 | 
							
								        self.callers = collections.defaultdict(
							 | 
						||
| 
								 | 
							
								            lambda: collections.defaultdict(int)
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def collect(self, stack_frames):
							 | 
						||
| 
								 | 
							
								        for thread_id, frames in stack_frames:
							 | 
						||
| 
								 | 
							
								            if not frames:
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # Process each frame in the stack to track cumulative calls
							 | 
						||
| 
								 | 
							
								            for frame in frames:
							 | 
						||
| 
								 | 
							
								                location = (frame.filename, frame.lineno, frame.funcname)
							 | 
						||
| 
								 | 
							
								                self.result[location]["cumulative_calls"] += 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # The top frame gets counted as an inline call (directly executing)
							 | 
						||
| 
								 | 
							
								            top_frame = frames[0]
							 | 
						||
| 
								 | 
							
								            top_location = (
							 | 
						||
| 
								 | 
							
								                top_frame.filename,
							 | 
						||
| 
								 | 
							
								                top_frame.lineno,
							 | 
						||
| 
								 | 
							
								                top_frame.funcname,
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            self.result[top_location]["direct_calls"] += 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # Track caller-callee relationships for call graph
							 | 
						||
| 
								 | 
							
								            for i in range(1, len(frames)):
							 | 
						||
| 
								 | 
							
								                callee_frame = frames[i - 1]
							 | 
						||
| 
								 | 
							
								                caller_frame = frames[i]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                callee = (
							 | 
						||
| 
								 | 
							
								                    callee_frame.filename,
							 | 
						||
| 
								 | 
							
								                    callee_frame.lineno,
							 | 
						||
| 
								 | 
							
								                    callee_frame.funcname,
							 | 
						||
| 
								 | 
							
								                )
							 | 
						||
| 
								 | 
							
								                caller = (
							 | 
						||
| 
								 | 
							
								                    caller_frame.filename,
							 | 
						||
| 
								 | 
							
								                    caller_frame.lineno,
							 | 
						||
| 
								 | 
							
								                    caller_frame.funcname,
							 | 
						||
| 
								 | 
							
								                )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                self.callers[callee][caller] += 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def export(self, filename):
							 | 
						||
| 
								 | 
							
								        self.create_stats()
							 | 
						||
| 
								 | 
							
								        self._dump_stats(filename)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _dump_stats(self, file):
							 | 
						||
| 
								 | 
							
								        stats_with_marker = dict(self.stats)
							 | 
						||
| 
								 | 
							
								        stats_with_marker[("__sampled__",)] = True
							 | 
						||
| 
								 | 
							
								        with open(file, "wb") as f:
							 | 
						||
| 
								 | 
							
								            marshal.dump(stats_with_marker, f)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Needed for compatibility with pstats.Stats
							 | 
						||
| 
								 | 
							
								    def create_stats(self):
							 | 
						||
| 
								 | 
							
								        sample_interval_sec = self.sample_interval_usec / 1_000_000
							 | 
						||
| 
								 | 
							
								        callers = {}
							 | 
						||
| 
								 | 
							
								        for fname, call_counts in self.result.items():
							 | 
						||
| 
								 | 
							
								            total = call_counts["direct_calls"] * sample_interval_sec
							 | 
						||
| 
								 | 
							
								            cumulative_calls = call_counts["cumulative_calls"]
							 | 
						||
| 
								 | 
							
								            cumulative = cumulative_calls * sample_interval_sec
							 | 
						||
| 
								 | 
							
								            callers = dict(self.callers.get(fname, {}))
							 | 
						||
| 
								 | 
							
								            self.stats[fname] = (
							 | 
						||
| 
								 | 
							
								                call_counts["direct_calls"],  # cc = direct calls for sample percentage
							 | 
						||
| 
								 | 
							
								                cumulative_calls,  # nc = cumulative calls for cumulative percentage
							 | 
						||
| 
								 | 
							
								                total,
							 | 
						||
| 
								 | 
							
								                cumulative,
							 | 
						||
| 
								 | 
							
								                callers,
							 | 
						||
| 
								 | 
							
								            )
							 |