From c8e24f09048abeb136fcc6cb9c6f65069b0970df Mon Sep 17 00:00:00 2001
From: Brian Warner <warner@allmydata.com>
Date: Mon, 3 Mar 2008 14:48:52 -0700
Subject: [PATCH] webish: make upload timings visible on the recent
 uploads/downloads status page

---
 src/allmydata/client.py                       |  11 ++
 src/allmydata/download.py                     |   3 +
 src/allmydata/interfaces.py                   |  22 ++--
 src/allmydata/test/test_web.py                |  18 +--
 src/allmydata/upload.py                       |  12 ++
 ...nked-upload.xhtml => upload-results.xhtml} |   0
 src/allmydata/web/upload-status.xhtml         |  36 ++++++
 src/allmydata/webish.py                       | 116 ++++++++++--------
 8 files changed, 153 insertions(+), 65 deletions(-)
 rename src/allmydata/web/{unlinked-upload.xhtml => upload-results.xhtml} (100%)

diff --git a/src/allmydata/client.py b/src/allmydata/client.py
index c8c1b183..c20d422b 100644
--- a/src/allmydata/client.py
+++ b/src/allmydata/client.py
@@ -267,6 +267,7 @@ class Client(node.Node, testutil.PollMixin):
         uploader = self.getServiceNamed("uploader")
         return uploader.upload(uploadable)
 
+
     def list_all_uploads(self):
         uploader = self.getServiceNamed("uploader")
         return uploader.list_all_uploads()
@@ -275,6 +276,16 @@ class Client(node.Node, testutil.PollMixin):
         downloader = self.getServiceNamed("downloader")
         return downloader.list_all_downloads()
 
+
+    def list_active_uploads(self):
+        uploader = self.getServiceNamed("uploader")
+        return uploader.list_active_uploads()
+
+    def list_active_downloads(self):
+        downloader = self.getServiceNamed("downloader")
+        return downloader.list_active_downloads()
+
+
     def list_recent_uploads(self):
         uploader = self.getServiceNamed("uploader")
         return uploader.list_recent_uploads()
diff --git a/src/allmydata/download.py b/src/allmydata/download.py
index 83bebb37..907f9387 100644
--- a/src/allmydata/download.py
+++ b/src/allmydata/download.py
@@ -949,5 +949,8 @@ class Downloader(service.MultiService):
 
     def list_all_downloads(self):
         return self._all_downloads.keys()
+    def list_active_downloads(self):
+        return [d.get_download_status() for d in self._all_downloads.keys()
+                if d.get_download_status().get_active()]
     def list_recent_downloads(self):
         return self._recent_download_status
diff --git a/src/allmydata/interfaces.py b/src/allmydata/interfaces.py
index 35bf0a34..2d7f71ed 100644
--- a/src/allmydata/interfaces.py
+++ b/src/allmydata/interfaces.py
@@ -1389,19 +1389,21 @@ class IClient(Interface):
 
 class IClientStatus(Interface):
     def list_all_uploads():
-        """Return a list of IUploadStatus objects, one for each upload which
-        currently has an object available. This uses weakrefs to track the
-        objects, so it may report uploads which have already finished. Use
-        get_active() to filter these out."""
+        """Return a list of uploader objects, one for each upload which
+        currently has an object available (tracked with weakrefs). This is
+        intended for debugging purposes."""
+    def list_active_uploads():
+        """Return a list of active IUploadStatus objects."""
     def list_recent_uploads():
         """Return a list of IUploadStatus objects for the most recently
         started uploads."""
 
     def list_all_downloads():
-        """Return a list of IDownloadStatus objects, one for each download
-        which currently has an object available. This uses weakrefs to track
-        the objects, so it may report downloadswhich have already finished.
-        Use get_active() to filter these out."""
+        """Return a list of downloader objects, one for each download which
+        currently has an object available (tracked with weakrefs). This is
+        intended for debugging purposes."""
+    def list_active_downloads():
+        """Return a list of active IDownloadStatus objects."""
     def list_recent_downloads():
         """Return a list of IDownloadStatus objects for the most recently
         started downloads."""
