[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[or-cvs] [metrics-web/master] Replace all graphs with dynamically generated ones.



Author: Karsten Loesing <karsten.loesing@xxxxxxx>
Date: Thu, 7 Oct 2010 09:26:22 +0200
Subject: Replace all graphs with dynamically generated ones.
Commit: c8c0406a2152eedd41acb4f80175539f92c136ec

Instead of static graphs that are generated every 2 hours, we now serve
dynamically generated graphs and allow the user to customize them. This
allows us to clean up the graphs pages a lot and add new graph types more
easily. Also clean up navigation by grouping related graphs on four graphs
sub pages and by removing the metrics database processor log.
---
 rserve/graphs.R                                    |   13 +-
 .../torproject/ernie/web/ExoneraTorServlet.java    |    1 -
 .../torproject/ernie/web/GraphImageServlet.java    |   10 +-
 .../torproject/ernie/web/RelaySearchServlet.java   |    1 -
 war/WEB-INF/templates/banner.tpl.jsp               |   29 +--
 war/WEB-INF/templates/graphs.tpl.jsp               |  122 +--------
 war/WEB-INF/templates/graphs_bridge-users.tpl.jsp  |   72 -----
 war/WEB-INF/templates/graphs_custom-graph.tpl.jsp  |   99 -------
 war/WEB-INF/templates/graphs_exit-relays.tpl.jsp   |   57 ----
 war/WEB-INF/templates/graphs_gettor.tpl.jsp        |   12 -
 war/WEB-INF/templates/graphs_network-size.tpl.jsp  |   65 -----
 war/WEB-INF/templates/graphs_network.tpl.jsp       |  284 +++++++++++++++++++
 war/WEB-INF/templates/graphs_new-users.tpl.jsp     |   68 -----
 war/WEB-INF/templates/graphs_packages.tpl.jsp      |   71 +++++
 war/WEB-INF/templates/graphs_performance.tpl.jsp   |   80 ++++++
 .../templates/graphs_recurring-users.tpl.jsp       |   73 -----
 war/WEB-INF/templates/graphs_torperf.tpl.jsp       |   37 ---
 war/WEB-INF/templates/graphs_users.tpl.jsp         |  292 ++++++++++++++++++++
 war/WEB-INF/templates/index.tpl.jsp                |   54 +---
 war/WEB-INF/templates/status.tpl.jsp               |    3 -
 war/WEB-INF/urlrewrite.xml                         |   32 +--
 war/WEB-INF/web.xml                                |    4 -
 war/bridge-users-graphs.jsp                        |    4 -
 war/consensus-graphs.jsp                           |    4 -
 war/custom-graph.jsp                               |    6 -
 war/exit-relays-graphs.jsp                         |    4 -
 war/gettor-graphs.jsp                              |    4 -
 war/network.jsp                                    |    4 +
 war/new-users-graphs.jsp                           |    4 -
 war/packages.jsp                                   |    4 +
 war/performance.jsp                                |    4 +
 war/recurring-users-graphs.jsp                     |    4 -
 war/torperf-graphs.jsp                             |    4 -
 war/users.jsp                                      |    4 +
 34 files changed, 792 insertions(+), 737 deletions(-)
 delete mode 100644 war/WEB-INF/templates/graphs_bridge-users.tpl.jsp
 delete mode 100644 war/WEB-INF/templates/graphs_custom-graph.tpl.jsp
 delete mode 100644 war/WEB-INF/templates/graphs_exit-relays.tpl.jsp
 delete mode 100644 war/WEB-INF/templates/graphs_gettor.tpl.jsp
 delete mode 100644 war/WEB-INF/templates/graphs_network-size.tpl.jsp
 create mode 100644 war/WEB-INF/templates/graphs_network.tpl.jsp
 delete mode 100644 war/WEB-INF/templates/graphs_new-users.tpl.jsp
 create mode 100644 war/WEB-INF/templates/graphs_packages.tpl.jsp
 create mode 100644 war/WEB-INF/templates/graphs_performance.tpl.jsp
 delete mode 100644 war/WEB-INF/templates/graphs_recurring-users.tpl.jsp
 delete mode 100644 war/WEB-INF/templates/graphs_torperf.tpl.jsp
 create mode 100644 war/WEB-INF/templates/graphs_users.tpl.jsp
 delete mode 100644 war/bridge-users-graphs.jsp
 delete mode 100644 war/consensus-graphs.jsp
 delete mode 100644 war/custom-graph.jsp
 delete mode 100644 war/exit-relays-graphs.jsp
 delete mode 100644 war/gettor-graphs.jsp
 create mode 100644 war/network.jsp
 delete mode 100644 war/new-users-graphs.jsp
 create mode 100644 war/packages.jsp
 create mode 100644 war/performance.jsp
 delete mode 100644 war/recurring-users-graphs.jsp
 delete mode 100644 war/torperf-graphs.jsp
 create mode 100644 war/users.jsp

diff --git a/rserve/graphs.R b/rserve/graphs.R
index d94553a..b786990 100644
--- a/rserve/graphs.R
+++ b/rserve/graphs.R
@@ -159,7 +159,7 @@ plot_new_users <- function(start, end, country, path) {
       "WHERE (source = '68333D0761BCF397A587A0C0B963E4A9E99EC4D3' ",
       "OR source = 'F2044413DAC2E02E3D6BCF4735A19BCA1DE97281') ",
       "AND date >= '", start, "' AND date <= '", end, "' AND country = '",
-      country, "'", sep = "")
+      ifelse(country == "all", "zy", country), "'", sep = "")
   rs <- dbSendQuery(con, q)
   newusers <- fetch(rs, n = -1)
   dbDisconnect(con)
@@ -179,7 +179,7 @@ plot_new_users <- function(start, end, country, path) {
     "Iranian", "Italian", "Japanese", "South Korean", "Burmese", "Polish",
     "Russian", "Saudi", "Swedish", "Syrian", "Tunisian", "Turkmen",
     "U.S.", "Uzbek", "Vietnamese", "Yemeni"), stringsAsFactors = FALSE)
-  title <- ifelse(country == "zy",
+  title <- ifelse(country == "all",
     "Total new or returning, directly connecting Tor users (all data)\n",
     paste("New or returning, directly connecting",
     peoples[peoples$country == country, "people"], "Tor users\n"))
@@ -200,7 +200,7 @@ plot_direct_users <- function(start, end, country, path) {
       "FROM dirreq_stats WHERE share >= 1 ",
       "AND source = '8522EB98C91496E80EC238E732594D1509158E77' ",
       "AND date >= '", start, "' AND date <= '", end, "' AND country = '",
-      country, "'", sep = "")
+      ifelse(country == "all", "zy", country), "'", sep = "")
   rs <- dbSendQuery(con, q)
   directusers <- fetch(rs, n = -1)
   dbDisconnect(con)
@@ -220,7 +220,7 @@ plot_direct_users <- function(start, end, country, path) {
     "Iranian", "Italian", "Japanese", "South Korean", "Burmese", "Polish",
     "Russian", "Saudi", "Swedish", "Syrian", "Tunisian", "Turkmen",
     "U.S.", "Uzbek", "Vietnamese", "Yemeni"), stringsAsFactors = FALSE)
-  title <- ifelse(country == "zy",
+  title <- ifelse(country == "all",
     "Total recurring, directly connecting Tor users (all data)\n",
     paste("Recurring, directly connecting",
     peoples[peoples$country == country, "people"], "Tor users\n"))
@@ -240,7 +240,8 @@ plot_bridge_users <- function(start, end, country, path) {
   q <- paste("SELECT date, users FROM bridge_stats ",
       "WHERE date >= '", start, "' AND date <= '", end, "' ",
       "AND date < (SELECT MAX(date) FROM bridge_stats) ",
-      "AND country = '", country, "'", sep = "")
+      " AND country = '", ifelse(country == "all", "zy", country), "'",
+      sep = "")
   rs <- dbSendQuery(con, q)
   bridgeusers <- fetch(rs, n = -1)
   dbDisconnect(con)
@@ -260,7 +261,7 @@ plot_bridge_users <- function(start, end, country, path) {
     "Iranian", "Italian", "Japanese", "South Korean", "Burmese", "Polish",
     "Russian", "Saudi", "Swedish", "Syrian", "Tunisian", "Turkmen",
     "U.S.", "Uzbek", "Vietnamese", "Yemeni"), stringsAsFactors = FALSE)
