[tor-commits] [arm/master] Thread safety for the graph panel
atagar at torproject.org
atagar at torproject.org
Wed Mar 18 17:05:29 UTC 2015
commit b46698efd09a1935aa71661bc0547bfcfe6cea3b
Author: Damian Johnson <atagar at torproject.org>
Date: Wed Mar 18 10:05:20 2015 -0700
Thread safety for the graph panel
Similar pattern as the header panel. The draw method now makes a named tuple
for the attributes it needs then works off of that.
---
seth/graph_panel.py | 104 +++++++++++++++++++++++++++------------------------
1 file changed, 56 insertions(+), 48 deletions(-)
diff --git a/seth/graph_panel.py b/seth/graph_panel.py
index ce5e6bb..da07cbc 100644
--- a/seth/graph_panel.py
+++ b/seth/graph_panel.py
@@ -11,6 +11,7 @@ Downloaded (0.0 B/sec): Uploaded (0.0 B/sec):
25s 50 1m 1.6 2.0 25s 50 1m 1.6 2.0
"""
+import collections
import copy
import curses
import time
@@ -28,6 +29,8 @@ GraphStat = enum.Enum(('BANDWIDTH', 'bandwidth'), ('CONNECTIONS', 'connections')
Interval = enum.Enum(('EACH_SECOND', 'each second'), ('FIVE_SECONDS', '5 seconds'), ('THIRTY_SECONDS', '30 seconds'), ('MINUTELY', 'minutely'), ('FIFTEEN_MINUTE', '15 minute'), ('THIRTY_MINUTE', '30 minute'), ('HOURLY', 'hourly'), ('DAILY', 'daily'))
Bounds = enum.Enum(('GLOBAL_MAX', 'global_max'), ('LOCAL_MAX', 'local_max'), ('TIGHT', 'tight'))
+DrawAttributes = collections.namedtuple('DrawAttributes', ('stat', 'subgraph_height', 'subgraph_width', 'interval', 'bounds_type', 'accounting'))
+
INTERVAL_SECONDS = {
Interval.EACH_SECOND: 1,
Interval.FIVE_SECONDS: 5,
@@ -545,57 +548,66 @@ class GraphPanel(panel.Panel):
return
stat = self.get_attr('_stats')[self.displayed_stat]
- subgraph_width = min(width / 2, CONFIG['features.graph.max_width'])
+
+ attr = DrawAttributes(
+ stat = type(stat)(stat), # clone the GraphCategory
+ subgraph_height = self._graph_height + 2, # graph rows + header + x-axis label
+ subgraph_width = min(width / 2, CONFIG['features.graph.max_width']),
+ interval = self.update_interval,
+ bounds_type = self.bounds_type,
+ accounting = self.get_attr('_accounting_stats'),
+ )
if self.is_title_visible():
- self.addstr(0, 0, stat.title(width), curses.A_STANDOUT)
+ self.addstr(0, 0, attr.stat.title(width), curses.A_STANDOUT)
- self._draw_subgraph(stat.primary, 0, subgraph_width, PRIMARY_COLOR)
- self._draw_subgraph(stat.secondary, subgraph_width, subgraph_width, SECONDARY_COLOR)
+ self._draw_subgraph(attr, attr.stat.primary, 0, PRIMARY_COLOR)
+ self._draw_subgraph(attr, attr.stat.secondary, attr.subgraph_width, SECONDARY_COLOR)
- if self.displayed_stat == GraphStat.BANDWIDTH:
+ if attr.stat.stat_type() == GraphStat.BANDWIDTH:
if width <= COLLAPSE_WIDTH:
- self._draw_bandwidth_stats(stat, width, subgraph_width)
+ self._draw_bandwidth_stats(attr, width)
- self._draw_accounting_stats()
+ if attr.accounting:
+ self._draw_accounting_stats(attr)
- def _draw_subgraph(self, data, x, width, color):
- subgraph_columns = width - 5
- min_bound, max_bound = self._get_graph_bounds(data, subgraph_columns)
+ def _draw_subgraph(self, attr, data, x, color):
+ subgraph_columns = attr.subgraph_width - 5
+ min_bound, max_bound = self._get_graph_bounds(attr, data, subgraph_columns)
- y_axis_labels = self._get_y_axis_labels(data, min_bound, max_bound)
+ x_axis_labels = self._get_x_axis_labels(attr, subgraph_columns)
+ y_axis_labels = self._get_y_axis_labels(attr, data, min_bound, max_bound)
axis_offset = max([len(label) for label in y_axis_labels.values()])
- x_axis_labels = self._get_x_axis_labels(subgraph_columns, axis_offset)
- self.addstr(1, x, data.header(width), curses.A_BOLD, color)
+ self.addstr(1, x, data.header(attr.subgraph_width), curses.A_BOLD, color)
for x_offset, label in x_axis_labels.items():
- self.addstr(self._graph_height + 2, x + x_offset, label, color)
+ self.addstr(attr.subgraph_height, x + x_offset + axis_offset, label, color)
for y, label in y_axis_labels.items():
self.addstr(y, x, label, color)
for col in range(subgraph_columns):
- column_count = int(data.values[self.update_interval][col]) - min_bound
- column_height = int(min(self._graph_height, self._graph_height * column_count / (max(1, max_bound) - min_bound)))
+ column_count = int(data.values[attr.interval][col]) - min_bound
+ column_height = int(min(attr.subgraph_height - 2, (attr.subgraph_height - 2) * column_count / (max(1, max_bound) - min_bound)))
for row in range(column_height):
- self.addstr(self._graph_height + 1 - row, x + col + axis_offset + 1, ' ', curses.A_STANDOUT, color)
+ self.addstr(attr.subgraph_height - 1 - row, x + col + axis_offset + 1, ' ', curses.A_STANDOUT, color)
- def _get_graph_bounds(self, data, subgraph_columns):
+ def _get_graph_bounds(self, attr, data, subgraph_columns):
"""
Provides the range the graph shows (ie, its minimum and maximum value).
"""
min_bound, max_bound = 0, 0
- values = data.values[self.update_interval][:subgraph_columns]
+ values = data.values[attr.interval][:subgraph_columns]
- if self.bounds_type == Bounds.GLOBAL_MAX:
- max_bound = data.max_value[self.update_interval]
+ if attr.bounds_type == Bounds.GLOBAL_MAX:
+ max_bound = data.max_value[attr.interval]
elif subgraph_columns > 0:
max_bound = max(values) # local maxima
- if self.bounds_type == Bounds.TIGHT and subgraph_columns > 0:
+ if attr.bounds_type == Bounds.TIGHT and subgraph_columns > 0:
min_bound = min(values)
# if the max = min pick zero so we still display something
@@ -605,7 +617,7 @@ class GraphPanel(panel.Panel):
return min_bound, max_bound
- def _get_y_axis_labels(self, data, min_bound, max_bound):
+ def _get_y_axis_labels(self, attr, data, min_bound, max_bound):
"""
Provides the labels for the y-axis. This is a mapping of the position it
should be drawn at to its text.
@@ -613,25 +625,25 @@ class GraphPanel(panel.Panel):
y_axis_labels = {
2: data.y_axis_label(max_bound),
- self._graph_height + 1: data.y_axis_label(min_bound),
+ attr.subgraph_height - 1: data.y_axis_label(min_bound),
}
- ticks = (self._graph_height - 3) / 2
+ ticks = (attr.subgraph_height - 5) / 2
for i in range(ticks):
- row = self._graph_height - (2 * i) - 3
+ row = attr.subgraph_height - (2 * i) - 5
- if self._graph_height % 2 == 0 and i >= (ticks / 2):
+ if attr.subgraph_height % 2 == 0 and i >= (ticks / 2):
row -= 1 # make extra gap be in the middle when we're an even size
- val = (max_bound - min_bound) * (self._graph_height - row - 1) / (self._graph_height - 1)
+ val = (max_bound - min_bound) * (attr.subgraph_height - row - 3) / (attr.subgraph_height - 3)
if val not in (min_bound, max_bound):
y_axis_labels[row + 2] = data.y_axis_label(val)
return y_axis_labels
- def _get_x_axis_labels(self, subgraph_columns, axis_offset):
+ def _get_x_axis_labels(self, attr, subgraph_columns):
"""
Provides the labels for the x-axis. We include the units for only its first
value, then bump the precision for subsequent units. For example...
@@ -641,7 +653,7 @@ class GraphPanel(panel.Panel):
x_axis_labels = {}
- interval_sec = INTERVAL_SECONDS[self.update_interval]
+ interval_sec = INTERVAL_SECONDS[attr.interval]
interval_spacing = 10 if subgraph_columns >= WIDE_LABELING_GRAPH_COL else 5
units_label, decimal_precision = None, 0
@@ -659,44 +671,40 @@ class GraphPanel(panel.Panel):
# if constrained on space then strips labeling since already provided
time_label = time_label[:-1]
- x_axis_labels[axis_offset + x] = time_label
+ x_axis_labels[x] = time_label
return x_axis_labels
- def _draw_bandwidth_stats(self, stat, width, subgraph_width):
+ def _draw_bandwidth_stats(self, attr, width):
"""
Replaces the x-axis labeling with bandwidth stats. This is done on small
screens since this information otherwise wouldn't fit.
"""
- labeling_line = DEFAULT_CONTENT_HEIGHT + self._graph_height - 2
+ labeling_line = DEFAULT_CONTENT_HEIGHT + attr.subgraph_height - 4
self.addstr(labeling_line, 0, ' ' * width) # clear line
- runtime = time.time() - stat.start_time
- primary_footer = 'total: %s, avg: %s/sec' % (_size_label(stat.primary.total), _size_label(stat.primary.total / runtime))
- secondary_footer = 'total: %s, avg: %s/sec' % (_size_label(stat.secondary.total), _size_label(stat.secondary.total / runtime))
+ runtime = time.time() - attr.stat.start_time
+ primary_footer = 'total: %s, avg: %s/sec' % (_size_label(attr.stat.primary.total), _size_label(attr.stat.primary.total / runtime))
+ secondary_footer = 'total: %s, avg: %s/sec' % (_size_label(attr.stat.secondary.total), _size_label(attr.stat.secondary.total / runtime))
self.addstr(labeling_line, 1, primary_footer, PRIMARY_COLOR)
- self.addstr(labeling_line, subgraph_width + 1, secondary_footer, SECONDARY_COLOR)
+ self.addstr(labeling_line, attr.subgraph_width + 1, secondary_footer, SECONDARY_COLOR)
- def _draw_accounting_stats(self):
- accounting_stats = self.get_attr('_accounting_stats')
- y = DEFAULT_CONTENT_HEIGHT + self._graph_height
-
- if not accounting_stats:
- return
+ def _draw_accounting_stats(self, attr):
+ y = DEFAULT_CONTENT_HEIGHT + attr.subgraph_height - 2
if tor_controller().is_alive():
- hibernate_color = CONFIG['attr.hibernate_color'].get(accounting_stats.status, 'red')
+ hibernate_color = CONFIG['attr.hibernate_color'].get(attr.accounting.status, 'red')
x = self.addstr(y, 0, 'Accounting (', curses.A_BOLD)
- x = self.addstr(y, x, accounting_stats.status, curses.A_BOLD, hibernate_color)
+ x = self.addstr(y, x, attr.accounting.status, curses.A_BOLD, hibernate_color)
x = self.addstr(y, x, ')', curses.A_BOLD)
- self.addstr(y, 35, 'Time to reset: %s' % str_tools.short_time_label(accounting_stats.time_until_reset))
+ self.addstr(y, 35, 'Time to reset: %s' % str_tools.short_time_label(attr.accounting.time_until_reset))
- self.addstr(y + 1, 2, '%s / %s' % (accounting_stats.read_bytes, accounting_stats.read_limit), PRIMARY_COLOR)
- self.addstr(y + 1, 37, '%s / %s' % (accounting_stats.written_bytes, accounting_stats.write_limit), SECONDARY_COLOR)
+ self.addstr(y + 1, 2, '%s / %s' % (attr.accounting.read_bytes, attr.accounting.read_limit), PRIMARY_COLOR)
+ self.addstr(y + 1, 37, '%s / %s' % (attr.accounting.written_bytes, attr.accounting.write_limit), SECONDARY_COLOR)
else:
self.addstr(y, 0, 'Accounting:', curses.A_BOLD)
self.addstr(y, 12, 'Connection Closed...')
More information about the tor-commits
mailing list