From 5b8320442a836cc145e4982f3f6bb9b2f841abe1 Mon Sep 17 00:00:00 2001
From: Brian Warner <warner@allmydata.com>
Date: Mon, 14 Apr 2008 14:17:08 -0700
Subject: [PATCH] stats: add /statistics web page to show them, add tests

---
 src/allmydata/stats.py             |  8 +++--
 src/allmydata/test/test_system.py  | 12 ++++++++
 src/allmydata/web/statistics.xhtml | 28 +++++++++++++++++
 src/allmydata/web/status.py        | 49 ++++++++++++++++++++++++++++--
 src/allmydata/webish.py            |  1 +
 5 files changed, 93 insertions(+), 5 deletions(-)
 create mode 100644 src/allmydata/web/statistics.xhtml

diff --git a/src/allmydata/stats.py b/src/allmydata/stats.py
index 96fea7a6..1a0ae0d8 100644
--- a/src/allmydata/stats.py
+++ b/src/allmydata/stats.py
@@ -103,15 +103,17 @@ class StatsProvider(foolscap.Referenceable, service.MultiService):
     def register_producer(self, stats_producer):
         self.stats_producers.append(IStatsProducer(stats_producer))
 
-    def remote_get_stats(self):
+    def get_stats(self):
         stats = {}
         for sp in self.stats_producers:
             stats.update(sp.get_stats())
-        #return { 'counters': self.counters, 'stats': stats }
         ret = { 'counters': self.counters, 'stats': stats }
-        log.msg(format='get_stats() -> %s', args=(pprint.pformat(ret),), level=12)
+        log.msg(format='get_stats() -> %(stats)s', stats=ret, level=log.NOISY)
         return ret
 
+    def remote_get_stats(self):
+        return self.get_stats()
+
     def _connected(self, gatherer, nickname):
         gatherer.callRemoteOnly('provide', self, nickname or '')
 
diff --git a/src/allmydata/test/test_system.py b/src/allmydata/test/test_system.py
index c191671b..970c5ada 100644
--- a/src/allmydata/test/test_system.py
+++ b/src/allmydata/test/test_system.py
@@ -1354,6 +1354,18 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase):
             self.failUnlessEqual(data, {})
         d.addCallback(_got_non_helper_status_json)
 
+        # see if the statistics page exists
+        d.addCallback(lambda res: self.GET("statistics"))
+        def _got_stats(res):
+            self.failUnless("Node Statistics" in res)
+            self.failUnless("  'downloader.files_downloaded': 8," in res)
+        d.addCallback(_got_stats)
+        d.addCallback(lambda res: self.GET("statistics?t=json"))
+        def _got_stats_json(res):
+            data = simplejson.loads(res)
+            self.failUnlessEqual(data["counters"]["uploader.files_uploaded"], 5)
+            self.failUnlessEqual(data["stats"]["chk_upload_helper.upload_need_upload"], 1)
+        d.addCallback(_got_stats_json)
 
         # TODO: mangle the second segment of a file, to test errors that
         # occur after we've already sent some good data, which uses a
