commit c02be3ee32b4d8cba8a9f4eee5cc97b0fd27bdef Author: Damian Johnson atagar@torproject.org Date: Sat Sep 16 13:36:01 2017 -0700
Reduce graph panel cpu usage
Our graph panel updates and redraws itself every second, making it a non-trivial load on cpu. Two actions in particular account for most of it...
* Cloning stats on every redraw. This constantly consumes 0.3% cpu on my relay. Being lock-free is nice, but in this case a lock really makes sense.
* Performing a flurry of addstr calls for each cell in the graph. Doing a single vline call for each column instead drops cpu by another 0.4%.
On my relay this drops cpu usage of the graph panel from 1% to 0.4% (60% less)...
* 0.3% cpu usage when only the header panel is rendered * 1.3% cpu usage when the graph panel was rendered too * 0.7% cpu usage when the graph panel was rendered with these changes --- nyx/curses.py | 14 ++++++++++---- nyx/panel/graph.py | 39 ++++++++++++++++++++------------------- test/panel/graph.py | 4 ++-- web/changelog/index.html | 1 + 4 files changed, 33 insertions(+), 25 deletions(-)
diff --git a/nyx/curses.py b/nyx/curses.py index cb47bb6..0caf8f4 100644 --- a/nyx/curses.py +++ b/nyx/curses.py @@ -902,17 +902,23 @@ class _Subwindow(object):
return x
- def hline(self, x, y, length, *attr): + def hline(self, x, y, length, *attr, **kwargs): + char = kwargs.get('char', curses.ACS_HLINE) + char = ord(char) if isinstance(char, str) else char + if self.width > x and self.height > y: try: - self._curses_subwindow.hline(max(0, y), max(0, x), curses.ACS_HLINE | curses_attr(*attr), min(length, self.width - x)) + self._curses_subwindow.hline(max(0, y), max(0, x), char | curses_attr(*attr), min(length, self.width - x)) except: pass
- def vline(self, x, y, length, *attr): + def vline(self, x, y, length, *attr, **kwargs): + char = kwargs.get('char', curses.ACS_VLINE) + char = ord(char) if isinstance(char, str) else char + if self.width > x and self.height > y: try: - self._curses_subwindow.vline(max(0, y), max(0, x), curses.ACS_VLINE | curses_attr(*attr), min(length, self.height - y)) + self._curses_subwindow.vline(max(0, y), max(0, x), char | curses_attr(*attr), min(length, self.height - y)) except: pass
diff --git a/nyx/panel/graph.py b/nyx/panel/graph.py index cec0a71..e385fe6 100644 --- a/nyx/panel/graph.py +++ b/nyx/panel/graph.py @@ -16,6 +16,7 @@ Downloaded (0.0 B/sec): Uploaded (0.0 B/sec):
import copy import functools +import threading import time
import nyx.curses @@ -425,6 +426,7 @@ class GraphPanel(nyx.panel.Panel): GraphStat.SYSTEM_RESOURCES: ResourceStats(), }
+ self._stats_lock = threading.RLock() self._stats_paused = None
if CONFIG['show_connections']: @@ -559,18 +561,18 @@ class GraphPanel(nyx.panel.Panel): stat = self._stats_paused[self._displayed_stat] accounting_stats = self._accounting_stats_paused
- stat = type(stat)(stat) # clone the GraphCategory - subgraph_height = self._graph_height + 2 # graph rows + header + x-axis label - subgraph_width = min(subwindow.width // 2, CONFIG['max_graph_width']) - interval, bounds_type = self._update_interval, self._bounds_type + with self._stats_lock: + subgraph_height = self._graph_height + 2 # graph rows + header + x-axis label + subgraph_width = min(subwindow.width // 2, CONFIG['max_graph_width']) + interval, bounds_type = self._update_interval, self._bounds_type
- subwindow.addstr(0, 0, stat.title(subwindow.width), HIGHLIGHT) + subwindow.addstr(0, 0, stat.title(subwindow.width), HIGHLIGHT)
- _draw_subgraph(subwindow, stat.primary, 0, subgraph_width, subgraph_height, bounds_type, interval, PRIMARY_COLOR) - _draw_subgraph(subwindow, stat.secondary, subgraph_width, subgraph_width, subgraph_height, bounds_type, interval, SECONDARY_COLOR) + _draw_subgraph(subwindow, stat.primary, 0, subgraph_width, subgraph_height, bounds_type, interval, PRIMARY_COLOR) + _draw_subgraph(subwindow, stat.secondary, subgraph_width, subgraph_width, subgraph_height, bounds_type, interval, SECONDARY_COLOR)
- if stat.stat_type() == GraphStat.BANDWIDTH and accounting_stats: - _draw_accounting_stats(subwindow, DEFAULT_CONTENT_HEIGHT + subgraph_height - 2, accounting_stats) + if stat.stat_type() == GraphStat.BANDWIDTH and accounting_stats: + _draw_accounting_stats(subwindow, DEFAULT_CONTENT_HEIGHT + subgraph_height - 2, accounting_stats)
def _update_accounting(self, event): if not CONFIG['show_accounting']: @@ -587,15 +589,16 @@ class GraphPanel(nyx.panel.Panel): nyx_interface().redraw()
def _update_stats(self, event): - for stat in self._stats.values(): - stat.bandwidth_event(event) + with self._stats_lock: + for stat in self._stats.values(): + stat.bandwidth_event(event)
- if self._displayed_stat: - param = self._stats[self._displayed_stat] - update_rate = INTERVAL_SECONDS[self._update_interval] + if self._displayed_stat: + param = self._stats[self._displayed_stat] + update_rate = INTERVAL_SECONDS[self._update_interval]
- if param.primary.tick % update_rate == 0: - self.redraw() + if param.primary.tick % update_rate == 0: + self.redraw()
def _draw_subgraph(subwindow, data, x, width, height, bounds_type, interval, color, fill_char = ' '): @@ -622,9 +625,7 @@ def _draw_subgraph(subwindow, data, x, width, height, bounds_type, interval, col for col in range(columns): column_count = int(data.values[interval][col]) - min_bound column_height = int(min(height - 2, (height - 2) * column_count / (max(1, max_bound) - min_bound))) - - for row in range(column_height): - subwindow.addstr(x + col + axis_offset + 1, height - 1 - row, fill_char, color, HIGHLIGHT) + subwindow.vline(x + col + axis_offset + 1, height - column_height, column_height, color, HIGHLIGHT, char = fill_char)
def _x_axis_labels(interval, columns): diff --git a/test/panel/graph.py b/test/panel/graph.py index 99139e4..513a757 100644 --- a/test/panel/graph.py +++ b/test/panel/graph.py @@ -26,12 +26,12 @@ Download:
EXPECTED_ACCOUNTING = """ Accounting (awake) Time to reset: 01:02 - 4.7 KB / 105.2 KB 2.0 KB / 9.2 KB + 4.7 KB / 105.3 KB 2.0 KB / 9.3 KB """.strip()
EXPECTED_GRAPH = """ Download: -6 KB * +7 KB * * 3 KB ** * * **** diff --git a/web/changelog/index.html b/web/changelog/index.html index 7977afd..686ec0e 100644 --- a/web/changelog/index.html +++ b/web/changelog/index.html @@ -68,6 +68,7 @@ <ul> <li>Graph prepopulation no longer requires shifting to 15 minute intervals</li> <li>Graphing bandwidth as bytes by default, rather than bits</li> + <li>Reduced cpu usage of rendering this panel by 60%</li> </ul> </li>