-  title <- ifelse(country == "zy",
+  title <- ifelse(country == "all",
     "Total users via bridges (all data)\n",
     paste(peoples[peoples$country == country, "people"],
     "users via bridges\n"))
diff --git a/src/org/torproject/ernie/web/ExoneraTorServlet.java b/src/org/torproject/ernie/web/ExoneraTorServlet.java
index da44e2f..f1efeb0 100644
--- a/src/org/torproject/ernie/web/ExoneraTorServlet.java
+++ b/src/org/torproject/ernie/web/ExoneraTorServlet.java
@@ -48,7 +48,6 @@ public class ExoneraTorServlet extends HttpServlet {
           + "              <a href=\"relay-search.html\">Relay Search</a>\n"
 
           + "              <a href=\"consensus-health.html\">Consensus Health</a>\n"
-          + "              <a href=\"log.html\">Last Log</a>\n"
           + "            </font>\n"
           + "          </td>\n"
         + "          <td class=\"banner-right\"></td>\n"
diff --git a/src/org/torproject/ernie/web/GraphImageServlet.java b/src/org/torproject/ernie/web/GraphImageServlet.java
index 304b103..8534049 100644
--- a/src/org/torproject/ernie/web/GraphImageServlet.java
+++ b/src/org/torproject/ernie/web/GraphImageServlet.java
@@ -46,9 +46,9 @@ public class GraphImageServlet extends HttpServlet {
     this.knownParameterValues = new HashMap<String, String>();
     this.knownParameterValues.put("flag",
         "Running,Exit,Guard,Fast,Stable");
-    this.knownParameterValues.put("country", "au,bh,br,ca,cn,cu,de,et,"
-         + "fr,gb,ir,it,jp,kr,mm,pl,ru,sa,se,sy,tn,tm,us,uz,vn,ye");
-    this.knownParameterValues.put("bundle", "en,zh_cn,fa");
+    this.knownParameterValues.put("country", "all,au,bh,br,ca,cn,cu,de,"
+        + "et,fr,gb,ir,it,jp,kr,mm,pl,ru,sa,se,sy,tn,tm,us,uz,vn,ye");
+    this.knownParameterValues.put("bundle", "all,en,zh_CN,fa");
     this.knownParameterValues.put("source", "siv,moria,torperf");
     this.knownParameterValues.put("filesize", "50kb,1mb,5mb");
   }
@@ -149,7 +149,7 @@ public class GraphImageServlet extends HttpServlet {
     }
 
     /* Parse country codes if supported by the graph type. If no countries
-     * are passed, use country code "zy" (all countries) as default. */
+     * are passed, use country code "all" (all countries) as default. */
     if (supportedGraphParameters.contains("country")) {
       String[] countryParameters = request.getParameterValues("country");
       List<String> knownCountries = Arrays.asList(
@@ -163,7 +163,7 @@ public class GraphImageServlet extends HttpServlet {
           }
         }
       } else {
-        countryParameters = new String[] { "zy" };
+        countryParameters = new String[] { "all" };
       }
       recognizedGraphParameters.put("country", countryParameters);
     }
diff --git a/src/org/torproject/ernie/web/RelaySearchServlet.java b/src/org/torproject/ernie/web/RelaySearchServlet.java
index 83f1a64..f6c44a7 100644
--- a/src/org/torproject/ernie/web/RelaySearchServlet.java
+++ b/src/org/torproject/ernie/web/RelaySearchServlet.java
@@ -119,7 +119,6 @@ public class RelaySearchServlet extends HttpServlet {
         + "              <a class=\"current\">Relay Search</a>\n"
         + "              <a href=\"consensus-health.html\">Consensus "
           + "Health</a>\n"
-        + "              <a href=\"log.html\">Last Log</a>\n"
         + "            </font>\n"
         + "          </td>\n"
         + "          <td class=\"banner-right\"></td>\n"
diff --git a/war/WEB-INF/templates/banner.tpl.jsp b/war/WEB-INF/templates/banner.tpl.jsp
index 53e9d1c..bb29789 100644
--- a/war/WEB-INF/templates/banner.tpl.jsp
+++ b/war/WEB-INF/templates/banner.tpl.jsp
@@ -18,30 +18,18 @@
     <%if (template.getTemplateName().startsWith("graphs")) {%>
       <br/>
       <font size="2">
-        <a <%if (template.getTemplateName().contains("network-size")){%>
+        <a <%if (template.getTemplateName().contains("network")){%>
             class="current" <%} else {%>
-            href="/consensus-graphs.html"<%}%>>Network Size</a>
-        <a <%if (template.getTemplateName().contains("exit-relays")) {%>
+            href="/network.html"<%}%>>Network</a>
+        <a <%if (template.getTemplateName().contains("users")) {%>
             class="current" <%} else {%>
-            href="/exit-relays-graphs.html"<%}%>>Exit Relays</a>
-        <a <%if (template.getTemplateName().contains("new-users")) {%>
+            href="/users.html"<%}%>>Users</a>
+        <a <%if (template.getTemplateName().contains("packages")) {%>
             class="current" <%} else {%>
-            href="/new-users-graphs.html"<%}%>>New Users</a>
-        <a <%if (template.getTemplateName().contains("recurring-users")) {%>
+            href="/packages.html"<%}%>>Packages</a>
+        <a <%if (template.getTemplateName().contains("performance")) {%>
             class="current" <%} else {%>
-            href="/recurring-users-graphs.html"<%}%>>Recurring Users</a>
-        <a <%if (template.getTemplateName().contains("bridge-users")) {%>
-            class="current" <%} else {%>
-            href="/bridge-users-graphs.html"<%}%>>Bridge Users</a>
-        <a <%if (template.getTemplateName().contains("torperf")) {%>
-            class="current" <%} else {%>
-            href="/torperf-graphs.html"<%}%>>torperf</a>
-        <a <%if (template.getTemplateName().contains("gettor")) {%>
-            class="current" <%} else {%>
-            href="/gettor-graphs.html"<%}%>>GetTor</a>
-        <a <%if (template.getTemplateName().contains("custom-graph")) {%>
-            class="current" <%} else {%>
-            href="/custom-graph.html"<%}%>>Custom Graph</a>
+            href="/performance.html"<%}%>>Performance</a>
       </font>
     <%} else if (template.getTemplateName().startsWith("status")) {%>
       <br/>
@@ -49,7 +37,6 @@
         <a href="/exonerator.html">ExoneraTor</a>
         <a href="/relay-search.html">Relay Search</a>
         <a href="/consensus-health.html">Consensus Health</a>
-        <a href="/log.html">Last Log</a>
       </font>
     <%} else if (template.getTemplateName().startsWith("research")) { %>
       <br/>
diff --git a/war/WEB-INF/templates/graphs.tpl.jsp b/war/WEB-INF/templates/graphs.tpl.jsp
index 4dc8624..f6a4647 100644
--- a/war/WEB-INF/templates/graphs.tpl.jsp
+++ b/war/WEB-INF/templates/graphs.tpl.jsp
@@ -1,116 +1,18 @@
         <h2>Tor Metrics Portal: Graphs</h2>
         <br/>
         <p>The graphs on this page visualize a small portion of the data
-        gathered in the Tor Metrics Project. They are generated by a
-        combination of shell scripts, Java applications, and R code (for
-        details see the <a href="tools.html">Tools</a> section). The
-        following graphs are available:</p>
+        gathered in the Tor Metrics Project. The following graphs are
+        available:</p>
         <ul>
-          <li><a href="#relays">Relays in the Tor network</a></li>
-          <li><a href="#newusers">New or returning, directly connecting
-            Tor users</a></li>
-          <li><a href="#recurringusers">Recurring, directly connecting Tor
-            users</a></li>
-          <li><a href="#bridgeusers">Tor users via bridges</a></li>
-          <li><a href="#torperf">Time to complete requests</a></li>
-          <li><a href="#gettor">Packages requested from GetTor</a></li>
-          <li><a href="#versions">Relay versions</a></li>
-          <li><a href="#platforms">Relay platforms</a></li>
-          <li><a href="#bandwidth">Relay bandwidth</a></li>
+          <li>The <a href="network.html">Network page</a> has numerous
+          statistics on the network of relays and bridges.</li>
+          <li>The <a href="users.html">Users page</a> attempts to estimate
+          the number of users in the network.</li>
+          <li>There are numerous ways to download the Tor software. The
+          <a href="packages.html">Packages page</a> has statistics on the
+          number of packages requested from GetTor.</li>
+          <li>There are active and passive performance measurements of the
+          Tor network available on the
+          <a href="performance.html">Performance page</a>.</li>
         </ul>
         <br/>
-        <a id="relays"/>
-        <h3>Relays in the Tor network</h3>
-        <br/>
-        <p>The number of relays in the Tor network can be extracted from
-        the hourly published network status consensuses.</p>
-        <img src="graphs/networksize/networksize-30d.png"/>
-        <p>Other graphs related to <a href="consensus-graphs.html">network
-        size</a> and <a href="exit-relays-graphs.html">exit relays</a> can
-        be found on separate pages.</p>
-        <br/>
-        <a id="newusers"/>
-        <h3>New or returning, directly connecting Tor users</h3>
-        <br/>
-        <p>Users connecting to the Tor network for the first time request
-        a list of running relays from one of currently seven directory
-        authorities. Likewise, returning users whose network information
-        is out of date connect to one of the directory authorities to
-        download a fresh list of relays. The following graphs display an
-        estimate of new or returning Tor users based on the requests as
-        seen by gabelmoo, one of the directory authorities.</p>
-        <img src="graphs/new-users/iran-new-30d.png"/>
-        <p>Graphs for other countries can be found on a
-        <a href="new-users-graphs.html">separate page</a>.</p>
-        <br/>
-        <a id="recurringusers"/>
-        <h3>Recurring, directly connecting Tor users</h3>
-        <br/>
-        <p>After being connected to the Tor network, users need to refresh
-        their list of running relays on a regular basis. They send their
-        requests to one out of a few hundred directory mirrors to save
-        bandwidth of the directory authorities. The following graphs show
-        an estimate of recurring Tor users based on the requests as seen
-        by trusted, a particularly fast directory mirror.</p>
-        <img src="graphs/direct-users/iran-direct-30d.png"/>
-        <p>Graphs for other countries can be found on a
-        <a href="recurring-users-graphs.html">separate page</a>.</p>
-        <br/>
-        <a id="bridgeusers"/>
-        <h3>Tor users via bridges</h3>
-        <br/>
-        <p>Users who cannot connect directly to the Tor network instead
-        connect via bridges, which are non-public relays. The following
-        graphs display an estimate of Tor users via bridges based on the
-        unique IP addresses as seen by a few hundred bridges.</p>
-        <img src="graphs/bridge-users/iran-bridges-30d.png"/>
-        <p>Graphs for other countries can be found on a
-        <a href="bridge-users-graphs.html">separate page</a>.</p>
-        <br/>
-        <a id="torperf"/>
-        <h3>Time to complete requests</h3>
-        <br/>
-        <p>The following graphs show the performance of the Tor network as
-        experienced by its users. The graphs contain the average (median)
-        time to request files of three different sizes over the network as
-        well as first and third quartile of request times.</p>
-        <img src="graphs/torperf/torperf-50kb-torperf-6m.png"/>
-        <p>Graphs for other file sizes or time intervals can be found on a
-        <a href="torperf-graphs.html">separate page</a>.</p>
-        <br/>
-        <a id="gettor"/>
-        <h3>Packages requested from GetTor</h3>
-        <br/>
-        <p>GetTor allows users to fetch Tor via email. The following
-        graphs show the number of requested packages per day.</p>
-        <img src="graphs/gettor/gettor-total.png"/>
-        <p>More graphs about specific packages can be found on a 
-        <a href="gettor-graphs.html">separate page</a>.</p>
-        <br/>
-        <a id="versions"/>
-        <h3>Relay versions</h3>
-        <br/>
-        <p>Relays report the Tor version that they are running in their
-        server descriptors that they send to the directory authorities.
-        The following graph shows the number of relays running specific
-        Tor versions.</p>
-        <img src="versions.png" width=576 height=360 />
-        <br/>
-        <a id="platforms"/>
-        <h3>Relay platforms</h3>
-        <br/>
-        <p>Relays further report the operating systems in their server
-        descriptors that they send to the directory authorities. The
-        following graph shows the number of relays running specific
-        platforms.</p>
-        <img src="platforms.png" width=576 height=360 />
-        <br/>
-        <a id="bandwidth"/>
-        <h3>Relay bandwidth</h3>
-        <br/>
-        <p>Relays advertise how much bandwidth they are willing and
-        to contribute in their server descriptors. The following graph
-        shows the sum of advertised bandwidth of all relays in the
-        network.</p>
-        <img src="bandwidth.png" width=576 height=360 />
-        <br/>
diff --git a/war/WEB-INF/templates/graphs_bridge-users.tpl.jsp b/war/WEB-INF/templates/graphs_bridge-users.tpl.jsp
deleted file mode 100644
index 526160c..0000000
--- a/war/WEB-INF/templates/graphs_bridge-users.tpl.jsp
+++ /dev/null
@@ -1,72 +0,0 @@
-<%@page import="java.io.*" %>
-<%@page import="java.util.*" %>
-        <h2>Tor Metrics Portal: Graphs</h2>
-        <br/>
-        <h3>Tor users via bridges</h3>
-        <br/>
-        <p>Users who cannot connect directly to the Tor network instead
-        connect via bridges, which are non-public relays. The following
-        graphs display an estimate of Tor users via bridges based on the
-        unique IP addresses as seen by a few hundred bridges.</p>
-        <ul>
-          <li><a href="#total">All users</a></li>
-          <li><a href="#australia">Australia</a></li>
-          <li><a href="#bahrain">Bahrain</a></li>
-          <li><a href="#brazil">Brazil</a></li>
-          <li><a href="#burma">Burma</a></li>
-          <li><a href="#canada">Canada</a></li>
-          <li><a href="#china">China</a></li>
-          <li><a href="#cuba">Cuba</a></li>
-          <li><a href="#ethiopia">Ethiopia</a></li>
-          <li><a href="#france">France</a></li>
-          <li><a href="#germany">Germany</a></li>
-          <li><a href="#iran">Iran</a></li>
-          <li><a href="#italy">Italy</a></li>
-          <li><a href="#japan">Japan</a></li>
-          <li><a href="#poland">Poland</a></li>
-          <li><a href="#russia">Russia</a></li>
-          <li><a href="#saudi">Saudi</a></li>
-          <li><a href="#southkorea">South Korea</a></li>
-          <li><a href="#sweden">Sweden</a></li>
-          <li><a href="#syria">Syria</a></li>
-          <li><a href="#tunisia">Tunisia</a></li>
-          <li><a href="#turkmenistan">Turkmenistan</a></li>
-          <li><a href="#uk">U.K.</a></li>
-          <li><a href="#usa">U.S.A.</a></li>
-          <li><a href="#uzbekistan">Uzbekistan</a></li>
-          <li><a href="#vietnam">Vietnam</a></li>
-          <li><a href="#yemen">Yemen</a></li>
-        </ul>
-        <ul>
-          <li><a href="csv/bridge-users.csv">CSV</a> file containing all
-          data.</li>
-          <li><a href="csv/monthly-users-peak.csv">CSV</a> file
-          containing peak daily Tor users (recurring and bridge) per month
-          by country.</li>
-          <li><a href="csv/monthly-users-average.csv">CSV</a> file
-          containing average daily Tor users (recurring and bridge) per
-          month by country.</li>
-        </ul>
-<%
-    List<String> countries = Arrays.asList(("total,australia,bahrain,"
-        + "brazil,burma,canada,china,cuba,ethiopia,france,germany,iran,"
-        + "italy,japan,poland,russia,saudi,southkorea,sweden,syria,"
-        + "tunisia,turkmenistan,uk,usa,uzbekistan,vietnam,yemen").
-        split(","));
-    List<String> suffixes = new ArrayList<String>(Arrays.asList(
-        "30d,90d,180d,all".split(",")));
-    Calendar now = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
-    suffixes.add(String.format("%tY", now));
-    suffixes.add(String.format("%1$tY-q%2$d", now,
-        1 + now.get(Calendar.MONTH) / 3));
-    suffixes.add(String.format("%1$tY-%1$tm", now));
-    for (String country : countries) {
-      out.print("<p><a id=\"" + country + "\"/>\n");
-      for (String suffix : suffixes) {
-        out.print("        <img src=\"graphs/bridge-users/" + country
-            + "-bridges-" + suffix + ".png\"/>\n");
-      }
-      out.print("        </p>");
-    }
-    out.print("<br/>\n");
-%>
diff --git a/war/WEB-INF/templates/graphs_custom-graph.tpl.jsp b/war/WEB-INF/templates/graphs_custom-graph.tpl.jsp
deleted file mode 100644
index 6df8b7b..0000000
--- a/war/WEB-INF/templates/graphs_custom-graph.tpl.jsp
+++ /dev/null
@@ -1,99 +0,0 @@
-<jsp:useBean id="customgraph"
-    class="org.torproject.ernie.web.CustomGraphController"
-    scope="request" />
-<h2>Tor Metrics Portal: Custom Graph</h2>
-<br/>
-<p>Custom graphs can be requested based on a specific range of
-dates and parameters available. Many similar graphs are available in the
-other sections of the metrics portal, however, the need may arise
-for tracking a more specific part of the Tor network.</p>
-<p>Date format: yyyy-mm-dd</p>
-<div id="graphmenu">
-  <form action="<%=request.getRequestURI()%>">
-    <div class="formrow">
-      <label class="graphname">Network size</label>
-      <input type="hidden" name="graph" value="networksize"/>
-      <label class="startend" for="start">start</label>
-        <input type="text" name="start" id="start"
-          value="<%=("networksize".equals(request.getParameter("graph")) &&
-                     request.getParameter("start") != null) ?
-                        request.getParameter("start") : ""%>"/>
-      <label class="startend" for="end">end</label>
-        <input type="text" name="end" id="end"
-          value="<%=("networksize".equals(request.getParameter("graph")) &&
-                     request.getParameter("end") != null) ?
-                        request.getParameter("end") : ""%>"/>
-      <input class="submit" type="submit"/>
-    </div>
-  </form>
-  <form action="<%=request.getRequestURI()%>">
-    <div class="formrow">
-      <label class="graphname">Platforms</label>
-      <input type="hidden" name="graph" value="platforms"/>
-      <label class="startend" for="start">start</label>
-        <input type="text" name="start" id="start"
-          value="<%=("platforms".equals(request.getParameter("graph"))  &&
-                     request.getParameter("start") != null) ?
-                        request.getParameter("start") : ""%>"/>
-      <label class="startend" for="end">end</label>
-        <input type="text" name="end" id="end"
-          value="<%=("platforms".equals(request.getParameter("graph"))  &&
-                     request.getParameter("end") != null) ?
-                        request.getParameter("end") : ""%>"/>
-      <input class="submit" type="submit"/>
-    </div>
-  </form>
-  <form action="<%=request.getRequestURI()%>">
-    <div class="formrow">
-      <label class="graphname">Versions</label>
-      <input type="hidden" name="graph" value="versions"/>
-      <label class="startend" for="start">start</label>
-        <input type="text" name="start" id="start"
-          value="<%=("versions".equals(request.getParameter("graph")) &&
-                     request.getParameter("start") != null) ?
-                        request.getParameter("start") : ""%>"/>
-      <label class="startend" for="end">end</label>
-        <input type="text" name="end" id="end"
-          value="<%=("versions".equals(request.getParameter("graph")) &&
-                     request.getParameter("end") != null) ?
-                        request.getParameter("end") : ""%>"/>
-      <input class="submit" type="submit"/>
-    </div>
-  </form>
-  <form action="<%=request.getRequestURI()%>">
-    <div class="formrow">
-      <label class="graphname">Bandwidth</label>
-      <input type="hidden" name="graph" value="bandwidth"/>
-      <label class="startend" for="start">start</label>
-        <input type="text" name="start" id="start"
-          value="<%=("bandwidth".equals(request.getParameter("graph")) &&
-                     request.getParameter("start") != null) ?
-                        request.getParameter("start") : ""%>"/>
-      <label class="startend" for="end">end</label>
-        <input type="text" name="end" id="end"
-          value="<%=("bandwidth".equals(request.getParameter("graph")) &&
-                     request.getParameter("end") != null) ?
-                        request.getParameter("end") : ""%>"/>
-      <input class="submit" type="submit"/>
-    </div>
-  </form>
-  <br/>
-</div>
-
-<%if (request.getParameter("start") != null &&
-      request.getParameter("end") != null &&
-      request.getParameter("graph") != null) {
-  customgraph.getGraphURL();
-  if (!customgraph.getError().isEmpty()) {
-    for(String err : customgraph.getError())  { %>
-    <p class="error"><%=err%></p>
-  <%} } else {%>
-  <p><strong><jsp:getProperty name="customgraph" property="graphName"/> graph from
-  <jsp:getProperty name="customgraph" property="graphStart"/> to
-  <jsp:getProperty name="customgraph" property="graphEnd"/></strong></p>
-  <img src="<jsp:getProperty name="customgraph" property="graphURL"/>"
-       href="<jsp:getProperty name="customgraph" property="graphURL"/>"
-       width=576 height=360 />
-  <%}
-}%>
-<div style="clear:both;"></div>
diff --git a/war/WEB-INF/templates/graphs_exit-relays.tpl.jsp b/war/WEB-INF/templates/graphs_exit-relays.tpl.jsp
deleted file mode 100644
index f433d1e..0000000
--- a/war/WEB-INF/templates/graphs_exit-relays.tpl.jsp
+++ /dev/null
@@ -1,57 +0,0 @@
-<%@page import="java.io.*" %>
-<%@page import="java.util.*" %>
-        <h2>Tor Metrics Portal: Graphs</h2>
-        <br/>
-        <h3>Exit relays in the Tor network</h3>
-        <br/>
-        <p>The number of exit relays in the Tor network can be extracted from
-        the hourly published network status consensuses.</p>
-        <ul>
-          <li>Past <a href="#exit-72h">72 hours</a> up to
-              now</li>
-          <li>Past <a href="#exit-30d">30</a>,
-              <a href="#exit-90d">90</a>,
-              <a href="#exit-180d">180</a> days</li>
-          <li><a href="#exit-all">All data</a> up to today</li>
-<%
-    Calendar now = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
-    Calendar lastQuarter = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
-    lastQuarter.add(Calendar.MONTH, -3);
-    Calendar lastMonth = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
-    lastMonth.add(Calendar.MONTH, -1);
-    out.print("          <li>Annual graphs of\n");
-    for (int i = now.get(Calendar.YEAR); i > 2006; i--) {
-      out.print("              <a href=\"#exit-" + i + "\">"
-          + i + "</a>,\n");
-    }
-    out.print("              <a href=\"#exit-2006\">2006</a></li>\n");
-    out.print("          <li>Quarterly graphs of\n");
-    out.print(String.format("              <a href=\"#exit-%1$tY-q%2$d\">"
-        + "Q%2$d %1$tY</a>,%n", now, 1 + now.get(Calendar.MONTH) / 3));
-    out.print(String.format("              <a href=\"#exit-%1$tY-q%2$d\">"
-        + "Q%2$d %1$tY</a></li>%n", lastQuarter, 1 + lastQuarter.get(Calendar.MONTH) / 3));
-    out.print("          <li>Monthly graphs of\n");
-    out.print(String.format("              <a href=\"#exit-%1$tY-%1$tm\">"
-        + "%1$tb %1$tY</a>,%n", now));
-    out.print(String.format("              <a href=\"#exit-%1$tY-%1$tm\">"
-        + "%1$tb %1$tY</a></li>%n", lastMonth));
-    out.print("          <li><a href=\"csv/exit.csv\">CSV</a> file\n"
-        + "              containing raw data</li>\n"
-        + "        </ul>\n");
-    out.print("        </p><p><a id=\"exit-72h\"/><img src=\"graphs/exit/exit-72h.png\"/>\n"
-        + "        </p><p><a id=\"exit-30d\"/><img src=\"graphs/exit/exit-30d.png\"/>\n"
-        + "        </p><p><a id=\"exit-90d\"/><img src=\"graphs/exit/exit-90d.png\"/>\n"
-        + "        </p><p><a id=\"exit-180d\"/><img src=\"graphs/exit/exit-180d.png\"/>\n"
-        + "        </p><p><a id=\"exit-all\"/><img src=\"graphs/exit/exit-all.png\"/>\n");
-    for (int i = now.get(Calendar.YEAR); i > 2006; i--) {
-      out.print("        </p><p><a id=\"exit-" + i + "\"/><img src=\"graphs/exit/exit-" + i + ".png\"/>\n");
-    }
-    out.print("        </p><p><a id=\"exit-2006\"/><img src=\"graphs/exit/exit-2006.png\"/>\n");
-    out.print(String.format("        </p><p><a id=\"exit-%1$tY-q%2$d\"/><img src=\"graphs/exit/exit-%1$tY-q%2$d.png\"/>\n",
-        now, 1 + now.get(Calendar.MONTH) / 3));
-    out.print(String.format("        </p><p><a id=\"exit-%1$tY-q%2$d\"/><img src=\"graphs/exit/exit-%1$tY-q%2$d.png\"/>\n",
-        lastQuarter, 1 + lastQuarter.get(Calendar.MONTH) / 3));
-    out.print(String.format("        </p><p><a id=\"exit-%1$tY-%1$tm\"/><img src=\"graphs/exit/exit-%1$tY-%1$tm.png\"/>\n", now));
-    out.print(String.format("        </p><p><a id=\"exit-%1$tY-%1$tm\"/><img src=\"graphs/exit/exit-%1$tY-%1$tm.png\"/>\n", lastMonth));
-    out.print("        </p><br/>\n");
-%>
diff --git a/war/WEB-INF/templates/graphs_gettor.tpl.jsp b/war/WEB-INF/templates/graphs_gettor.tpl.jsp
deleted file mode 100644
index 0d03363..0000000
--- a/war/WEB-INF/templates/graphs_gettor.tpl.jsp
+++ /dev/null
@@ -1,12 +0,0 @@
-        <h2>Tor Metrics Portal: Graphs</h2>
-        <br/>
-        <h3>Packages requested from GetTor</h3>
-        <br/>
-        <p>GetTor allows users to fetch Tor via email. The following
-        graphs show the number of requested packages per day.</p>
-        <p><a href="csv/gettor.csv">CSV</a> file containing all data.</p>
-        <img src="graphs/gettor/gettor-total.png"/>
-        <img src="graphs/gettor/gettor-en.png"/>
-        <img src="graphs/gettor/gettor-zh_cn.png"/>
-        <img src="graphs/gettor/gettor-fa.png"/>
-        <br/>
diff --git a/war/WEB-INF/templates/graphs_network-size.tpl.jsp b/war/WEB-INF/templates/graphs_network-size.tpl.jsp
deleted file mode 100644
index 199fbec..0000000
--- a/war/WEB-INF/templates/graphs_network-size.tpl.jsp
+++ /dev/null
@@ -1,65 +0,0 @@
-<%@page import="java.util.*" %>
-<%@page import="java.io.*" %>
-        <h2>Tor Metrics Portal: Graphs</h2>
-        <br/>
-        <h3>Relays and bridges in the Tor network</h3>
-        <br/>
-        <p>The number of relays and bridges in the Tor network can be
-        extracted from the hourly published network status consensuses
-        and sanitized bridge statuses.</p>
-        <ul>
-          <li>Past <a href="#networksize-30d">30</a>,
-              <a href="#networksize-90d">90</a>,
-              <a href="#networksize-180d">180</a> days</li>
-          <li><a href="#networksize-all">All data</a> up to today</li>
-<%
-    Calendar now = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
-    Calendar lastQuarter = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
-    lastQuarter.add(Calendar.MONTH, -3);
-    Calendar lastMonth = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
-    lastMonth.add(Calendar.MONTH, -1);
-    out.print("          <li>Annual graphs of\n");
-    for (int i = now.get(Calendar.YEAR); i > 2006; i--) {
-      out.print("              <a href=\"#networksize-" + i + "\">"
-          + i + "</a>,\n");
-    }
-    out.print("              <a href=\"#networksize-2006\">2006</a></li>\n");
-    out.print("          <li>Quarterly graphs of\n");
-    out.print(String.format("              <a href=\"#networksize-%1$tY-q%2$d\">"
-        + "Q%2$d %1$tY</a>,%n", now, 1 + now.get(Calendar.MONTH) / 3));
-    out.print(String.format("              <a href=\"#networksize-%1$tY-q%2$d\">"
-        + "Q%2$d %1$tY</a></li>%n", lastQuarter, 1 + lastQuarter.get(Calendar.MONTH) / 3));
-    out.print("          <li>Monthly graphs of\n");
-    out.print(String.format("              <a href=\"#networksize-%1$tY-%1$tm\">"
-        + "%1$tb %1$tY</a>,%n", now));
-    out.print(String.format("              <a href=\"#networksize-%1$tY-%1$tm\">"
-        + "%1$tb %1$tY</a></li>%n", lastMonth));
-    out.print("          <li><a href=\"csv/networksize.csv\">CSV</a> file\n"
-        + "              containing raw data</li>\n"
-        + "        </ul>\n"
-        + "        <p><a id=\"networksize-30d\"/>\n"
-        + "          <img src=\"graphs/networksize/networksize-30d.png\"/>\n"
-        + "        </p><p><a id=\"networksize-90d\"/>\n"
-        + "          <img src=\"graphs/networksize/networksize-90d.png\"/>\n"
-        + "        </p><p><a id=\"networksize-180d\"/>\n"
-        + "          <img src=\"graphs/networksize/networksize-180d.png\"/>\n"
-        + "        </p><p><a id=\"networksize-all\"/>\n"
-        + "          <img src=\"graphs/networksize/networksize-all.png\"/>\n");
-    for (int i = now.get(Calendar.YEAR); i > 2006; i--) {
-      out.print("        </p><p><a id=\"networksize-" + i + "\"/>\n"
-        + "          <img src=\"graphs/networksize/networksize-" + i + ".png\"/>\n");
-    }
-    out.print("        </p><p><a id=\"networksize-2006\"/>\n"
-        + "          <img src=\"graphs/networksize/networksize-2006.png\"/>\n");
-    out.print(String.format("        </p><p><a id=\"networksize-%1$tY-q%2$d\"/>\n"
-        + "          <img src=\"graphs/networksize/networksize-%1$tY-q%2$d.png\"/>\n",
-        now, 1 + now.get(Calendar.MONTH) / 3));
-    out.print(String.format("        </p><p><a id=\"networksize-%1$tY-q%2$d\"/>\n"
-        + "          <img src=\"graphs/networksize/networksize-%1$tY-q%2$d.png\"/>\n",
-        lastQuarter, 1 + lastQuarter.get(Calendar.MONTH) / 3));
-    out.print(String.format("        </p><p><a id=\"networksize-%1$tY-%1$tm\"/>\n"
-        + "          <img src=\"graphs/networksize/networksize-%1$tY-%1$tm.png\"/>\n", now));
-    out.print(String.format("        </p><p><a id=\"networksize-%1$tY-%1$tm\"/>\n"
-        + "          <img src=\"graphs/networksize/networksize-%1$tY-%1$tm.png\"/>\n", lastMonth));
-    out.print("        </p><br/>\n");
-%>
diff --git a/war/WEB-INF/templates/graphs_network.tpl.jsp b/war/WEB-INF/templates/graphs_network.tpl.jsp
new file mode 100644
index 0000000..88f7087
--- /dev/null
+++ b/war/WEB-INF/templates/graphs_network.tpl.jsp
@@ -0,0 +1,284 @@
+<%@page import="java.util.*" %>
+<h2>Tor Metrics Portal: Network</h2>
+<br/>
+<h3>Relays and bridges in the network</h3>
+<br/>
+<p>The following graph shows the average daily number of relays and
+bridges in the network.</p>
+<a id="networksize" />
+<%
+StringBuilder networksizeUrl = new StringBuilder("networksize.png");
+if ("networksize".equals(request.getParameter("graph"))) {
+  List<String> parameters = new ArrayList<String>();
+// TODO check values here!
+  String startParameter = request.getParameter("start"),
+      endParameter = request.getParameter("end");
+  if (startParameter != null && startParameter.length() > 0) {
+    parameters.add("start=" + startParameter);
+  }
+  if (endParameter != null && endParameter.length() > 0) {
+    parameters.add("end=" + endParameter);
+  }
+  if (parameters.size() > 0) {
+    networksizeUrl.append("?" + parameters.get(0));
+    if (parameters.size() > 1) {
+      for (int i = 1; i < parameters.size(); i++) {
+        networksizeUrl.append("&" + parameters.get(i));
+      }
+    }
+  }
+}
+out.println("<img src=\"" + networksizeUrl.toString() + "\" width=\"576\" "
+    + "height=\"360\" />");
+%>
+</p>
+<form action="network.html#networksize">
+  <div class="formrow">
+    <input type="hidden" name="graph" value="networksize"/>
+    <p>
+    <label class="startend" for="start">Start date (yyyy-mm-dd):</label>
+      <input type="text" name="start" id="start" size="10"
+        value="<%=("networksize".equals(request.getParameter("graph")) &&
+                   request.getParameter("start") != null) ?
+                      request.getParameter("start") : ""%>"/>
+    <label class="startend" for="end">End date (yyyy-mm-dd):</label>
+      <input type="text" name="end" id="end" size="10"
+        value="<%=("networksize".equals(request.getParameter("graph")) &&
+                   request.getParameter("end") != null) ?
+                      request.getParameter("end") : ""%>"/>
+    </p><p>
+    <input class="submit" type="submit" value="Update graph"/>
+    </p>
+  </div>
+</form>
+<p><a href="csv/networksize.csv">CSV</a> file containing all data.</p>
+<br/>
+
+<h3>Relays with Exit, Fast, Guard, and Stable flags</h3>
+<br/>
+<p>The directory authorities assign certain flags to relays that clients
+use for their path selection decisions. The following graph shows the
+average number of relays with these flags assigned.</p>
+<a id="relayflags" />
+<%
+StringBuilder relayflagsUrl = new StringBuilder("relayflags.png");
+if ("relayflags".equals(request.getParameter("graph"))) {
+  List<String> parameters = new ArrayList<String>();
+// TODO check values here!
+  String startParameter = request.getParameter("start"),
+      endParameter = request.getParameter("end");
+  String[] flagParameters = request.getParameterValues("flag");
+  if (startParameter != null && startParameter.length() > 0) {
+    parameters.add("start=" + startParameter);
+  }
+  if (endParameter != null && endParameter.length() > 0) {
+    parameters.add("end=" + endParameter);
+  }
+  if (flagParameters != null && flagParameters.length > 0) {
+    for (String flag : flagParameters) {
+      if (flag != null && flag.length() > 0) {
+        parameters.add("flag=" + flag);
+      }
+    }
+  }
+  if (parameters.size() > 0) {
+    relayflagsUrl.append("?" + parameters.get(0));
+    if (parameters.size() > 1) {
+      for (int i = 1; i < parameters.size(); i++) {
+        relayflagsUrl.append("&" + parameters.get(i));
+      }
+    }
+  }
+}
+out.println("<img src=\"" + relayflagsUrl.toString() + "\" width=\"576\" "
+    + "height=\"360\" />");
+%>
+</p>
+<form action="network.html#relayflags">
+  <div class="formrow">
+    <input type="hidden" name="graph" value="relayflags"/>
+    <p>
+    <label class="startend" for="start">Start date (yyyy-mm-dd):</label>
+      <input type="text" name="start" id="start" size="10"
+        value="<%=("relayflags".equals(request.getParameter("graph")) &&
+                   request.getParameter("start") != null) ?
+                      request.getParameter("start") : ""%>"/>
+    <label class="startend" for="end">End date (yyyy-mm-dd):</label>
+      <input type="text" name="end" id="end" size="10"
+        value="<%=("relayflags".equals(request.getParameter("graph")) &&
+                   request.getParameter("end") != null) ?
+                      request.getParameter("end") : ""%>"/>
+    </p><p>
+      <label>Relay flags: </label>
+      <input type="checkbox" name="flag" value="Running"> Running</input>
+      <input type="checkbox" name="flag" value="Exit"> Exit</input>
+      <input type="checkbox" name="flag" value="Fast"> Fast</input>
+      <input type="checkbox" name="flag" value="Guard"> Guard</input>
+      <input type="checkbox" name="flag" value="Stable"> Stable</input>
+    </p><p>
+    <input class="submit" type="submit" value="Update graph"/>
+    </p>
+  </div>
+</form>
+<br/>
+
+<h3>Relays by version</h3>
+<br/>
+<p>Relays report the Tor version that they are running to the directory
+authorities. The following graph shows the number of relays by
+version.</p>
+<a id="versions" />
+<%
+StringBuilder versionsUrl = new StringBuilder("versions.png");
+if ("versions".equals(request.getParameter("graph"))) {
+  List<String> parameters = new ArrayList<String>();
+// TODO check values here!
+  String startParameter = request.getParameter("start"),
+      endParameter = request.getParameter("end");
+  if (startParameter != null && startParameter.length() > 0) {
+    parameters.add("start=" + startParameter);
+  }
+  if (endParameter != null && endParameter.length() > 0) {
+    parameters.add("end=" + endParameter);
+  }
+  if (parameters.size() > 0) {
+    versionsUrl.append("?" + parameters.get(0));
+    if (parameters.size() > 1) {
+      for (int i = 1; i < parameters.size(); i++) {
+        versionsUrl.append("&" + parameters.get(i));
+      }
+    }
+  }
+}
+out.println("<img src=\"" + versionsUrl.toString() + "\" width=\"576\" "
+    + "height=\"360\" />");
+%>
+</p>
+<form action="network.html#versions">
+  <div class="formrow">
+    <input type="hidden" name="graph" value="versions"/>
+    <p>
+    <label class="startend" for="start">Start date (yyyy-mm-dd):</label>
+      <input type="text" name="start" id="start" size="10"
+        value="<%=("versions".equals(request.getParameter("graph")) &&
+                   request.getParameter("start") != null) ?
+                      request.getParameter("start") : ""%>"/>
+    <label class="startend" for="end">End date (yyyy-mm-dd):</label>
+      <input type="text" name="end" id="end" size="10"
+        value="<%=("versions".equals(request.getParameter("graph")) &&
+                   request.getParameter("end") != null) ?
+                      request.getParameter("end") : ""%>"/>
+    </p><p>
+    <input class="submit" type="submit" value="Update graph"/>
+    </p>
+  </div>
+</form>
+<br/>
+
+<h3>Relays by platform</h3>
+<br/>
+<p>Relays report the operating system they are running to the directory
+authorities. The following graph shows the number of relays by
+platform.</p>
+<a id="platforms" />
+<%
+StringBuilder platformsUrl = new StringBuilder("platforms.png");
+if ("platforms".equals(request.getParameter("graph"))) {
+  List<String> parameters = new ArrayList<String>();
+// TODO check values here!
+  String startParameter = request.getParameter("start"),
+      endParameter = request.getParameter("end");
+  if (startParameter != null && startParameter.length() > 0) {
+    parameters.add("start=" + startParameter);
+  }
+  if (endParameter != null && endParameter.length() > 0) {
+    parameters.add("end=" + endParameter);
+  }
+  if (parameters.size() > 0) {
+    platformsUrl.append("?" + parameters.get(0));
+    if (parameters.size() > 1) {
+      for (int i = 1; i < parameters.size(); i++) {
+        platformsUrl.append("&" + parameters.get(i));
+      }
+    }
+  }
+}
+out.println("<img src=\"" + platformsUrl.toString() + "\" width=\"576\" "
+    + "height=\"360\" />");
+%>
+</p>
+<form action="network.html#platforms">
+  <div class="formrow">
+    <input type="hidden" name="graph" value="platforms"/>
+    <p>
+    <label class="startend" for="start">Start date (yyyy-mm-dd):</label>
+      <input type="text" name="start" id="start" size="10"
+        value="<%=("platforms".equals(request.getParameter("graph")) &&
+                   request.getParameter("start") != null) ?
+                      request.getParameter("start") : ""%>"/>
+    <label class="startend" for="end">End date (yyyy-mm-dd):</label>
+      <input type="text" name="end" id="end" size="10"
+        value="<%=("platforms".equals(request.getParameter("graph")) &&
+                   request.getParameter("end") != null) ?
+                      request.getParameter("end") : ""%>"/>
+    </p><p>
+    <input class="submit" type="submit" value="Update graph"/>
+    </p>
+  </div>
+</form>
+<br/>
+
+<h3>Total relay bandwidth in the network</h3>
+<br/>
+<p>Relays report how much bandwidth they are willing to contribute and how
+many bytes they have read and written in the past 24 hours. The following
+graph shows total advertised bandwidth and bandwidth history of all relays
+in the network.</p>
+<a id="bandwidth" />
+<%
+StringBuilder bandwidthUrl = new StringBuilder("bandwidth.png");
+if ("bandwidth".equals(request.getParameter("graph"))) {
+  List<String> parameters = new ArrayList<String>();
+// TODO check values here!
+  String startParameter = request.getParameter("start"),
+      endParameter = request.getParameter("end");
+  if (startParameter != null && startParameter.length() > 0) {
+    parameters.add("start=" + startParameter);
+  }
+  if (endParameter != null && endParameter.length() > 0) {
+    parameters.add("end=" + endParameter);
+  }
+  if (parameters.size() > 0) {
+    bandwidthUrl.append("?" + parameters.get(0));
+    if (parameters.size() > 1) {
+      for (int i = 1; i < parameters.size(); i++) {
+        bandwidthUrl.append("&" + parameters.get(i));
+      }
+    }
+  }
+}
+out.println("<img src=\"" + bandwidthUrl.toString() + "\" width=\"576\" "
+    + "height=\"360\" />");
+%>
+</p>
+<form action="network.html#bandwidth">
+  <div class="formrow">
+    <input type="hidden" name="graph" value="bandwidth"/>
+    <p>
+    <label class="startend" for="start">Start date (yyyy-mm-dd):</label>
+      <input type="text" name="start" id="start" size="10"
+        value="<%=("bandwidth".equals(request.getParameter("graph")) &&
+                   request.getParameter("start") != null) ?
+                      request.getParameter("start") : ""%>"/>
+    <label class="startend" for="end">End date (yyyy-mm-dd):</label>
+      <input type="text" name="end" id="end" size="10"
+        value="<%=("bandwidth".equals(request.getParameter("graph")) &&
+                   request.getParameter("end") != null) ?
+                      request.getParameter("end") : ""%>"/>
+    </p><p>
+    <input class="submit" type="submit" value="Update graph"/>
+    </p>
+  </div>
+</form>
+<br/>
+
diff --git a/war/WEB-INF/templates/graphs_new-users.tpl.jsp b/war/WEB-INF/templates/graphs_new-users.tpl.jsp
deleted file mode 100644
index e5c5f95..0000000
--- a/war/WEB-INF/templates/graphs_new-users.tpl.jsp
+++ /dev/null
@@ -1,68 +0,0 @@
-<%@page import="java.util.*" %>
-        <h2>Tor Metrics Portal: Graphs</h2>
-        <br/>
-        <h3>New or returning, directly connecting Tor users</h3>
-        <br/>
-        <p>Users connecting to the Tor network for the first time request
-        a list of running relays from one of currently seven directory
-        authorities. Likewise, returning users whose network information is
-        out of date connect to one of the directory authorities to
-        download a fresh list of relays. The following graphs display an
-        estimate of new or returning Tor users based on the requests as
-        seen by gabelmoo, one of the directory authorities.</p>
-        <ul>
-          <li><a href="#total">All users</a></li>
-          <li><a href="#australia">Australia</a></li>
-          <li><a href="#bahrain">Bahrain</a></li>
-          <li><a href="#brazil">Brazil</a></li>
-          <li><a href="#burma">Burma</a></li>
-          <li><a href="#canada">Canada</a></li>
-          <li><a href="#china">China</a></li>
-          <li><a href="#cuba">Cuba</a></li>
-          <li><a href="#ethiopia">Ethiopia</a></li>
-          <li><a href="#france">France</a></li>
-          <li><a href="#germany">Germany</a></li>
-          <li><a href="#iran">Iran</a></li>
-          <li><a href="#italy">Italy</a></li>
-          <li><a href="#japan">Japan</a></li>
-          <li><a href="#poland">Poland</a></li>
-          <li><a href="#russia">Russia</a></li>
-          <li><a href="#saudi">Saudi</a></li>
-          <li><a href="#southkorea">South Korea</a></li>
-          <li><a href="#sweden">Sweden</a></li>
-          <li><a href="#syria">Syria</a></li>
-          <li><a href="#tunisia">Tunisia</a></li>
-          <li><a href="#turkmenistan">Turkmenistan</a></li>
-          <li><a href="#uk">U.K.</a></li>
-          <li><a href="#usa">U.S.A.</a></li>
-          <li><a href="#uzbekistan">Uzbekistan</a></li>
-          <li><a href="#vietnam">Vietnam</a></li>
-          <li><a href="#yemen">Yemen</a></li>
-        </ul>
-        <ul>
-          <li><a href="csv/new-users.csv">CSV</a> file containing all
-          data.</li>
-        </ul>
-<%
-    List<String> countries = Arrays.asList(("total,australia,bahrain,"
-        + "brazil,burma,canada,china,cuba,ethiopia,france,germany,iran,"
-        + "italy,japan,poland,russia,saudi,southkorea,sweden,syria,"
-        + "tunisia,turkmenistan,uk,usa,uzbekistan,vietnam,yemen").
-        split(","));
-    List<String> suffixes = new ArrayList<String>(Arrays.asList(
-        "30d,90d,180d,all".split(",")));
-    Calendar now = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
-    suffixes.add(String.format("%tY", now));
-    suffixes.add(String.format("%1$tY-q%2$d", now,
-        1 + now.get(Calendar.MONTH) / 3));
-    suffixes.add(String.format("%1$tY-%1$tm", now));
-    for (String country : countries) {
-      out.print("        <p><a id=\"" + country + "\"/>\n");
-      for (String suffix : suffixes) {
-        out.print("        <img src=\"graphs/new-users/" + country
-            + "-new-" + suffix + ".png\"/>\n");
-      }
-      out.print("        </p>");
-    }
-%>
-        <br/>
diff --git a/war/WEB-INF/templates/graphs_packages.tpl.jsp b/war/WEB-INF/templates/graphs_packages.tpl.jsp
new file mode 100644
index 0000000..704dcc8
--- /dev/null
+++ b/war/WEB-INF/templates/graphs_packages.tpl.jsp
@@ -0,0 +1,71 @@
+<%@page import="java.util.*" %>
+<h2>Tor Metrics Portal: Downloaded Packages</h2>
+<br/>
+<h3>Packages requested from GetTor</h3>
+<br/>
+<p>GetTor allows users to fetch the Tor software via email. The following
+graph shows the number of packages requested from GetTor per day.</p>
+<p>
+<a id="gettor" />
+<%
+if ("gettor".equals(request.getParameter("graph"))) {
+  List<String> parameters = new ArrayList<String>();
+// TODO check values here!
+  String startParameter = request.getParameter("start"),
+      endParameter = request.getParameter("end"),
+      bundleParameter = request.getParameter("bundle");
+  if (startParameter != null && startParameter.length() > 0) {
+    parameters.add("start=" + startParameter);
+  }
+  if (endParameter != null && endParameter.length() > 0) {
+    parameters.add("end=" + endParameter);
+  }
+  if (bundleParameter != null && bundleParameter.length() > 0) {
+    parameters.add("bundle=" + bundleParameter);
+  }
+  StringBuilder url = new StringBuilder("gettor.png");
+  if (parameters.size() > 0) {
+    url.append("?" + parameters.get(0));
+    if (parameters.size() > 1) {
+      for (int i = 1; i < parameters.size(); i++) {
+        url.append("&" + parameters.get(i));
+      }
+    }
+  }
+  out.println("<img src=\"" + url.toString() + "\" width=\"576\" "
+      + "height=\"360\" />");
+} else {%>
+  <img src="gettor.png" width="576" height="360" />
+<%
+}
+%>
+</p>
+
+<form action="packages.html#gettor">
+  <div class="formrow">
+    <input type="hidden" name="graph" value="gettor"/>
+    <p>
+    <label class="startend" for="start">Start date (yyyy-mm-dd):</label>
+      <input type="text" name="start" id="start" size="10"
+        value="<%=("gettor".equals(request.getParameter("graph")) &&
+                   request.getParameter("start") != null) ?
+                      request.getParameter("start") : ""%>"/>
+    <label class="startend" for="end">End date (yyyy-mm-dd):</label>
+      <input type="text" name="end" id="end" size="10"
+        value="<%=("gettor".equals(request.getParameter("graph")) &&
+                   request.getParameter("end") != null) ?
+                      request.getParameter("end") : ""%>"/>
+    </p><p>
+      <label>Packages: </label>
+      <input type="radio" name="bundle" value="all"> Total packages</input>
+      <input type="radio" name="bundle" value="en"> TBB (en)</input>
+      <input type="radio" name="bundle" value="zh_CN"> TBB (zh_CN)</input>
+      <input type="radio" name="bundle" value="fa"> TBB (fa)</input>
+    </p><p>
+    <input class="submit" type="submit" value="Update graph"/>
+    </p>
+  </div>
+</form>
+
+<p><a href="csv/gettor.csv">CSV</a> file containing all data.</p>
+<br/>
diff --git a/war/WEB-INF/templates/graphs_performance.tpl.jsp b/war/WEB-INF/templates/graphs_performance.tpl.jsp
new file mode 100644
index 0000000..862b435
--- /dev/null
+++ b/war/WEB-INF/templates/graphs_performance.tpl.jsp
@@ -0,0 +1,80 @@
+<%@page import="java.util.*" %>
+<h2>Tor Metrics Portal: Performance</h2>
+<br/>
+<h3>Time to download files over Tor</h3>
+<br/>
+<p>The following graphs show the performance of the Tor network as
+experienced by its users. The graphs contain the average (median) time to
+request files of three different sizes over Tor as well as first and third
+quartile of request times.</p>
+<a id="torperf" />
+<%
+if ("torperf".equals(request.getParameter("graph"))) {
+  List<String> parameters = new ArrayList<String>();
+// TODO check values here!
+  String startParameter = request.getParameter("start"),
+      endParameter = request.getParameter("end"),
+      sourceParameter = request.getParameter("source"),
+      filesizeParameter = request.getParameter("filesize");
+  if (startParameter != null && startParameter.length() > 0) {
+    parameters.add("start=" + startParameter);
+  }
+  if (endParameter != null && endParameter.length() > 0) {
+    parameters.add("end=" + endParameter);
+  }
+  if (sourceParameter != null && sourceParameter.length() > 0) {
+    parameters.add("source=" + sourceParameter);
+  }
+  if (filesizeParameter != null && filesizeParameter.length() > 0) {
+    parameters.add("filesize=" + filesizeParameter);
+  }
+  StringBuilder url = new StringBuilder("torperf.png");
+  if (parameters.size() > 0) {
+    url.append("?" + parameters.get(0));
+    if (parameters.size() > 1) {
+      for (int i = 1; i < parameters.size(); i++) {
+        url.append("&" + parameters.get(i));
+      }
+    }
+  }
+  out.println("<img src=\"" + url.toString() + "\" width=\"576\" "
+      + "height=\"360\" />");
+} else {%>
+  <img src="torperf.png" width="576" height="360" />
+<%
+}
+%>
+
+</p>
+<form action="performance.html#torperf">
+  <div class="formrow">
+    <input type="hidden" name="graph" value="torperf"/>
+    <p>
+    <label class="startend" for="start">Start date (yyyy-mm-dd):</label>
+      <input type="text" name="start" id="start" size="10"
+        value="<%=("torperf".equals(request.getParameter("graph")) &&
+                   request.getParameter("start") != null) ?
+                      request.getParameter("start") : ""%>"/>
+    <label class="startend" for="end">End date (yyyy-mm-dd):</label>
+      <input type="text" name="end" id="end" size="10"
+        value="<%=("torperf".equals(request.getParameter("graph")) &&
+                   request.getParameter("end") != null) ?
+                      request.getParameter("end") : ""%>"/>
+    </p><p>
+      <label>Source: </label>
+      <input type="radio" name="source" value="torperf"> torperf</input>
+      <input type="radio" name="source" value="moria"> moria</input>
+      <input type="radio" name="source" value="siv"> siv</input>
+    </p><p>
+      <label>File size: </label>
+      <input type="radio" name="filesize" value="50kb"> 50 KiB</input>
+      <input type="radio" name="filesize" value="1mb"> 1 MiB</input>
+      <input type="radio" name="filesize" value="5mb"> 5 MiB</input>
+    </p><p>
+    <input class="submit" type="submit" value="Update graph"/>
+    </p>
+  </div>
+</form>
+
+<p><a href="csv/torperf.csv">CSV</a> file containing all data.</p>
+<br/>
diff --git a/war/WEB-INF/templates/graphs_recurring-users.tpl.jsp b/war/WEB-INF/templates/graphs_recurring-users.tpl.jsp
deleted file mode 100644
index 0235f02..0000000
--- a/war/WEB-INF/templates/graphs_recurring-users.tpl.jsp
+++ /dev/null
@@ -1,73 +0,0 @@
-<%@ page import="java.util.*" %>
-        <h2>Tor Metrics Portal: Graphs</h2>
-        <br/>
-        <h3>Recurring, directly connecting Tor users</h3>
-        <br/>
-        <p>After being connected to the Tor network, users need to refresh
-        their list of running relays on a regular basis. They send their
-        requests to one out of a few hundred directory mirrors to save
-        bandwidth of the directory authorities. The following graphs show
-        an estimate of recurring Tor users based on the requests as seen
-        by trusted, a particularly fast directory mirror.</p>
-        <ul>
-          <li><a href="#total">All users</a></li>
-          <li><a href="#australia">Australia</a></li>
-          <li><a href="#bahrain">Bahrain</a></li>
-          <li><a href="#brazil">Brazil</a></li>
-          <li><a href="#burma">Burma</a></li>
-          <li><a href="#canada">Canada</a></li>
-          <li><a href="#china">China</a></li>
-          <li><a href="#cuba">Cuba</a></li>
-          <li><a href="#ethiopia">Ethiopia</a></li>
-          <li><a href="#france">France</a></li>
-          <li><a href="#germany">Germany</a></li>
-          <li><a href="#iran">Iran</a></li>
-          <li><a href="#italy">Italy</a></li>
-          <li><a href="#japan">Japan</a></li>
-          <li><a href="#poland">Poland</a></li>
-          <li><a href="#russia">Russia</a></li>
-          <li><a href="#saudi">Saudi</a></li>
-          <li><a href="#southkorea">South Korea</a></li>
-          <li><a href="#sweden">Sweden</a></li>
-          <li><a href="#syria">Syria</a></li>
-          <li><a href="#tunisia">Tunisia</a></li>
-          <li><a href="#turkmenistan">Turkmenistan</a></li>
-          <li><a href="#uk">U.K.</a></li>
-          <li><a href="#usa">U.S.A.</a></li>
-          <li><a href="#uzbekistan">Uzbekistan</a></li>
-          <li><a href="#vietnam">Vietnam</a></li>
-          <li><a href="#yemen">Yemen</a></li>
-        </ul>
-        <ul>
-          <li><a href="csv/recurring-users.csv">CSV</a> file containing
-          all data.</li>
-          <li><a href="csv/monthly-users-peak.csv">CSV</a> file containing
-          peak daily Tor users (recurring and bridge) per month by
-          country.</li>
-          <li><a href="csv/monthly-users-average.csv">CSV</a> file
-          containing average daily Tor users (recurring and bridge) per
-          month by country.</li>
-        </ul>
-<%
-    List<String> countries = Arrays.asList(("total,australia,bahrain,"
-        + "brazil,burma,canada,china,cuba,ethiopia,france,germany,iran,"
-        + "italy,japan,poland,russia,saudi,southkorea,sweden,syria,"
-        + "tunisia,turkmenistan,uk,usa,uzbekistan,vietnam,yemen").
-        split(","));
-    List<String> suffixes = new ArrayList<String>(Arrays.asList(
-        "30d,90d,180d,all".split(",")));
-    Calendar now = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
-    suffixes.add(String.format("%tY", now));
-    suffixes.add(String.format("%1$tY-q%2$d", now,
-        1 + now.get(Calendar.MONTH) / 3));
-    suffixes.add(String.format("%1$tY-%1$tm", now));
-    for (String country : countries) {
-      out.print("        <p><a id=\"" + country + "\"/>\n");
-      for (String suffix : suffixes) {
-        out.print("        <img src=\"graphs/direct-users/" + country
-            + "-direct-" + suffix + ".png\"/>\n");
-      }
-      out.print("        </p>\n");
-    }
-%>
-        <br/>
diff --git a/war/WEB-INF/templates/graphs_torperf.tpl.jsp b/war/WEB-INF/templates/graphs_torperf.tpl.jsp
deleted file mode 100644
index f617240..0000000
--- a/war/WEB-INF/templates/graphs_torperf.tpl.jsp
+++ /dev/null
@@ -1,37 +0,0 @@
-        <h2>Tor Metrics Portal: Graphs</h2>
-        <br/>
-        <h3>Time to complete requests</h3>
-        <br/>
-        <p>The following graphs show the performance of the Tor network as
-        experienced by its users. The graphs contain the average (median)
-        time to request files of three different sizes over the network as
-        well as first and third quartile of request times.</p>
-        <p><a href="csv/torperf.csv">CSV</a> file containing all data.</p>
-        <img src="graphs/torperf/torperf-50kb-torperf-12m.png"/>
-        <img src="graphs/torperf/torperf-50kb-moria-12m.png"/>
-        <img src="graphs/torperf/torperf-50kb-siv-12m.png"/>
-        <img src="graphs/torperf/torperf-50kb-torperf-6m.png"/>
-        <img src="graphs/torperf/torperf-50kb-moria-6m.png"/>
-        <img src="graphs/torperf/torperf-50kb-siv-6m.png"/>
-        <img src="graphs/torperf/torperf-50kb-torperf-2w.png"/>
-        <img src="graphs/torperf/torperf-50kb-moria-2w.png"/>
-        <img src="graphs/torperf/torperf-50kb-siv-2w.png"/>
-        <img src="graphs/torperf/torperf-1mb-torperf-12m.png"/>
-        <img src="graphs/torperf/torperf-1mb-moria-12m.png"/>
-        <img src="graphs/torperf/torperf-1mb-siv-12m.png"/>
-        <img src="graphs/torperf/torperf-1mb-torperf-6m.png"/>
-        <img src="graphs/torperf/torperf-1mb-moria-6m.png"/>
-        <img src="graphs/torperf/torperf-1mb-siv-6m.png"/>
-        <img src="graphs/torperf/torperf-1mb-torperf-2w.png"/>
-        <img src="graphs/torperf/torperf-1mb-moria-2w.png"/>
-        <img src="graphs/torperf/torperf-1mb-siv-2w.png"/>
-        <img src="graphs/torperf/torperf-5mb-torperf-12m.png"/>
-        <img src="graphs/torperf/torperf-5mb-moria-12m.png"/>
-        <img src="graphs/torperf/torperf-5mb-siv-12m.png"/>
-        <img src="graphs/torperf/torperf-5mb-torperf-6m.png"/>
-        <img src="graphs/torperf/torperf-5mb-moria-6m.png"/>
-        <img src="graphs/torperf/torperf-5mb-siv-6m.png"/>
-        <img src="graphs/torperf/torperf-5mb-torperf-2w.png"/>
-        <img src="graphs/torperf/torperf-5mb-moria-2w.png"/>
-        <img src="graphs/torperf/torperf-5mb-siv-2w.png"/>
-        <br/>
diff --git a/war/WEB-INF/templates/graphs_users.tpl.jsp b/war/WEB-INF/templates/graphs_users.tpl.jsp
new file mode 100644
index 0000000..d8ea449
--- /dev/null
+++ b/war/WEB-INF/templates/graphs_users.tpl.jsp
@@ -0,0 +1,292 @@
+<%@page import="java.util.*" %>
+<h2>Tor Metrics Portal: Users</h2>
+<br/>
+<h3>New or returning, directly connecting Tor users</h3>
+<br/>
+<p>Users connecting to the Tor network for the first time request a list
+of running relays from one of currently seven directory authorities.
+Likewise, returning users whose network information is out of date connect
+to one of the directory authorities to download a fresh list of relays.
+The following graphs display an estimate of new or returning Tor users
+based on the requests as seen by gabelmoo, one of the directory
+authorities.</p>
+<a id="new-users" />
+<%
+StringBuilder newUsersUrl = new StringBuilder("new-users.png");
+if ("new-users".equals(request.getParameter("graph"))) {
+  List<String> parameters = new ArrayList<String>();
+// TODO check values here!
+  String startParameter = request.getParameter("start"),
+      endParameter = request.getParameter("end");
+  String[] countryParameters = request.getParameterValues("country");
+  if (startParameter != null && startParameter.length() > 0) {
+    parameters.add("start=" + startParameter);
+  }
+  if (endParameter != null && endParameter.length() > 0) {
+    parameters.add("end=" + endParameter);
+  }
+  if (countryParameters != null && countryParameters.length > 0) {
+    for (String country : countryParameters) {
+      if (country != null && country.length() > 0) {
+        parameters.add("country=" + country);
+      }
+    }
+  }
+  if (parameters.size() > 0) {
+    newUsersUrl.append("?" + parameters.get(0));
+    if (parameters.size() > 1) {
+      for (int i = 1; i < parameters.size(); i++) {
+        newUsersUrl.append("&" + parameters.get(i));
+      }
+    }
+  }
+}
+out.println("<img src=\"" + newUsersUrl.toString() + "\" width=\"576\" "
+    + "height=\"360\" />");
+%>
+</p>
+<form action="users.html#new-users">
+  <div class="formrow">
+    <input type="hidden" name="graph" value="new-users"/>
+    <p>
+    <label class="startend" for="start">Start date (yyyy-mm-dd):</label>
+      <input type="text" name="start" id="start" size="10"
+        value="<%=("new-users".equals(request.getParameter("graph")) &&
+                   request.getParameter("start") != null) ?
+                      request.getParameter("start") : ""%>"/>
+    <label class="startend" for="end">End date (yyyy-mm-dd):</label>
+      <input type="text" name="end" id="end" size="10"
+        value="<%=("new-users".equals(request.getParameter("graph")) &&
+                   request.getParameter("end") != null) ?
+                      request.getParameter("end") : ""%>"/>
+    </p><p>
+      <label>Source: </label>
+      <input type="radio" name="country" value="all">All users</input>
+      <input type="radio" name="country" value="au">Australia</input>
+      <input type="radio" name="country" value="bh">Bahrain</input>
+      <input type="radio" name="country" value="br">Brazil</input>
+      <input type="radio" name="country" value="ca">Canada</input>
+      <input type="radio" name="country" value="cn">China</input>
+      <input type="radio" name="country" value="cu">Cuba</input>
+      <input type="radio" name="country" value="de">Germany</input>
+      <input type="radio" name="country" value="et">Ethiopia</input>
+      <input type="radio" name="country" value="fr">France</input>
+      <input type="radio" name="country" value="gb">U.K.</input>
+      <input type="radio" name="country" value="ir">Iran</input>
+      <input type="radio" name="country" value="it">Italy</input>
+      <input type="radio" name="country" value="jp">Japan</input>
+      <input type="radio" name="country" value="kr">South Korea</input>
+      <input type="radio" name="country" value="mm">Burma</input>
+      <input type="radio" name="country" value="pl">Poland</input>
+      <input type="radio" name="country" value="ru">Russia</input>
+      <input type="radio" name="country" value="sa">Saudi Arabia</input>
+      <input type="radio" name="country" value="se">Sweden</input>
+      <input type="radio" name="country" value="sy">Syria</input>
+      <input type="radio" name="country" value="tn">Tunisia</input>
+      <input type="radio" name="country" value="tm">Turkmenistan</input>
+      <input type="radio" name="country" value="us">U.S.A.</input>
+      <input type="radio" name="country" value="uz">Uzbekistan</input>
+      <input type="radio" name="country" value="vn">Vietnam</input>
+      <input type="radio" name="country" value="ye">Yemen</input>
+    </p><p>
+    <input class="submit" type="submit" value="Update graph"/>
+    </p>
+  </div>
+</form>
+<p><a href="csv/new-users.csv">CSV</a> file containing all data.</p>
+<br/>
+
+<h3>Recurring, directly connecting Tor users</h3>
+<br/>
+<p>After being connected to the Tor network, users need to refresh their
+list of running relays on a regular basis. They send their requests to one
+out of a few hundred directory mirrors to save bandwidth of the directory
+authorities. The following graphs show an estimate of recurring Tor users
+based on the requests as seen by trusted, a particularly fast directory
+mirror.</p>
+<a id="direct-users" />
+<%
+StringBuilder directUsersUrl = new StringBuilder("direct-users.png");
+if ("direct-users".equals(request.getParameter("graph"))) {
+  List<String> parameters = new ArrayList<String>();
+// TODO check values here!
+  String startParameter = request.getParameter("start"),
+      endParameter = request.getParameter("end");
+  String[] countryParameters = request.getParameterValues("country");
+  if (startParameter != null && startParameter.length() > 0) {
+    parameters.add("start=" + startParameter);
+  }
+  if (endParameter != null && endParameter.length() > 0) {
+    parameters.add("end=" + endParameter);
+  }
+  if (countryParameters != null && countryParameters.length > 0) {
+    for (String country : countryParameters) {
+      if (country != null && country.length() > 0) {
+        parameters.add("country=" + country);
+      }
+    }
+  }
+  if (parameters.size() > 0) {
+    directUsersUrl.append("?" + parameters.get(0));
+    if (parameters.size() > 1) {
+      for (int i = 1; i < parameters.size(); i++) {
+        directUsersUrl.append("&" + parameters.get(i));
+      }
+    }
+  }
+}
+out.println("<img src=\"" + directUsersUrl.toString() + "\" width=\"576\" "
+    + "height=\"360\" />");
+%>
+</p>
+<form action="users.html#direct-users">
+  <div class="formrow">
+    <input type="hidden" name="graph" value="direct-users"/>
+    <p>
+    <label class="startend" for="start">Start date (yyyy-mm-dd):</label>
+      <input type="text" name="start" id="start" size="10"
+        value="<%=("direct-users".equals(request.getParameter("graph")) &&
+                   request.getParameter("start") != null) ?
+                      request.getParameter("start") : ""%>"/>
+    <label class="startend" for="end">End date (yyyy-mm-dd):</label>
+      <input type="text" name="end" id="end" size="10"
+        value="<%=("direct-users".equals(request.getParameter("graph")) &&
+                   request.getParameter("end") != null) ?
+                      request.getParameter("end") : ""%>"/>
+    </p><p>
+      <label>Source: </label>
+      <input type="radio" name="country" value="all">All users</input>
+      <input type="radio" name="country" value="au">Australia</input>
+      <input type="radio" name="country" value="bh">Bahrain</input>
+      <input type="radio" name="country" value="br">Brazil</input>
+      <input type="radio" name="country" value="ca">Canada</input>
+      <input type="radio" name="country" value="cn">China</input>
+      <input type="radio" name="country" value="cu">Cuba</input>
+      <input type="radio" name="country" value="de">Germany</input>
+      <input type="radio" name="country" value="et">Ethiopia</input>
+      <input type="radio" name="country" value="fr">France</input>
+      <input type="radio" name="country" value="gb">U.K.</input>
+      <input type="radio" name="country" value="ir">Iran</input>
+      <input type="radio" name="country" value="it">Italy</input>
+      <input type="radio" name="country" value="jp">Japan</input>
+      <input type="radio" name="country" value="kr">South Korea</input>
+      <input type="radio" name="country" value="mm">Burma</input>
+      <input type="radio" name="country" value="pl">Poland</input>
+      <input type="radio" name="country" value="ru">Russia</input>
+      <input type="radio" name="country" value="sa">Saudi Arabia</input>
+      <input type="radio" name="country" value="se">Sweden</input>
+      <input type="radio" name="country" value="sy">Syria</input>
+      <input type="radio" name="country" value="tn">Tunisia</input>
+      <input type="radio" name="country" value="tm">Turkmenistan</input>
+      <input type="radio" name="country" value="us">U.S.A.</input>
+      <input type="radio" name="country" value="uz">Uzbekistan</input>
+      <input type="radio" name="country" value="vn">Vietnam</input>
+      <input type="radio" name="country" value="ye">Yemen</input>
+    </p><p>
+    <input class="submit" type="submit" value="Update graph"/>
+    </p>
+  </div>
+</form>
+<p><a href="csv/direct-users.csv">CSV</a> file containing all data.</p>
+<p><a href="csv/monthly-users-peak.csv">CSV</a> file containing peak daily
+Tor users (recurring and bridge) per month by country.</p>
+<p><a href="csv/monthly-users-average.csv">CSV</a> file containing average
+daily Tor users (recurring and bridge) per month by country.</p>
+<br/>
+
+<h3>Tor users via bridges</h3>
+<br/>
+<p>Users who cannot connect directly to the Tor network instead connect
+via bridges, which are non-public relays. The following graphs display an
+estimate of Tor users via bridges based on the unique IP addresses as seen
+by a few hundred bridges.</p>
+<a id="bridge-users" />
+<%
+StringBuilder bridgeUsersUrl = new StringBuilder("bridge-users.png");
+if ("bridge-users".equals(request.getParameter("graph"))) {
+  List<String> parameters = new ArrayList<String>();
+// TODO check values here!
+  String startParameter = request.getParameter("start"),
+      endParameter = request.getParameter("end");
+  String[] countryParameters = request.getParameterValues("country");
+  if (startParameter != null && startParameter.length() > 0) {
+    parameters.add("start=" + startParameter);
+  }
+  if (endParameter != null && endParameter.length() > 0) {
+    parameters.add("end=" + endParameter);
+  }
+  if (countryParameters != null && countryParameters.length > 0) {
+    for (String country : countryParameters) {
+      if (country != null && country.length() > 0) {
+        parameters.add("country=" + country);
+      }
+    }
+  }
+  if (parameters.size() > 0) {
+    bridgeUsersUrl.append("?" + parameters.get(0));
+    if (parameters.size() > 1) {
+      for (int i = 1; i < parameters.size(); i++) {
+        bridgeUsersUrl.append("&" + parameters.get(i));
+      }
+    }
+  }
+}
+out.println("<img src=\"" + bridgeUsersUrl.toString() + "\" width=\"576\" "
+    + "height=\"360\" />");
+%>
+</p>
+<form action="users.html#bridge-users">
+  <div class="formrow">
+    <input type="hidden" name="graph" value="bridge-users"/>
+    <p>
+    <label class="startend" for="start">Start date (yyyy-mm-dd):</label>
+      <input type="text" name="start" id="start" size="10"
+        value="<%=("bridge-users".equals(request.getParameter("graph")) &&
+                   request.getParameter("start") != null) ?
+                      request.getParameter("start") : ""%>"/>
+    <label class="startend" for="end">End date (yyyy-mm-dd):</label>
+      <input type="text" name="end" id="end" size="10"
+        value="<%=("bridge-users".equals(request.getParameter("graph")) &&
+                   request.getParameter("end") != null) ?
+                      request.getParameter("end") : ""%>"/>
+    </p><p>
+      <label>Source: </label>
+      <input type="radio" name="country" value="all">All users</input>
+      <input type="radio" name="country" value="au">Australia</input>
+      <input type="radio" name="country" value="bh">Bahrain</input>
+      <input type="radio" name="country" value="br">Brazil</input>
+      <input type="radio" name="country" value="ca">Canada</input>
+      <input type="radio" name="country" value="cn">China</input>
+      <input type="radio" name="country" value="cu">Cuba</input>
+      <input type="radio" name="country" value="de">Germany</input>
+      <input type="radio" name="country" value="et">Ethiopia</input>
+      <input type="radio" name="country" value="fr">France</input>
+      <input type="radio" name="country" value="gb">U.K.</input>
+      <input type="radio" name="country" value="ir">Iran</input>
+      <input type="radio" name="country" value="it">Italy</input>
+      <input type="radio" name="country" value="jp">Japan</input>
+      <input type="radio" name="country" value="kr">South Korea</input>
+      <input type="radio" name="country" value="mm">Burma</input>
+      <input type="radio" name="country" value="pl">Poland</input>
+      <input type="radio" name="country" value="ru">Russia</input>
+      <input type="radio" name="country" value="sa">Saudi Arabia</input>
+      <input type="radio" name="country" value="se">Sweden</input>
+      <input type="radio" name="country" value="sy">Syria</input>
+      <input type="radio" name="country" value="tn">Tunisia</input>
+      <input type="radio" name="country" value="tm">Turkmenistan</input>
+      <input type="radio" name="country" value="us">U.S.A.</input>
+      <input type="radio" name="country" value="uz">Uzbekistan</input>
+      <input type="radio" name="country" value="vn">Vietnam</input>
+      <input type="radio" name="country" value="ye">Yemen</input>
+    </p><p>
+    <input class="submit" type="submit" value="Update graph"/>
+    </p>
+  </div>
+</form>
+<p><a href="csv/bridge-users.csv">CSV</a> file containing all data.</p>
+<p><a href="csv/monthly-users-peak.csv">CSV</a> file containing peak daily
+Tor users (recurring and bridge) per month by country.</p>
+<p><a href="csv/monthly-users-average.csv">CSV</a> file containing average
+daily Tor users (recurring and bridge) per month by country.</p>
+<br/>
+
diff --git a/war/WEB-INF/templates/index.tpl.jsp b/war/WEB-INF/templates/index.tpl.jsp
index 8bf9132..f3c5472 100644
--- a/war/WEB-INF/templates/index.tpl.jsp
+++ b/war/WEB-INF/templates/index.tpl.jsp
@@ -20,58 +20,22 @@
         <br/>
         <h3>News</h3>
         <ul>
