[tor-commits] [stem/master] 'Tortoise and the Hare' tutorial

atagar at torproject.org atagar at torproject.org
Wed Mar 13 16:19:37 UTC 2013


commit c1abfa73fc24528713be233347cbd0f8796c537e
Author: Damian Johnson <atagar at torproject.org>
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
 





More information about the tor-commits mailing list