Test Failed
Pull Request — develop (#3389)
by
unknown
02:40
created

glances.plugins.profiler.profiler   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 123
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 58
dl 0
loc 123
rs 10
c 0
b 0
f 0
wmc 15

7 Methods

Rating   Name   Duplication   Size   Complexity  
A PluginModel.exit() 0 6 3
A PluginModel.update_views() 0 11 3
A PluginModel.get_key() 0 3 1
A PluginModel.__init__() 0 32 3
A PluginModel._callback() 0 6 1
A PluginModel.update() 0 34 3
A PluginModel.reset() 0 4 1
1
"""Profiler plugin."""
2
3
import sys
4
from collections import Counter
5
6
from glances.logger import logger
7
from glances.plugins.plugin.model import GlancesPluginModel
8
9
# Constants for sys.monitoring
10
TOOL_ID = 2  # ID 0 is reserved, 1 was used in test, 2 should be safe
11
# We will use PY_START to count function entries
12
EVENT_ID = getattr(sys.monitoring.events, 'PY_START', None) if hasattr(sys, 'monitoring') else None
13
14
15
class PluginModel(GlancesPluginModel):
16
    """Glances' Profiler Plugin.
17
18
    stats is a list of dict (function name, count)
19
    """
20
21
    def __init__(self, args=None, config=None):
22
        """Init the plugin."""
23
        super().__init__(args=args, config=config)
24
25
        # We want to display the stats in the UI
26
        self.args = args
27
28
        # Init the internal counter
29
        self._counts = Counter()
30
        self._monitoring_active = False
31
32
        # Check availability
33
        if not hasattr(sys, 'monitoring'):
34
            logger.warning("sys.monitoring not available. Profiler plugin disabled.")
35
            self.actions.disable()
36
            return
37
38
        try:
39
            sys.monitoring.use_tool_id(TOOL_ID, "glances_profiler")
40
            logger.info(f"sys.monitoring tool ID {TOOL_ID} registered.")
41
            self._monitoring_active = True
42
43
            # Register callback
44
            sys.monitoring.register_callback(TOOL_ID, EVENT_ID, self._callback)
45
46
            # Enable events
47
            sys.monitoring.set_events(TOOL_ID, EVENT_ID)
48
49
        except ValueError as e:
50
            logger.error(f"Failed to register sys.monitoring tool: {e}")
51
            self.actions.disable()
52
            self._monitoring_active = False
53
54
    def exit(self):
55
        """Stop monitoring."""
56
        if self._monitoring_active and hasattr(sys, 'monitoring'):
57
            sys.monitoring.set_events(TOOL_ID, 0)
58
            sys.monitoring.free_tool_id(TOOL_ID)
59
        super().exit()
60
61
    def _callback(self, code, instruction_offset):
62
        """Callback for sys.monitoring."""
63
        # This is called VERY frqeuently. Keep it minimal.
64
        # We just increment the counter for the code object name.
65
        self._counts[code.co_name] += 1
66
        return sys.monitoring.DISABLE
67
68
    def get_key(self):
69
        """Return the key of the list."""
70
        return 'function'
71
72
    def update_views(self):
73
        """Update the views."""
74
        # Standard table view
75
        self.views = {}
76
        if not self.stats:
77
            return self.views
78
79
        for i in self.stats:
80
            self.views[i[self.get_key()]] = {'hidden': False}
81
82
        return self.views
83
84
    def reset(self):
85
        """Reset stats."""
86
        self.stats = []
87
        self._counts.clear()
88
89
    def update(self):
90
        """Update stats."""
91
        # Reset stats
92
        self.reset()
93
94
        if not self._monitoring_active:
95
            return self.stats
96
97
        # Get the top 10 most frequent functions
98
        # We take the counter snapshot and reset it maybe?
99
        # Or just show cumulative? Let's show rate (per second/update) if possible.
100
        # For now, let's just show top N in the current interval.
101
102
        # NOTE: To show rate, we would need to diff with previous.
103
        # But for simplicity V1, let's just show the accumulated counts since start (or allow reset).
104
        # Actually, showing "Hot functions right now" implying per-update interval is better.
105
106
        # Snapshot and reset internal counter for the next interval?
107
        # WARNING: _callback runs in another thread/context potentially?
108
        # In simple Python (GIL), it is safe-ish, but let's be careful.
109
        # sys.monitoring callback runs synchronously.
110
111
        # Let's copy the current state
112
        current_counts = self._counts.copy()
113
        # self._counts.clear() # If we want per-interval stats, we should clear.
114
115
        # Sort by count desc
116
        top_n = current_counts.most_common(10)
117
118
        for func_name, count in top_n:
119
            stat = {'function': func_name, 'count': count}
120
            self.stats.append(stat)
121
122
        return self.stats
123