-          <li>September 9, 2010: <a href="custom-graph.html">Custom
-          graphs</a> on network size, relay platforms, versions, and
+          <li>October 7, 2010: Custom graphs are now available for all
+          <a href="graphs.html">graphs</a>. Based on work by Kevin
+          Berry.</li>
+          <li>September 9, 2010: Custom
+          graphs on network size, relay platforms, versions, and
           observed bandwidth available. Implemented by Kevin Berry.</li>
           <li>September 2, 2010: New <a href="relay-search.html">relay
           search</a> feature available.</li>
           <li>August 31, 2010: Named relays can now be found more easily
           on the consensus-health page by using anchors, e.g.,
           <a href="consensus-health.html#gabelmoo">https://metrics.torproject.org/consensus-health.html#gabelmoo</a> .</li>
-          <li>August 16, 2010: There are now graphs for total
-          <a href="new-users-graphs.html">new or returning</a> and
-          <a href="recurring-users-graphs.html">recurring</a> directly
-          connecting users and total
-          <a href="bridge-users-graphs.html">bridge users</a> as well as
+          <li>August 16, 2010: There are now
+          <a href="users.html">graphs</a> for total new or returning and
+          recurring directly connecting users and total bridge users as
+          well as
           for users coming from Australia, Brazil, Canada, France,
           Germany, Italy, Japan, Poland, Russia, South Korea, Sweden,
           U.K., and U.S.A.</li>