diff --git a/src/allmydata/web/statistics.xhtml b/src/allmydata/web/statistics.xhtml
new file mode 100644
index 00000000..fa956ab7
--- /dev/null
+++ b/src/allmydata/web/statistics.xhtml
@@ -0,0 +1,28 @@
+<html xmlns:n="http://nevow.com/ns/nevow/0.1">
+  <head>
+    <title>Stats - AllMyData Tahoe</title>
+    <!-- <link href="http://www.allmydata.com/common/css/styles.css"
+          rel="stylesheet" type="text/css"/> -->
+    <link href="/webform_css" rel="stylesheet" type="text/css"/>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  </head>
+  <body>
+
+<h1>Node Statistics</h1>
+
+<ul>
+  <li>Load Average: <span n:render="load_average" /></li>
+  <li>Peak Load: <span n:render="peak_load" /></li>
+  <li>Files Uploaded (immutable): <span n:render="uploads" /></li>
+  <li>Files Downloaded (immutable): <span n:render="downloads" /></li>
+  <li>Files Published (mutable): <span n:render="publishes" /></li>
+  <li>Files Retrieved (mutable): <span n:render="retrieves" /></li>
+</ul>
+
+<h2>Raw Stats:</h2>
+<pre n:render="raw" />
+
+<div>Return to the <a href="/">Welcome Page</a></div>
+
+  </body>
+</html>
diff --git a/src/allmydata/web/status.py b/src/allmydata/web/status.py
index 49518f98..1b07045d 100644
--- a/src/allmydata/web/status.py
+++ b/src/allmydata/web/status.py
@@ -1,11 +1,11 @@
 
-import time
+import time, pprint
 import simplejson
 from twisted.internet import defer
 from nevow import rend, inevow, tags as T
 from allmydata.util import base32, idlib
 from allmydata.web.common import IClient, getxmlfile, abbreviate_time, \
-     abbreviate_rate, get_arg
+     abbreviate_rate, abbreviate_size, get_arg
 from allmydata.interfaces import IUploadStatus, IDownloadStatus, \
      IPublishStatus, IRetrieveStatus
 
@@ -808,3 +808,48 @@ class HelperStatus(rend.Page):
     def render_upload_bytes_encoded(self, ctx, data):
         return str(data["chk_upload_helper.encoded_bytes"])
 
+
+class Statistics(rend.Page):
+    docFactory = getxmlfile("statistics.xhtml")
+
+    def renderHTTP(self, ctx):
+        provider = IClient(ctx).stats_provider
+        stats = {'stats': {}, 'counters': {}}
+        if provider:
+            stats = provider.get_stats()
+        t = get_arg(inevow.IRequest(ctx), "t")
+        if t == "json":
+            return simplejson.dumps(stats, indent=1)
+        # is there a better way to provide 'data' to all rendering methods?
+        self.original = stats
+        return rend.Page.renderHTTP(self, ctx)
+
+    def render_load_average(self, ctx, data):
+        return str(data["stats"].get("load_monitor.avg_load"))
+
+    def render_peak_load(self, ctx, data):
+        return str(data["stats"].get("load_monitor.max_load"))
+
+    def render_uploads(self, ctx, data):
+        files = data["counters"].get("uploader.files_uploaded")
+        bytes = data["counters"].get("uploader.bytes_uploaded")
+        return ("%s files / %s bytes (%s)" %
+                (files, bytes, abbreviate_size(bytes)))
+
+    def render_downloads(self, ctx, data):
+        files = data["counters"].get("downloader.files_uploaded")
+        bytes = data["counters"].get("downloader.bytes_uploaded")
+        return ("%s files / %s bytes (%s)" %
+                (files, bytes, abbreviate_size(bytes)))
+
+    def render_publishes(self, ctx, data):
+        files = data["counters"].get("mutable.files_published")
+        return "%s files" % (files,)
+
+    def render_retrieves(self, ctx, data):
+        files = data["counters"].get("mutable.files_retrieved")
+        return "%s files" % (files,)
+
+    def render_raw(self, ctx, data):
+        raw = pprint.pformat(data)
+        return ctx.tag[raw]
diff --git a/src/allmydata/webish.py b/src/allmydata/webish.py
index 5afc3fb9..4f0bebf6 100644
--- a/src/allmydata/webish.py
+++ b/src/allmydata/webish.py
@@ -1446,6 +1446,7 @@ class Root(rend.Page):
     child_provisioning = provisioning.ProvisioningTool()
     child_status = status.Status()
     child_helper_status = status.HelperStatus()
+    child_statistics = status.Statistics()
 
     def data_version(self, ctx, data):
         return get_package_versions_string()
-- 
2.45.2