[tor-commits] [nyx/master] Reduce graph panel cpu usage

atagar at torproject.org atagar at torproject.org
Sat Sep 16 22:37:17 UTC 2017


commit c02be3ee32b4d8cba8a9f4eee5cc97b0fd27bdef
Author: Damian Johnson <atagar at 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>
 



More information about the tor-commits mailing list