-          <li>June 7, 2010: The <a href="exonerator.html">ExoneraTor</a>
-          now uses the most recent network information as its data basis
-          (as opposed to monthly snapshots). As a by-product,
-          <a href="consensus?valid-after=2010-06-01-12-00-00">network
-          status consensuses</a> and <a href="serverdesc?desc-id=b7461d591738b4c7d54393acde0efc6d66d1e7fc">server
-          descriptors</a> are now browsable by valid-after time and
-          descriptor digest, respectively. More sophisticated descriptor
-          search functions will follow.</li>
-          <li>June 7, 2010: The <a href="torperf-graphs.html">graphs on
-          user-experienced download times</a> are now drawn using
-          (a modified) <a href="http://had.co.nz/ggplot2/";>ggplot2</a>.
-          Modifications to ggplot2 include
-          <a href="https://stat.ethz.ch/pipermail/r-help/2010-June/241559.html";>drawing
-          a ribbon only for intervals with non-NA values</a> and
-          <a href="https://stat.ethz.ch/pipermail/r-help/2010-June/241618.html";>positioning
-          the legend at the top of a graph</a>. This concludes the
-          transition to ggplot2 and will greatly facilitate dynamic graph
-          generation in the future.</li>
-          <li>May 26, 2010: The monthly
-          <a href="data.html#relaydesc">relay</a> and
-          <a href="data.html#bridgedesc">bridge descriptor tarballs</a>
-          are now updated every day. Beginning with May 2010, the bridge
-          descriptor tarballs do not contain country codes anymore,
-          because it was tough to maintain. If your research requires this
-          or any other detail, contact us and we'll sort something out.
-          <li>May 25, 2010: The
-          <a href="bridge-users-graphs.html">graphs on daily bridge users</a>
-          are now more
-          accurate by excluding broken "geoip-stats" data of 0.2.2.x
-          versions and including "bridge-stats" data of bridges running
-          0.2.2.7-alpha or higher.</li>
-          <li>May 19, 2010: The graphs on
-          <a href="recurring-users-graphs.html">recurring users</a> and
-          <a href="bridge-users-graphs.html">bridge users</a> are now
-          accompanied by CSV files containing
-          <a href="csv/monthly-users-peak.csv">peak</a> and
-          <a href="csv/monthly-users-average.csv">average</a>
-          daily users per month by country.</li>
         </ul>
diff --git a/war/WEB-INF/templates/status.tpl.jsp b/war/WEB-INF/templates/status.tpl.jsp
index 9470edb..760efcf 100644
--- a/war/WEB-INF/templates/status.tpl.jsp
+++ b/war/WEB-INF/templates/status.tpl.jsp
@@ -9,6 +9,3 @@
         descriptor archive for a relay, and the
         <a href="consensus-health.html">Consensus Health</a> summarizes
         information about the latest network consensus voting process.
-        The <a href="log.html">Last Log</a> of the metrics portal software
-        execution also falls under this category to evaluate how recent
-        and complete the presented data are.
diff --git a/war/WEB-INF/urlrewrite.xml b/war/WEB-INF/urlrewrite.xml
index 349793f..6c8b654 100644
--- a/war/WEB-INF/urlrewrite.xml
+++ b/war/WEB-INF/urlrewrite.xml
@@ -31,36 +31,20 @@
       <to>/status.jsp</to>
     </rule>
     <rule>
-      <from>/consensus-graphs.html</from>
-      <to>/consensus-graphs.jsp</to>
+      <from>/network.html</from>
+      <to>/network.jsp</to>
     </rule>
     <rule>
-      <from>/exit-relays-graphs.html</from>
-      <to>/exit-relays-graphs.jsp</to>
+      <from>/users.html</from>
+      <to>/users.jsp</to>
     </rule>
     <rule>