@@ -1433,6 +1435,10 @@ class IUploadStatus(Interface):
         three numbers and report the sum to the user."""
     def get_active():
         """Return True if the upload is currently active, False if not."""
+    def get_results():
+        """Return an instance of UploadResults (which contains timing and
+        sharemap information). Might return None if the upload is not yet
+        finished."""
     def get_counter():
         """Each upload status gets a unique number: this method returns that
         number. This provides a handle to this particular upload, so a web
diff --git a/src/allmydata/test/test_web.py b/src/allmydata/test/test_web.py
index 7f686d2b..9678ae52 100644
--- a/src/allmydata/test/test_web.py
+++ b/src/allmydata/test/test_web.py
@@ -32,8 +32,8 @@ class FakeClient(service.MultiService):
                 }
     introducer_furl = "None"
     introducer_client = FakeIntroducerClient()
-    _all_uploads = [upload.UploadStatus()]
-    _all_downloads = [download.DownloadStatus()]
+    _all_upload_status = [upload.UploadStatus()]
+    _all_download_status = [download.DownloadStatus()]
 
     def connected_to_introducer(self):
         return False
@@ -71,14 +71,18 @@ class FakeClient(service.MultiService):
         return d
 
     def list_all_uploads(self):
-        return self._all_uploads
+        return []
     def list_all_downloads(self):
-        return self._all_downloads
+        return []
 
+    def list_active_uploads(self):
+        return self._all_upload_status
+    def list_active_downloads(self):
+        return self._all_download_status
     def list_recent_uploads(self):
-        return self._all_uploads
+        return self._all_upload_status
     def list_recent_downloads(self):
-        return self._all_downloads
+        return self._all_download_status
 
 
 class WebMixin(object):
@@ -1331,7 +1335,7 @@ class Web(WebMixin, unittest.TestCase):
                                                   "ctime": 1002777696.7564139,
                                                   "mtime": 1002777696.7564139
                                                  }
