Control: tags 881375 + patch
Control: tags 881375 + pending

Dear maintainer,

I've prepared an NMU for sen (versioned as 0.6.0-0.1) and
uploaded it.
Regards.
diff -Nru sen-0.5.1/debian/changelog sen-0.6.0/debian/changelog
--- sen-0.5.1/debian/changelog	2017-11-18 04:11:21.000000000 +0100
+++ sen-0.6.0/debian/changelog	2018-04-03 12:28:44.000000000 +0200
@@ -1,3 +1,11 @@
+sen (0.6.0-0.1) unstable; urgency=medium
+
+  * Non-maintainer upload
+  * New upstream release (Closes: #881375)
+  * Drop catchlog dependency, now merged into pytest
+
+ -- Gianfranco Costamagna <locutusofb...@debian.org>  Tue, 03 Apr 2018 12:28:44 +0200
+
 sen (0.5.1-1) unstable; urgency=medium
 
   * New upstream release.
diff -Nru sen-0.5.1/debian/control sen-0.6.0/debian/control
--- sen-0.5.1/debian/control	2017-11-18 04:09:18.000000000 +0100
+++ sen-0.6.0/debian/control	2018-04-03 12:28:44.000000000 +0200
@@ -10,8 +10,7 @@
                python3-docker,
                python3-flexmock,
                python3-humanize,
-               python3-pytest,
-               python3-pytest-catchlog,
+               python3-pytest (>= 3.3.0),
                python3-setuptools,
                python3-urwid,
                python3-urwidtrees,
diff -Nru sen-0.5.1/docs/releasing.md sen-0.6.0/docs/releasing.md
--- sen-0.5.1/docs/releasing.md	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/docs/releasing.md	2018-03-22 08:34:32.000000000 +0100
@@ -5,5 +5,6 @@
 3. do **NOT** create feature branch with same name as tag
 4. prepare changelog (`CHANGELOG.md` and release notes)
 5. when tagged with release, verify that release was successful on travis
-6. bump version in setup.py to `x.y.z-dev`
+6. correct Docker Hub tags - let latest release point to `latest`
+7. bump version in setup.py to `x.y.z-dev`
 
diff -Nru sen-0.5.1/PKG-INFO sen-0.6.0/PKG-INFO
--- sen-0.5.1/PKG-INFO	2017-03-18 22:50:27.000000000 +0100
+++ sen-0.6.0/PKG-INFO	2018-03-22 08:35:11.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: sen
-Version: 0.5.1
+Version: 0.6.0
 Summary: Terminal User Interface for Docker Engine
 Home-page: https://github.com/TomasTomecek/sen/
 Author: Tomas Tomecek
diff -Nru sen-0.5.1/README.md sen-0.6.0/README.md
--- sen-0.5.1/README.md	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/README.md	2018-03-22 08:34:32.000000000 +0100
@@ -1,7 +1,6 @@
 # sen
 
-[![Circle CI](https://circleci.com/gh/TomasTomecek/sen.svg?style=svg)](https://circleci.com/gh/TomasTomecek/sen)
-[![](https://images.microbadger.com/badges/image/tomastomecek/sen.svg)](https://microbadger.com/images/tomastomecek/sen "Get your own image badge on microbadger.com")
+[![Build Status](https://travis-ci.org/TomasTomecek/sen.svg?branch=master)](https://travis-ci.org/TomasTomecek/sen)
 
 
 `sen` is a terminal user interface for docker engine:
@@ -16,6 +15,7 @@
  * sen notifies you whenever something happens (and reports slow queries)
  * supports a lot of vim-like keybindings (`j`, `k`, `gg`, `/`, ...)
  * you can get interactive tree view of all images (equivalent of `docker images --tree`)
+ * see how much space containers, images and volumes occupy (just type `:df`)
 
 You can [see the features yourself](/docs/features.md).
 
@@ -34,7 +34,7 @@
 This is the recommended way of running `sen` in a container:
 
 ```
-$ docker run --privileged -v /var/run/docker.sock:/run/docker.sock -ti -e TERM tomastomecek/sen
+$ docker run -v /var/run/docker.sock:/run/docker.sock -ti -e TERM tomastomecek/sen
 ```
 
 Some distros have `/var/run` simlinked to `/run`, so you can do `/run/docker.sock:/run/docker.sock` instead.
@@ -48,12 +48,21 @@
 
 ```
 $ docker build --tag=$USER/sen https://github.com/tomastomecek/sen
-$ docker run --privileged -v /var/run/docker.sock:/run/docker.sock -ti -e TERM $USER/sen
+$ docker run -v /var/run/docker.sock:/run/docker.sock -ti -e TERM $USER/sen
 ```
 
 
 ## PyPI
 
+`sen` is using [`urwidtrees`](https://github.com/pazz/urwidtrees) as a dependency. Unfortunately, the upstream
+maintainer doesn't maintain it on PyPI so we need to install it directly from
+git, before installing sen (the forked PyPI version has a [bug](https://github.com/TomasTomecek/sen/issues/128) in
+installation process):
+
+```
+$ pip3 install git+https://github.com/pazz/urwidtrees.git@9142c59d3e41421ff6230708d08b6a134e0a8eed#egg=urwidtrees-1.0.3.dev
+```
+
 `sen` releases are available on PyPI:
 
 ```
@@ -159,6 +168,7 @@
 ```
 i         inspect image
 d         remove image (irreversible!)
+D         remove image forcibly (irreversible!)
 enter     display detailed info about image (when layer is focused)
 ```
 
@@ -169,6 +179,7 @@
 l         display logs of container
 f         follow logs of container
 d         remove container (irreversible!)
+D         remove container forcibly (irreversible!)
 t         stop container
 s         start container
 r         restart container
@@ -202,6 +213,11 @@
 ```
 
 
+## Disk usage buffer
+
+You can enter it by typing command `df`.
+
+
 # Why I started sen?
 
 Since I started using docker, I always dreamed of having a docker TUI. Something like [tig](https://github.com/jonas/tig), [htop](http://hisham.hm/htop/) or [alot](https://github.com/pazz/alot). Some appeared over time. Such as [docker-mon](https://github.com/icecrime/docker-mon) or [ctop](https://github.com/yadutaf/ctop). Unfortunately, those are not proper docker TUIs. They are meant for monitoring and diagnostics.
diff -Nru sen-0.5.1/requirements-test.txt sen-0.6.0/requirements-test.txt
--- sen-0.5.1/requirements-test.txt	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/requirements-test.txt	2018-03-22 08:34:32.000000000 +0100
@@ -1,3 +1,2 @@
-pytest
+pytest>=3.4.0
 flexmock
-pytest-capturelog
diff -Nru sen-0.5.1/requirements.txt sen-0.6.0/requirements.txt
--- sen-0.5.1/requirements.txt	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/requirements.txt	2018-03-22 08:34:32.000000000 +0100
@@ -1,4 +1,5 @@
 urwid
 docker
-# this is 1.0.2, pazz doesn't maintain urwidtrees on PyPI
--e git://github.com/pazz/urwidtrees.git@b8159036521100c60ecaefdfe0bed4c50dc3ef23#egg=urwidtrees
+# this is latest upstream commit (April 2017)
+# upstream maintainer of urwidtrees doesn't maintain PyPI urwidtrees
+-e git+https://github.com/pazz/urwidtrees.git@9142c59d3e41421ff6230708d08b6a134e0a8eed#egg=urwidtrees
diff -Nru sen-0.5.1/sen/cli.py sen-0.6.0/sen/cli.py
--- sen-0.5.1/sen/cli.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/sen/cli.py	2018-03-22 08:34:32.000000000 +0100
@@ -21,6 +21,7 @@
 import argparse
 import logging
 
+import sen
 from sen import set_logging
 from sen.exceptions import TerminateApplication
 from sen.tui.init import Application
@@ -33,8 +34,17 @@
     parser = argparse.ArgumentParser(
         description="Terminal User Interface for Docker Engine"
     )
+    parser.add_argument(
+        "--yolo", "--skip-prompt-for-irreversible-action",
+        action="store_true",
+        default=False,
+        help="Don't prompt when performing irreversible actions, a.k.a. YOLO!"
+    )
     exclusive_group = parser.add_mutually_exclusive_group()
-    exclusive_group.add_argument("--debug", action="store_true", default=None)
+    exclusive_group.add_argument(
+            "--debug", action="store_true", default=None,
+            help="Set logging level to debug"
+            )
 
     args = parser.parse_args()
 
@@ -43,19 +53,20 @@
     # don't want in a terminal app (thanks to Slavek Kabrda for explanation)
     if args.debug:
         set_logging(level=logging.DEBUG, path=get_log_file_path())
+        logger.debug("sen loaded from %s", sen.__file__)
     else:
         set_logging(level=logging.INFO, path=get_log_file_path())
 
     logger.info("application started")
 
     try:
-        ui = Application()
+        app = Application(yolo=args.yolo)
     except TerminateApplication as ex:
         print("Error: {0}".format(str(ex)), file=sys.stderr)
         return 1
 
     try:
-        ui.run()
+        app.run()
     except KeyboardInterrupt:
         print("Quitting on user request.")
         return 1
diff -Nru sen-0.5.1/sen/docker_backend.py sen-0.6.0/sen/docker_backend.py
--- sen-0.5.1/sen/docker_backend.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/sen/docker_backend.py	2018-03-22 08:34:32.000000000 +0100
@@ -1,4 +1,3 @@
-import copy
 import functools
 import json
 import logging
@@ -7,14 +6,20 @@
 from operator import attrgetter
 
 from sen.constants import ISO_DATETIME_PARSE_STRING
-from sen.exceptions import TerminateApplication, NotifyError, NotAvailableAnymore
+from sen.exceptions import (
+    TerminateApplication, NotifyError, NotAvailableAnymore
+)
 
 import docker
 import docker.errors
 
 from sen.net import NetData
-from sen.util import calculate_cpu_percent, calculate_blkio_bytes, calculate_network_bytes, repeater, \
-    humanize_time
+from sen.util import (
+    calculate_cpu_percent, calculate_cpu_percent2, calculate_blkio_bytes,
+    calculate_network_bytes, repeater,
+    humanize_time,
+    graceful_chain_get
+)
 
 logger = logging.getLogger(__name__)
 
@@ -248,19 +253,14 @@
         return hash(self._id)
 
 
-def graceful_chain_get(d, *args):
-    if not d:
-        return None
-    t = copy.deepcopy(d)
-    for arg in args:
-        try:
-            t = t[arg]
-        except (AttributeError, KeyError, TypeError):
-            return None
-    return t
-
-
 class DockerImage(DockerObject):
+    def __init__(self, data, docker_backend, object_id=None):
+        super().__init__(data, docker_backend, object_id=object_id)
+        self._unique_size = None
+        self._total_size = None
+        self._virtual_size = None
+        self._shared_size = None
+
     @property
     def image_id(self):
         return self.object_id
@@ -372,22 +372,36 @@
         return self.metadata_get(["Comment"])
 
     @property
-    def size(self):
+    def total_size(self):
         """
         Size of ALL layers in bytes
 
-        :return: int
+        :return: int or None
         """
-        return self.data.get("VirtualSize", 0)
+        return self._total_size or self.data.get("Size", 0)
 
     @property
-    def layer_size(self):
+    def unique_size(self):
         """
         Size of ONLY this particular layer
 
-        :return: int
+        :return: int or None
         """
-        return self.data.get("Size", 0)
+        self._virtual_size = self._virtual_size or \
+                             graceful_chain_get(self.data, "VirtualSize", default=0)
+        try:
+            return self._virtual_size - self._shared_size
+        except TypeError:
+            return 0
+
+    @property
+    def shared_size(self):
+        """
+        I guess this is size of layers which are shared with some other image
+
+        :return: int or None
+        """
+        return self._shared_size
 
     @property
     def names(self):
@@ -443,8 +457,8 @@
         return self._inspect
 
     @operation("{object_type} {object_short_name} removed!")
-    def remove(self):
-        return self.d.remove_image(self.image_id)
+    def remove(self, force=False):
+        return self.d.remove_image(self.image_id, force=force)
 
     @operation("Tag of {object_type} {object_short_name} removed!")
     def remove_tag(self, tag):
@@ -501,6 +515,11 @@
     Container related logic
     """
 
+    def __init__(self, data, docker_backend, object_id=None):
+        super(DockerContainer, self).__init__(data, docker_backend, object_id)
+        self.size_root_fs = None
+        self.size_rw_fs = None
+
     def __str__(self):
         return "{} ({})".format(self.container_id, self.short_name)
 
@@ -653,13 +672,23 @@
 
     @operation("Get resources statistics.")
     def stats(self):
+        cpu_total = 0.0
+        cpu_system = 0.0
+        cpu_percent = 0.0
         for x in self.d.stats(self.container_id, decode=True, stream=True):
             blk_read, blk_write = calculate_blkio_bytes(x)
             net_r, net_w = calculate_network_bytes(x)
             mem_current = x["memory_stats"]["usage"]
             mem_total = x["memory_stats"]["limit"]
+
+            try:
+                cpu_percent, cpu_system, cpu_total = calculate_cpu_percent2(x, cpu_total, cpu_system)
+            except KeyError as e:
+                logger.error("error while getting new CPU stats: %r, falling back")
+                cpu_percent = calculate_cpu_percent(x)
+
             r = {
-                "cpu_percent": calculate_cpu_percent(x),
+                "cpu_percent": cpu_percent,
                 "mem_current": mem_current,
                 "mem_total": x["memory_stats"]["limit"],
                 "mem_percent": (mem_current / mem_total) * 100.0,
@@ -712,8 +741,8 @@
             return logs_data
 
     @operation("{object_type} {object_short_name} removed!")
-    def remove(self):
-        self.d.remove_container(self.container_id)
+    def remove(self, force=False):
+        self.d.remove_container(self.container_id, force=force)
 
     @operation("{object_type} {object_short_name} started.")
     def start(self):
@@ -749,6 +778,7 @@
         self._containers = None
         self._images = None  # displayed images
         self._all_images = None  # docker images -a
+        self._df = None
 
         kwargs = {"version": "auto"}
         kwargs.update(docker.utils.kwargs_from_env(assert_hostname=False))
@@ -777,6 +807,7 @@
                 img = DockerImage(i, self)
                 self._images[img.image_id] = img
             self._all_images = {}
+            # FIXME: performance: do just all=True
             all_images_response = repeater(self.client.images, kwargs={"all": True}) or []
             for i in all_images_response:
                 img = DockerImage(i, self)
@@ -796,6 +827,30 @@
             return [x for x in list(self._containers.values()) if x.running]
         return list(self._containers.values())
 
+    @operation("Get disk usage.")
+    def df(self, cached=True):
+        if cached is False or self._df is None:
+            logger.debug("getting disk-usage")
+            # TODO: wrap in try/execpt
+            self._df = self.client.df()
+            # TODO: attach these to real containers and images
+            # # since DOCKER API-1.25 (v.1.13.0)
+            # df = self.client.df()
+            # if 'Containers' in df:
+            #     df_containers = df['Containers']
+            containers_data = graceful_chain_get(self._df, "Containers")
+            for c_data in containers_data:
+                c = graceful_chain_get(self._containers, graceful_chain_get(c_data, "Id"))
+                c.size_root_fs = graceful_chain_get(c_data, "SizeRootFs")
+                c.size_rw_fs = graceful_chain_get(c_data, "SizeRw")
+            images_data = graceful_chain_get(self._df, "Images")
+            for i_data in images_data:
+                i = graceful_chain_get(self._images, graceful_chain_get(i_data, "Id"))
+                i._total_size = graceful_chain_get(i_data, "Size")
+                i._shared_size = graceful_chain_get(i_data, "SharedSize")
+                i._virtual_size = graceful_chain_get(i_data, "VirtualSize")
+        return self._df
+
     def realtime_updates(self):
         event = it = None
         while True:
diff -Nru sen-0.5.1/sen/__init__.py sen-0.6.0/sen/__init__.py
--- sen-0.5.1/sen/__init__.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/sen/__init__.py	2018-03-22 08:34:32.000000000 +0100
@@ -2,7 +2,7 @@
 
 from sen.constants import FALLBACK_LOG_PATH
 
-__version__ = "0.5.1"
+__version__ = "0.6.0"
 
 
 def set_logging(name="sen", level=logging.DEBUG, path=FALLBACK_LOG_PATH):
diff -Nru sen-0.5.1/sen/tui/buffer.py sen-0.6.0/sen/tui/buffer.py
--- sen-0.5.1/sen/tui/buffer.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/sen/tui/buffer.py	2018-03-22 08:34:32.000000000 +0100
@@ -3,6 +3,7 @@
 from sen.docker_backend import DockerContainer, RootImage
 from sen.exceptions import NotifyError
 from sen.tui.commands.base import Command
+from sen.tui.views.disk_usage import DfBufferView
 from sen.tui.views.help import HelpBufferView, HelpCommandView
 from sen.tui.views.main import MainListBox
 from sen.tui.views.image_info import ImageInfoWidget
@@ -114,7 +115,8 @@
 
 
 class ImageInfoBuffer(Buffer):
-    description = "Dashboard for information about selected image."
+    description = "Dashboard for information about selected image.\n" + \
+                  "You can run command `df` to get more detailed info about disk usage."
     keybinds = {
         "enter": "display-info",
         "d": "rm",
@@ -184,7 +186,8 @@
     display_name = "Listing"
     description = "List of all known docker images and containers display in a single list"
     keybinds = {
-        "d": "rm",  # TODO: do also rmi
+        "d": "rm",
+        "D": "rm -f",
         "s": "start",
         "t": "stop",
         "r": "restart",
@@ -293,3 +296,20 @@
             self.widget = HelpCommandView(ui, inp)
 
         super().__init__()
+
+
+class DfBuffer(Buffer):
+    description = "Show information about how much disk space container, images and volumes take."
+    display_name = "Disk Usage"
+
+    def __init__(self, ui):
+        """
+        :param ui: UI instance
+        """
+        self.ui = ui
+        self.widget = DfBufferView(ui, self)
+
+        super().__init__()
+
+    def refresh(self, df=None, containers=None, images=None):
+        self.widget.refresh(df=df, containers=containers, images=images)
diff -Nru sen-0.5.1/sen/tui/chunks/container.py sen-0.6.0/sen/tui/chunks/container.py
--- sen-0.5.1/sen/tui/chunks/container.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/sen/tui/chunks/container.py	2018-03-22 08:34:32.000000000 +0100
@@ -28,7 +28,11 @@
     container_id = SelectableText(docker_container.short_id)
     row.append(container_id)
 
-    command = SelectableText(docker_container.command, get_map(defult="main_list_ddg"))
+    commands = docker_container.command.split("\n")
+    command_str = commands[0]
+    if len(commands) > 0:
+        command_str += "..."
+    command = SelectableText(command_str, get_map(defult="main_list_ddg"))
     row.append(command)
 
     image = SelectableText(docker_container.image_name())
diff -Nru sen-0.5.1/sen/tui/chunks/image.py sen-0.6.0/sen/tui/chunks/image.py
--- sen-0.5.1/sen/tui/chunks/image.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/sen/tui/chunks/image.py	2018-03-22 08:34:32.000000000 +0100
@@ -96,6 +96,6 @@
 
     if with_size:
         text_markup.append(" ")
-        text_markup.append(("main_list_ddg", "(%s)" % humanize_bytes(docker_image.layer_size)))
+        text_markup.append(("main_list_ddg", "(%s)" % humanize_bytes(docker_image.total_size)))
 
     return text_markup
diff -Nru sen-0.5.1/sen/tui/commands/backend.py sen-0.6.0/sen/tui/commands/backend.py
--- sen-0.5.1/sen/tui/commands/backend.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/sen/tui/commands/backend.py	2018-03-22 08:34:32.000000000 +0100
@@ -5,7 +5,7 @@
 import logging
 import webbrowser
 
-from sen.tui.buffer import LogsBuffer, InspectBuffer
+from sen.tui.buffer import LogsBuffer, InspectBuffer, DfBuffer
 from sen.tui.commands.base import BackendCommand, register_command, Option
 from sen.tui.widgets.list.util import get_operation_notify_widget
 from sen.docker_backend import DockerContainer
@@ -15,12 +15,12 @@
 
 
 class OperationCommand(BackendCommand):
-    def do(self, fn_name, pre_message=None, notif_level="info"):
+    def do(self, fn_name, pre_message=None, notif_level="info", **kwargs):
         pre_message = pre_message or self.pre_info_message.format(
             container_name=self.docker_object.short_name)
         self.ui.notify_message(pre_message)
         try:
-            operation = getattr(self.docker_object, fn_name)()
+            operation = getattr(self.docker_object, fn_name)(**kwargs)
         except AttributeError:
             log_txt = "you can't {} {}".format(fn_name, self.docker_object)
             logger.error(log_txt)
@@ -49,14 +49,38 @@
 class RemoveCommand(OperationCommand):
     name = "rm"
     description = "remove provided object, image or container"
+    options_definitions = [Option("force",
+                                  "Force removal of the selected object.",
+                                  default=False, aliases=["-f", "f"]),
+                           Option("yes",
+                                  "Don't ask before removing.",
+                                  default=False, aliases=["-y"])]
 
-    # FIXME: split this into rm and rmi
     def run(self):
+        logger.debug("remove %s force=%s yes=%s", self.docker_object, self.arguments.force,
+                     self.arguments.yes)
+        if not self.arguments.yes and not self.ui.yolo:
+            logger.debug("we need confirmation from user")
+            cmd = "rm -y" if not self.arguments.force else "rm -y -f"
+            self.ui.run_command(
+                "prompt prompt-text=\"You are about to remove %s %s, type enter to continue: \" "
+                "initial-text=\"%s\"" % (
+                    self.docker_object.pretty_object_type.lower(),
+                    self.docker_object.short_name,
+                    cmd
+                ),
+                docker_object=self.docker_object
+            )
+            return
+
+        if self.arguments.force:
+            self.ui.notify_message("Removing forcibly!", level="important")
         self.do("remove",
                 pre_message="Removing {} {}...".format(
                     self.docker_object.pretty_object_type.lower(),
                     self.docker_object.short_name),
-                notif_level="important")
+                notif_level="important",
+                force=self.arguments.force)
 
 
 @register_command
@@ -125,6 +149,20 @@
 
 
 @register_command
+class DfCommand(BackendCommand):
+    name = "df"
+    description = "show disk usage"
+
+    def run(self):
+        b = DfBuffer(self.ui)
+        self.ui.add_and_display_buffer(b)
+        df = self.docker_backend.df()
+        b.refresh(df=df.response,
+                  containers=self.docker_backend.get_containers(cached=True, stopped=True).response,
+                  images=self.docker_backend.get_images(cached=True).response)
+
+
+@register_command
 class OpenPortsInBrowser(BackendCommand):
     name = "open-browser"
     # TODO: user should be able to specify port and ip: by hitting keybind in container info view
diff -Nru sen-0.5.1/sen/tui/commands/display.py sen-0.6.0/sen/tui/commands/display.py
--- sen-0.5.1/sen/tui/commands/display.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/sen/tui/commands/display.py	2018-03-22 08:34:32.000000000 +0100
@@ -8,8 +8,11 @@
 
 from sen.docker_backend import DockerImage, DockerContainer
 from sen.exceptions import NotifyError
-from sen.tui.buffer import TreeBuffer, HelpBuffer, ImageInfoBuffer, ContainerInfoBuffer, \
+from sen.tui.buffer import (
+    TreeBuffer, HelpBuffer,
+    ImageInfoBuffer, ContainerInfoBuffer,
     MainListBuffer
+)
 from sen.tui.commands.base import FrontendCommand, register_command
 
 
diff -Nru sen-0.5.1/sen/tui/commands/ui.py sen-0.6.0/sen/tui/commands/ui.py
--- sen-0.5.1/sen/tui/commands/ui.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/sen/tui/commands/ui.py	2018-03-22 08:34:32.000000000 +0100
@@ -8,8 +8,11 @@
 
 from sen.exceptions import NotifyError
 from sen.tui.buffer import HelpBuffer, TreeBuffer
-from sen.tui.commands.base import register_command, SameThreadCommand, Option, Argument, \
+from sen.tui.commands.base import (
+    register_command, SameThreadCommand,
+    Option, Argument,
     NoSuchCommand
+)
 from sen.util import log_traceback, log_last_traceback
 
 
@@ -132,7 +135,7 @@
 
 
 @log_traceback
-def run_command_callback(ui, edit_widget, text_input):
+def run_command_callback(ui, docker_object, edit_widget, text_input):
     logger.debug("%r %r", edit_widget, text_input)
     if "\n" in text_input:
         inp = text_input.strip()
@@ -141,7 +144,7 @@
         ui.prompt_bar = None
         ui.set_focus("body")
         try:
-            ui.run_command(inp)
+            ui.run_command(inp, docker_object=docker_object)
         except NoSuchCommand as ex:
             logger.info("non-existent command initiated: %r", inp)
             ui.notify_message(str(ex), level="error")
@@ -181,7 +184,8 @@
         self.ui.reload_footer()
         self.ui.set_focus("footer")
 
-        urwid.connect_signal(editpart, "change", run_command_callback, user_args=[self.ui])
+        urwid.connect_signal(editpart, "change", run_command_callback,
+                             user_args=[self.ui, self.docker_object])
 
 
 @register_command
diff -Nru sen-0.5.1/sen/tui/init.py sen-0.6.0/sen/tui/init.py
--- sen-0.5.1/sen/tui/init.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/sen/tui/init.py	2018-03-22 08:34:32.000000000 +0100
@@ -17,11 +17,13 @@
 
 
 class Application:
-    def __init__(self):
+    def __init__(self, yolo=False):
         self.d = DockerBackend()
 
         self.loop, self.ui = get_app_in_loop(PALLETE)
 
+        self.ui.yolo = yolo
+
         self.ui.commander = Commander(self.ui, self.d)
 
         self.rt_thread = threading.Thread(target=self.realtime_updates, daemon=True)
diff -Nru sen-0.5.1/sen/tui/ui.py sen-0.6.0/sen/tui/ui.py
--- sen-0.5.1/sen/tui/ui.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/sen/tui/ui.py	2018-03-22 08:34:32.000000000 +0100
@@ -10,8 +10,10 @@
 import urwid
 
 from sen.exceptions import NotifyError
-from sen.tui.commands.base import FrontendPriority, BackendPriority, SameThreadPriority, \
-    KeyNotMapped
+from sen.tui.commands.base import (
+    FrontendPriority, BackendPriority,
+    SameThreadPriority, KeyNotMapped
+)
 from sen.tui.constants import CLEAR_NOTIF_BAR_MESSAGE_IN
 from sen.tui.widgets.util import ThreadSafeFrame
 from sen.util import log_traceback, OrderedSet
diff -Nru sen-0.5.1/sen/tui/views/container_info.py sen-0.6.0/sen/tui/views/container_info.py
--- sen-0.5.1/sen/tui/views/container_info.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/sen/tui/views/container_info.py	2018-03-22 08:34:32.000000000 +0100
@@ -14,7 +14,10 @@
 from sen.tui.widgets.list.base import WidgetBase
 from sen.tui.widgets.list.util import RowWidget, UnselectableRowWidget
 from sen.tui.widgets.table import assemble_rows
-from sen.tui.widgets.util import SelectableText, get_map, ColorText, UnselectableListBox
+from sen.tui.widgets.util import (
+    SelectableText, get_map,
+    ColorText, UnselectableListBox
+)
 from sen.util import log_traceback, humanize_bytes
 
 
@@ -223,6 +226,16 @@
                 [SelectableText("Name", maps=get_map("main_list_green")),
                  SelectableText("".join(self.docker_container.names))],
             )
+
+        if self.docker_container.size_root_fs:
+            data.append(
+                [SelectableText("Image Size", maps=get_map("main_list_green")),
+                 SelectableText(humanize_bytes(self.docker_container.size_root_fs))])
+        if self.docker_container.size_rw_fs:
+            data.append(
+                [SelectableText("Writable Layer Size", maps=get_map("main_list_green")),
+                 SelectableText(humanize_bytes(self.docker_container.size_rw_fs))])
+
         self.view_widgets.extend(assemble_rows(data, ignore_columns=[1]))
 
     def _net(self):
@@ -264,9 +277,10 @@
             self.view_widgets.extend(assemble_rows(data, dividechars=3, ignore_columns=[1]))
 
     def _image(self):
-        self.view_widgets.append(RowWidget([SelectableText("")]))
-        self.view_widgets.append(RowWidget([SelectableText("Image", maps=get_map("main_list_white"))]))
-        self.view_widgets.append(RowWidget([LayerWidget(self.ui, self.docker_container.image)]))
+        if self.docker_container.image:
+            self.view_widgets.append(RowWidget([SelectableText("")]))
+            self.view_widgets.append(RowWidget([SelectableText("Image", maps=get_map("main_list_white"))]))
+            self.view_widgets.append(RowWidget([LayerWidget(self.ui, self.docker_container.image)]))
 
     def _resources(self):
         self.view_widgets.append(RowWidget([SelectableText("")]))
@@ -322,6 +336,7 @@
                         continue
                     logger.error("error while getting stats: %r", ex)
                     self.ui.notify_message("Error while getting stats: %s" % ex, level="error")
+                    # TODO: if debug raise
                     break
 
                 if self.stop.is_set():
diff -Nru sen-0.5.1/sen/tui/views/disk_usage.py sen-0.6.0/sen/tui/views/disk_usage.py
--- sen-0.5.1/sen/tui/views/disk_usage.py	1970-01-01 01:00:00.000000000 +0100
+++ sen-0.6.0/sen/tui/views/disk_usage.py	2018-03-22 08:34:32.000000000 +0100
@@ -0,0 +1,114 @@
+"""
+TODO:
+ * nicer list
+ * summary
+ * clickable items
+ * enable deleting volumes
+"""
+import urwid
+
+from sen.util import humanize_bytes, graceful_chain_get
+from sen.tui.views.base import View
+from sen.tui.widgets.list.util import SingleTextRow
+from sen.tui.widgets.table import assemble_rows
+from sen.tui.constants import MAIN_LIST_FOCUS
+from sen.tui.widgets.util import SelectableText, get_map
+from sen.tui.widgets.list.base import WidgetBase
+
+
+class DfBufferView(WidgetBase, View):
+    def __init__(self, ui, buffer):
+        """
+
+        :param ui:
+        :param buffer: Buffer instance, display help about this buffer
+        """
+        self.ui = ui
+        self.buffer = buffer
+        self.walker = urwid.SimpleFocusListWalker([])
+        super().__init__(ui, self.walker)
+
+    def refresh(self, df=None, containers=None, images=None):
+        content = []
+        if df is None:
+            content += [
+                SingleTextRow("Data is being loaded, it may even take a couple minutes.",
+                              maps={"normal": "main_list_white", "focus": MAIN_LIST_FOCUS}),
+            ]
+        else:
+            if containers:
+                content += [
+                    SingleTextRow("Containers",
+                                  maps={"normal": "main_list_white", "focus": MAIN_LIST_FOCUS}),
+                    SingleTextRow("")
+                ]
+                containers_content = [[
+                    SelectableText("Name", maps=get_map("main_list_lg")),
+                    SelectableText("Image Size", maps=get_map("main_list_lg")),
+                    SelectableText("Writable Layer Size", maps=get_map("main_list_lg")),
+                ]]
+                for c in containers:
+                    containers_content.append(
+                        [SelectableText(c.short_name),
+                         SelectableText(humanize_bytes(c.size_root_fs or 0)),
+                         SelectableText(humanize_bytes(c.size_rw_fs or 0)),
+                    ])
+                content.extend(assemble_rows(containers_content, dividechars=3))
+                content += [
+                    SingleTextRow("")
+                ]
+            if images:
+                content += [
+                    SingleTextRow("Images",
+                                  maps={"normal": "main_list_white", "focus": MAIN_LIST_FOCUS}),
+                    SingleTextRow("")
+                ]
+                images_content = [[
+                    SelectableText("Name", maps=get_map("main_list_lg")),
+                    SelectableText("Size", maps=get_map("main_list_lg")),
+                    SelectableText("Shared Size", maps=get_map("main_list_lg")),
+                    SelectableText("Unique Size", maps=get_map("main_list_lg"))
+                ]]
+                for i in images:
+                    images_content.append([
+                        SelectableText(i.short_name, maps=get_map("main_list_dg")),
+                        SelectableText(
+                            humanize_bytes(i.total_size or 0),
+                            maps=get_map("main_list_dg")),
+                        SelectableText(
+                            humanize_bytes(i.shared_size or 0),
+                            maps=get_map("main_list_dg")),
+                        SelectableText(
+                            humanize_bytes(i.unique_size or 0),
+                            maps=get_map("main_list_dg"))
+                    ])
+                content.extend(assemble_rows(images_content, dividechars=3))
+                content += [
+                    SingleTextRow("")
+                ]
+            volumes = graceful_chain_get(df, "Volumes")
+            if volumes:
+                content += [
+                    SingleTextRow("Volumes",
+                                  maps={"normal": "main_list_white", "focus": MAIN_LIST_FOCUS}),
+                    SingleTextRow("")
+                ]
+                volumes_content = [[
+                    SelectableText("Name", maps=get_map("main_list_lg")),
+                    SelectableText("Links", maps=get_map("main_list_lg")),
+                    SelectableText("Size", maps=get_map("main_list_lg")),
+                ]]
+                for v in volumes:
+                    v_name = graceful_chain_get(v, "Name", default="")
+                    v_size = graceful_chain_get(v, "UsageData", "Size", default=0)
+                    v_links = graceful_chain_get(v, "UsageData", "RefCount", default=0)
+                    volumes_content.append([
+                        SelectableText(v_name, maps=get_map("main_list_dg")),
+                        SelectableText("%s" % v_links, maps=get_map("main_list_dg")),
+                        SelectableText(
+                            humanize_bytes(v_size),
+                            maps=get_map("main_list_dg")),
+                    ])
+                content.extend(assemble_rows(volumes_content, dividechars=3))
+        self.set_body(content)
+        self.set_focus(0)
diff -Nru sen-0.5.1/sen/tui/views/image_info.py sen-0.6.0/sen/tui/views/image_info.py
--- sen-0.5.1/sen/tui/views/image_info.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/sen/tui/views/image_info.py	2018-03-22 08:34:32.000000000 +0100
@@ -65,10 +65,18 @@
              SelectableText("{0}, {1}".format(self.docker_image.display_formal_time_created(),
                                               self.docker_image.display_time_created()))],
             [SelectableText("Size", maps=get_map("main_list_green")),
-             SelectableText(humanize_bytes(self.docker_image.size))],
-            [SelectableText("Command", maps=get_map("main_list_green")),
-             SelectableText(self.docker_image.container_command)],
+             SelectableText(humanize_bytes(self.docker_image.total_size))],
         ]
+        if self.docker_image.unique_size:
+            data.append(
+                [SelectableText("Unique Size", maps=get_map("main_list_green")),
+                 SelectableText(humanize_bytes(self.docker_image.unique_size))])
+        if self.docker_image.shared_size:
+            data.append(
+                [SelectableText("Shared Size", maps=get_map("main_list_green")),
+                 SelectableText(humanize_bytes(self.docker_image.shared_size))])
+        data.append([SelectableText("Command", maps=get_map("main_list_green")),
+                     SelectableText(self.docker_image.container_command)])
         self.walker.extend(assemble_rows(data, ignore_columns=[1]))
 
     def _image_names(self):
diff -Nru sen-0.5.1/sen/tui/widgets/list/base.py sen-0.6.0/sen/tui/widgets/list/base.py
--- sen-0.5.1/sen/tui/widgets/list/base.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/sen/tui/widgets/list/base.py	2018-03-22 08:34:32.000000000 +0100
@@ -10,7 +10,7 @@
 
 class WidgetBase(urwid.ListBox):
     """
-    common class fot widgets
+    common class for widgets
     """
 
     def __init__(self, ui, *args, **kwargs):
diff -Nru sen-0.5.1/sen/tui/widgets/table.py sen-0.6.0/sen/tui/widgets/table.py
--- sen-0.5.1/sen/tui/widgets/table.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/sen/tui/widgets/table.py	2018-03-22 08:34:32.000000000 +0100
@@ -32,7 +32,7 @@
     return max_cols_lengths
 
 
-def assemble_rows(data, headers=None, max_allowed_lengths=None, dividechars=1,
+def assemble_rows(data, max_allowed_lengths=None, dividechars=1,
                   ignore_columns=None):
     """
     :param data: list of lists:
@@ -40,7 +40,6 @@
      ["row 2 column 1", "row 2 column 2"]]
     each item consists of instance of urwid.Text
 
-    :param headers: list of str, headers of table
     :param max_allowed_lengths: dict:
         {col_index: maximum_allowed_length}
     :param ignore_columns: list of ints, indexes which should not be calculated
@@ -67,15 +66,6 @@
             max_lengths[col_index] = max(l, max_lengths[col_index])
             col_index += 1
 
-    if headers:
-        header_widgets = []
-        for header in headers:
-            try:
-                header_widgets.append(urwid.Text(*header[0], **header[1]))
-            except IndexError:
-                header_widgets.append(urwid.Text(*header[0]))
-        rows.append(urwid.Columns(header_widgets, dividechars=1))
-
     for row in data:
         row_widgets = []
         for idx, item in enumerate(row):
diff -Nru sen-0.5.1/sen/util.py sen-0.6.0/sen/util.py
--- sen-0.5.1/sen/util.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/sen/util.py	2018-03-22 08:34:32.000000000 +0100
@@ -40,6 +40,10 @@
 
 
 def setup_dirs():
+    """Make required directories to hold logfile.
+
+    :returns: str
+    """
     try:
         top_dir = os.path.abspath(os.path.expanduser(os.environ["XDG_CACHE_HOME"]))
     except KeyError:
@@ -166,6 +170,23 @@
         cpu_percent = cpu_delta / system_delta * 100.0 * cpu_count
     return cpu_percent
 
+# again taken directly from docker:
+#   https://github.com/docker/cli/blob/2bfac7fcdafeafbd2f450abb6d1bb3106e4f3ccb/cli/command/container/stats_helpers.go#L168
+# precpu_stats in 1.13+ is completely broken, doesn't contain any values
+def calculate_cpu_percent2(d, previous_cpu, previous_system):
+    # import json
+    # du = json.dumps(d, indent=2)
+    # logger.debug("XXX: %s", du)
+    cpu_percent = 0.0
+    cpu_total = float(d["cpu_stats"]["cpu_usage"]["total_usage"])
+    cpu_delta = cpu_total - previous_cpu
+    cpu_system = float(d["cpu_stats"]["system_cpu_usage"])
+    system_delta = cpu_system - previous_system
+    online_cpus = d["cpu_stats"].get("online_cpus", len(d["cpu_stats"]["cpu_usage"]["percpu_usage"]))
+    if system_delta > 0.0:
+        cpu_percent = (cpu_delta / system_delta) * online_cpus * 100.0
+    return cpu_percent, cpu_system, cpu_total
+
 
 def calculate_blkio_bytes(d):
     """
@@ -209,7 +230,7 @@
     for a in args:
         try:
             t = t[a]
-        except (KeyError, ValueError, TypeError) as ex:
+        except (KeyError, ValueError, TypeError, AttributeError):
             logger.debug("can't get %r from %s", a, t)
             return default
     return t
diff -Nru sen-0.5.1/sen.egg-info/dependency_links.txt sen-0.6.0/sen.egg-info/dependency_links.txt
--- sen-0.5.1/sen.egg-info/dependency_links.txt	2017-03-18 22:50:27.000000000 +0100
+++ sen-0.6.0/sen.egg-info/dependency_links.txt	2018-03-22 08:35:11.000000000 +0100
@@ -1 +1 @@
-
+git+https://github.com/pazz/urwidtrees.git@9142c59d3e41421ff6230708d08b6a134e0a8eed#egg=urwidtrees-1.0.3.dev
diff -Nru sen-0.5.1/sen.egg-info/PKG-INFO sen-0.6.0/sen.egg-info/PKG-INFO
--- sen-0.5.1/sen.egg-info/PKG-INFO	2017-03-18 22:50:27.000000000 +0100
+++ sen-0.6.0/sen.egg-info/PKG-INFO	2018-03-22 08:35:11.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: sen
-Version: 0.5.1
+Version: 0.6.0
 Summary: Terminal User Interface for Docker Engine
 Home-page: https://github.com/TomasTomecek/sen/
 Author: Tomas Tomecek
diff -Nru sen-0.5.1/sen.egg-info/requires.txt sen-0.6.0/sen.egg-info/requires.txt
--- sen-0.5.1/sen.egg-info/requires.txt	2017-03-18 22:50:27.000000000 +0100
+++ sen-0.6.0/sen.egg-info/requires.txt	2018-03-22 08:35:11.000000000 +0100
@@ -1,3 +1,3 @@
 urwid
-urwidtrees
 docker
+urwidtrees
diff -Nru sen-0.5.1/sen.egg-info/SOURCES.txt sen-0.6.0/sen.egg-info/SOURCES.txt
--- sen-0.5.1/sen.egg-info/SOURCES.txt	2017-03-18 22:50:27.000000000 +0100
+++ sen-0.6.0/sen.egg-info/SOURCES.txt	2018-03-22 08:35:11.000000000 +0100
@@ -39,6 +39,7 @@
 sen/tui/views/__init__.py
 sen/tui/views/base.py
 sen/tui/views/container_info.py
+sen/tui/views/disk_usage.py
 sen/tui/views/help.py
 sen/tui/views/image_info.py
 sen/tui/views/main.py
@@ -62,15 +63,4 @@
 tests/test_net.py
 tests/test_util.py
 tests/test_widgets.py
-tests/utils.py
-tests/__pycache__/__init__.cpython-33.pyc
-tests/__pycache__/constants.cpython-33.pyc
-tests/__pycache__/real.cpython-33.pyc
-tests/__pycache__/test_commands.cpython-33-PYTEST.pyc
-tests/__pycache__/test_concurrency.cpython-33-PYTEST.pyc
-tests/__pycache__/test_container_info.cpython-33-PYTEST.pyc
-tests/__pycache__/test_docker_backend.cpython-33-PYTEST.pyc
-tests/__pycache__/test_net.cpython-33-PYTEST.pyc
-tests/__pycache__/test_util.cpython-33-PYTEST.pyc
-tests/__pycache__/test_widgets.cpython-33-PYTEST.pyc
-tests/__pycache__/utils.cpython-33.pyc
\ No newline at end of file
+tests/utils.py
\ No newline at end of file
diff -Nru sen-0.5.1/setup.cfg sen-0.6.0/setup.cfg
--- sen-0.5.1/setup.cfg	2017-03-18 22:50:27.000000000 +0100
+++ sen-0.6.0/setup.cfg	2018-03-22 08:35:11.000000000 +0100
@@ -2,7 +2,6 @@
 description-file = README.md
 
 [egg_info]
-tag_svn_revision = 0
-tag_date = 0
 tag_build = 
+tag_date = 0
 
diff -Nru sen-0.5.1/setup.py sen-0.6.0/setup.py
--- sen-0.5.1/setup.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/setup.py	2018-03-22 08:34:32.000000000 +0100
@@ -4,15 +4,20 @@
 
 from setuptools import setup, find_packages
 
+# vcs+proto://host/path@revision#egg=project-version
+# this is latest upstream commit (April 2017)
+# upstream maintainer of urwidtrees doesn't maintain PyPI urwidtrees
+urwidtrees_source = "git+https://github.com/pazz/urwidtrees.git@9142c59d3e41421ff6230708d08b6a134e0a8eed#egg=urwidtrees-1.0.3.dev";
+
 requirements = [
     "urwid",
-    "urwidtrees",
-    "docker"
+    "docker",
+    "urwidtrees"
 ]
 
 setup(
     name='sen',
-    version='0.5.1',
+    version='0.6.0',
     description="Terminal User Interface for Docker Engine",
     author='Tomas Tomecek',
     author_email='to...@tomecek.net',
@@ -23,6 +28,7 @@
     },
     packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]),
     install_requires=requirements,
+    dependency_links=[urwidtrees_source],
     classifiers=['Development Status :: 5 - Production/Stable',
                  'Intended Audience :: Developers',
                  'Intended Audience :: System Administrators',
Binary files /tmp/IN7Zw8Skz3/sen-0.5.1/tests/__pycache__/constants.cpython-33.pyc and /tmp/o_7aodhgqh/sen-0.6.0/tests/__pycache__/constants.cpython-33.pyc differ
Binary files /tmp/IN7Zw8Skz3/sen-0.5.1/tests/__pycache__/__init__.cpython-33.pyc and /tmp/o_7aodhgqh/sen-0.6.0/tests/__pycache__/__init__.cpython-33.pyc differ
Binary files /tmp/IN7Zw8Skz3/sen-0.5.1/tests/__pycache__/real.cpython-33.pyc and /tmp/o_7aodhgqh/sen-0.6.0/tests/__pycache__/real.cpython-33.pyc differ
Binary files /tmp/IN7Zw8Skz3/sen-0.5.1/tests/__pycache__/test_commands.cpython-33-PYTEST.pyc and /tmp/o_7aodhgqh/sen-0.6.0/tests/__pycache__/test_commands.cpython-33-PYTEST.pyc differ
Binary files /tmp/IN7Zw8Skz3/sen-0.5.1/tests/__pycache__/test_concurrency.cpython-33-PYTEST.pyc and /tmp/o_7aodhgqh/sen-0.6.0/tests/__pycache__/test_concurrency.cpython-33-PYTEST.pyc differ
Binary files /tmp/IN7Zw8Skz3/sen-0.5.1/tests/__pycache__/test_container_info.cpython-33-PYTEST.pyc and /tmp/o_7aodhgqh/sen-0.6.0/tests/__pycache__/test_container_info.cpython-33-PYTEST.pyc differ
Binary files /tmp/IN7Zw8Skz3/sen-0.5.1/tests/__pycache__/test_docker_backend.cpython-33-PYTEST.pyc and /tmp/o_7aodhgqh/sen-0.6.0/tests/__pycache__/test_docker_backend.cpython-33-PYTEST.pyc differ
Binary files /tmp/IN7Zw8Skz3/sen-0.5.1/tests/__pycache__/test_net.cpython-33-PYTEST.pyc and /tmp/o_7aodhgqh/sen-0.6.0/tests/__pycache__/test_net.cpython-33-PYTEST.pyc differ
Binary files /tmp/IN7Zw8Skz3/sen-0.5.1/tests/__pycache__/test_util.cpython-33-PYTEST.pyc and /tmp/o_7aodhgqh/sen-0.6.0/tests/__pycache__/test_util.cpython-33-PYTEST.pyc differ
Binary files /tmp/IN7Zw8Skz3/sen-0.5.1/tests/__pycache__/test_widgets.cpython-33-PYTEST.pyc and /tmp/o_7aodhgqh/sen-0.6.0/tests/__pycache__/test_widgets.cpython-33-PYTEST.pyc differ
Binary files /tmp/IN7Zw8Skz3/sen-0.5.1/tests/__pycache__/utils.cpython-33.pyc and /tmp/o_7aodhgqh/sen-0.6.0/tests/__pycache__/utils.cpython-33.pyc differ
diff -Nru sen-0.5.1/tests/real.py sen-0.6.0/tests/real.py
--- sen-0.5.1/tests/real.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/tests/real.py	2018-03-22 08:34:32.000000000 +0100
@@ -383,6 +383,928 @@
   }
 }
 
+# stats 1.13
+stats_1_13 = [
+{
+  "read": "2017-08-14T09:51:41.318110362Z",
+  "preread": "2017-08-14T09:51:40.318034402Z",
+  "pids_stats": {
+    "current": 1
+  },
+  "blkio_stats": {
+    "io_service_bytes_recursive": [
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Read",
+        "value": 36864
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Write",
+        "value": 12288
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Sync",
+        "value": 49152
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Total",
+        "value": 49152
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Read",
+        "value": 36864
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Write",
+        "value": 12288
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Sync",
+        "value": 49152
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Total",
+        "value": 49152
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Read",
+        "value": 46022656
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Write",
+        "value": 155648
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Sync",
+        "value": 46178304
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Total",
+        "value": 46178304
+      }
+    ],
+    "io_serviced_recursive": [
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Read",
+        "value": 6
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Write",
+        "value": 3
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Sync",
+        "value": 9
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Total",
+        "value": 9
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Read",
+        "value": 6
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Write",
+        "value": 3
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Sync",
+        "value": 9
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Total",
+        "value": 9
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Read",
+        "value": 1074
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Write",
+        "value": 17
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Sync",
+        "value": 1091
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Total",
+        "value": 1091
+      }
+    ],
+    "io_queue_recursive": [],
+    "io_service_time_recursive": [],
+    "io_wait_time_recursive": [],
+    "io_merged_recursive": [],
+    "io_time_recursive": [],
+    "sectors_recursive": []
+  },
+  "num_procs": 0,
+  "storage_stats": {},
+  "cpu_stats": {
+    "cpu_usage": {
+      "total_usage": 4325370082,
+      "percpu_usage": [
+        1583545267,
+        1441574399,
+        932669369,
+        367581047,
+        0,
+        0,
+        0,
+        0
+      ],
+      "usage_in_kernelmode": 490000000,
+      "usage_in_usermode": 3800000000
+    },
+    "system_cpu_usage": 111726360000000,
+    "throttling_data": {
+      "periods": 0,
+      "throttled_periods": 0,
+      "throttled_time": 0
+    }
+  },
+  "precpu_stats": {
+    "cpu_usage": {
+      "total_usage": 4172062391,
+      "percpu_usage": [
+        1545098206,
+        1408480094,
+        870204053,
+        348280038,
+        0,
+        0,
+        0,
+        0
+      ],
+      "usage_in_kernelmode": 430000000,
+      "usage_in_usermode": 3710000000
+    },
+    "system_cpu_usage": 111722350000000,
+    "throttling_data": {
+      "periods": 0,
+      "throttled_periods": 0,
+      "throttled_time": 0
+    }
+  },
+  "memory_stats": {
+    "usage": 179355648,
+    "max_usage": 179404800,
+    "stats": {
+      "active_anon": 53280768,
+      "active_file": 11571200,
+      "cache": 119050240,
+      "dirty": 51224576,
+      "hierarchical_memory_limit": 9223372036854771712,
+      "hierarchical_memsw_limit": 9223372036854771712,
+      "inactive_anon": 0,
+      "inactive_file": 107479040,
+      "mapped_file": 21954560,
+      "pgfault": 37991,
+      "pgmajfault": 259,
+      "pgpgin": 64385,
+      "pgpgout": 22312,
+      "recent_rotated_anon": 35307,
+      "recent_rotated_file": 2829,
+      "recent_scanned_anon": 35307,
+      "recent_scanned_file": 31903,
+      "rss": 53280768,
+      "rss_huge": 0,
+      "swap": 0,
+      "total_active_anon": 53280768,
+      "total_active_file": 11571200,
+      "total_cache": 119050240,
+      "total_dirty": 51224576,
+      "total_inactive_anon": 0,
+      "total_inactive_file": 107479040,
+      "total_mapped_file": 21954560,
+      "total_pgfault": 37991,
+      "total_pgmajfault": 259,
+      "total_pgpgin": 64385,
+      "total_pgpgout": 22312,
+      "total_rss": 53280768,
+      "total_rss_huge": 0,
+      "total_swap": 0,
+      "total_unevictable": 0,
+      "total_writeback": 0,
+      "unevictable": 0,
+      "writeback": 0
+    },
+    "limit": 12283822080
+  },
+  "name": "/hungry_brown",
+  "id": "98a9cd0009984e61a24499aec44bd244078a6a09486782015b3966916eee5c08",
+  "networks": {
+    "eth0": {
+      "rx_bytes": 63518221,
+      "rx_packets": 46327,
+      "rx_errors": 0,
+      "rx_dropped": 0,
+      "tx_bytes": 1601338,
+      "tx_packets": 23966,
+      "tx_errors": 0,
+      "tx_dropped": 0
+    }
+  }
+},
+{
+  "read": "2017-08-14T09:51:42.318182309Z",
+  "preread": "2017-08-14T09:51:41.318110362Z",
+  "pids_stats": {
+    "current": 1
+  },
+  "blkio_stats": {
+    "io_service_bytes_recursive": [
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Read",
+        "value": 36864
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Write",
+        "value": 12288
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Sync",
+        "value": 49152
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Total",
+        "value": 49152
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Read",
+        "value": 36864
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Write",
+        "value": 12288
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Sync",
+        "value": 49152
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Total",
+        "value": 49152
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Read",
+        "value": 46039040
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Write",
+        "value": 155648
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Sync",
+        "value": 46194688
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Total",
+        "value": 46194688
+      }
+    ],
+    "io_serviced_recursive": [
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Read",
+        "value": 6
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Write",
+        "value": 3
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Sync",
+        "value": 9
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Total",
+        "value": 9
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Read",
+        "value": 6
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Write",
+        "value": 3
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Sync",
+        "value": 9
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Total",
+        "value": 9
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Read",
+        "value": 1075
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Write",
+        "value": 17
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Sync",
+        "value": 1092
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Total",
+        "value": 1092
+      }
+    ],
+    "io_queue_recursive": [],
+    "io_service_time_recursive": [],
+    "io_wait_time_recursive": [],
+    "io_merged_recursive": [],
+    "io_time_recursive": [],
+    "sectors_recursive": []
+  },
+  "num_procs": 0,
+  "storage_stats": {},
+  "cpu_stats": {
+    "cpu_usage": {
+      "total_usage": 4733027699,
+      "percpu_usage": [
+        1598666891,
+        1746779810,
+        999006836,
+        388574162,
+        0,
+        0,
+        0,
+        0
+      ],
+      "usage_in_kernelmode": 550000000,
+      "usage_in_usermode": 4140000000
+    },
+    "system_cpu_usage": 111730350000000,
+    "throttling_data": {
+      "periods": 0,
+      "throttled_periods": 0,
+      "throttled_time": 0
+    }
+  },
+  "precpu_stats": {
+    "cpu_usage": {
+      "total_usage": 4325370082,
+      "percpu_usage": [
+        1583545267,
+        1441574399,
+        932669369,
+        367581047,
+        0,
+        0,
+        0,
+        0
+      ],
+      "usage_in_kernelmode": 490000000,
+      "usage_in_usermode": 3800000000
+    },
+    "system_cpu_usage": 111726360000000,
+    "throttling_data": {
+      "periods": 0,
+      "throttled_periods": 0,
+      "throttled_time": 0
+    }
+  },
+  "memory_stats": {
+    "usage": 186646528,
+    "max_usage": 186646528,
+    "stats": {
+      "active_anon": 53792768,
+      "active_file": 12742656,
+      "cache": 125739008,
+      "dirty": 57917440,
+      "hierarchical_memory_limit": 9223372036854771712,
+      "hierarchical_memsw_limit": 9223372036854771712,
+      "inactive_anon": 0,
+      "inactive_file": 112996352,
+      "mapped_file": 21954560,
+      "pgfault": 38262,
+      "pgmajfault": 259,
+      "pgpgin": 66151,
+      "pgpgout": 22318,
+      "recent_rotated_anon": 35432,
+      "recent_rotated_file": 3121,
+      "recent_scanned_anon": 35432,
+      "recent_scanned_file": 33834,
+      "rss": 53800960,
+      "rss_huge": 0,
+      "swap": 0,
+      "total_active_anon": 53792768,
+      "total_active_file": 12742656,
+      "total_cache": 125739008,
+      "total_dirty": 57917440,
+      "total_inactive_anon": 0,
+      "total_inactive_file": 112996352,
+      "total_mapped_file": 21954560,
+      "total_pgfault": 38262,
+      "total_pgmajfault": 259,
+      "total_pgpgin": 66151,
+      "total_pgpgout": 22318,
+      "total_rss": 53800960,
+      "total_rss_huge": 0,
+      "total_swap": 0,
+      "total_unevictable": 0,
+      "total_writeback": 0,
+      "unevictable": 0,
+      "writeback": 0
+    },
+    "limit": 12283822080
+  },
+  "name": "/hungry_brown",
+  "id": "98a9cd0009984e61a24499aec44bd244078a6a09486782015b3966916eee5c08",
+  "networks": {
+    "eth0": {
+      "rx_bytes": 70507552,
+      "rx_packets": 51415,
+      "rx_errors": 0,
+      "rx_dropped": 0,
+      "tx_bytes": 1766536,
+      "tx_packets": 26469,
+      "tx_errors": 0,
+      "tx_dropped": 0
+    }
+  }
+},
+{
+  "read": "2017-08-14T09:51:43.318289249Z",
+  "preread": "2017-08-14T09:51:42.318182309Z",
+  "pids_stats": {
+    "current": 1
+  },
+  "blkio_stats": {
+    "io_service_bytes_recursive": [
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Read",
+        "value": 36864
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Write",
+        "value": 12288
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Sync",
+        "value": 49152
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Total",
+        "value": 49152
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Read",
+        "value": 36864
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Write",
+        "value": 12288
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Sync",
+        "value": 49152
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Total",
+        "value": 49152
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Read",
+        "value": 46039040
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Write",
+        "value": 155648
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Sync",
+        "value": 46194688
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Total",
+        "value": 46194688
+      }
+    ],
+    "io_serviced_recursive": [
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Read",
+        "value": 6
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Write",
+        "value": 3
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Sync",
+        "value": 9
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 7,
+        "minor": 0,
+        "op": "Total",
+        "value": 9
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Read",
+        "value": 6
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Write",
+        "value": 3
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Sync",
+        "value": 9
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 253,
+        "minor": 1,
+        "op": "Total",
+        "value": 9
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Read",
+        "value": 1075
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Write",
+        "value": 17
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Sync",
+        "value": 1092
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Async",
+        "value": 0
+      },
+      {
+        "major": 253,
+        "minor": 3,
+        "op": "Total",
+        "value": 1092
+      }
+    ],
+    "io_queue_recursive": [],
+    "io_service_time_recursive": [],
+    "io_wait_time_recursive": [],
+    "io_merged_recursive": [],
+    "io_time_recursive": [],
+    "sectors_recursive": []
+  },
+  "num_procs": 0,
+  "storage_stats": {},
+  "cpu_stats": {
+    "cpu_usage": {
+      "total_usage": 5723669129,
+      "percpu_usage": [
+        1598666891,
+        2737421240,
+        999006836,
+        388574162,
+        0,
+        0,
+        0,
+        0
+      ],
+      "usage_in_kernelmode": 570000000,
+      "usage_in_usermode": 5120000000
+    },
+    "system_cpu_usage": 111734320000000,
+    "throttling_data": {
+      "periods": 0,
+      "throttled_periods": 0,
+      "throttled_time": 0
+    }
+  },
+  "precpu_stats": {
+    "cpu_usage": {
+      "total_usage": 4733027699,
+      "percpu_usage": [
+        1598666891,
+        1746779810,
+        999006836,
+        388574162,
+        0,
+        0,
+        0,
+        0
+      ],
+      "usage_in_kernelmode": 550000000,
+      "usage_in_usermode": 4140000000
+    },
+    "system_cpu_usage": 111730350000000,
+    "throttling_data": {
+      "periods": 0,
+      "throttled_periods": 0,
+      "throttled_time": 0
+    }
+  },
+  "memory_stats": {
+    "usage": 218624000,
+    "max_usage": 218624000,
+    "stats": {
+      "active_anon": 85700608,
+      "active_file": 18706432,
+      "cache": 125739008,
+      "dirty": 57917440,
+      "hierarchical_memory_limit": 9223372036854771712,
+      "hierarchical_memsw_limit": 9223372036854771712,
+      "inactive_anon": 0,
+      "inactive_file": 107032576,
+      "mapped_file": 21954560,
+      "pgfault": 46060,
+      "pgmajfault": 259,
+      "pgpgin": 73949,
+      "pgpgout": 22318,
+      "recent_rotated_anon": 43222,
+      "recent_rotated_file": 4577,
+      "recent_scanned_anon": 43222,
+      "recent_scanned_file": 35290,
+      "rss": 85741568,
+      "rss_huge": 0,
+      "swap": 0,
+      "total_active_anon": 85700608,
+      "total_active_file": 18706432,
+      "total_cache": 125739008,
+      "total_dirty": 57917440,
+      "total_inactive_anon": 0,
+      "total_inactive_file": 107032576,
+      "total_mapped_file": 21954560,
+      "total_pgfault": 46060,
+      "total_pgmajfault": 259,
+      "total_pgpgin": 73949,
+      "total_pgpgout": 22318,
+      "total_rss": 85741568,
+      "total_rss_huge": 0,
+      "total_swap": 0,
+      "total_unevictable": 0,
+      "total_writeback": 0,
+      "unevictable": 0,
+      "writeback": 0
+    },
+    "limit": 12283822080
+  },
+  "name": "/hungry_brown",
+  "id": "98a9cd0009984e61a24499aec44bd244078a6a09486782015b3966916eee5c08",
+  "networks": {
+    "eth0": {
+      "rx_bytes": 70507552,
+      "rx_packets": 51415,
+      "rx_errors": 0,
+      "rx_dropped": 0,
+      "tx_bytes": 1766606,
+      "tx_packets": 26470,
+      "tx_errors": 0,
+      "tx_dropped": 0
+    }
+  }
+}
+]
+
 # docker 1.11; docker inspect fedora
 inspect_image_data = [
     {
@@ -477,6 +1399,10 @@
     global stats_data
     return iter([stats_data])
 
+def stats_response_1_13(*args, **kwargs):
+    global stats_1_13
+    return iter(stats_1_13)
+
 
 def mock():
     try:
@@ -488,5 +1414,5 @@
     flexmock(client_class, containers=containers_response)
     flexmock(client_class, version=lambda *args, **kwargs: version_data)
     flexmock(client_class, top=lambda *args, **kwargs: top_data)
-    flexmock(client_class, stats=stats_response)
+    flexmock(client_class, stats=stats_response_1_13)
     flexmock(client_class, inspect_image=lambda *args, **kwargs: inspect_image_data)
diff -Nru sen-0.5.1/tests/test_docker_backend.py sen-0.6.0/tests/test_docker_backend.py
--- sen-0.5.1/tests/test_docker_backend.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/tests/test_docker_backend.py	2018-03-22 08:34:32.000000000 +0100
@@ -1,4 +1,5 @@
 from sen.docker_backend import DockerBackend
+from sen.util import calculate_cpu_percent2, calculate_cpu_percent
 from .real import image_data, mock, container_data
 
 
@@ -51,3 +52,12 @@
     operation = c0.stats()
     stats_stream = operation.response
     assert next(stats_stream)
+
+    for x in c0.d.stats('x', decode=True, stream=True):
+        calculate_cpu_percent(x)
+
+    t = 0.0
+    s = 0.0
+    for x in c0.d.stats('x', decode=True, stream=True):
+        calculate_cpu_percent(x)
+        _, s, t = calculate_cpu_percent2(x, t, s)
diff -Nru sen-0.5.1/tests/test_util.py sen-0.6.0/tests/test_util.py
--- sen-0.5.1/tests/test_util.py	2017-03-18 22:49:56.000000000 +0100
+++ sen-0.6.0/tests/test_util.py	2018-03-22 08:34:32.000000000 +0100
@@ -1,10 +1,9 @@
 # -*- coding: utf-8 -*-
+import logging
 import time
 from concurrent.futures.thread import ThreadPoolExecutor
 from datetime import datetime, timedelta
 
-from flexmock import flexmock
-
 from sen.tui.widgets.list.common import strip_from_ansi_esc_sequences
 from sen.util import _ensure_unicode, log_traceback, repeater, humanize_time, \
     OrderedSet
@@ -26,34 +25,37 @@
     @log_traceback
     def f():
         raise Exception()
+    caplog.set_level(logging.DEBUG)
     f()
-    assert caplog.records()[0].message.endswith(" is about to be started")
-    assert caplog.records()[1].message.startswith("Traceback")
-    assert caplog.records()[1].message.endswith("Exception\n")
+    assert caplog.records[0].message.endswith(" is about to be started")
+    assert caplog.records[1].message.startswith("Traceback")
+    assert caplog.records[1].message.endswith("Exception\n")
 
 
 def test_log_traceback_without_tb(caplog):
     @log_traceback
     def f():
         pass
+    caplog.set_level(logging.DEBUG)
     f()
-    assert caplog.records()[0].message.endswith(" is about to be started")
-    assert caplog.records()[1].message.endswith(" finished")
+    assert caplog.records[0].message.endswith(" is about to be started")
+    assert caplog.records[1].message.endswith(" finished")
 
 
 def test_log_traceback_threaded(caplog):
     @log_traceback
     def f():
         raise Exception()
+    caplog.set_level(logging.DEBUG)
 
     e = ThreadPoolExecutor(max_workers=1)
     f = e.submit(f)
     while f.running():
         time.sleep(0.1)
 
-    assert caplog.records()[0].message.endswith(" is about to be started")
-    assert caplog.records()[1].message.startswith("Traceback")
-    assert caplog.records()[1].message.endswith("Exception\n")
+    assert caplog.records[0].message.endswith(" is about to be started")
+    assert caplog.records[1].message.startswith("Traceback")
+    assert caplog.records[1].message.endswith("Exception\n")
 
 
 # def test_log_vars_from_tback(caplog):

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to