-      <from>/new-users-graphs.html</from>
-      <to>/new-users-graphs.jsp</to>
+      <from>/packages.html</from>
+      <to>/packages.jsp</to>
     </rule>
     <rule>
-      <from>/recurring-users-graphs.html</from>
-      <to>/recurring-users-graphs.jsp</to>
-    </rule>
-    <rule>
-      <from>/bridge-users-graphs.html</from>
-      <to>/bridge-users-graphs.jsp</to>
-    </rule>
-    <rule>
-      <from>/torperf-graphs.html</from>
-      <to>/torperf-graphs.jsp</to>
-    </rule>
-    <rule>
-      <from>/gettor-graphs.html</from>
-      <to>/gettor-graphs.jsp</to>
-    </rule>
-    <rule>
-      <from>/custom-graph.html</from>
-      <to>/custom-graph.jsp</to>
+      <from>/performance.html</from>
+      <to>/performance.jsp</to>
     </rule>
     <rule>
       <from>/papers.html</from>
diff --git a/war/WEB-INF/web.xml b/war/WEB-INF/web.xml
index ed0ce53..be2137a 100644
--- a/war/WEB-INF/web.xml
+++ b/war/WEB-INF/web.xml
@@ -127,10 +127,6 @@
   </servlet>
   <servlet-mapping>
     <servlet-name>ErnieGeneratedFile</servlet-name>
-    <url-pattern>/log.html</url-pattern>
-  </servlet-mapping>
-  <servlet-mapping>
-    <servlet-name>ErnieGeneratedFile</servlet-name>
     <url-pattern>/consensus-health.html</url-pattern>
   </servlet-mapping>
   <servlet-mapping>
diff --git a/war/bridge-users-graphs.jsp b/war/bridge-users-graphs.jsp
deleted file mode 100644
index 50278a0..0000000
--- a/war/bridge-users-graphs.jsp
+++ /dev/null
@@ -1,4 +0,0 @@
-<jsp:useBean id="template" class="org.torproject.ernie.web.TemplateController" scope="request" />
-<jsp:setProperty name="template" property="template" value="graphs_bridge-users"/>
-<jsp:setProperty name="template" property="title" value="Tor Metrics Portal: Graphs"/>
-<%@ include file="/WEB-INF/templates/main.tpl.jsp" %>
diff --git a/war/consensus-graphs.jsp b/war/consensus-graphs.jsp
deleted file mode 100644
index 028bf41..0000000
--- a/war/consensus-graphs.jsp
+++ /dev/null
@@ -1,4 +0,0 @@
-<jsp:useBean id="template" class="org.torproject.ernie.web.TemplateController" scope="request" />
-<jsp:setProperty name="template" property="template" value="graphs_network-size"/>
-<jsp:setProperty name="template" property="title" value="Tor Metrics Portal: Graphs"/>
-<%@ include file="/WEB-INF/templates/main.tpl.jsp" %>
diff --git a/war/custom-graph.jsp b/war/custom-graph.jsp
deleted file mode 100644
index ef9faf4..0000000
--- a/war/custom-graph.jsp
+++ /dev/null
@@ -1,6 +0,0 @@
-<jsp:useBean id="template" class="org.torproject.ernie.web.TemplateController" scope="request" />
-<jsp:useBean id="customgraph" class="org.torproject.ernie.web.CustomGraphController" scope="request" />
-<jsp:setProperty name="customgraph" property="parameterMap" value="<%=request.getParameterMap()%>" />
-<jsp:setProperty name="template" property="template" value="graphs_custom-graph"/>
-<jsp:setProperty name="template" property="title" value="Tor Metrics Portal: Custom Graph"/>
-<%@ include file="/WEB-INF/templates/main.tpl.jsp" %>
diff --git a/war/exit-relays-graphs.jsp b/war/exit-relays-graphs.jsp
deleted file mode 100644
index 0a4c393..0000000
--- a/war/exit-relays-graphs.jsp
+++ /dev/null
@@ -1,4 +0,0 @@
-<jsp:useBean id="template" class="org.torproject.ernie.web.TemplateController" scope="request" />
-<jsp:setProperty name="template" property="template" value="graphs_exit-relays"/>
-<jsp:setProperty name="template" property="title" value="Tor Metrics Portal: Graphs"/>
-<%@ include file="/WEB-INF/templates/main.tpl.jsp" %>
diff --git a/war/gettor-graphs.jsp b/war/gettor-graphs.jsp
deleted file mode 100644
index 0d7d7c4..0000000
--- a/war/gettor-graphs.jsp
+++ /dev/null
@@ -1,4 +0,0 @@
-<jsp:useBean id="template" class="org.torproject.ernie.web.TemplateController" scope="request" />
-<jsp:setProperty name="template" property="template" value="graphs_gettor"/>
-<jsp:setProperty name="template" property="title" value="Tor Metrics Portal: Graphs"/>
-<%@ include file="/WEB-INF/templates/main.tpl.jsp" %>
diff --git a/war/network.jsp b/war/network.jsp
new file mode 100644
index 0000000..e144505
--- /dev/null
+++ b/war/network.jsp
@@ -0,0 +1,4 @@
+<jsp:useBean id="template" class="org.torproject.ernie.web.TemplateController" scope="request" />
+<jsp:setProperty name="template" property="template" value="graphs_network"/>
+<jsp:setProperty name="template" property="title" value="Tor Metrics Portal: Network"/>
+<%@ include file="/WEB-INF/templates/main.tpl.jsp" %>
diff --git a/war/new-users-graphs.jsp b/war/new-users-graphs.jsp
deleted file mode 100644
index 60469e3..0000000
--- a/war/new-users-graphs.jsp
+++ /dev/null
@@ -1,4 +0,0 @@
-<jsp:useBean id="template" class="org.torproject.ernie.web.TemplateController" scope="request" />
-<jsp:setProperty name="template" property="template" value="graphs_new-users"/>
-<jsp:setProperty name="template" property="title" value="Tor Metrics Portal: Graphs"/>
-<%@ include file="/WEB-INF/templates/main.tpl.jsp" %>
diff --git a/war/packages.jsp b/war/packages.jsp
new file mode 100644
index 0000000..904b248
--- /dev/null
+++ b/war/packages.jsp
@@ -0,0 +1,4 @@
+<jsp:useBean id="template" class="org.torproject.ernie.web.TemplateController" scope="request" />
+<jsp:setProperty name="template" property="template" value="graphs_packages"/>
+<jsp:setProperty name="template" property="title" value="Tor Metrics Portal: Packages"/>
+<%@ include file="/WEB-INF/templates/main.tpl.jsp" %>
diff --git a/war/performance.jsp b/war/performance.jsp
new file mode 100644
index 0000000..56204b4
--- /dev/null
+++ b/war/performance.jsp
@@ -0,0 +1,4 @@
+<jsp:useBean id="template" class="org.torproject.ernie.web.TemplateController" scope="request" />
+<jsp:setProperty name="template" property="template" value="graphs_performance"/>
+<jsp:setProperty name="template" property="title" value="Tor Metrics Portal: Performance"/>
+<%@ include file="/WEB-INF/templates/main.tpl.jsp" %>
diff --git a/war/recurring-users-graphs.jsp b/war/recurring-users-graphs.jsp
deleted file mode 100644
index 2b6230a..0000000
--- a/war/recurring-users-graphs.jsp
+++ /dev/null
@@ -1,4 +0,0 @@
-<jsp:useBean id="template" class="org.torproject.ernie.web.TemplateController" scope="request" />
-<jsp:setProperty name="template" property="template" value="graphs_recurring-users"/>
-<jsp:setProperty name="template" property="title" value="Tor Metrics Portal: Graphs"/>
-<%@ include file="/WEB-INF/templates/main.tpl.jsp" %>
diff --git a/war/torperf-graphs.jsp b/war/torperf-graphs.jsp
deleted file mode 100644
index 5cc8a62..0000000
--- a/war/torperf-graphs.jsp
+++ /dev/null
@@ -1,4 +0,0 @@
-<jsp:useBean id="template" class="org.torproject.ernie.web.TemplateController" scope="request" />
-<jsp:setProperty name="template" property="template" value="graphs_torperf"/>
-<jsp:setProperty name="template" property="title" value="Tor Metrics Portal: Graphs"/>
-<%@ include file="/WEB-INF/templates/main.tpl.jsp" %>
diff --git a/war/users.jsp b/war/users.jsp
new file mode 100644
index 0000000..bb0c143
--- /dev/null
+++ b/war/users.jsp
@@ -0,0 +1,4 @@
+<jsp:useBean id="template" class="org.torproject.ernie.web.TemplateController" scope="request" />
+<jsp:setProperty name="template" property="template" value="graphs_users"/>
+<jsp:setProperty name="template" property="title" value="Tor Metrics Portal: Users"/>
+<%@ include file="/WEB-INF/templates/main.tpl.jsp" %>
-- 
1.7.1