-                                               } ], 
+                                               } ],
                      "atomic_added_2": [ "filenode", { "rw_uri": "%s",
                                                 "size": 1,
                                                 "metadata": {
diff --git a/src/allmydata/upload.py b/src/allmydata/upload.py
index 04cc9317..c3dd29df 100644
--- a/src/allmydata/upload.py
+++ b/src/allmydata/upload.py
@@ -573,6 +573,7 @@ class UploadStatus:
         self.status = "Not started"
         self.progress = [0.0, 0.0, 0.0]
         self.active = True
+        self.results = None
         self.counter = self.statusid_counter.next()
 
     def get_storage_index(self):
@@ -587,6 +588,8 @@ class UploadStatus:
         return tuple(self.progress)
     def get_active(self):
         return self.active
+    def get_results(self):
+        return self.results
     def get_counter(self):
         return self.counter
 
@@ -603,6 +606,8 @@ class UploadStatus:
         self.progress[which] = value
     def set_active(self, value):
         self.active = value
+    def set_results(self, value):
+        self.results = value
 
 class CHKUploader:
     peer_selector_class = Tahoe2PeerSelector
@@ -616,6 +621,7 @@ class CHKUploader:
         self._upload_status = UploadStatus()
         self._upload_status.set_helper(False)
         self._upload_status.set_active(True)
+        self._upload_status.set_results(self._results)
 
     def log(self, *args, **kwargs):
         if "parent" not in kwargs:
@@ -776,6 +782,7 @@ class LiteralUploader:
         s.set_helper(False)
         s.set_progress(0, 1.0)
         s.set_active(False)
+        s.set_results(self._results)
 
     def start(self, uploadable):
         uploadable = IUploadable(uploadable)
@@ -971,6 +978,7 @@ class AssistedUploader:
             return d
         self.log("helper says file is already uploaded")
         self._upload_status.set_progress(1, 1.0)
+        self._upload_status.set_results(upload_results)
         return upload_results
 
     def _build_readcap(self, upload_results):
@@ -996,6 +1004,7 @@ class AssistedUploader:
             r.timings["helper_total"] = r.timings["total"]
         r.timings["total"] = now - self._started
         self._upload_status.set_status("Done")
+        self._upload_status.set_results(r)
         return r
 
     def get_upload_status(self):
@@ -1200,5 +1209,8 @@ class Uploader(service.MultiService):
 
     def list_all_uploads(self):
         return self._all_uploads.keys()
+    def list_active_uploads(self):
+        return [u.get_upload_status() for u in self._all_uploads.keys()
+                if u.get_upload_status().get_active()]
     def list_recent_uploads(self):
         return self._recent_upload_status
diff --git a/src/allmydata/web/unlinked-upload.xhtml b/src/allmydata/web/upload-results.xhtml
similarity index 100%
rename from src/allmydata/web/unlinked-upload.xhtml
rename to src/allmydata/web/upload-results.xhtml
diff --git a/src/allmydata/web/upload-status.xhtml b/src/allmydata/web/upload-status.xhtml
index 10a6e08a..1d8c7e0f 100644
--- a/src/allmydata/web/upload-status.xhtml
+++ b/src/allmydata/web/upload-status.xhtml
@@ -20,6 +20,42 @@
   <li>Status: <span n:render="status"/></li>
 </ul>
 
+<div n:render="results">
+  <h2>Upload Results</h2>
+  <ul>
+    <li>Sharemap: <span n:render="sharemap" /></li>
+    <li>Servermap: <span n:render="servermap" /></li>
+    <li>Timings:</li>
+    <ul>
+      <li>File Size: <span n:render="string" n:data="file_size" /> bytes</li>
+      <li>Total: <span n:render="time" n:data="time_total" />
+      (<span n:render="rate" n:data="rate_total" />)</li>
+      <ul>
+        <li>Storage Index: <span n:render="time" n:data="time_storage_index" />
+        (<span n:render="rate" n:data="rate_storage_index" />)</li>
+        <li>[Contacting Helper]: <span n:render="time" n:data="time_contacting_helper" /></li>
+        <ul>
+          <li>[Helper Already-In-Grid Check]: <span n:render="time" n:data="time_existence_check" /></li>
+        </ul>
+        <li>[Upload Ciphertext To Helper]: <span n:render="time" n:data="time_cumulative_fetch" />
+        (<span n:render="rate" n:data="rate_ciphertext_fetch" />)</li>
+
+        <li>Peer Selection: <span n:render="time" n:data="time_peer_selection" /></li>
+        <li>Encode And Push: <span n:render="time" n:data="time_total_encode_and_push" />
+        (<span n:render="rate" n:data="rate_encode_and_push" />)</li>
+        <ul>
+          <li>Cumulative Encoding: <span n:render="time" n:data="time_cumulative_encoding" />
+          (<span n:render="rate" n:data="rate_encode" />)</li>
+          <li>Cumulative Pushing: <span n:render="time" n:data="time_cumulative_sending" />
+          (<span n:render="rate" n:data="rate_push" />)</li>
+          <li>Send Hashes And Close: <span n:render="time" n:data="time_hashes_and_close" /></li>
+        </ul>
+        <li>[Helper Total]: <span n:render="time" n:data="time_helper_total" /></li>
+      </ul>
+    </ul>
+  </ul>
+</div>
+
 <div>Return to the <a href="/">Welcome Page</a></div>
 
   </body>
diff --git a/src/allmydata/webish.py b/src/allmydata/webish.py
index 31192991..427e7ef7 100644
--- a/src/allmydata/webish.py
+++ b/src/allmydata/webish.py
@@ -1372,51 +1372,8 @@ class UnlinkedPUTCreateDirectory(rend.Page):
         # XXX add redirect_to_result
         return d
 
-
-class UnlinkedPOSTCHKUploader(rend.Page):
-    """'POST /uri', to create an unlinked file."""
-    docFactory = getxmlfile("unlinked-upload.xhtml")
-
-    def __init__(self, client, req):
-        rend.Page.__init__(self)
-        # we start the upload now, and distribute notification of its
-        # completion to render_ methods with an ObserverList
-        assert req.method == "POST"
-        self._done = observer.OneShotObserverList()
-        fileobj = req.fields["file"].file
-        uploadable = FileHandle(fileobj)
-        d = client.upload(uploadable)
-        d.addBoth(self._done.fire)
-
-    def renderHTTP(self, ctx):
-        req = inevow.IRequest(ctx)
-        when_done = get_arg(req, "when_done", None)
-        if when_done:
-            # if when_done= is provided, return a redirect instead of our
-            # usual upload-results page
-            d = self._done.when_fired()
-            d.addCallback(lambda res: url.URL.fromString(when_done))
-            return d
-        return rend.Page.renderHTTP(self, ctx)
-
-    def upload_results(self):
-        return self._done.when_fired()
-
-    def data_done(self, ctx, data):
-        d = self.upload_results()
-        d.addCallback(lambda res: "done!")
-        return d
-
-    def data_uri(self, ctx, data):
-        d = self.upload_results()
-        d.addCallback(lambda res: res.uri)
-        return d
-
-    def render_download_link(self, ctx, data):
-        d = self.upload_results()
-        d.addCallback(lambda res: T.a(href="/uri/" + urllib.quote(res.uri))
-                      ["/uri/" + res.uri])
-        return d
+class UploadResultsRendererMixin:
+    # this requires a method named 'upload_results'
 
     def render_sharemap(self, ctx, data):
         d = self.upload_results()
@@ -1574,6 +1531,51 @@ class UnlinkedPOSTCHKUploader(rend.Page):
         d.addCallback(_convert)
         return d
 
+class UnlinkedPOSTCHKUploader(UploadResultsRendererMixin, rend.Page):
+    """'POST /uri', to create an unlinked file."""
+    docFactory = getxmlfile("upload-results.xhtml")
+
+    def __init__(self, client, req):
+        rend.Page.__init__(self)
+        # we start the upload now, and distribute notification of its
+        # completion to render_ methods with an ObserverList
+        assert req.method == "POST"
+        self._done = observer.OneShotObserverList()
+        fileobj = req.fields["file"].file
+        uploadable = FileHandle(fileobj)
+        d = client.upload(uploadable)
+        d.addBoth(self._done.fire)
+
+    def renderHTTP(self, ctx):
+        req = inevow.IRequest(ctx)
+        when_done = get_arg(req, "when_done", None)
+        if when_done:
+            # if when_done= is provided, return a redirect instead of our
+            # usual upload-results page
+            d = self._done.when_fired()
+            d.addCallback(lambda res: url.URL.fromString(when_done))
+            return d
+        return rend.Page.renderHTTP(self, ctx)
+
+    def upload_results(self):
+        return self._done.when_fired()
+
+    def data_done(self, ctx, data):
+        d = self.upload_results()
+        d.addCallback(lambda res: "done!")
+        return d
+
+    def data_uri(self, ctx, data):
+        d = self.upload_results()
+        d.addCallback(lambda res: res.uri)
+        return d
+
+    def render_download_link(self, ctx, data):
+        d = self.upload_results()
+        d.addCallback(lambda res: T.a(href="/uri/" + urllib.quote(res.uri))
+                      ["/uri/" + res.uri])
+        return d
+
 class UnlinkedPOSTSSKUploader(rend.Page):
     def renderHTTP(self, ctx):
         req = inevow.IRequest(ctx)
@@ -1608,9 +1610,25 @@ class UnlinkedPOSTCreateDirectory(rend.Page):
             d.addCallback(lambda dirnode: dirnode.get_uri())
         return d
 
-class UploadStatusPage(rend.Page):
+class UploadStatusPage(UploadResultsRendererMixin, rend.Page):
     docFactory = getxmlfile("upload-status.xhtml")
 
+    def __init__(self, data):
+        rend.Page.__init__(self, data)
+        self.upload_status = data
+
+    def upload_results(self):
+        return defer.maybeDeferred(self.upload_status.get_results)
+
+    def render_results(self, ctx, data):
+        d = self.upload_results()
+        def _got_results(results):
+            if results:
+                return ctx.tag
+            return ""
+        d.addCallback(_got_results)
+        return d
+
     def render_si(self, ctx, data):
         si_s = base32.b2a_or_none(data.get_storage_index())
         if si_s is None:
@@ -1677,11 +1695,9 @@ class Status(rend.Page):
     addSlash = True
 
     def data_active_uploads(self, ctx, data):
-        return [u for u in IClient(ctx).list_all_uploads()
-                if u.get_active()]
+        return [u for u in IClient(ctx).list_active_uploads()]
     def data_active_downloads(self, ctx, data):
-        return [d for d in IClient(ctx).list_all_downloads()
-                if d.get_active()]
+        return [d for d in IClient(ctx).list_active_downloads()]
     def data_recent_uploads(self, ctx, data):
         return [u for u in IClient(ctx).list_recent_uploads()
                 if not u.get_active()]
-- 
2.45.2