[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] 'Tortoise and the Hare' tutorial
commit c1abfa73fc24528713be233347cbd0f8796c537e
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date: Wed Mar 13 09:09:54 2013 -0700
'Tortoise and the Hare' tutorial
Adding a tutorial for event listening. This has the most substantial example
yet, making a curses graph for tor's bandwidth usage.
---
docs/_static/bandwidth_graph_output.png | Bin 0 -> 8702 bytes
.../label/resources/tortoise_and_the_hare.xcf | Bin 0 -> 8081 bytes
docs/_static/label/tortoise_and_the_hare.png | Bin 0 -> 3750 bytes
docs/_static/section/tutorial/tortoise.png | Bin 0 -> 14700 bytes
docs/tutorial/tortoise_and_the_hare.rst | 204 ++++++++++++++++++++
docs/tutorials.rst | 19 ++-
6 files changed, 221 insertions(+), 2 deletions(-)
diff --git a/docs/_static/bandwidth_graph_output.png b/docs/_static/bandwidth_graph_output.png
new file mode 100644
index 0000000..9fa31e6
Binary files /dev/null and b/docs/_static/bandwidth_graph_output.png differ
diff --git a/docs/_static/label/resources/tortoise_and_the_hare.xcf b/docs/_static/label/resources/tortoise_and_the_hare.xcf
new file mode 100644
index 0000000..4a97fc0
Binary files /dev/null and b/docs/_static/label/resources/tortoise_and_the_hare.xcf differ
diff --git a/docs/_static/label/tortoise_and_the_hare.png b/docs/_static/label/tortoise_and_the_hare.png
new file mode 100644
index 0000000..e215db1
Binary files /dev/null and b/docs/_static/label/tortoise_and_the_hare.png differ
diff --git a/docs/_static/section/tutorial/tortoise.png b/docs/_static/section/tutorial/tortoise.png
new file mode 100644
index 0000000..52b5da0
Binary files /dev/null and b/docs/_static/section/tutorial/tortoise.png differ
diff --git a/docs/tutorial/tortoise_and_the_hare.rst b/docs/tutorial/tortoise_and_the_hare.rst
new file mode 100644
index 0000000..f575853
--- /dev/null
+++ b/docs/tutorial/tortoise_and_the_hare.rst
@@ -0,0 +1,204 @@
+Tortoise and the Hare
+=====================
+
+Controllers have two methods of talking with Tor...
+
+* **Synchronous** - Most commonly you make a request to Tor then receive its
+ reply. The :func:`~stem.control.Controller.get_info` calls in the `first
+ tutorial <the_little_relay_that_could.html>`_ are an example of this.
+
+* **Asynchronous** - Controllers can subscribe to be notified when various
+ kinds of events occur within Tor (see the :data:`~stem.control.EventType`).
+ Stem's users provide a callback function to
+ :func:`~stem.control.Controller.add_event_listener` which is then notified
+ when the event occurs.
+
+Try to avoid lengthy operations within event callbacks. They're notified by a
+single dedicated event thread, and blocking this thread will prevent the
+delivery of further events.
+
+With that out of the way lets see an example. The following is a `curses
+<http://docs.python.org/2/howto/curses.html>`_ application that graphs the
+bandwidth usage of Tor...
+
+.. image:: /_static/bandwidth_graph_output.png
+
+To do this it listens to **BW events**
+(the class for which is a :class:`~stem.response.events.BandwidthEvent`). These
+are events that Tor emits each second saying the number of bytes downloaded and
+uploaded.
+
+::
+
+ import curses
+ import functools
+
+ from stem.control import EventType, Controller
+ from stem.util import str_tools
+
+ # colors that curses can handle
+
+ COLOR_LIST = {
+ "red": curses.COLOR_RED,
+ "green": curses.COLOR_GREEN,
+ "yellow": curses.COLOR_YELLOW,
+ "blue": curses.COLOR_BLUE,
+ "cyan": curses.COLOR_CYAN,
+ "magenta": curses.COLOR_MAGENTA,
+ "black": curses.COLOR_BLACK,
+ "white": curses.COLOR_WHITE,
+ }
+
+ GRAPH_WIDTH = 40
+ GRAPH_HEIGHT = 8
+
+ DOWNLOAD_COLOR = "green"
+ UPLOAD_COLOR = "blue"
+
+ def main():
+ with Controller.from_port(port = 9051) as controller:
+ controller.authenticate()
+
+ try:
+ curses.wrapper(drawBandwidthGraph, controller)
+ except KeyboardInterrupt:
+ pass # the user hit ctrl+c
+
+ def drawBandwidthGraph(stdscr, controller):
+ window = Window(stdscr)
+
+ # (downloaded, uploaded) tuples for the last 40 seconds
+
+ bandwidth_rates = [(0, 0)] * GRAPH_WIDTH
+
+ # Making a partial that wraps the window and bandwidth_rates with a function
+ # for Tor to call when it gets a BW event.
+
+ bw_event_handler = functools.partial(_handle_bandwidth_event, window, bandwidth_rates)
+
+ # Registering this listener with Tor. Tor reports a BW event each second.
+
+ controller.add_event_listener(bw_event_handler, EventType.BW)
+
+ # Pause the main thread until the user hits any key... and no, don't you dare
+ # ask where the 'any' key is. :P
+
+ stdscr.getch()
+
+ def _handle_bandwidth_event(window, bandwidth_rates, event):
+ # callback for when tor provides us with a BW event
+
+ bandwidth_rates.insert(0, (event.read, event.written))
+ bandwidth_rates = bandwidth_rates[:GRAPH_WIDTH] # truncate old values
+ _render_graph(window, bandwidth_rates)
+
+ def _render_graph(window, bandwidth_rates):
+ window.erase()
+
+ download_rates = [entry[0] for entry in bandwidth_rates]
+ upload_rates = [entry[1] for entry in bandwidth_rates]
+
+ # show the latest values at the top
+
+ label = "Downloaded (%s/s):" % str_tools.get_size_label(download_rates[0], 1)
+ window.addstr(0, 1, label, DOWNLOAD_COLOR, curses.A_BOLD)
+
+ label = "Uploaded (%s/s):" % str_tools.get_size_label(upload_rates[0], 1)
+ window.addstr(0, GRAPH_WIDTH + 7, label, UPLOAD_COLOR, curses.A_BOLD)
+
+ # draw the graph bounds in KB
+
+ max_download_rate = max(download_rates)
+ max_upload_rate = max(upload_rates)
+
+ window.addstr(1, 1, "%4i" % (max_download_rate / 1024), DOWNLOAD_COLOR)
+ window.addstr(GRAPH_HEIGHT, 1, " 0", DOWNLOAD_COLOR)
+
+ window.addstr(1, GRAPH_WIDTH + 7, "%4i" % (max_upload_rate / 1024), UPLOAD_COLOR)
+ window.addstr(GRAPH_HEIGHT, GRAPH_WIDTH + 7, " 0", UPLOAD_COLOR)
+
+ # draw the graph
+
+ for col in xrange(GRAPH_WIDTH):
+ col_height = GRAPH_HEIGHT * download_rates[col] / max(max_download_rate, 1)
+
+ for row in xrange(col_height):
+ window.addstr(GRAPH_HEIGHT - row, col + 6, " ", DOWNLOAD_COLOR, curses.A_STANDOUT)
+
+ col_height = GRAPH_HEIGHT * upload_rates[col] / max(max_upload_rate, 1)
+
+ for row in xrange(col_height):
+ window.addstr(GRAPH_HEIGHT - row, col + GRAPH_WIDTH + 12, " ", UPLOAD_COLOR, curses.A_STANDOUT)
+
+ window.refresh()
+
+ class Window(object):
+ """
+ Simple wrapper for the curses standard screen object.
+ """
+
+ def __init__(self, stdscr):
+ self._stdscr = stdscr
+
+ # Mappings of names to the curses color attribute. Initially these all
+ # reference black text, but if the terminal can handle color then
+ # they're set with that foreground color.
+
+ self._colors = dict([(color, 0) for color in COLOR_LIST])
+
+ # allows for background transparency
+
+ try:
+ curses.use_default_colors()
+ except curses.error:
+ pass
+
+ # makes the cursor invisible
+
+ try:
+ curses.curs_set(0)
+ except curses.error:
+ pass
+
+ # initializes colors if the terminal can handle them
+
+ try:
+ if curses.has_colors():
+ color_pair = 1
+
+ for name, foreground in COLOR_LIST.items():
+ background = -1 # allows for default (possibly transparent) background
+ curses.init_pair(color_pair, foreground, background)
+ self._colors[name] = curses.color_pair(color_pair)
+ color_pair += 1
+ except curses.error:
+ pass
+
+ def addstr(self, y, x, msg, color = None, attr = curses.A_NORMAL):
+ # Curses throws an error if we try to draw a message that spans out of the
+ # window's bounds (... seriously?), so doing our best to avoid that.
+
+ if color is not None:
+ if color not in self._colors:
+ recognized_colors = ", ".join(self._colors.keys())
+ raise ValueError("The '%s' color isn't recognized: %s" % (color, recognized_colors))
+
+ attr |= self._colors[color]
+
+ max_y, max_x = self._stdscr.getmaxyx()
+
+ if max_x > x and max_y > y:
+ try:
+ self._stdscr.addstr(y, x, msg[:max_x - x], attr)
+ except:
+ pass # maybe an edge case while resizing the window
+
+ def erase(self):
+ self._stdscr.erase()
+
+ def refresh(self):
+ self._stdscr.refresh()
+
+ if __name__ == '__main__':
+ main()
+
diff --git a/docs/tutorials.rst b/docs/tutorials.rst
index 28ea3d5..646af20 100644
--- a/docs/tutorials.rst
+++ b/docs/tutorials.rst
@@ -1,5 +1,5 @@
-Tutorials
-=========
+Tutorial
+========
.. Image Sources:
@@ -15,6 +15,11 @@ Tutorials
License: Public Domain (not a subject of copyright according the Russian civil code)
Alternate: https://openclipart.org/detail/85555/communist-sabbatarian-ribbon-by-rones-85555
+ * Tortoise and the Hare - tortoise.png
+ Source: https://openclipart.org/detail/27911/green-tortoise-%28cartoon%29-by-arking-27911
+ Author: arking
+ License: Public Domain
+
* Mirror Mirror On The Wall - mirror.png
Source: https://openclipart.org/detail/152155/mirror-frame-by-gsagri04
Author: Unknown (gsagri04?)
@@ -52,6 +57,16 @@ feet wet by jumping straight in with some tutorials...
tutorial we'll programmatically start Tor then use it to read a site
through mother Russia!
+ * - .. image:: /_static/section/tutorial/tortoise.png
+ :target: tutorial/tortoise_and_the_hare.html
+
+ - .. image:: /_static/label/tortoise_and_the_hare.png
+ :target: tutorial/tortoise_and_the_hare.html
+
+ As Tor runs it generates a variety of **events** that controllers can
+ subscribe to be notified of. In this tutorial we'll do just that,
+ writing a curses application that graphs the bandwidth usage of Tor.
+
* - .. image:: /_static/section/tutorial/mirror.png
:target: tutorial/mirror_mirror_on_the_wall.html
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits