[tor-commits] [metrics-web/master] Read all metric details from metrics.json.

karsten at torproject.org karsten at torproject.org
Tue Jan 26 09:51:23 UTC 2016


commit cc210f2c9dfa18e3bceaa18b0c4885b299ecb508
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Tue Jan 26 10:48:03 2016 +0100

    Read all metric details from metrics.json.
    
    This change will hopefully make it obsolete to touch Java classes or
    JSP files for modifying or even adding graphs or tables.  We'll learn
    for sure once we're actually doing that, but this change is almost
    certainly a step in the right direction.
---
 website/build.xml                                  |    9 +-
 website/etc/metrics.json                           |  838 ++++++++++++++++++++
 website/etc/web.xml                                |   50 +-
 website/rserve/tables.R                            |    2 +-
 .../org/torproject/metrics/web/DataServlet.java    |   42 +
 .../org/torproject/metrics/web/GraphServlet.java   |  210 +++++
 .../org/torproject/metrics/web/IndexServlet.java   |  123 +--
 .../org/torproject/metrics/web/LinkServlet.java    |   39 +
 website/src/org/torproject/metrics/web/Metric.java |   63 ++
 .../org/torproject/metrics/web/MetricServlet.java  |  116 +++
 .../torproject/metrics/web/MetricsProvider.java    |   37 +
 .../org/torproject/metrics/web/TableServlet.java   |   97 +++
 .../metrics/web/graphs/GraphParameterChecker.java  |   19 +-
 .../metrics/web/graphs/GraphsSubpagesServlet.java  |  221 ------
 .../metrics/web/graphs/RObjectGenerator.java       |  116 +--
 .../metrics/web/graphs/TableParameterChecker.java  |   16 +-
 website/web/WEB-INF/about.jsp                      |    7 +-
 website/web/WEB-INF/advbwdist-data.jsp             |   62 --
 website/web/WEB-INF/advbwdist-perc.jsp             |   94 ---
 website/web/WEB-INF/advbwdist-relay.jsp            |   81 --
 website/web/WEB-INF/bandwidth-data.jsp             |   77 --
 website/web/WEB-INF/bandwidth-flags.jsp            |   69 --
 website/web/WEB-INF/bandwidth.jsp                  |   65 --
 website/web/WEB-INF/bubbles.jsp                    |   11 +-
 website/web/WEB-INF/bwhist-flags.jsp               |   66 --
 website/web/WEB-INF/clients-data.jsp               |   96 ---
 website/web/WEB-INF/cloudbridges.jsp               |   64 --
 website/web/WEB-INF/connbidirect-data.jsp          |   70 --
 website/web/WEB-INF/connbidirect.jsp               |   67 --
 website/web/WEB-INF/connbidirect2-data.jsp         |   67 --
 website/web/WEB-INF/data.jsp                       |   52 ++
 website/web/WEB-INF/dirbytes.jsp                   |   69 --
 website/web/WEB-INF/error.jsp                      |    7 +-
 website/web/WEB-INF/graph.jsp                      |  159 ++++
 website/web/WEB-INF/hidserv-data.jsp               |   66 --
 website/web/WEB-INF/hidserv-dir-onions-seen.jsp    |   71 --
 website/web/WEB-INF/hidserv-frac-reporting.jsp     |   70 --
 website/web/WEB-INF/hidserv-rend-relayed-cells.jsp |   72 --
 website/web/WEB-INF/index.jsp                      |    7 +-
 website/web/WEB-INF/link.jsp                       |   43 +
 website/web/WEB-INF/networksize.jsp                |   63 --
 website/web/WEB-INF/oxford-anonymous-internet.jsp  |   46 --
 website/web/WEB-INF/platforms.jsp                  |   63 --
 website/web/WEB-INF/relayflags.jsp                 |   76 --
 website/web/WEB-INF/servers-data.jsp               |   99 ---
 website/web/WEB-INF/table.jsp                      |   74 ++
 website/web/WEB-INF/torperf-data.jsp               |   68 --
 website/web/WEB-INF/torperf-failures.jsp           |   74 --
 website/web/WEB-INF/torperf.jsp                    |   73 --
 website/web/WEB-INF/uncharted-data-flow.jsp        |   36 -
 website/web/WEB-INF/userstats-bridge-country.jsp   |   73 --
 website/web/WEB-INF/userstats-bridge-table.jsp     |   77 --
 website/web/WEB-INF/userstats-bridge-transport.jsp |   79 --
 website/web/WEB-INF/userstats-bridge-version.jsp   |   71 --
 .../web/WEB-INF/userstats-censorship-events.jsp    |   75 --
 website/web/WEB-INF/userstats-relay-country.jsp    |   89 ---
 website/web/WEB-INF/userstats-relay-table.jsp      |   78 --
 website/web/WEB-INF/versions.jsp                   |   68 --
 58 files changed, 1904 insertions(+), 2888 deletions(-)

diff --git a/website/build.xml b/website/build.xml
index 52f1780..a0ec33e 100644
--- a/website/build.xml
+++ b/website/build.xml
@@ -15,8 +15,10 @@
       <classpath>
         <fileset dir="/usr/share/java">
           <include name="commons-codec.jar"/>
+          <include name="commons-lang.jar"/>
           <include name="postgresql-jdbc3.jar"/>
-          <include name="servlet-api-3.0.jar"/>
+          <include name="servlet-api-2.5.jar"/>
+          <include name="gson-2.1.jar"/>
         </fileset>
         <fileset dir="lib">
           <include name="REngine.jar"/>
@@ -40,12 +42,17 @@
       </lib>
       <lib dir="/usr/share/java">
           <include name="commons-codec.jar"/>
+          <include name="commons-lang.jar"/>
           <include name="postgresql-jdbc3.jar"/>
+          <include name="gson-2.1.jar"/>
       </lib>
       <classes dir="classes"/>
       <zipfileset dir="etc"
                   prefix="WEB-INF/classes"
                   includes="logging.properties"/>
+      <zipfileset dir="etc"
+                  prefix="WEB-INF/classes"
+                  includes="metrics.json"/>
       <metainf dir="etc"
                includes="context.xml"/>
     </war>
diff --git a/website/etc/metrics.json b/website/etc/metrics.json
new file mode 100644
index 0000000..2e1ee4f
--- /dev/null
+++ b/website/etc/metrics.json
@@ -0,0 +1,838 @@
+[
+  {
+    "id": "networksize",
+    "title": "Relays and bridges in the network",
+    "tags": [
+      "Relays",
+      "Bridges"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows the number of running <a href=\"about.html#relay\">relays</a> and <a href=\"about.html#bridge\">bridges</a> in the network.</p>",
+    "function": "plot_networksize",
+    "parameters": [
+      "start",
+      "end"
+    ],
+    "data": [
+      "servers-data"
+    ],
+    "related": [
+      "relayflags",
+      "versions",
+      "platforms",
+      "cloudbridges"
+    ]
+  },
+  {
+    "id": "relayflags",
+    "title": "Relays with Exit, Fast, Guard, Stable, and HSDir flags",
+    "tags": [
+      "Relays"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows the number of running <a href=\"about.html#relay\">relays</a> that have had certain <a href=\"about.html#relay-flag\">flags</a> assigned by the <a href=\"about.html#directory-authority\">directory authorities</a>.  These flags indicate that a relay should be preferred for either guard (\"Guard\") or exit positions (\"Exit\"), that a relay is suitable for high-bandwidth (\"Fast\") or long-lived circuits (\"Stable\"), or that a relay is considered a hidden service directory (\"HSDir\").</p>",
+    "function": "plot_relayflags",
+    "parameters": [
+      "start",
+      "end",
+      "flag"
+    ],
+    "data": [
+      "servers-data"
+    ],
+    "related": [
+      "networksize",
+      "versions",
+      "platforms"
+    ]
+  },
+  {
+    "id": "versions",
+    "title": "Relays by version",
+    "tags": [
+      "Relays",
+      "Diversity"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows the number of running <a href=\"about.html#relay\">relays</a> by tor software version.  Relays report their tor software version when they announce themselves in the network.  More details on when these versions were declared stable or unstable can be found on the <a href=\"https://www.torproject.org/download/download.html\">download page</a> and in the <a href=\"https://gitweb.torproject.org/tor.git/tree/ChangeLog\">changes file</a>.</p>",
+    "function": "plot_versions",
+    "parameters": [
+      "start",
+      "end"
+    ],
+    "data": [
+      "servers-data"
+    ],
+    "related": [
+      "networksize",
+      "relayflags",
+      "platforms"
+    ]
+  },
+  {
+    "id": "platforms",
+    "title": "Relays by platform",
+    "tags": [
+      "Relays",
+      "Diversity"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows the number of running <a href=\"about.html#relay\">relays</a> by operating system.  Relays report their operating system when they announce themselves in the network.</p>",
+    "function": "plot_platforms",
+    "parameters": [
+      "start",
+      "end"
+    ],
+    "data": [
+      "servers-data"
+    ],
+    "related": [
+      "networksize",
+      "relayflags",
+      "versions"
+    ]
+  },
+  {
+    "id": "cloudbridges",
+    "title": "Tor Cloud bridges",
+    "tags": [
+      "Bridges"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows the number of running <a href=\"about.html#bridge\">bridges</a> that are deployed in <a href=\"http://aws.amazon.com/ec2/\">Amazon's EC2 cloud</a> using images provided by <a href=\"https://cloud.torproject.org/\">Tor Cloud</a>.  These bridges have, by default, a nickname that starts with \"ec2bridger\" which bridge operators usually leave unchanged.</p>",
+    "function": "plot_cloudbridges",
+    "parameters": [
+      "start",
+      "end"
+    ],
+    "data": [
+      "servers-data"
+    ],
+    "related": [
+      "networksize"
+    ]
+  },
+  {
+    "id": "servers-data",
+    "title": "Number of relays and bridges",
+    "tags": [
+      "Relays",
+      "Bridges",
+      "Diversity"
+    ],
+    "type": "Data",
+    "level": "Advanced",
+    "description": "<p>The following data file contains the number of running <a href=\"about.html#relay\">relays</a> and <a href=\"about.html#bridge\">bridges</a> in the network.  Statistics include subsets of relays or bridges by <a href=\"about.html#relay-flag\">relay flag</a> (relays only), country code (relays only, and only until February 2013), tor software version (relays only), operating system (relays only), and by whether or not they are running in the EC2 cloud (bridges only).  The data file contains daily (mean) averages of relay and bridge numbers.</p>",
+    "data_file": "stats/servers.csv",
+    "data_column_spec": [
+      "<b>date:</b> UTC date (YYYY-MM-DD) when relays or bridges have been listed as running.",
+      "<b>flag:</b> Relay flag assigned by the directory authorities.  Examples are <b>\"Exit\"</b>, <b>\"Guard\"</b>, <b>\"Fast\"</b>, <b>\"Stable\"</b>, and <b>\"HSDir\"</b>.  Relays can have none, some, or all these relay flags assigned.  Relays that don't have the <b>\"Running\"</b> flag are not included in these statistics regardless of their other flags.  If this column contains the empty string, all running relays are included, regardless of assigned flags.  There are no statistics on the number of bridges by relay flag.",
+      "<b>country:</b> Two-letter lower-case country code as found in a GeoIP database by resolving the relay's first onion-routing IP address, or <b>\"??\"</b> if an IP addresses could not be resolved.  If this column contains the empty string, all running relays are included, regardless of their resolved country code.  Statistics on relays by country code are only available until January 31, 2013.  There are no statistics on the number of bridges by country code.",
+      "<b>version:</b> First three dotted numbers of the Tor software version as reported by the relay.  An example is <b>\"0.2.5\"</b>.  If this column contains the empty string, all running relays are included, regardless of the Tor software version they run.  There are no statistics on the number of bridges by Tor software version.",
+      "<b>platform:</b> Operating system as reported by the relay.  Examples are <b>\"Linux\"</b>, <b>\"Darwin\"</b> (Mac OS X), <b>\"BSD\"</b>, <b>\"Windows\"</b>, and <b>\"Other\"</b>.  If this column contains the empty string, all running relays are included, regardless of the operating system they run on.  There are no statistics on the number of bridges by operating system.",
+      "<b>ec2bridge:</b> Whether bridges are running in the EC2 cloud or not.  More precisely, bridges in the EC2 cloud running an image provided by Tor by default set their nickname to <b>\"ec2bridger\"</b> plus 8 random hex characters.  This column either contains <b>\"t\"</b> for bridges matching this naming scheme, or the empty string for all bridges regardless of their nickname.  There are no statistics on the number of relays running in the EC2 cloud.",
+      "<b>relays:</b> The average number of relays matching the criteria in the previous columns.  If the values in previous columns are specific to bridges only, this column contains the empty string.",
+      "<b>bridges:</b> The average number of bridges matching the criteria in the previous columns.  If the values in previous columns are specific to relays only, this column contains the empty string."
+    ],
+    "related": [
+      "networksize",
+      "relayflags",
+      "versions",
+      "platforms",
+      "cloudbridges"
+    ]
+  },
+  {
+    "id": "bandwidth",
+    "title": "Total relay bandwidth in the network",
+    "tags": [
+      "Relays",
+      "Bandwidth"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows the total <a href=\"about.html#advertised-bandwidth\">advertised</a> and <a href=\"about.html#bandwidth-history\">consumed bandwidth</a> of all <a href=\"about.html#relay\">relays</a> in the network.  <font color=\"red\">Note that the unit has recently changed from MiB/s to Gbit/s.</font></p>",
+    "function": "plot_bandwidth",
+    "parameters": [
+      "start",
+      "end"
+    ],
+    "data": [
+      "bandwidth-data"
+    ],
+    "related": [
+      "bwhist-flags",
+      "bandwidth-flags",
+      "dirbytes"
+    ]
+  },
+  {
+    "id": "bwhist-flags",
+    "title": "Relay bandwidth by Exit and/or Guard flags",
+    "tags": [
+      "Relays",
+      "Bandwidth"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows the <a href=\"about.html#bandwidth-history\">consumed bandwidth</a> reported by relays, subdivided into four distinct subsets by assigned \"Exit\" and/or \"Guard\" <a href=\"about.html#relay-flag\">flags</a>.  <font color=\"red\">Note that the unit has recently changed from MiB/s to Gbit/s.</font></p>",
+    "function": "plot_bwhist_flags",
+    "parameters": [
+      "start",
+      "end"
+    ],
+    "data": [
+      "bandwidth-data"
+    ],
+    "related": [
+      "relayflags",
+      "bandwidth",
+      "bandwidth-flags",
+      "dirbytes"
+    ]
+  },
+  {
+    "id": "bandwidth-flags",
+    "title": "Advertised bandwidth and bandwidth history by relay flags",
+    "tags": [
+      "Relays",
+      "Bandwidth"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows <a href=\"about.html#advertised-bandwidth\">advertised</a> and <a href=\"about.html#bandwidth-history\">consumed bandwidth</a> of relays with either \"Exit\" or \"Guard\" <a href=\"about.html#relay-flag\">flags</a> assigned by the directory authorities.  These sets are not distinct, because a relay that has both the \"Exit\" and \"Guard\" flags assigned will be included in both sets.  <font color=\"red\">Note that the unit has recently changed from MiB/s to Gbit/s.</font></p>",
+    "function": "plot_bandwidth_flags",
+    "parameters": [
+      "start",
+      "end"
+    ],
+    "data": [
+      "bandwidth-data"
+    ],
+    "related": [
+      "relayflags",
+      "bandwidth",
+      "bwhist-flags",
+      "dirbytes"
+    ]
+  },
+  {
+    "id": "dirbytes",
+    "title": "Number of bytes spent on answering directory requests",
+    "tags": [
+      "Relays",
+      "Bandwidth"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows the portion of <a href=\"about.html#bandwidth-history\">consumed bandwidth</a> that <a href=\"about.html#directory-authority\">directory authorities</a> and <a href=\"about.html#directory-mirror\">mirrors</a> have spent on answering directory requests.  Not all directories report these statistics, so the graph shows an estimation of total consumed bandwidth as it would be observed if all directories reported these statistics.  <font color=\"red\">Note that the unit has recently changed from MiB/s to Gbit/s.</font></p>",
+    "function": "plot_dirbytes",
+    "parameters": [
+      "start",
+      "end"
+    ],
+    "data": [
+      "bandwidth-data"
+    ],
+    "related": [
+      "bandwidth",
+      "bwhist-flags",
+      "bandwidth-flags"
+    ]
+  },
+  {
+    "id": "advbwdist-perc",
+    "title": "Advertised bandwidth distribution",
+    "tags": [
+      "Relays",
+      "Bandwidth"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows the distribution of the <a href=\"about.html#advertised-bandwidth\">advertised bandwidth</a> of relays in the network.  Each percentile represents the advertised bandwidth that a given percentage of relays does not exceed (and that in turn the remaining relays either match or exceed).  For example, 99% of relays advertise at most the bandwidth value shown in the 99th percentile line (and the remaining 1% advertise at least that amount).  <font color=\"red\">Note that the unit has recently changed from MiB/s to Gbit/s.</font></p>",
+    "function": "plot_advbwdist_perc",
+    "parameters": [
+      "start",
+      "end",
+      "p"
+    ],
+    "data": [
+      "advbwdist-data"
+    ],
+    "related": [
+      "advbwdist-relay"
+    ]
+  },
+  {
+    "id": "advbwdist-relay",
+    "title": "Advertised bandwidth of n-th fastest relays",
+    "tags": [
+      "Relays",
+      "Bandwidth"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows the <a href=\"about.html#advertised-bandwidth\">advertised bandwidth</a> of the n-th fastest relays in the network for different values of n.  <font color=\"red\">Note that the unit has recently changed from MiB/s to Gbit/s.</font></p>",
+    "function": "plot_advbwdist_relay",
+    "parameters": [
+      "start",
+      "end",
+      "n"
+    ],
+    "data": [
+      "advbwdist-data"
+    ],
+    "related": [
+      "advbwdist-perc"
+    ]
+  },
+  {
+    "id": "bandwidth-data",
+    "title": "Bandwidth provided and consumed by relays",
+    "tags": [
+      "Relays",
+      "Bandwidth"
+    ],
+    "type": "Data",
+    "level": "Advanced",
+    "description": "<p>The following data file contains statistics on <a href=\"about.html#advertised-bandwidth\">advertised</a> and <a href=\"about.html#bandwidth-history\">consumed bandwidth</a> of <a href=\"about.html#relay\">relays</a> in the network.  Statistics on advertised bandwidth include any kind of traffic handled by a relay, whereas statistics on consumed bandwidth are available either for all traffic combined, or specifically for directory traffic.  Some of the statistics are available for subsets of relays that have the \"Exit\" and/or the \"Guard\" <a href=\"about.html#relay-flag\">flag</a>.  The data file contains daily (mean) averages of bandwidth numbers.</p>",
+    "data_file": "stats/bandwidth.csv",
+    "data_column_spec": [
+      "<b>date:</b> UTC date (YYYY-MM-DD) that relays reported bandwidth data for.",
+      "<b>isexit:</b> Whether relays included in this line have the <b>\"Exit\"</b> relay flag or not, which can be <b>\"t\"</b> or <b>\"f\"</b>.  If this column contains the empty string, bandwidth data from all running relays are included, regardless of assigned relay flags.",
+      "<b>isguard:</b> Whether relays included in this line have the <b>\"Guard\"</b> relay flag or not, which can be <b>\"t\"</b> or <b>\"f\"</b>.  If this column contains the empty string, bandwidth data from all running relays are included, regardless of assigned relay flags.",
+      "<b>advbw:</b> Total advertised bandwidth in bytes per second that relays are capable to provide.",
+      "<b>bwread:</b> Total bandwidth in bytes per second that relays have read.  This metric includes any kind of traffic.",
+      "<b>bwwrite:</b> Similar to <b>bwread</b>, but for traffic written by relays.",
+      "<b>dirread:</b> Bandwidth in bytes per second that relays have read when serving directory data.  Not all relays report how many bytes they read when serving directory data which is why this value is an estimate from the available data.  This metric is not available for subsets of relays with certain relay flags, so that this column will contain the empty string if either <b>isexit</b> or <b>isguard</b> is non-empty.",
+      "<b>dirwrite:</b> Similar to <b>dirread</b>, but for traffic written by relays when serving directory data."
+    ],
+    "related": [
+      "bandwidth",
+      "bwhist-flags",
+      "bandwidth-flags",
+      "dirbytes",
+      "bandwidth-data"
+    ]
+  },
+  {
+    "id": "advbwdist-data",
+    "title": "Advertised bandwidth distribution and n-th fastest relays",
+    "tags": [
+      "Relays",
+      "Bandwidth"
+    ],
+    "type": "Data",
+    "level": "Advanced",
+    "description": "<p>The following data file contains statistics on the distribution of <a href=\"about.html#advertised-bandwidth\">advertised bandwidth</a> of relays in the network.  These statistics include advertised bandwidth percentiles and advertised bandwidth values of the n-th fastest relays.  All values are obtained from advertised bandwidths of running relays in a <a href=\"about.html#consensus\">network status consensus</a>.  The data file contains daily (median) averages of percentiles and n-th largest values.</p>",
+    "data_file": "stats/advbwdist.csv",
+    "data_column_spec": [
+      "<b>date:</b> UTC date (YYYY-MM-DD) when relays have been listed as running.",
+      "<b>isexit:</b> Whether relays included in this line have the <b>\"Exit\"</b> relay flag, which would be indicated as <b>\"t\"</b>.  If this column contains the empty string, advertised bandwidths from all running relays are included, regardless of assigned relay flags.",
+      "<b>relay:</b> Position of the relay in an ordered list of all advertised bandwidths, starting at 1 for the fastest relay in the network.  May be the empty string if this line contains advertised bandwidth by percentile.",
+      "<b>percentile:</b> Advertised bandwidth percentile given in this line.  May be the empty string if this line contains advertised bandwidth by fastest relays.",
+      "<b>advbw:</b> Advertised bandwidth in B/s."
+    ],
+    "related": [
+      "advbwdist-perc",
+      "advbwdist-relay"
+    ]
+  },
+  {
+    "id": "bubbles",
+    "title": "Network bubble graphs",
+    "tags": [
+      "Relays",
+      "Diversity"
+    ],
+    "type": "Graph",
+    "level": "Basic"
+  },
+  {
+    "id": "userstats-relay-country",
+    "title": "Direct users by country",
+    "tags": [
+      "Clients"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows the estimated number of directly-connecting <a href=\"about.html#client\">clients</a>; that is, it excludes clients connecting via <a href=\"about.html#bridge\">bridges</a>.  These estimates are derived from the number of directory requests counted on <a href=\"about.html#directory-authority\">directory authorities</a> and <a href=\"about.html#directory-mirror\">mirrors</a>.  Relays resolve client IP addresses to country codes, so that graphs are available for most countries.  Furthermore, it is possible to display indications of censorship events as obtained from an anomaly-based censorship-detection system (for more details, see this <a href=\"https://research.torproject.org/techreports/detector-2011-09-09.pdf\">technical report</a>).  For further details see these <a href=\"https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt\">questions and answers about user statistics</a>.</p>",
+    "function": "plot_userstats_relay_country",
+    "parameters": [
+      "start",
+      "end",
+      "country",
+      "events"
+    ],
+    "data": [
+      "clients-data"
+    ],
+    "related": [
+      "userstats-relay-table",
+      "userstats-censorship-events",
+      "userstats-bridge-country",
+      "userstats-bridge-table",
+      "oxford-anonymous-internet"
+    ]
+  },
+  {
+    "id": "userstats-relay-table",
+    "title": "Top-10 countries by directly connecting users",
+    "tags": [
+      "Clients"
+    ],
+    "type": "Table",
+    "level": "Basic",
+    "description": "<p>The following table shows the top-10 countries by estimated number of directly-connecting <a href=\"about.html#client\">clients</a>.  These numbers are derived from directory requests counted on <a href=\"about.html#directory-authority\">directory authorities</a> and <a href=\"about.html#directory-mirror\">mirrors</a>.  Relays resolve client IP addresses to country codes, so that numbers are available for most countries.  For further details see these <a href=\"https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt\">questions and answers about user statistics</a>.</p>",
+    "function": "write_userstats_relay",
+    "parameters": [
+      "start",
+      "end"
+    ],
+    "table_headers": [
+      "Country",
+      "Mean daily users"
+    ],
+    "table_cell_formats": [
+      "<a href=\"userstats-relay-country.html?graph=userstats-relay-country&country=${cc}\">${country}</a> ",
+      "${abs} (${rel} %)"
+    ],
+    "data": [
+      "clients-data"
+    ],
+    "related": [
+      "userstats-relay-country",
+      "userstats-censorship-events",
+      "userstats-bridge-country",
+      "userstats-bridge-table",
+      "oxford-anonymous-internet"
+    ]
+  },
+  {
+    "id": "userstats-censorship-events",
+    "title": "Top-10 countries by possible censorship events",
+    "tags": [
+      "Clients"
+    ],
+    "type": "Table",
+    "level": "Basic",
+    "description": "<p>The following table shows the top-10 countries by possible censorship events, as obtained from an anomaly-based censorship-detection system (for more details, see this <a href=\"https://research.torproject.org/techreports/detector-2011-09-09.pdf\">technical report</a>).  For further details see these <a href=\"https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt\">questions and answers about user statistics</a>.</p>",
+    "function": "write_userstats_censorship_events",
+    "parameters": [
+      "start",
+      "end"
+    ],
+    "table_headers": [
+      "Country",
+      "Downturns",
+      "Upturns"
+    ],
+    "table_cell_formats": [
+      "<a href=\"userstats-relay-country.html?graph=userstats-relay-country&country=${cc}&events=on\">${country}</a> ",
+      "${downturns}",
+      "${upturns}"
+    ],
+    "data": [
+      "clients-data"
+    ],
+    "related": [
+      "userstats-relay-country",
+      "userstats-relay-table",
+      "userstats-censorship-events"
+    ]
+  },
+  {
+    "id": "userstats-bridge-country",
+    "title": "Bridge users by country",
+    "tags": [
+      "Clients"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows the estimated number of <a href=\"about.html#client\">clients</a> connecting via <a href=\"about.html#bridge\">bridges</a>.  These numbers are derived from directory requests counted on bridges.  Bridges resolve client IP addresses of incoming directory requests to country codes, so that graphs are available for most countries.  For further details see these <a href=\"https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt\">questions and answers about user statistics</a>.</p>",
+    "function": "plot_userstats_bridge_country",
+    "parameters": [
+      "start",
+      "end",
+      "country"
+    ],
+    "data": [
+      "clients-data"
+    ],
+    "related": [
+      "userstats-relay-country",
+      "userstats-relay-table",
+      "userstats-bridge-table"
+    ]
+  },
+  {
+    "id": "userstats-bridge-table",
+    "title": "Top-10 countries by bridge users",
+    "tags": [
+      "Clients"
+    ],
+    "type": "Table",
+    "level": "Basic",
+    "description": "<p>The following table shows the top-10 countries by estimated number of <a href=\"about.html#client\">clients</a> connecting via <a href=\"about.html#bridge\">bridges</a>.  These numbers are derived from directory requests counted on bridges.  Bridges resolve client IP addresses of incoming directory requests to country codes, so that numbers are available for most countries.  For further details see these <a href=\"https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt\">questions and answers about user statistics</a>.</p>",
+    "function": "write_userstats_bridge",
+    "parameters": [
+      "start",
+      "end"
+    ],
+    "table_headers": [
+      "Country",
+      "Mean daily users"
+    ],
+    "table_cell_formats": [
+      "<a href=\"userstats-bridge-country.html?graph=userstats-bridge-country&country=${cc}\">${country}</a> ",
+      "${abs} (${rel} %)"
+    ],
+    "data": [
+      "clients-data"
+    ],
+    "related": [
+      "userstats-relay-country",
+      "userstats-relay-table",
+      "userstats-bridge-country",
+      "userstats-bridge-transport",
+      "userstats-bridge-version"
+    ]
+  },
+  {
+    "id": "userstats-bridge-transport",
+    "title": "Bridge users by transport",
+    "tags": [
+      "Clients",
+      "Pluggable transports"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows the estimated number of <a href=\"about.html#client\">clients</a> connecting via <a href=\"about.html#bridge\">bridges</a>.  These numbers are derived from directory requests counted on bridges.  Bridges distinguish connecting clients by transport protocol, which may include <a href=\"about.html#pluggable-transport\">pluggable transports</a>, so that graphs are available for different transports.  For further details see these <a href=\"https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt\">questions and answers about user statistics</a>.</p>",
+    "function": "plot_userstats_bridge_transport",
+    "parameters": [
+      "start",
+      "end",
+      "transport"
+    ],
+    "data": [
+      "clients-data"
+    ],
+    "related": [
+      "userstats-bridge-country",
+      "userstats-bridge-table",
+      "userstats-bridge-version"
+    ]
+  },
+  {
+    "id": "userstats-bridge-version",
+    "title": "Bridge users by IP version",
+    "tags": [
+      "Clients"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows the estimated number of <a href=\"about.html#client\">clients</a> connecting via <a href=\"about.html#bridge\">bridges</a>.  These numbers are derived from directory requests counted on bridges.  Bridges distinguish connecting clients by IP version, so that graphs are available for both IP versions 4 and 6.  For further details see these <a href=\"https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt\">questions and answers about user statistics</a>.</p>",
+    "function": "plot_userstats_bridge_version",
+    "parameters": [
+      "start",
+      "end",
+      "version"
+    ],
+    "data": [
+      "clients-data"
+    ],
+    "related": [
+      "userstats-bridge-country",
+      "userstats-bridge-table",
+      "userstats-bridge-transport"
+    ]
+  },
+  {
+    "id": "oxford-anonymous-internet",
+    "title": "Tor users as percentage of larger Internet population",
+    "tags": [
+      "Clients"
+    ],
+    "type": "Link",
+    "level": "Basic",
+    "description": "<p>The Oxford Internet Institute made a cartogram visualization of Tor users as compared to the overall Internet population.  They used the average number of Tor <a href=\"about.html#client\">users</a> per country from August 2012 to August 2013 and put it in relation to total Internet users per country.  More details and conclusions can be found on the <a href=\"http://geography.oii.ox.ac.uk/?page=tor\">Information Geographies website at the Oxford Internet Institute</a>.</p><p><a href=\"http://geography.oii.ox.ac.uk/?page=tor\"><img src=\"images/oxford-anonymous-internet.png\" alt=\"The anonymous Internet\"></a></p>",
+    "related": [
+      "userstats-relay-country",
+      "userstats-relay-table",
+      "clients-data"
+    ]
+  },
+  {
+    "id": "clients-data",
+    "title": "Estimated number of clients in the Tor network",
+    "tags": [
+      "Clients",
+      "Pluggable transports"
+    ],
+    "type": "Data",
+    "level": "Advanced",
+    "description": "<p>The following data file contains estimates on the number of <a href=\"about.html#client\">clients</a> in the network.  These numbers are derived from directory requests counted on <a href=\"about.html#directory-authority\">directory authorities</a>, <a href=\"about.html#directory-mirror\">directory mirrors</a>, and <a href=\"about.html#bridge\">bridges</a>.  Statistics are available for clients connecting directly relays and clients connecting via bridges.  There are statistics available by country (for both directly-connecting clients and clients connecting via bridges), by transport protocol (only for clients connecting via bridges), and by IP version (only for clients connecting via bridges).  Statistics also include predicted client numbers from past observations, which can be used to detect censorship events.</p>",
+    "data_file": "stats/clients.csv",
+    "data_column_spec": [
+      "<b>date:</b> UTC date (YYYY-MM-DD) for which client numbers are estimated.",
+      "<b>node:</b> The node type to which clients connect first, which can be either <b>\"relay\"</b> or <b>\"bridge\"</b>.",
+      "<b>country:</b> Two-letter lower-case country code as found in a GeoIP database by resolving clients' IP addresses, or <b>\"??\"</b> if client IP addresses could not be resolved.  If this column contains the empty string, all clients are included, regardless of their country code.",
+      "<b>transport:</b> Transport name used by clients to connect to the Tor network using bridges.  Examples are <b>\"obfs2\"</b>, <b>\"obfs3\"</b>, <b>\"websocket\"</b>, or <b>\"<OR>\"</b> (original onion routing protocol).  If this column contains the empty string, all clients are included, regardless of their transport.  There are no statistics on the number of clients by transport that connect to the Tor network via relays.",
+      "<b>version:</b> IP version used by clients to connect to the Tor network using bridges.  Examples are <b>\"v4\"</b> and <b>\"v6\"</b>.  If this column contains the empty string, all clients are included, regardless of their IP version.  There are no statistics on the number of clients by IP version that connect directly to the Tor network using relays.",
+      "<b>lower:</b> Lower number of expected clients under the assumption that there has been no censorship event.  If this column contains the empty string, there are no expectations on the number of clients.",
+      "<b>upper:</b> Upper number of expected clients under the assumption that there has been no release of censorship.  If this column contains the empty string, there are no expectations on the number of clients.",
+      "<b>clients:</b> Estimated number of clients.",
+      "<b>frac:</b> Fraction of relays or bridges in percent that the estimate is based on.  The higher this value, the more reliable is the estimate.  Values above 50 can be considered reliable enough for most purposes, lower values should be handled with more care."
+    ],
+    "related": [
+      "userstats-relay-country",
+      "userstats-relay-table",
+      "userstats-censorship-events",
+      "userstats-bridge-country",
+      "userstats-bridge-table",
+      "userstats-bridge-transport",
+      "userstats-bridge-version",
+      "oxford-anonymous-internet"
+    ]
+  },
+  {
+    "id": "torperf",
+    "title": "Time to download files over Tor",
+    "tags": [
+      "Performance"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows overall performance when downloading static files of different sizes over Tor.  The graph shows the range of measurements from first to third quartile, and highlights the median.  The slowest and fastest quarter of measurements are omitted from the graph.</p>",
+    "function": "plot_torperf",
+    "parameters": [
+      "start",
+      "end",
+      "source",
+      "filesize"
+    ],
+    "data": [
+      "torperf-data"
+    ],
+    "related": [
+      "torperf-failures"
+    ]
+  },
+  {
+    "id": "torperf-failures",
+    "title": "Timeouts and failures of downloading files over Tor",
+    "tags": [
+      "Performance"
+    ],
+    "type": "Graph",
+    "level": "Advanced",
+    "description": "<p>The following graph shows the fraction of timeouts and failures when downloading static files of different sizes over Tor.  A timeout occurs when a download does not complete within the scheduled time, in which case it is aborted in order not to overlap with the next scheduled download.  A failure occurs when the download completes, but the response is smaller than expected.</p>",
+    "function": "plot_torperf_failures",
+    "parameters": [
+      "start",
+      "end",
+      "source",
+      "filesize"
+    ],
+    "data": [
+      "torperf-data"
+    ],
+    "related": [
+      "torperf"
+    ]
+  },
+  {
+    "id": "connbidirect",
+    "title": "Fraction of connections used uni-/bidirectionally",
+    "tags": [
+      "Performance"
+    ],
+    "type": "Graph",
+    "level": "Advanced",
+    "description": "<p>The following graph shows the fraction of direct connections between a <a href=\"about.html#relay\">relay</a> and other nodes in the network that are used uni- or bi-directionally.  Every 10 seconds, relays determine for every direct connection whether they read and wrote less than a threshold of 20 KiB.  Connections below this threshold are excluded from the graph.  For the remaining connections, relays determine whether they read/wrote at least 10 times as many bytes as they wrote/read.  If so, they classify a connection as \"Mostly reading\" or \"Mostly writing\", respectively.  All other connections are classified as \"Both reading and writing\".  After classifying connections, read and write counters are reset for the next 10-second interval.  The graph shows daily medians and inter-quartile ranges of reported fractions.</p>",
+    "function": "plot_connbidirect",
+    "parameters": [
+      "start",
+      "end"
+    ],
+    "data": [
+      "connbidirect2-data"
+    ]
+  },
+  {
+    "id": "torperf-data",
+    "title": "Performance of downloading static files over Tor",
+    "tags": [
+      "Performance"
+    ],
+    "type": "Data",
+    "level": "Advanced",
+    "description": "<p>The following data file contains aggregate statistics on performance when downloading static files of different sizes over Tor.  These statistics are generated by the <a href=\"https://gitweb.torproject.org/torperf.git\">Torperf</a> tool, which periodically fetches static files over Tor and records several timestamps in the process.  The data file contains daily medians and quartiles as well as total numbers of requests, timeouts, and failures.  Raw Torperf measurement data are available on the <a href=\"https://collector.torproject.org/formats.html#torperf\">CollecTor</a> website.</p>",
+    "data_file": "stats/torperf.csv",
+    "data_column_spec": [
+      "<b>date:</b> UTC date (YYYY-MM-DD) when download performance was measured.",
+      "<b>size:</b> Size of the downloaded file in bytes.",
+      "<b>source:</b> Name of the Torperf service performing measurements.  If this column contains the empty string, all measurements are included, regardless of which Torperf service performed them.  Examples are <b>\"moria\"</b>, <b>\"siv\"</b>, and <b>\"torperf\"</b>.",
+      "<b>q1:</b> First quartile of time until receiving the last byte in milliseconds.",
+      "<b>md:</b> Median of time until receiving the last byte in milliseconds.",
+      "<b>q3:</b> Third quartile of time until receiving the last byte in milliseconds.",
+      "<b>timeouts:</b> Number of timeouts that occurred when attempting to download the static file over Tor.",
+      "<b>failures:</b> Number of failures that occurred when attempting to download the static file over Tor.",
+      "<b>requests:</b> Total number of requests made to download the static file over Tor."
+    ],
+    "related": [
+      "torperf",
+      "torperf-failures"
+    ]
+  },
+  {
+    "id": "connbidirect-data",
+    "title": "Fraction of connections used uni-/bidirectionally (deprecated)",
+    "tags": [
+      "Performance"
+    ],
+    "type": "Data",
+    "level": "Advanced",
+    "description": "<p><font color=\"red\">As of August 25, 2015, this page and the linked data file have been replaced by <a href=\"connbidirect2-data.html\">this page and the data file linked from there</a>.  Starting on January 11, 2016, the linked data file is not updated anymore.  This page and the linked data file will be removed in the future.</font></p><p>The following data file contains statistics on the fraction of direct connections between a <a href=\"about.html#relay\">relay</a> and other nodes in the network that are used uni- or bidirectionally.  Every 10 seconds, relays determine for every direct connection whether they read and wrote less than a threshold of 20 KiB.  For the remaining connections, relays determine whether they read/wrote at least 10 times as many bytes as they wrote/read.  If so, they classify a connection as \"mostly reading\" or \"mostly writing\", respectively.  All other connections are classified as \"both reading and writing\".  After classify
 ing connections, read and write counters are reset for the next 10-second interval.  The data file contains the absolute number of 10-second intervals per relay, aggregated over 24-hour periods.</p>",
+    "data_file": "stats/connbidirect.csv",
+    "data_column_spec": [
+      "<b>date:</b> UTC date (YYYY-MM-DD) for which statistics on uni-/bidirectional connection usage were reported.",
+      "<b>source:</b> Fingerprint of the relay reporting statistics.",
+      "<b>below:</b> Number of 10-second intervals of connections with less than 20 KiB read and written data.",
+      "<b>read:</b> Number of 10-second intervals of connections with 10 times as many read bytes as written bytes.",
+      "<b>write:</b> Number of 10-second intervals of connections with 10 times as many written bytes as read bytes.",
+      "<b>both:</b> Number of 10-second intervals of connections with less than 10 times as many written or read bytes as in the other direction."
+    ],
+    "related": [
+      "connbidirect"
+    ]
+  },
+  {
+    "id": "connbidirect2-data",
+    "title": "Fraction of connections used uni-/bidirectionally",
+    "tags": [
+      "Performance"
+    ],
+    "type": "Data",
+    "level": "Advanced",
+    "description": "<p>The following data file contains statistics on the fraction of direct connections between a <a href=\"about.html#relay\">relay</a> and other nodes in the network that are used uni- or bidirectionally.  Every 10 seconds, relays determine for every direct connection whether they read and wrote less than a threshold of 20 KiB.  Connections below this threshold are excluded from the statistics file.  For the remaining connections, relays determine whether they read/wrote at least 10 times as many bytes as they wrote/read.  If so, they classify a connection as \"mostly reading\" or \"mostly writing\", respectively.  All other connections are classified as \"both reading and writing\".  After classifying connections, read and write counters are reset for the next 10-second interval.  The data file contains daily medians and quartiles of reported fractions.</p>",
+    "data_file": "stats/connbidirect2.csv",
+    "data_column_spec": [
+      "<b>date:</b> UTC date (YYYY-MM-DD) for which statistics on uni-/bidirectional connection usage were reported.",
+      "<b>direction:</b> Direction of reported fraction, which can be <b>\"read\"</b>, <b>\"write\"</b>, or <b>\"both\"</b> for connections classified as \"mostly reading\", \"mostly writing\", or \"both reading as writing\".  Connections below the threshold have been removed from this statistics file entirely.",
+      "<b>quantile:</b> Quantile of the reported fraction when considering all statistics reported for this date.  Examples are <b>\"0.5\"</b> for the median and <b>\"0.25\"</b> and <b>\"0.75\"</b> for the lower and upper quartile.",
+      "<b>fraction:</b> Fraction of connections in percent for the given date, direction, and quantile.  For each daily statistic reported by a relay, fractions for the three directions \"read\", \"write\", and \"both\" sum up to exactly 100."
+    ],
+    "related": [
+      "connbidirect"
+    ]
+  },
+  {
+    "id": "hidserv-dir-onions-seen",
+    "title": "Unique .onion addresses",
+    "tags": [
+      "Hidden services"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows the number of unique .onion addresses in the network per day.  These numbers are extrapolated from aggregated statistics on unique .onion addresses reported by single <a href=\"about.html#relay\">relays</a> acting as <a href=\"about.html#hidden-service\">hidden-service</a> directories, if at least 1% of relays reported these statistics.  For more details on the extrapolation algorithm, see <a href=\"https://blog.torproject.org/blog/some-statistics-about-onions\">this blog post</a> and <a href=\"https://research.torproject.org/techreports/extrapolating-hidserv-stats-2015-01-31.pdf\">this technical report</a>.</p>",
+    "function": "plot_hidserv_dir_onions_seen",
+    "parameters": [
+      "start",
+      "end"
+    ],
+    "data": [
+      "hidserv-data"
+    ],
+    "related": [
+      "hidserv-rend-relayed-cells",
+      "hidserv-frac-reporting"
+    ]
+  },
+  {
+    "id": "hidserv-rend-relayed-cells",
+    "title": "Hidden-service traffic",
+    "tags": [
+      "Hidden services"
+    ],
+    "type": "Graph",
+    "level": "Basic",
+    "description": "<p>The following graph shows the amount of hidden-service traffic in the network per day.  This number is extrapolated from aggregated statistics on hidden-service traffic reported by single <a href=\"about.html#relay\">relays</a> acting as rendezvous points for <a href=\"about.html#hidden-service\">hidden services</a>, if at least 1% of relays reported these statistics.  For more details on the extrapolation algorithm, see <a href=\"https://blog.torproject.org/blog/some-statistics-about-onions\">this blog post</a> and <a href=\"https://research.torproject.org/techreports/extrapolating-hidserv-stats-2015-01-31.pdf\">this technical report</a>.</p>",
+    "function": "plot_hidserv_rend_relayed_cells",
+    "parameters": [
+      "start",
+      "end"
+    ],
+    "data": [
+      "hidserv-data"
+    ],
+    "related": [
+      "hidserv-dir-onions-seen",
+      "hidserv-frac-reporting"
+    ]
+  },
+  {
+    "id": "hidserv-frac-reporting",
+    "title": "Fraction of relays reporting hidden-service statistics",
+    "tags": [
+      "Hidden services"
+    ],
+    "type": "Graph",
+    "level": "Advanced",
+    "description": "<p>The following graph shows the fraction of <a href=\"about.html#relay\">relays</a> that report statistics on <a href=\"about.html#hidden-service\">hidden service</a> usage.  If at least 1% of relays report a statistic, it gets extrapolated towards a network total, where higher fractions are produce more accurate results.  For more details on the extrapolation algorithm, see <a href=\"https://blog.torproject.org/blog/some-statistics-about-onions\">this blog post</a> and <a href=\"https://research.torproject.org/techreports/extrapolating-hidserv-stats-2015-01-31.pdf\">this technical report</a>.</p>",
+    "function": "plot_hidserv_frac_reporting",
+    "parameters": [
+      "start",
+      "end"
+    ],
+    "data": [
+      "hidserv-data"
+    ],
+    "related": [
+      "hidserv-dir-onions-seen",
+      "hidserv-rend-relayed-cells"
+    ]
+  },
+  {
+    "id": "hidserv-data",
+    "title": "Hidden-service statistics",
+    "tags": [
+      "Hidden services"
+    ],
+    "type": "Data",
+    "level": "Advanced",
+    "description": "<p>The following data file contains <a href=\"about.html#hidden-service\">hidden-service</a> statistics gathered by a small subset of <a href=\"about.html#relay\">relays</a> and extrapolated to network totals.  Statistics include the amount of hidden-service traffic and the number of hidden-service addresses in the network per day.  For more details on the extrapolation algorithm, see <a href=\"https://blog.torproject.org/blog/some-statistics-about-onions\">this blog post</a> and <a href=\"https://research.torproject.org/techreports/extrapolating-hidserv-stats-2015-01-31.pdf\">this technical report</a>.</p>",
+    "data_file": "stats/hidserv.csv",
+    "data_column_spec": [
+      "<b>date:</b> UTC date (YYYY-MM-DD) when relays or bridges have been listed as running.",
+      "<b>type:</b> Type of hidden-service statistic reported by relays and extrapolated to network totals.  Examples include <b>\"rend-relayed-cells\"</b> for the number of cells on rendezvous circuits observed by rendezvous points and <b>\"dir-onions-seen\"</b> for the number of unique .onion addresses observed by hidden-service directories.",
+      "<b>wmean:</b> Weighted mean of extrapolated network totals.",
+      "<b>wmedian:</b> Weighted median of extrapolated network totals.",
+      "<b>wiqm:</b> Weighted interquartile mean of extrapolated network totals.",
+      "<b>frac:</b> Total network fraction of reported statistics.",
+      "<b>stats:</b> Number of reported statistics with non-zero computed network fraction."
+    ],
+    "related": [
+      "hidserv-dir-onions-seen",
+      "hidserv-rend-relayed-cells",
+      "hidserv-frac-reporting"
+    ]
+  },
+  {
+    "id": "uncharted-data-flow",
+    "title": "Data flow in the Tor network",
+    "tags": [
+      "Relays",
+      "Hidden services",
+      "Bandwidth"
+    ],
+    "type": "Link",
+    "level": "Basic",
+    "description": "<p>Uncharted made a visualization of data flow in the Tor network where they place each <a href=\"about.html#relay\">relay</a> on a world map and illustrate traffic exchanged between relays as animated dots. More details can be found on the <a href=\"https://torflow.uncharted.software/\">Uncharted website</a>.</p><p><a href=\"https://torflow.uncharted.software/\"><img src=\"images/uncharted-data-flow.png\" alt=\"Data flow in the Tor network\"></a></p>"
+  }
+]
diff --git a/website/etc/web.xml b/website/etc/web.xml
index 2081067..674be4c 100644
--- a/website/etc/web.xml
+++ b/website/etc/web.xml
@@ -18,13 +18,13 @@
   </servlet-mapping>
 
   <servlet>
-    <servlet-name>GraphsSubpages</servlet-name>
+    <servlet-name>Graph</servlet-name>
     <servlet-class>
-      org.torproject.metrics.web.graphs.GraphsSubpagesServlet
+      org.torproject.metrics.web.GraphServlet
     </servlet-class>
   </servlet>
   <servlet-mapping>
-    <servlet-name>GraphsSubpages</servlet-name>
+    <servlet-name>Graph</servlet-name>
     <url-pattern>/networksize.html</url-pattern>
     <url-pattern>/relayflags.html</url-pattern>
     <url-pattern>/versions.html</url-pattern>
@@ -37,16 +37,38 @@
     <url-pattern>/advbwdist-perc.html</url-pattern>
     <url-pattern>/advbwdist-relay.html</url-pattern>
     <url-pattern>/userstats-relay-country.html</url-pattern>
-    <url-pattern>/userstats-relay-table.html</url-pattern>
-    <url-pattern>/userstats-censorship-events.html</url-pattern>
     <url-pattern>/userstats-bridge-country.html</url-pattern>
-    <url-pattern>/userstats-bridge-table.html</url-pattern>
     <url-pattern>/userstats-bridge-transport.html</url-pattern>
     <url-pattern>/userstats-bridge-version.html</url-pattern>
-    <url-pattern>/oxford-anonymous-internet.html</url-pattern>
     <url-pattern>/torperf.html</url-pattern>
     <url-pattern>/torperf-failures.html</url-pattern>
     <url-pattern>/connbidirect.html</url-pattern>
+    <url-pattern>/hidserv-dir-onions-seen.html</url-pattern>
+    <url-pattern>/hidserv-rend-relayed-cells.html</url-pattern>
+    <url-pattern>/hidserv-frac-reporting.html</url-pattern>
+  </servlet-mapping>
+
+  <servlet>
+    <servlet-name>Table</servlet-name>
+    <servlet-class>
+      org.torproject.metrics.web.TableServlet
+    </servlet-class>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>Table</servlet-name>
+    <url-pattern>/userstats-relay-table.html</url-pattern>
+    <url-pattern>/userstats-censorship-events.html</url-pattern>
+    <url-pattern>/userstats-bridge-table.html</url-pattern>
+  </servlet-mapping>
+
+  <servlet>
+    <servlet-name>Data</servlet-name>
+    <servlet-class>
+      org.torproject.metrics.web.DataServlet
+    </servlet-class>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>Data</servlet-name>
     <url-pattern>/servers-data.html</url-pattern>
     <url-pattern>/bandwidth-data.html</url-pattern>
     <url-pattern>/advbwdist-data.html</url-pattern>
@@ -55,9 +77,17 @@
     <url-pattern>/connbidirect-data.html</url-pattern>
     <url-pattern>/connbidirect2-data.html</url-pattern>
     <url-pattern>/hidserv-data.html</url-pattern>
-    <url-pattern>/hidserv-dir-onions-seen.html</url-pattern>
-    <url-pattern>/hidserv-rend-relayed-cells.html</url-pattern>
-    <url-pattern>/hidserv-frac-reporting.html</url-pattern>
+  </servlet-mapping>
+
+  <servlet>
+    <servlet-name>Link</servlet-name>
+    <servlet-class>
+      org.torproject.metrics.web.LinkServlet
+    </servlet-class>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>Link</servlet-name>
+    <url-pattern>/oxford-anonymous-internet.html</url-pattern>
     <url-pattern>/uncharted-data-flow.html</url-pattern>
   </servlet-mapping>
 
diff --git a/website/rserve/tables.R b/website/rserve/tables.R
index 54d887c..bb2cbc8 100644
--- a/website/rserve/tables.R
+++ b/website/rserve/tables.R
@@ -21,7 +21,7 @@ write_userstats <- function(start, end, node, path) {
     cc = as.character(u$country),
     country = sub('the ', '', countrynames(as.character(u$country))),
     abs = round(u$users),
-    rel = round(100 * u$users / total, 2))
+    rel = sprintf("%.2f", round(100 * u$users / total, 2)))
   write.csv(u, path, quote = FALSE, row.names = FALSE)
 }
 
diff --git a/website/src/org/torproject/metrics/web/DataServlet.java b/website/src/org/torproject/metrics/web/DataServlet.java
new file mode 100644
index 0000000..bbc60c5
--- /dev/null
+++ b/website/src/org/torproject/metrics/web/DataServlet.java
@@ -0,0 +1,42 @@
+/* Copyright 2016 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.metrics.web;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+ at SuppressWarnings("serial")
+public class DataServlet extends MetricServlet {
+
+  public void doGet(HttpServletRequest request,
+      HttpServletResponse response) throws IOException, ServletException {
+    String requestURI = request.getRequestURI();
+    if (requestURI == null || !requestURI.endsWith(".html")) {
+      response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+      return;
+    }
+    String requestedId = requestURI.substring(
+        requestURI.contains("/") ? requestURI.lastIndexOf("/") + 1 : 0,
+        requestURI.length() - 5);
+    if (!this.idsByType.containsKey("Data") ||
+        !this.idsByType.get("Data").contains(requestedId)) {
+      response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+      return;
+    }
+    request.setAttribute("id", requestedId);
+    request.setAttribute("title", this.titles.get(requestedId));
+    request.setAttribute("description",
+        this.descriptions.get(requestedId));
+    request.setAttribute("data_file", this.dataFiles.get(requestedId));
+    request.setAttribute("data_column_spec",
+        this.dataColumnSpecs.get(requestedId));
+    request.setAttribute("data", this.data.get(requestedId));
+    request.setAttribute("related", this.related.get(requestedId));
+    request.getRequestDispatcher("WEB-INF/data.jsp").forward(request,
+        response);
+  }
+}
+
diff --git a/website/src/org/torproject/metrics/web/GraphServlet.java b/website/src/org/torproject/metrics/web/GraphServlet.java
new file mode 100644
index 0000000..4dfae9b
--- /dev/null
+++ b/website/src/org/torproject/metrics/web/GraphServlet.java
@@ -0,0 +1,210 @@
+/* Copyright 2016 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.metrics.web;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.torproject.metrics.web.graphs.Countries;
+import org.torproject.metrics.web.graphs.GraphParameterChecker;
+
+ at SuppressWarnings("serial")
+public class GraphServlet extends MetricServlet {
+
+  private Map<String, String[][]> defaultParameters =
+      new HashMap<String, String[][]>();
+
+  public void init() throws ServletException {
+    super.init();
+    this.defaultParameters.put("p", new String[][] {
+        { "100", " checked", "100 (maximum)" },
+        { "99", "", "99" },
+        { "98", "", "98" },
+        { "97", "", "97" },
+        { "95", "", "95" },
+        { "91", "", "91" },
+        { "90", "", "90" },
+        { "80", "", "80" },
+        { "75", "", "75 (3rd quartile)" },
+        { "70", "", "70" },
+        { "60", "", "60" },
+        { "50", "", "50 (median)" },
+        { "40", "", "40" },
+        { "30", "", "30" },
+        { "25", "", "25 (first quartile)" },
+        { "20", "", "20" },
+        { "10", "", "10" },
+        { "9", "", "9" },
+        { "5", "", "5" },
+        { "3", "", "3" },
+        { "2", "", "2" },
+        { "1", "", "1" },
+        { "0", "", "0 (minimum)" } });
+    this.defaultParameters.put("n", new String[][] {
+        { "1", " checked" },
+        { "2", "" },
+        { "3", "" },
+        { "5", "" },
+        { "10", "" },
+        { "20", "" },
+        { "30", "" },
+        { "50", "" },
+        { "100", "" },
+        { "200", "" },
+        { "300", "" },
+        { "500", "" },
+        { "1000", "" },
+        { "2000", "" },
+        { "3000", "" },
+        { "5000", "" } });
+    this.defaultParameters.put("flag", new String[][] {
+        { "Running", " checked" },
+        { "Exit", " checked" },
+        { "Fast", " checked" },
+        { "Guard", " checked" },
+        { "Stable", " checked" },
+        { "HSDir", "" } });
+    List<String[]> knownCountries =
+        Countries.getInstance().getCountryList();
+    String[][] countries = new String[knownCountries.size() + 1][];
+    int i = 0;
+    countries[i++] = new String[] { "all", " selected", "All users" };
+    for (String[] country : knownCountries) {
+      countries[i++] = new String[] { country[0], "", country[1] };
+    }
+    this.defaultParameters.put("country", countries);
+    this.defaultParameters.put("events", new String[][] {
+        { "off", " selected", "Off" },
+        { "on", "", "On: both points and expected range" },
+        { "points", "", "On: points only, no expected range" } });
+    this.defaultParameters.put("transport", new String[][] {
+        { "!<OR>", " checked", "Any pluggable transport" },
+        { "obfs2", "", "obfs2" },
+        { "obfs3", "", "obfs3" },
+        { "obfs4", "", "obfs4" },
+        { "websocket", "", "Flash proxy/websocket" },
+        { "fte", "", "FTE" },
+        { "meek", "", "meek" },
+        { "scramblesuit", "", "Unknown pluggable transport(s)" },
+        { "<OR>", "", "Default OR protocol" } });
+    this.defaultParameters.put("version", new String[][] {
+        { "v4", " selected", "IPv4" },
+        { "v6", "", "IPv6" } });
+    this.defaultParameters.put("source", new String[][] {
+        { "all", " checked" },
+        { "torperf", "" },
+        { "moria", "" },
+        { "siv", "" } });
+    this.defaultParameters.put("filesize", new String[][] {
+        { "50kb", " checked", "50 KiB" },
+        { "1mb", "", "1 MiB" },
+        { "5mb", "", "5 MiB" } });
+  }
+
+  protected void doGet(HttpServletRequest request,
+      HttpServletResponse response) throws ServletException, IOException {
+    String requestURI = request.getRequestURI();
+    if (requestURI == null || !requestURI.endsWith(".html")) {
+      response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+      return;
+    }
+    String requestedId = requestURI.substring(
+        requestURI.contains("/") ? requestURI.lastIndexOf("/") + 1 : 0,
+        requestURI.length() - 5);
+    if (!this.idsByType.containsKey("Graph") ||
+        !this.idsByType.get("Graph").contains(requestedId)) {
+      response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+      return;
+    }
+    request.setAttribute("id", requestedId);
+    request.setAttribute("title", this.titles.get(requestedId));
+    request.setAttribute("description",
+        this.descriptions.get(requestedId));
+    request.setAttribute("data", this.data.get(requestedId));
+    request.setAttribute("related", this.related.get(requestedId));
+    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+    Date defaultEndDate = new Date();
+    Date defaultStartDate = new Date(defaultEndDate.getTime()
+        - 90L * 24L * 60L * 60L * 1000L);
+    if (this.parameters.containsKey(requestedId)) {
+      Map<String, String[]> checkedParameters = GraphParameterChecker.
+          getInstance().checkParameters(requestedId,
+          request.getParameterMap());
+      StringBuilder urlBuilder = new StringBuilder();
+      for (String parameter : this.parameters.get(requestedId)) {
+        if (parameter.equals("start") || parameter.equals("end")) {
+          String[] requestParameter;
+          if (checkedParameters != null &&
+              checkedParameters.containsKey(parameter)) {
+            requestParameter = checkedParameters.get(parameter);
+          } else {
+            requestParameter = new String[] {
+                dateFormat.format(parameter.equals("start")
+                ? defaultStartDate : defaultEndDate) };
+          }
+          urlBuilder.append(String.format("&%s=%s", parameter,
+              requestParameter[0]));
+          request.setAttribute(parameter, requestParameter);
+        } else if (parameter.equals("p") ||
+            parameter.equals("n") ||
+            parameter.equals("flag") ||
+            parameter.equals("country") ||
+            parameter.equals("events") ||
+            parameter.equals("transport") ||
+            parameter.equals("version") ||
+            parameter.equals("source") ||
+            parameter.equals("filesize")) {
+          String[][] defaultParameters =
+              this.defaultParameters.get(parameter);
+          String[][] requestParameters =
+              new String[defaultParameters.length][];
+          Set<String> checked = null;
+          if (checkedParameters != null &&
+              checkedParameters.containsKey(parameter)) {
+            checked = new HashSet<String>(Arrays.asList(
+                checkedParameters.get(parameter)));
+          }
+          String checkedOrSelected = parameter.equals("country") ||
+              parameter.equals("events") || parameter.equals("version")
+              ? " selected" : " checked";
+          for (int i = 0; i < defaultParameters.length; i++) {
+            requestParameters[i] =
+                new String[defaultParameters[i].length];
+            System.arraycopy(defaultParameters[i], 0,
+                requestParameters[i], 0, defaultParameters[i].length);
+            if (checked != null) {
+              if (checked.contains(requestParameters[i][0])) {
+                requestParameters[i][1] = checkedOrSelected;
+                urlBuilder.append(String.format("&%s=%s", parameter,
+                    requestParameters[i][0]));
+              } else {
+                requestParameters[i][1] = "";
+              }
+            }
+          }
+          request.setAttribute(parameter, requestParameters);
+        }
+      }
+      if (urlBuilder.length() > 5) {
+        String url = "?" + urlBuilder.toString().substring(5);
+        request.setAttribute("parameters", url);
+      }
+    }
+    request.getRequestDispatcher("WEB-INF/graph.jsp").forward(request,
+        response);
+  }
+}
+
diff --git a/website/src/org/torproject/metrics/web/IndexServlet.java b/website/src/org/torproject/metrics/web/IndexServlet.java
index 47cb4cd..d3c0b35 100644
--- a/website/src/org/torproject/metrics/web/IndexServlet.java
+++ b/website/src/org/torproject/metrics/web/IndexServlet.java
@@ -1,4 +1,4 @@
-/* Copyright 2011--2015 The Tor Project
+/* Copyright 2011--2016 The Tor Project
  * See LICENSE for licensing information */
 package org.torproject.metrics.web;
 
@@ -58,117 +58,16 @@ public class IndexServlet extends HttpServlet {
   };
   private static final String[] defaultOrders = new String[] { "type" };
 
-  private final static List<Metric> availableMetrics;
-  static {
-    availableMetrics = new ArrayList<Metric>();
-    availableMetrics.add(new Metric("networksize.html",
-        "Relays and bridges in the network",
-        new String[] { "Relays", "Bridges" }, "Graph", "Basic"));
-    availableMetrics.add(new Metric("relayflags.html",
-        "Relays with Exit, Fast, Guard, Stable, and HSDir flags",
-        new String[] { "Relays" }, "Graph", "Basic"));
-    availableMetrics.add(new Metric("versions.html",
-        "Relays by version", new String[] { "Relays", "Diversity" },
-        "Graph", "Basic"));
-    availableMetrics.add(new Metric("platforms.html",
-        "Relays by platform", new String[] { "Relays", "Diversity" },
-        "Graph", "Basic"));
-    availableMetrics.add(new Metric("cloudbridges.html",
-        "Tor Cloud bridges", new String[] { "Bridges" }, "Graph",
-        "Basic"));
-    availableMetrics.add(new Metric("servers-data.html",
-        "Number of relays and bridges",
-        new String[] { "Relays", "Bridges", "Diversity" }, "Data",
-        "Advanced"));
-    availableMetrics.add(new Metric("bandwidth.html",
-        "Total relay bandwidth in the network",
-        new String[] { "Relays", "Bandwidth" }, "Graph", "Basic"));
-    availableMetrics.add(new Metric("bwhist-flags.html",
-        "Relay bandwidth by Exit and/or Guard flags",
-        new String[] { "Relays", "Bandwidth" }, "Graph", "Basic"));
-    availableMetrics.add(new Metric("bandwidth-flags.html",
-        "Advertised bandwidth and bandwidth history by relay flags",
-        new String[] { "Relays", "Bandwidth" }, "Graph", "Basic"));
-    availableMetrics.add(new Metric("dirbytes.html",
-        "Number of bytes spent on answering directory requests",
-        new String[] { "Relays", "Bandwidth" }, "Graph", "Basic"));
-    availableMetrics.add(new Metric("advbwdist-perc.html",
-        "Advertised bandwidth distribution",
-        new String[] { "Relays", "Bandwidth" }, "Graph", "Basic"));
-    availableMetrics.add(new Metric("advbwdist-relay.html",
-        "Advertised bandwidth of n-th fastest relays",
-        new String[] { "Relays", "Bandwidth" }, "Graph", "Basic"));
-    availableMetrics.add(new Metric("bandwidth-data.html",
-        "Bandwidth provided and consumed by relays",
-        new String[] { "Relays", "Bandwidth" }, "Data", "Advanced"));
-    availableMetrics.add(new Metric("advbwdist-data.html",
-        "Advertised bandwidth distribution and n-th fastest relays",
-        new String[] { "Relays", "Bandwidth" }, "Data", "Advanced"));
-    availableMetrics.add(new Metric("bubbles.html",
-        "Network bubble graphs", new String[] { "Relays", "Diversity" },
-        "Graph", "Basic"));
-    availableMetrics.add(new Metric("userstats-relay-country.html",
-        "Direct users by country", new String[] { "Clients" }, "Graph",
-        "Basic"));
-    availableMetrics.add(new Metric("userstats-relay-table.html",
-        "Top-10 countries by directly connecting users",
-        new String[] { "Clients" }, "Table", "Basic"));
-    availableMetrics.add(new Metric("userstats-censorship-events.html",
-        "Top-10 countries by possible censorship events",
-        new String[] { "Clients" }, "Table", "Basic"));
-    availableMetrics.add(new Metric("userstats-bridge-country.html",
-        "Bridge users by country", new String[] { "Clients" }, "Graph",
-        "Basic"));
-    availableMetrics.add(new Metric("userstats-bridge-table.html",
-        "Top-10 countries by bridge users", new String[] { "Clients" },
-        "Table", "Basic"));
-    availableMetrics.add(new Metric("userstats-bridge-transport.html",
-        "Bridge users by transport",
-        new String[] { "Clients", "Pluggable transports" }, "Graph",
-        "Basic"));
-    availableMetrics.add(new Metric("userstats-bridge-version.html",
-        "Bridge users by IP version", new String[] { "Clients" }, "Graph",
-        "Basic"));
-    availableMetrics.add(new Metric("oxford-anonymous-internet.html",
-        "Tor users as percentage of larger Internet population",
-        new String[] { "Clients" }, "Link", "Basic"));
-    availableMetrics.add(new Metric("clients-data.html",
-        "Estimated number of clients in the Tor network",
-        new String[] { "Clients", "Pluggable transports" }, "Data",
-        "Advanced"));
-    availableMetrics.add(new Metric("torperf.html",
-        "Time to download files over Tor", new String[] { "Performance" },
-        "Graph", "Basic"));
-    availableMetrics.add(new Metric("torperf-failures.html",
-        "Timeouts and failures of downloading files over Tor",
-        new String[] { "Performance" }, "Graph", "Advanced"));
-    availableMetrics.add(new Metric("connbidirect.html",
-        "Fraction of connections used uni-/bidirectionally",
-        new String[] { "Performance" }, "Graph", "Advanced"));
-    availableMetrics.add(new Metric("torperf-data.html",
-        "Performance of downloading static files over Tor",
-        new String[] { "Performance" }, "Data", "Advanced"));
-    availableMetrics.add(new Metric("connbidirect-data.html",
-        "Fraction of connections used uni-/bidirectionally (deprecated)",
-        new String[] { "Performance" }, "Data", "Advanced"));
-    availableMetrics.add(new Metric("connbidirect2-data.html",
-        "Fraction of connections used uni-/bidirectionally",
-        new String[] { "Performance" }, "Data", "Advanced"));
-    availableMetrics.add(new Metric("hidserv-dir-onions-seen.html",
-        "Unique .onion addresses", new String[] { "Hidden services" },
-        "Graph", "Basic"));
-    availableMetrics.add(new Metric("hidserv-rend-relayed-cells.html",
-        "Hidden-service traffic", new String[] { "Hidden services" },
-        "Graph", "Basic"));
-    availableMetrics.add(new Metric("hidserv-frac-reporting.html",
-        "Fraction of relays reporting hidden-service statistics",
-        new String[] { "Hidden services" }, "Graph", "Advanced"));
-    availableMetrics.add(new Metric("hidserv-data.html",
-        "Hidden-service statistics", new String[] { "Hidden services" },
-        "Data", "Advanced"));
-    availableMetrics.add(new Metric("uncharted-data-flow.html",
-        "Data flow in the Tor network", new String[] { "Relays",
-        "Hidden services", "Bandwidth" }, "Link", "Basic"));
+  private List<Metric> availableMetrics;
+
+  public void init() throws ServletException {
+    this.availableMetrics = new ArrayList<Metric>();
+    for (org.torproject.metrics.web.Metric metric :
+        MetricsProvider.getInstance().getMetricsList()) {
+      this.availableMetrics.add(new Metric(metric.getId() + ".html",
+          metric.getTitle(), metric.getTags(), metric.getType(),
+          metric.getLevel()));
+    }
   }
 
   public void doGet(HttpServletRequest request,
diff --git a/website/src/org/torproject/metrics/web/LinkServlet.java b/website/src/org/torproject/metrics/web/LinkServlet.java
new file mode 100644
index 0000000..4066909
--- /dev/null
+++ b/website/src/org/torproject/metrics/web/LinkServlet.java
@@ -0,0 +1,39 @@
+/* Copyright 2016 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.metrics.web;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+ at SuppressWarnings("serial")
+public class LinkServlet extends MetricServlet {
+
+  public void doGet(HttpServletRequest request,
+      HttpServletResponse response) throws IOException, ServletException {
+    String requestURI = request.getRequestURI();
+    if (requestURI == null || !requestURI.endsWith(".html")) {
+      response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+      return;
+    }
+    String requestedId = requestURI.substring(
+        requestURI.contains("/") ? requestURI.lastIndexOf("/") + 1 : 0,
+        requestURI.length() - 5);
+    if (!this.idsByType.containsKey("Link") ||
+        !this.idsByType.get("Link").contains(requestedId)) {
+      response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+      return;
+    }
+    request.setAttribute("id", requestedId);
+    request.setAttribute("title", this.titles.get(requestedId));
+    request.setAttribute("description",
+        this.descriptions.get(requestedId));
+    request.setAttribute("data", this.data.get(requestedId));
+    request.setAttribute("related", this.related.get(requestedId));
+    request.getRequestDispatcher("WEB-INF/link.jsp").forward(request,
+        response);
+  }
+}
+
diff --git a/website/src/org/torproject/metrics/web/Metric.java b/website/src/org/torproject/metrics/web/Metric.java
new file mode 100644
index 0000000..03ff4af
--- /dev/null
+++ b/website/src/org/torproject/metrics/web/Metric.java
@@ -0,0 +1,63 @@
+/* Copyright 2016 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.metrics.web;
+
+public class Metric {
+  private String id;
+  private String title;
+  private String[] tags;
+  private String type;
+  private String level;
+  private String description;
+  private String function;
+  private String[] parameters;
+  private String[] data;
+  private String[] related;
+  private String[] table_headers;
+  private String[] table_cell_formats;
+  private String data_file;
+  private String[] data_column_spec;
+  public String getId() {
+    return this.id;
+  }
+  public String getTitle() {
+    return this.title;
+  }
+  public String[] getTags() {
+    return this.tags;
+  }
+  public String getType() {
+    return this.type;
+  }
+  public String getLevel() {
+    return this.level;
+  }
+  public String getDescription() {
+    return this.description;
+  }
+  public String getFunction() {
+    return this.function;
+  }
+  public String[] getParameters() {
+    return this.parameters;
+  }
+  public String[] getTableHeaders() {
+    return this.table_headers;
+  }
+  public String[] getTableCellFormats() {
+    return this.table_cell_formats;
+  }
+  public String getDataFile() {
+    return this.data_file;
+  }
+  public String[] getDataColumnSpec() {
+    return this.data_column_spec;
+  }
+  public String[] getData() {
+    return this.data;
+  }
+  public String[] getRelated() {
+    return this.related;
+  }
+}
+
diff --git a/website/src/org/torproject/metrics/web/MetricServlet.java b/website/src/org/torproject/metrics/web/MetricServlet.java
new file mode 100644
index 0000000..deb7fe3
--- /dev/null
+++ b/website/src/org/torproject/metrics/web/MetricServlet.java
@@ -0,0 +1,116 @@
+/* Copyright 2016 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.metrics.web;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+
+ at SuppressWarnings("serial")
+public abstract class MetricServlet extends HttpServlet {
+
+  protected List<Metric> metrics;
+
+  protected Map<String, Set<String>> idsByType =
+      new HashMap<String, Set<String>>();
+
+  protected Map<String, String> descriptions =
+      new HashMap<String, String>();
+
+  protected Map<String, String> titles = new HashMap<String, String>();
+
+  protected Map<String, List<String>> parameters =
+      new HashMap<String, List<String>>();
+
+  protected Map<String, String[]> tableHeaders =
+      new HashMap<String, String[]>();
+
+  protected Map<String, String[]> tableCellFormats =
+      new HashMap<String, String[]>();
+
+  protected Map<String, String> dataFiles = new HashMap<String, String>();
+
+  protected Map<String, String[]> dataColumnSpecs =
+      new HashMap<String, String[]>();
+
+  protected Map<String, List<String[]>> data =
+      new HashMap<String, List<String[]>>();
+
+  protected Map<String, List<String[]>> related =
+      new HashMap<String, List<String[]>>();
+
+  public void init() throws ServletException {
+    this.metrics = MetricsProvider.getInstance().getMetricsList();
+    Map<String, String> allTypesAndTitles = new HashMap<String, String>();
+    Map<String, String[]> dataIds = new HashMap<String, String[]>();
+    Map<String, String[]> relatedIds = new HashMap<String, String[]>();
+    for (Metric metric : this.metrics) {
+      String id = metric.getId();
+      String title = metric.getTitle();
+      String type = metric.getType();
+      allTypesAndTitles.put(id, String.format("%s: %s", type, title));
+      if (!this.idsByType.containsKey(type)) {
+        this.idsByType.put(type, new HashSet<String>());
+      }
+      this.idsByType.get(type).add(id);
+      this.titles.put(id, title);
+      this.descriptions.put(id, metric.getDescription());
+      if (metric.getParameters() != null) {
+        this.parameters.put(id, Arrays.asList(metric.getParameters()));
+      }
+      if (metric.getTableHeaders() != null) {
+        this.tableHeaders.put(id, metric.getTableHeaders());
+      }
+      if (metric.getTableCellFormats() != null) {
+        this.tableCellFormats.put(id, metric.getTableCellFormats());
+      }
+      if (metric.getDataFile() != null) {
+        this.dataFiles.put(id, metric.getDataFile());
+      }
+      if (metric.getDataColumnSpec() != null) {
+        this.dataColumnSpecs.put(id, metric.getDataColumnSpec());
+      }
+      if (metric.getData() != null) {
+        dataIds.put(id, metric.getData());
+      }
+      if (metric.getRelated() != null) {
+        relatedIds.put(id, metric.getRelated());
+      }
+    }
+    for (Set<String> ids : idsByType.values()) {
+      for (String id : ids) {
+        if (dataIds.containsKey(id)) {
+          List<String[]> dataLinksTypesAndTitles =
+              new ArrayList<String[]>();
+          for (String dataId : dataIds.get(id)) {
+            if (allTypesAndTitles.containsKey(dataId)) {
+              dataLinksTypesAndTitles.add(new String[] { dataId + ".html",
+                  allTypesAndTitles.get(dataId) } );
+            }
+          }
+          this.data.put(id, dataLinksTypesAndTitles);
+        }
+        if (relatedIds.containsKey(id)) {
+          List<String[]> relatedLinksTypesAndTitles =
+              new ArrayList<String[]>();
+          for (String relatedId : relatedIds.get(id)) {
+            if (allTypesAndTitles.containsKey(relatedId)) {
+              relatedLinksTypesAndTitles.add(new String[] {
+                  relatedId + ".html",
+                  allTypesAndTitles.get(relatedId) } );
+            }
+          }
+          this.related.put(id, relatedLinksTypesAndTitles);
+        }
+      }
+    }
+  }
+}
+
diff --git a/website/src/org/torproject/metrics/web/MetricsProvider.java b/website/src/org/torproject/metrics/web/MetricsProvider.java
new file mode 100644
index 0000000..9b66d7e
--- /dev/null
+++ b/website/src/org/torproject/metrics/web/MetricsProvider.java
@@ -0,0 +1,37 @@
+/* Copyright 2016 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.metrics.web;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+public class MetricsProvider {
+
+  private static MetricsProvider instance = new MetricsProvider();
+
+  public static MetricsProvider getInstance() {
+    return MetricsProvider.instance;
+  }
+
+  private List<Metric> metricsList;
+
+  private MetricsProvider() {
+    InputStream in = this.getClass().getClassLoader()
+        .getResourceAsStream("metrics.json");
+    Gson gson = new GsonBuilder().create();
+    Metric[] metricsArray = gson.fromJson(new InputStreamReader(in),
+        Metric[].class);
+    this.metricsList = Arrays.asList(metricsArray);
+  }
+
+  public List<Metric> getMetricsList() {
+    return new ArrayList<Metric>(this.metricsList);
+  }
+}
+
diff --git a/website/src/org/torproject/metrics/web/TableServlet.java b/website/src/org/torproject/metrics/web/TableServlet.java
new file mode 100644
index 0000000..beedfde
--- /dev/null
+++ b/website/src/org/torproject/metrics/web/TableServlet.java
@@ -0,0 +1,97 @@
+/* Copyright 2016 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.metrics.web;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang.text.StrSubstitutor;
+import org.torproject.metrics.web.graphs.RObjectGenerator;
+import org.torproject.metrics.web.graphs.TableParameterChecker;
+
+ at SuppressWarnings("serial")
+public class TableServlet extends MetricServlet {
+
+  private RObjectGenerator rObjectGenerator;
+
+  public void init() throws ServletException {
+    super.init();
+    this.rObjectGenerator = (RObjectGenerator) getServletContext().
+        getAttribute("RObjectGenerator");
+  }
+
+  protected void doGet(HttpServletRequest request,
+      HttpServletResponse response) throws ServletException, IOException {
+    String requestURI = request.getRequestURI();
+    if (requestURI == null || !requestURI.endsWith(".html")) {
+      response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+      return;
+    }
+    String requestedId = requestURI.substring(
+        requestURI.contains("/") ? requestURI.lastIndexOf("/") + 1 : 0,
+        requestURI.length() - 5);
+    if (!this.idsByType.containsKey("Table") ||
+        !this.idsByType.get("Table").contains(requestedId)) {
+      response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+      return;
+    }
+    request.setAttribute("id", requestedId);
+    request.setAttribute("title", this.titles.get(requestedId));
+    request.setAttribute("description",
+        this.descriptions.get(requestedId));
+    request.setAttribute("tableheader",
+        this.tableHeaders.get(requestedId));
+    request.setAttribute("data", this.data.get(requestedId));
+    request.setAttribute("related", this.related.get(requestedId));
+    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+    Date defaultEndDate = new Date();
+    Date defaultStartDate = new Date(defaultEndDate.getTime()
+        - 90L * 24L * 60L * 60L * 1000L);
+    if (this.parameters.containsKey(requestedId)) {
+      Map<String, String[]> checkedParameters = TableParameterChecker.
+          getInstance().checkParameters(requestedId,
+          request.getParameterMap());
+      for (String parameter : this.parameters.get(requestedId)) {
+        if (parameter.equals("start") || parameter.equals("end")) {
+          String[] requestParameter;
+          if (checkedParameters != null &&
+              checkedParameters.containsKey(parameter)) {
+            requestParameter = checkedParameters.get(parameter);
+          } else {
+            requestParameter = new String[] {
+                dateFormat.format(parameter.equals("start")
+                ? defaultStartDate : defaultEndDate) };
+          }
+          request.setAttribute(parameter, requestParameter);
+        }
+      }
+    }
+    List<Map<String, String>> tableData = rObjectGenerator.
+        generateTable(requestedId, request.getParameterMap(), true);
+    List<List<String>> formattedTableData =
+        new ArrayList<List<String>>();
+    String[] contents = this.tableCellFormats.get(requestedId);
+    for (Map<String, String> row : tableData) {
+      List<String> formattedRow = new ArrayList<String>();
+      StrSubstitutor sub = new StrSubstitutor(row);
+      for (String con : contents) {
+        formattedRow.add(sub.replace(con));
+      }
+      formattedTableData.add(formattedRow);
+    }
+    request.setAttribute("tabledata", formattedTableData);
+    request.getRequestDispatcher("WEB-INF/table.jsp").forward(request,
+        response);
+  }
+}
+
diff --git a/website/src/org/torproject/metrics/web/graphs/GraphParameterChecker.java b/website/src/org/torproject/metrics/web/graphs/GraphParameterChecker.java
index 5c3f3e1..58fd064 100644
--- a/website/src/org/torproject/metrics/web/graphs/GraphParameterChecker.java
+++ b/website/src/org/torproject/metrics/web/graphs/GraphParameterChecker.java
@@ -11,7 +11,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
-import java.util.regex.Pattern;
+
+import org.torproject.metrics.web.Metric;
+import org.torproject.metrics.web.MetricsProvider;
 
 /**
  * Checks request parameters passed to graph-generating servlets.
@@ -35,7 +37,7 @@ public class GraphParameterChecker {
   private SimpleDateFormat dateFormat;
 
   /* Available graphs with corresponding parameter lists. */
-  private Map<String, String> availableGraphs;
+  private Map<String, String[]> availableGraphs;
 
   /* Known parameters and parameter values. */
   private Map<String, String> knownParameterValues;
@@ -46,7 +48,12 @@ public class GraphParameterChecker {
   public GraphParameterChecker() {
     this.dateFormat = new SimpleDateFormat("yyyy-MM-dd");
     this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-
+    this.availableGraphs = new HashMap<String, String[]>();
+    for (Metric metric : MetricsProvider.getInstance().getMetricsList()) {
+      if ("Graph".equals(metric.getType())) {
+        this.availableGraphs.put(metric.getId(), metric.getParameters());
+      }
+    }
     this.knownParameterValues = new HashMap<String, String>();
     this.knownParameterValues.put("flag",
         "Running,Exit,Guard,Fast,Stable,HSDir");
@@ -67,10 +74,6 @@ public class GraphParameterChecker {
         + "500,1000,2000,3000,5000");
   }
 
-  public void setAvailableGraphs(Map<String, String> availableGraphs) {
-    this.availableGraphs = availableGraphs;
-  }
-
   /**
    * Checks request parameters for the given graph type and returns a map
    * of recognized parameters, or null if the graph type doesn't exist or
@@ -88,7 +91,7 @@ public class GraphParameterChecker {
     /* Find out which other parameters are supported by this graph type
      * and parse them if they are given. */
     Set<String> supportedGraphParameters = new HashSet<String>(Arrays.
-        asList(this.availableGraphs.get(graphType).split(",")));
+        asList(this.availableGraphs.get(graphType)));
     Map<String, String[]> recognizedGraphParameters =
         new HashMap<String, String[]>();
 
diff --git a/website/src/org/torproject/metrics/web/graphs/GraphsSubpagesServlet.java b/website/src/org/torproject/metrics/web/graphs/GraphsSubpagesServlet.java
deleted file mode 100644
index f484d8f..0000000
--- a/website/src/org/torproject/metrics/web/graphs/GraphsSubpagesServlet.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/* Copyright 2011, 2012 The Tor Project
- * See LICENSE for licensing information */
-package org.torproject.metrics.web.graphs;
-
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.TimeZone;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-public class GraphsSubpagesServlet extends HttpServlet {
-
-  private static final long serialVersionUID = -5959829347747628403L;
-
-  /* Available graphs subpages with corresponding JSP to which requests
-   * are forwarded. */
-  private Map<String, String> availableGraphsSubpages;
-
-  /* Available tables on graphs subpages. */
-  private Map<String, String> availableGraphsSubpageTables;
-
-  /* Country codes and names for per-country graphs. */
-  private List<String[]> knownCountries;
-
-  /* R object generator for generating table data. */
-  private RObjectGenerator rObjectGenerator;
-
-  public GraphsSubpagesServlet() {
-    this.availableGraphsSubpages = new HashMap<String, String>();
-    this.availableGraphsSubpages.put("networksize.html",
-        "WEB-INF/networksize.jsp");
-    this.availableGraphsSubpages.put("relayflags.html",
-        "WEB-INF/relayflags.jsp");
-    this.availableGraphsSubpages.put("versions.html",
-        "WEB-INF/versions.jsp");
-    this.availableGraphsSubpages.put("platforms.html",
-        "WEB-INF/platforms.jsp");
-    this.availableGraphsSubpages.put("cloudbridges.html",
-        "WEB-INF/cloudbridges.jsp");
-    this.availableGraphsSubpages.put("bandwidth.html",
-        "WEB-INF/bandwidth.jsp");
-    this.availableGraphsSubpages.put("bwhist-flags.html",
-        "WEB-INF/bwhist-flags.jsp");
-    this.availableGraphsSubpages.put("bandwidth-flags.html",
-        "WEB-INF/bandwidth-flags.jsp");
-    this.availableGraphsSubpages.put("dirbytes.html",
-        "WEB-INF/dirbytes.jsp");
-    this.availableGraphsSubpages.put("advbwdist-perc.html",
-        "WEB-INF/advbwdist-perc.jsp");
-    this.availableGraphsSubpages.put("advbwdist-relay.html",
-        "WEB-INF/advbwdist-relay.jsp");
-    this.availableGraphsSubpages.put("userstats-relay-country.html",
-        "WEB-INF/userstats-relay-country.jsp");
-    this.availableGraphsSubpages.put("userstats-relay-table.html",
-        "WEB-INF/userstats-relay-table.jsp");
-    this.availableGraphsSubpages.put("userstats-censorship-events.html",
-        "WEB-INF/userstats-censorship-events.jsp");
-    this.availableGraphsSubpages.put("userstats-bridge-country.html",
-        "WEB-INF/userstats-bridge-country.jsp");
-    this.availableGraphsSubpages.put("userstats-bridge-table.html",
-        "WEB-INF/userstats-bridge-table.jsp");
-    this.availableGraphsSubpages.put("userstats-bridge-transport.html",
-        "WEB-INF/userstats-bridge-transport.jsp");
-    this.availableGraphsSubpages.put("userstats-bridge-version.html",
-        "WEB-INF/userstats-bridge-version.jsp");
-    this.availableGraphsSubpages.put("oxford-anonymous-internet.html",
-        "WEB-INF/oxford-anonymous-internet.jsp");
-    this.availableGraphsSubpages.put("torperf.html",
-        "WEB-INF/torperf.jsp");
-    this.availableGraphsSubpages.put("torperf-failures.html",
-        "WEB-INF/torperf-failures.jsp");
-    this.availableGraphsSubpages.put("connbidirect.html",
-        "WEB-INF/connbidirect.jsp");
-    this.availableGraphsSubpages.put("servers-data.html",
-        "WEB-INF/servers-data.jsp");
-    this.availableGraphsSubpages.put("bandwidth-data.html",
-        "WEB-INF/bandwidth-data.jsp");
-    this.availableGraphsSubpages.put("advbwdist-data.html",
-        "WEB-INF/advbwdist-data.jsp");
-    this.availableGraphsSubpages.put("clients-data.html",
-        "WEB-INF/clients-data.jsp");
-    this.availableGraphsSubpages.put("torperf-data.html",
-        "WEB-INF/torperf-data.jsp");
-    this.availableGraphsSubpages.put("connbidirect-data.html",
-        "WEB-INF/connbidirect-data.jsp");
-    this.availableGraphsSubpages.put("connbidirect2-data.html",
-        "WEB-INF/connbidirect2-data.jsp");
-    this.availableGraphsSubpages.put("hidserv-data.html",
-        "WEB-INF/hidserv-data.jsp");
-    this.availableGraphsSubpages.put("hidserv-dir-onions-seen.html",
-        "WEB-INF/hidserv-dir-onions-seen.jsp");
-    this.availableGraphsSubpages.put("hidserv-rend-relayed-cells.html",
-        "WEB-INF/hidserv-rend-relayed-cells.jsp");
-    this.availableGraphsSubpages.put("hidserv-frac-reporting.html",
-        "WEB-INF/hidserv-frac-reporting.jsp");
-    this.availableGraphsSubpages.put("uncharted-data-flow.html",
-        "WEB-INF/uncharted-data-flow.jsp");
-
-    this.availableGraphsSubpageTables = new HashMap<String, String>();
-    this.availableGraphsSubpageTables.put("userstats-relay-table.html",
-        "userstats-relay");
-    this.availableGraphsSubpageTables.put(
-        "userstats-censorship-events.html",
-        "userstats-censorship-events");
-    this.availableGraphsSubpageTables.put("userstats-bridge-table.html",
-        "userstats-bridge");
-
-    this.knownCountries = Countries.getInstance().getCountryList();
-  }
-
-  public void init() {
-    /* Get a reference to the R object generator that we need to generate
-     * table data. */
-    this.rObjectGenerator = (RObjectGenerator) getServletContext().
-        getAttribute("RObjectGenerator");
-  }
-
-  public void doGet(HttpServletRequest request,
-      HttpServletResponse response) throws IOException, ServletException {
-
-    /* Find out which graph subpage was requested and look up which JSP
-     * handles this subpage. */
-    String requestedPage = request.getRequestURI();
-    if (requestedPage == null) {
-      response.sendError(HttpServletResponse.SC_BAD_REQUEST);
-      return;
-    }
-    if (requestedPage.contains("/")) {
-      requestedPage = requestedPage.substring(requestedPage.
-          lastIndexOf("/") + 1);
-    }
-    if (!availableGraphsSubpages.containsKey(requestedPage)) {
-      response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-      return;
-    }
-    String jsp = availableGraphsSubpages.get(requestedPage);
-
-    /* Find out which graph or table type was requested, if any. */
-    String requestedGraph = request.getParameter("graph");
-    String requestedTable = request.getParameter("table");
-    if (requestedGraph != null) {
-
-      /* Check if the passed parameters are valid. */
-      Map<String, String[]> checkedParameters = GraphParameterChecker.
-          getInstance().checkParameters(requestedGraph,
-          request.getParameterMap());
-      if (checkedParameters != null) {
-
-        /* Set the graph's attributes to the appropriate values, so that
-         * we can display the correct graph and prepopulate the form. */
-        StringBuilder urlBuilder = new StringBuilder();
-        for (Map.Entry<String, String[]> param :
-            checkedParameters.entrySet()) {
-          request.setAttribute(requestedGraph.replaceAll("-", "_") + "_"
-              + param.getKey(), param.getValue());
-          for (String paramValue : param.getValue()) {
-            urlBuilder.append("&" + param.getKey() + "=" + paramValue);
-          }
-        }
-        String url = "?" + urlBuilder.toString().substring(1);
-        request.setAttribute(requestedGraph.replaceAll("-", "_") + "_url",
-            url);
-      }
-    }
-    if (requestedTable != null) {
-
-      /* Check if the passed parameters are valid. */
-      Map<String, String[]> checkedParameters = TableParameterChecker.
-          getInstance().checkParameters(requestedTable,
-          request.getParameterMap());
-      if (checkedParameters != null) {
-
-        /* Set the table's attributes to the appropriate values, so that
-         * we can prepopulate the form. */
-        for (Map.Entry<String, String[]> param :
-            checkedParameters.entrySet()) {
-          request.setAttribute(requestedTable.replaceAll("-", "_") + "_"
-              + param.getKey(), param.getValue());
-        }
-      }
-    }
-
-    /* Generate table data if the graphs subpage has any tables,
-     * regardless of whether a table update was requested, and add the
-     * table data as request attribute. */
-    if (this.availableGraphsSubpageTables.containsKey(requestedPage)) {
-      String tableName = this.availableGraphsSubpageTables.get(
-          requestedPage);
-      List<Map<String, String>> tableData = rObjectGenerator.
-          generateTable(tableName, requestedTable,
-          request.getParameterMap(), true);
-      request.setAttribute(tableName.replaceAll("-", "_")
-            + "_tabledata", tableData);
-    }
-
-    /* Pass list of known countries in case we want to display them. */
-    request.setAttribute("countries", this.knownCountries);
-
-    /* Pass the default start and end dates. */
-    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
-    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-    Date defaultEndDate = new Date();
-    Date defaultStartDate = new Date(defaultEndDate.getTime()
-        - 90L * 24L * 60L * 60L * 1000L);
-    request.setAttribute("default_start_date",
-        dateFormat.format(defaultStartDate));
-    request.setAttribute("default_end_date",
-        dateFormat.format(defaultEndDate));
-
-    /* Forward the request to the JSP that does all the hard work. */
-    request.getRequestDispatcher(jsp).forward(request, response);
-  }
-}
-
diff --git a/website/src/org/torproject/metrics/web/graphs/RObjectGenerator.java b/website/src/org/torproject/metrics/web/graphs/RObjectGenerator.java
index f0fb0f5..fb7d7b0 100644
--- a/website/src/org/torproject/metrics/web/graphs/RObjectGenerator.java
+++ b/website/src/org/torproject/metrics/web/graphs/RObjectGenerator.java
@@ -13,10 +13,9 @@ import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 import javax.servlet.ServletContext;
 import javax.servlet.ServletContextEvent;
@@ -24,6 +23,8 @@ import javax.servlet.ServletContextListener;
 
 import org.rosuda.REngine.Rserve.RConnection;
 import org.rosuda.REngine.Rserve.RserveException;
+import org.torproject.metrics.web.Metric;
+import org.torproject.metrics.web.MetricsProvider;
 
 public class RObjectGenerator implements ServletContextListener {
 
@@ -35,9 +36,7 @@ public class RObjectGenerator implements ServletContextListener {
   private String cachedGraphsDirectory;
   private long maxCacheAge;
 
-  private Map<String, String> availableTables;
-  private Map<String, String> availableGraphs;
-  private Set<String> availableGraphFileTypes;
+  private Map<String, Metric> availableGraphs, availableTables;
 
   public void contextInitialized(ServletContextEvent event) {
 
@@ -51,52 +50,16 @@ public class RObjectGenerator implements ServletContextListener {
     this.cachedGraphsDirectory = servletContext.getInitParameter(
         "cachedGraphsDir");
 
-    this.availableTables = new HashMap<String, String>();
-    this.availableTables.put("userstats-relay", "start,end,filename");
-    this.availableTables.put("userstats-bridge", "start,end,filename");
-    this.availableTables.put("userstats-censorship-events",
-        "start,end,filename");
-    TableParameterChecker.getInstance().setAvailableTables(
-        availableTables);
-
-    this.availableGraphs = new HashMap<String, String>();
-    this.availableGraphs.put("networksize", "start,end,filename");
-    this.availableGraphs.put("cloudbridges", "start,end,filename");
-    this.availableGraphs.put("relaycountries",
-        "start,end,country,filename");
-    this.availableGraphs.put("relayflags", "start,end,flag,filename");
-    this.availableGraphs.put("versions", "start,end,filename");
-    this.availableGraphs.put("platforms", "start,end,filename");
-    this.availableGraphs.put("bandwidth", "start,end,filename");
-    this.availableGraphs.put("bandwidth-flags", "start,end,filename");
-    this.availableGraphs.put("bwhist-flags", "start,end,filename");
-    this.availableGraphs.put("dirbytes", "start,end,filename");
-    this.availableGraphs.put("torperf",
-         "start,end,source,filesize,filename");
-    this.availableGraphs.put("torperf-failures",
-         "start,end,source,filesize,filename");
-    this.availableGraphs.put("connbidirect", "start,end,filename");
-    this.availableGraphs.put("userstats-relay-country",
-        "start,end,country,events,filename");
-    this.availableGraphs.put("userstats-bridge-country",
-        "start,end,country,filename");
-    this.availableGraphs.put("userstats-bridge-transport",
-        "start,end,transport,filename");
-    this.availableGraphs.put("userstats-bridge-version",
-        "start,end,version,filename");
-    this.availableGraphs.put("advbwdist-perc", "start,end,p,filename");
-    this.availableGraphs.put("advbwdist-relay", "start,end,n,filename");
-    this.availableGraphs.put("hidserv-dir-onions-seen",
-        "start,end,filename");
-    this.availableGraphs.put("hidserv-rend-relayed-cells",
-        "start,end,filename");
-    this.availableGraphs.put("hidserv-frac-reporting",
-        "start,end,filename");
-
-    this.availableGraphFileTypes = new HashSet<String>(Arrays.asList(
-        "png,pdf,svg".split(",")));
-    GraphParameterChecker.getInstance().setAvailableGraphs(
-        availableGraphs);
+    this.availableGraphs = new LinkedHashMap<String, Metric>();
+    this.availableTables = new LinkedHashMap<String, Metric>();
+    for (Metric metric : MetricsProvider.getInstance().getMetricsList()) {
+      String type = metric.getType(), id = metric.getId();
+      if ("Graph".equals(type)) {
+        this.availableGraphs.put(id, metric);
+      } else if ("Table".equals(type)) {
+        this.availableTables.put(id, metric);
+      }
+    }
 
     /* Register ourself, so that servlets can use us. */
     servletContext.setAttribute("RObjectGenerator", this);
@@ -113,13 +76,11 @@ public class RObjectGenerator implements ServletContextListener {
             } catch (InterruptedException e) {
             }
           }
-          for (String tableName : availableTables.keySet()) {
-            generateTable(tableName, tableName, new HashMap(), false);
+          for (String tableId : availableTables.keySet()) {
+            generateTable(tableId, new HashMap(), false);
           }
-          for (String graphName : availableGraphs.keySet()) {
-            for (String fileType : availableGraphFileTypes) {
-              generateGraph(graphName, fileType, new HashMap(), false);
-            }
+          for (String graphId : availableGraphs.keySet()) {
+            generateGraph(graphId, "png", new HashMap(), false);
           }
           lastUpdated = System.currentTimeMillis();
         }
@@ -133,15 +94,19 @@ public class RObjectGenerator implements ServletContextListener {
 
   public RObject generateGraph(String requestedGraph, String fileType,
       Map parameterMap, boolean checkCache) {
+    if (!this.availableGraphs.containsKey(requestedGraph) ||
+        this.availableGraphs.get(requestedGraph).getFunction() == null) {
+      return null;
+    }
+    String function = this.availableGraphs.get(requestedGraph).
+        getFunction();
     Map<String, String[]> checkedParameters = GraphParameterChecker.
         getInstance().checkParameters(requestedGraph, parameterMap);
     if (checkedParameters == null) {
-      /* TODO We're going to take the blame by sending an internal server
-       * error to the client, but really the user is to blame. */
       return null;
     }
-    StringBuilder rQueryBuilder = new StringBuilder("plot_"
-        + requestedGraph.replaceAll("-", "_") + "("),
+    StringBuilder
+        rQueryBuilder = new StringBuilder().append(function).append("("),
         imageFilenameBuilder = new StringBuilder(requestedGraph);
     for (Map.Entry<String, String[]> parameter :
         checkedParameters.entrySet()) {
@@ -172,27 +137,22 @@ public class RObjectGenerator implements ServletContextListener {
         checkCache);
   }
 
-  public List<Map<String, String>> generateTable(String tableName,
-      String requestedTable, Map parameterMap, boolean checkCache) {
-
-    Map<String, String[]> checkedParameters = null;
-    if (tableName.equals(requestedTable)) {
-      checkedParameters = TableParameterChecker.
-          getInstance().checkParameters(requestedTable,
-          parameterMap);
-    } else {
-      checkedParameters = TableParameterChecker.
-          getInstance().checkParameters(tableName, null);
+  public List<Map<String, String>> generateTable(String requestedTable,
+      Map parameterMap, boolean checkCache) {
+    if (!this.availableTables.containsKey(requestedTable) ||
+        this.availableTables.get(requestedTable).getFunction() == null) {
+      return null;
     }
+    String function = this.availableTables.get(requestedTable).
+        getFunction();
+    Map<String, String[]> checkedParameters = TableParameterChecker.
+        getInstance().checkParameters(requestedTable, parameterMap);
     if (checkedParameters == null) {
-      /* TODO We're going to take the blame by sending an internal server
-       * error to the client, but really the user is to blame. */
       return null;
     }
-    StringBuilder rQueryBuilder = new StringBuilder("write_"
-        + tableName.replaceAll("-", "_") + "("),
-        tableFilenameBuilder = new StringBuilder(tableName);
-
+    StringBuilder
+        rQueryBuilder = new StringBuilder().append(function).append("("),
+        tableFilenameBuilder = new StringBuilder(requestedTable);
     for (Map.Entry<String, String[]> parameter :
         checkedParameters.entrySet()) {
       String parameterName = parameter.getKey();
diff --git a/website/src/org/torproject/metrics/web/graphs/TableParameterChecker.java b/website/src/org/torproject/metrics/web/graphs/TableParameterChecker.java
index 0be736e..c92393b 100644
--- a/website/src/org/torproject/metrics/web/graphs/TableParameterChecker.java
+++ b/website/src/org/torproject/metrics/web/graphs/TableParameterChecker.java
@@ -11,6 +11,9 @@ import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
 
+import org.torproject.metrics.web.Metric;
+import org.torproject.metrics.web.MetricsProvider;
+
 /**
  * Checks request parameters passed to generate tables.
  */
@@ -33,7 +36,7 @@ public class TableParameterChecker {
   private SimpleDateFormat dateFormat;
 
   /* Available tables with corresponding parameter lists. */
-  private Map<String, String> availableTables;
+  private Map<String, String[]> availableTables;
 
   /**
    * Initializes map with valid parameters for each of the graphs.
@@ -41,10 +44,13 @@ public class TableParameterChecker {
   public TableParameterChecker() {
     this.dateFormat = new SimpleDateFormat("yyyy-MM-dd");
     this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-  }
 
-  public void setAvailableTables(Map<String, String> availableTables) {
-    this.availableTables = availableTables;
+    this.availableTables = new HashMap<String, String[]>();
+    for (Metric metric : MetricsProvider.getInstance().getMetricsList()) {
+      if ("Table".equals(metric.getType())) {
+        this.availableTables.put(metric.getId(), metric.getParameters());
+      }
+    }
   }
 
   /**
@@ -64,7 +70,7 @@ public class TableParameterChecker {
     /* Find out which other parameters are supported by this table type
      * and parse them if they are given. */
     Set<String> supportedTableParameters = new HashSet<String>(Arrays.
-        asList(this.availableTables.get(tableType).split(",")));
+        asList(this.availableTables.get(tableType)));
     Map<String, String[]> recognizedTableParameters =
         new HashMap<String, String[]>();
 
diff --git a/website/web/WEB-INF/about.jsp b/website/web/WEB-INF/about.jsp
index b95da77..d5a6f78 100644
--- a/website/web/WEB-INF/about.jsp
+++ b/website/web/WEB-INF/about.jsp
@@ -3,14 +3,13 @@
 <head>
   <title>Tor Metrics — About</title>
   <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="../css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
+  <link href="css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
+  <link href="images/favicon.ico" type="image/x-icon" rel="shortcut icon">
 </head>
 <body>
   <div class="center">
-    <%@ include file="banner.jsp"%>
     <div class="main-column">
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — About</h2>
+<h2><a href="/"><img src="images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — About</h2>
 <br>
 
 <h3>Frequently used terms</h3>
diff --git a/website/web/WEB-INF/advbwdist-data.jsp b/website/web/WEB-INF/advbwdist-data.jsp
deleted file mode 100644
index 7b9ba12..0000000
--- a/website/web/WEB-INF/advbwdist-data.jsp
+++ /dev/null
@@ -1,62 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Advertised bandwidth distribution and n-th fastest relays</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Advertised bandwidth distribution and n-th fastest
-relays</h2>
-<br>
-<p>The following data file contains statistics on the distribution of
-<a href="about.html#advertised-bandwidth">advertised bandwidth</a> of
-relays in the network.
-These statistics include advertised bandwidth percentiles and advertised
-bandwidth values of the n-th fastest relays.
-All values are obtained from advertised bandwidths of running relays in a
-<a href="about.html#consensus">network status consensus</a>.
-The data file contains daily (median) averages of percentiles and n-th
-largest values.</p>
-
-<p><b>Download as <a href="stats/advbwdist.csv">CSV file</a>.</b></p>
-
-<p>The statistics file contains the following columns:</p>
-<ul>
-<li><b>date:</b> UTC date (YYYY-MM-DD) when relays have been listed as
-running.</li>
-<li><b>isexit:</b> Whether relays included in this line have the
-<b>"Exit"</b> relay flag, which would be indicated as <b>"t"</b>.
-If this column contains the empty string, advertised bandwidths from all
-running relays are included, regardless of assigned relay flags.</li>
-<li><b>relay:</b> Position of the relay in an ordered list of all
-advertised bandwidths, starting at 1 for the fastest relay in the network.
-May be the empty string if this line contains advertised bandwidth by
-percentile.</li>
-<li><b>percentile:</b> Advertised bandwidth percentile given in this line.
-May be the empty string if this line contains advertised bandwidth by
-fastest relays.</li>
-<li><b>advbw:</b> Advertised bandwidth in B/s.</li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="advbwdist-perc.html">Graph: Advertised bandwidth distribution</a></li>
-<li><a href="advbwdist-relay.html">Graph: Advertised bandwidth of n-th fastest relays</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
-
diff --git a/website/web/WEB-INF/advbwdist-perc.jsp b/website/web/WEB-INF/advbwdist-perc.jsp
deleted file mode 100644
index 4c90e31..0000000
--- a/website/web/WEB-INF/advbwdist-perc.jsp
+++ /dev/null
@@ -1,94 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Advertised bandwidth distribution</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Advertised bandwidth distribution</h2>
-<br>
-<p>The following graph shows the distribution of the
-<a href="about.html#advertised-bandwidth">advertised bandwidth</a> of
-relays in the network.
-Each percentile represents the advertised bandwidth that a given
-percentage of relays does not exceed (and that in turn the remaining
-relays either match or exceed).
-For example, 99% of relays advertise at most the bandwidth value shown in
-the 99th percentile line (and the remaining 1% advertise at least that
-amount).
-<font color="red">Note that the unit has recently changed from MiB/s to
-Gbit/s.</font></p>
-<img src="advbwdist-perc.png${advbwdist_perc_url}"
-     width="576" height="360"
-     alt="Advertised bandwidth distribution graph">
-<form action="advbwdist-perc.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="advbwdist-perc">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(advbwdist_perc_start) == 0}">${default_start_date}</c:when><c:otherwise>${advbwdist_perc_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(advbwdist_perc_end) == 0}">${default_end_date}</c:when><c:otherwise>${advbwdist_perc_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-      <label>Percentiles: </label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="100"<c:if test="${fn:length(advbwdist_perc_p) == 0 or fn:contains(fn:join(advbwdist_perc_p, ','), '100')}"> checked</c:if>> 100 (maximum)</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="99"<c:if test="${fn:length(advbwdist_perc_p) > 0 and fn:contains(fn:join(advbwdist_perc_p, ','), '99')}"> checked</c:if>> 99</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="98"<c:if test="${fn:length(advbwdist_perc_p) > 0 and fn:contains(fn:join(advbwdist_perc_p, ','), '98')}"> checked</c:if>> 98</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="97"<c:if test="${fn:length(advbwdist_perc_p) > 0 and fn:contains(fn:join(advbwdist_perc_p, ','), '97')}"> checked</c:if>> 97</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="95"<c:if test="${fn:length(advbwdist_perc_p) > 0 and fn:contains(fn:join(advbwdist_perc_p, ','), '95')}"> checked</c:if>> 95</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="91"<c:if test="${fn:length(advbwdist_perc_p) > 0 and fn:contains(fn:join(advbwdist_perc_p, ','), '91')}"> checked</c:if>> 91</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="90"<c:if test="${fn:length(advbwdist_perc_p) > 0 and fn:contains(fn:join(advbwdist_perc_p, ','), '90')}"> checked</c:if>> 90</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="80"<c:if test="${fn:length(advbwdist_perc_p) > 0 and fn:contains(fn:join(advbwdist_perc_p, ','), '80')}"> checked</c:if>> 80</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="75"<c:if test="${fn:length(advbwdist_perc_p) > 0 and fn:contains(fn:join(advbwdist_perc_p, ','), '75')}"> checked</c:if>> 75 (3rd quartile)</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="70"<c:if test="${fn:length(advbwdist_perc_p) > 0 and fn:contains(fn:join(advbwdist_perc_p, ','), '70')}"> checked</c:if>> 70</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="60"<c:if test="${fn:length(advbwdist_perc_p) > 0 and fn:contains(fn:join(advbwdist_perc_p, ','), '60')}"> checked</c:if>> 60</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="50"<c:if test="${fn:length(advbwdist_perc_p) > 0 and fn:contains(fn:join(advbwdist_perc_p, ','), '50')}"> checked</c:if>> 50 (median)</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="40"<c:if test="${fn:length(advbwdist_perc_p) > 0 and fn:contains(fn:join(advbwdist_perc_p, ','), '40')}"> checked</c:if>> 40</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="30"<c:if test="${fn:length(advbwdist_perc_p) > 0 and fn:contains(fn:join(advbwdist_perc_p, ','), '30')}"> checked</c:if>> 30</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="25"<c:if test="${fn:length(advbwdist_perc_p) > 0 and fn:contains(fn:join(advbwdist_perc_p, ','), '25')}"> checked</c:if>> 25 (first quartile)</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="20"<c:if test="${fn:length(advbwdist_perc_p) > 0 and fn:contains(fn:join(advbwdist_perc_p, ','), '20')}"> checked</c:if>> 20</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="10"<c:if test="${fn:length(advbwdist_perc_p) > 0 and (fn:startsWith(fn:join(advbwdist_perc_p, ','), '10,') or fn:contains(fn:join(advbwdist_perc_p, ','), ',10,') or fn:endsWith(fn:join(advbwdist_perc_p, ','), ',10') or (advbwdist_perc_p[0] == '10' and fn:length(advbwdist_perc_p) == 1))}"> checked</c:if>> 10</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="9"<c:if test="${fn:length(advbwdist_perc_p) > 0 and (fn:startsWith(fn:join(advbwdist_perc_p, ','), '9,') or fn:contains(fn:join(advbwdist_perc_p, ','), ',9,') or fn:endsWith(fn:join(advbwdist_perc_p, ','), ',9') or (advbwdist_perc_p[0] == '9' and fn:length(advbwdist_perc_p) == 1))}"> checked</c:if>> 9</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="5"<c:if test="${fn:length(advbwdist_perc_p) > 0 and (fn:startsWith(fn:join(advbwdist_perc_p, ','), '5,') or fn:contains(fn:join(advbwdist_perc_p, ','), ',5,') or fn:endsWith(fn:join(advbwdist_perc_p, ','), ',5') or (advbwdist_perc_p[0] == '5' and fn:length(advbwdist_perc_p) == 1))}"> checked</c:if>> 5</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="3"<c:if test="${fn:length(advbwdist_perc_p) > 0 and (fn:startsWith(fn:join(advbwdist_perc_p, ','), '3,') or fn:contains(fn:join(advbwdist_perc_p, ','), ',3,') or fn:endsWith(fn:join(advbwdist_perc_p, ','), ',3') or (advbwdist_perc_p[0] == '3' and fn:length(advbwdist_perc_p) == 1))}"> checked</c:if>> 3</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="2"<c:if test="${fn:length(advbwdist_perc_p) > 0 and (fn:startsWith(fn:join(advbwdist_perc_p, ','), '2,') or fn:contains(fn:join(advbwdist_perc_p, ','), ',2,') or fn:endsWith(fn:join(advbwdist_perc_p, ','), ',2') or (advbwdist_perc_p[0] == '2' and fn:length(advbwdist_perc_p) == 1))}"> checked</c:if>> 2</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="1"<c:if test="${fn:length(advbwdist_perc_p) > 0 and (fn:startsWith(fn:join(advbwdist_perc_p, ','), '1,') or fn:contains(fn:join(advbwdist_perc_p, ','), ',1,') or fn:endsWith(fn:join(advbwdist_perc_p, ','), ',1') or (advbwdist_perc_p[0] == '1' and fn:length(advbwdist_perc_p) == 1))}"> checked</c:if>> 1</label>
-      <label class="checkbox-label"><input type="checkbox" name="p" value="0"<c:if test="${fn:length(advbwdist_perc_p) > 0 and (fn:startsWith(fn:join(advbwdist_perc_p, ','), '0,') or fn:contains(fn:join(advbwdist_perc_p, ','), ',0,') or fn:endsWith(fn:join(advbwdist_perc_p, ','), ',0') or (advbwdist_perc_p[0] == '0' and fn:length(advbwdist_perc_p) == 1))}"> checked</c:if>> 0 (minimum)</label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="advbwdist-perc.pdf${advbwdist_perc_url}">PDF</a> or
-<a href="advbwdist-perc.svg${advbwdist_perc_url}">SVG</a>.</p>
-<br>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="advbwdist-data.html">Data: Advertised bandwidth distribution and n-th fastest relays</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="advbwdist-relay.html">Graph: Advertised bandwidth of n-th fastest relays</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/advbwdist-relay.jsp b/website/web/WEB-INF/advbwdist-relay.jsp
deleted file mode 100644
index 68981b2..0000000
--- a/website/web/WEB-INF/advbwdist-relay.jsp
+++ /dev/null
@@ -1,81 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Advertised bandwidth of n-th fastest relays</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Advertised bandwidth of n-th fastest relays</h2>
-<br>
-<p>The following graph shows the
-<a href="about.html#advertised-bandwidth">advertised bandwidth</a> of the
-n-th fastest relays in the network for different values of n.
-<font color="red">Note that the unit has recently changed from MiB/s to
-Gbit/s.</font></p>
-<img src="advbwdist-relay.png${advbwdist_relay_url}"
-     width="576" height="360"
-     alt="Advertised bandwidth of n-th fastest relays graph">
-<form action="advbwdist-relay.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="advbwdist-relay">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(advbwdist_relay_start) == 0}">${default_start_date}</c:when><c:otherwise>${advbwdist_relay_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(advbwdist_relay_end) == 0}">${default_end_date}</c:when><c:otherwise>${advbwdist_relay_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-      <label>n-th fastest relays: </label>
-      <label class="checkbox-label"><input type="checkbox" name="n" value="1"<c:if test="${fn:length(advbwdist_relay_n) == 0 or fn:contains(fn:join(advbwdist_relay_n, ','), '1,') or fn:endsWith(fn:join(advbwdist_relay_n, ','), '1')}"> checked</c:if>> 1</label>
-      <label class="checkbox-label"><input type="checkbox" name="n" value="2"<c:if test="${fn:length(advbwdist_relay_n) > 0 and (fn:contains(fn:join(advbwdist_relay_n, ','), '2,') or fn:endsWith(fn:join(advbwdist_relay_n, ','), '2'))}"> checked</c:if>> 2</label>
-      <label class="checkbox-label"><input type="checkbox" name="n" value="3"<c:if test="${fn:length(advbwdist_relay_n) > 0 and (fn:contains(fn:join(advbwdist_relay_n, ','), '3,') or fn:endsWith(fn:join(advbwdist_relay_n, ','), '3'))}"> checked</c:if>> 3</label>
-      <label class="checkbox-label"><input type="checkbox" name="n" value="5"<c:if test="${fn:length(advbwdist_relay_n) > 0 and (fn:contains(fn:join(advbwdist_relay_n, ','), '5,') or fn:endsWith(fn:join(advbwdist_relay_n, ','), '5'))}"> checked</c:if>> 5</label>
-      <label class="checkbox-label"><input type="checkbox" name="n" value="10"<c:if test="${fn:length(advbwdist_relay_n) > 0 and (fn:contains(fn:join(advbwdist_relay_n, ','), '10,') or fn:endsWith(fn:join(advbwdist_relay_n, ','), '10'))}"> checked</c:if>> 10</label>
-      <label class="checkbox-label"><input type="checkbox" name="n" value="20"<c:if test="${fn:length(advbwdist_relay_n) > 0 and (fn:contains(fn:join(advbwdist_relay_n, ','), '20,') or fn:endsWith(fn:join(advbwdist_relay_n, ','), '20'))}"> checked</c:if>> 20</label>
-      <label class="checkbox-label"><input type="checkbox" name="n" value="30"<c:if test="${fn:length(advbwdist_relay_n) > 0 and (fn:contains(fn:join(advbwdist_relay_n, ','), '30,') or fn:endsWith(fn:join(advbwdist_relay_n, ','), '30'))}"> checked</c:if>> 30</label>
-      <label class="checkbox-label"><input type="checkbox" name="n" value="50"<c:if test="${fn:length(advbwdist_relay_n) > 0 and (fn:contains(fn:join(advbwdist_relay_n, ','), '50,') or fn:endsWith(fn:join(advbwdist_relay_n, ','), '50'))}"> checked</c:if>> 50</label>
-      <label class="checkbox-label"><input type="checkbox" name="n" value="100"<c:if test="${fn:length(advbwdist_relay_n) > 0 and (fn:contains(fn:join(advbwdist_relay_n, ','), '100,') or fn:endsWith(fn:join(advbwdist_relay_n, ','), '100'))}"> checked</c:if>> 100</label>
-      <label class="checkbox-label"><input type="checkbox" name="n" value="200"<c:if test="${fn:length(advbwdist_relay_n) > 0 and (fn:contains(fn:join(advbwdist_relay_n, ','), '200,') or fn:endsWith(fn:join(advbwdist_relay_n, ','), '200'))}"> checked</c:if>> 200</label>
-      <label class="checkbox-label"><input type="checkbox" name="n" value="300"<c:if test="${fn:length(advbwdist_relay_n) > 0 and (fn:contains(fn:join(advbwdist_relay_n, ','), '300,') or fn:endsWith(fn:join(advbwdist_relay_n, ','), '300'))}"> checked</c:if>> 300</label>
-      <label class="checkbox-label"><input type="checkbox" name="n" value="500"<c:if test="${fn:length(advbwdist_relay_n) > 0 and (fn:contains(fn:join(advbwdist_relay_n, ','), '500,') or fn:endsWith(fn:join(advbwdist_relay_n, ','), '500'))}"> checked</c:if>> 500</label>
-      <label class="checkbox-label"><input type="checkbox" name="n" value="1000"<c:if test="${fn:length(advbwdist_relay_n) > 0 and fn:contains(fn:join(advbwdist_relay_n, ','), '1000')}"> checked</c:if>> 1000</label>
-      <label class="checkbox-label"><input type="checkbox" name="n" value="2000"<c:if test="${fn:length(advbwdist_relay_n) > 0 and fn:contains(fn:join(advbwdist_relay_n, ','), '2000')}"> checked</c:if>> 2000</label>
-      <label class="checkbox-label"><input type="checkbox" name="n" value="3000"<c:if test="${fn:length(advbwdist_relay_n) > 0 and fn:contains(fn:join(advbwdist_relay_n, ','), '3000')}"> checked</c:if>> 3000</label>
-      <label class="checkbox-label"><input type="checkbox" name="n" value="5000"<c:if test="${fn:length(advbwdist_relay_n) > 0 and fn:contains(fn:join(advbwdist_relay_n, ','), '5000')}"> checked</c:if>> 5000</label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="advbwdist-relay.pdf${advbwdist_relay_url}">PDF</a> or
-<a href="advbwdist-relay.svg${advbwdist_relay_url}">SVG</a>.</p>
-<br>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="advbwdist-data.html">Data: Advertised bandwidth distribution and n-th fastest relays</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="advbwdist-perc.html">Graph: Advertised bandwidth distribution</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/bandwidth-data.jsp b/website/web/WEB-INF/bandwidth-data.jsp
deleted file mode 100644
index cbb0271..0000000
--- a/website/web/WEB-INF/bandwidth-data.jsp
+++ /dev/null
@@ -1,77 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Bandwidth provided and consumed by relays</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Bandwidth provided and consumed by relays</h2>
-<br>
-<p>The following data file contains statistics on
-<a href="about.html#advertised-bandwidth">advertised</a> and
-<a href="about.html#bandwidth-history">consumed bandwidth</a> of
-<a href="about.html#relay">relays</a> in the network.
-Statistics on advertised bandwidth include any kind of traffic handled by
-a relay, whereas statistics on consumed bandwidth are available either for
-all traffic combined, or specifically for directory traffic.
-Some of the statistics are available for subsets of relays that have the
-"Exit" and/or the "Guard" <a href="about.html#relay-flag">flag</a>.
-The data file contains daily (mean) averages of bandwidth numbers.</p>
-
-<p><b>Download as <a href="stats/bandwidth.csv">CSV file</a>.</b></p>
-
-<p>The statistics file contains the following columns:</p>
-<ul>
-<li><b>date:</b> UTC date (YYYY-MM-DD) that relays reported bandwidth data
-for.</li>
-<li><b>isexit:</b> Whether relays included in this line have the
-<b>"Exit"</b> relay flag or not, which can be <b>"t"</b> or <b>"f"</b>.
-If this column contains the empty string, bandwidth data from all running
-relays are included, regardless of assigned relay flags.</li>
-<li><b>isguard:</b> Whether relays included in this line have the
-<b>"Guard"</b> relay flag or not, which can be <b>"t"</b> or <b>"f"</b>.
-If this column contains the empty string, bandwidth data from all running
-relays are included, regardless of assigned relay flags.</li>
-<li><b>advbw:</b> Total advertised bandwidth in bytes per second that
-relays are capable to provide.</li>
-<li><b>bwread:</b> Total bandwidth in bytes per second that relays have
-read.
-This metric includes any kind of traffic.</li>
-<li><b>bwwrite:</b> Similar to <b>bwread</b>, but for traffic written by
-relays.</li>
-<li><b>dirread:</b> Bandwidth in bytes per second that relays have read
-when serving directory data.
-Not all relays report how many bytes they read when serving directory data
-which is why this value is an estimate from the available data.
-This metric is not available for subsets of relays with certain relay
-flags, so that this column will contain the empty string if either
-<b>isexit</b> or <b>isguard</b> is non-empty.</li>
-<li><b>dirwrite:</b> Similar to <b>dirread</b>, but for traffic written by
-relays when serving directory data.</li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="bandwidth.html">Graph: Total relay bandwidth in the network</a></li>
-<li><a href="bwhist-flags.html">Graph: Relay bandwidth by Exit and/or Guard flags</a></li>
-<li><a href="bandwidth-flags.html">Graph: Advertised bandwidth and bandwidth history by relay flags</a></li>
-<li><a href="dirbytes.html">Graph: Number of bytes spent on answering directory requests</a></li>
-<li><a href="bandwidth-data.html">Data: Bandwidth provided and consumed by relays</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
-
diff --git a/website/web/WEB-INF/bandwidth-flags.jsp b/website/web/WEB-INF/bandwidth-flags.jsp
deleted file mode 100644
index 7ce7e62..0000000
--- a/website/web/WEB-INF/bandwidth-flags.jsp
+++ /dev/null
@@ -1,69 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Advertised bandwidth and bandwidth history by relay flags</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Advertised bandwidth and bandwidth history by relay flags</h2>
-<br>
-<p>The following graph shows
-<a href="about.html#advertised-bandwidth">advertised</a> and
-<a href="about.html#bandwidth-history">consumed bandwidth</a> of relays
-with either "Exit" or "Guard" <a href="about.html#relay-flag">flags</a>
-assigned by the directory authorities.
-These sets are not distinct, because a relay that has both the "Exit" and
-"Guard" flags assigned will be included in both sets.
-<font color="red">Note that the unit has recently changed from MiB/s to
-Gbit/s.</font></p>
-<img src="bandwidth-flags.png${bandwidth_flags_url}"
-     width="576" height="360" alt="Advertised bandwidth and bandwidth history by relay flags graph">
-<form action="bandwidth-flags.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="bandwidth-flags">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(bandwidth_flags_start) == 0}">${default_start_date}</c:when><c:otherwise>${bandwidth_flags_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(bandwidth_flags_end) == 0}">${default_end_date}</c:when><c:otherwise>${bandwidth_flags_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="bandwidth-flags.pdf${bandwidth_flags_url}">PDF</a> or
-<a href="bandwidth-flags.svg${bandwidth_flags_url}">SVG</a>.</p>
-<br>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="bandwidth-data.html">Data: Bandwidth provided and consumed by relays</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="relayflags.html">Graph: Relays with Exit, Fast, Guard, Stable, and HSDir flags</a></li>
-<li><a href="bandwidth.html">Graph: Total relay bandwidth in the network</a></li>
-<li><a href="bwhist-flags.html">Graph: Relay bandwidth by Exit and/or Guard flags</a></li>
-<li><a href="dirbytes.html">Graph: Number of bytes spent on answering directory requests</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/bandwidth.jsp b/website/web/WEB-INF/bandwidth.jsp
deleted file mode 100644
index b4e7edc..0000000
--- a/website/web/WEB-INF/bandwidth.jsp
+++ /dev/null
@@ -1,65 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Total relay bandwidth in the network</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Total relay bandwidth in the network</h2>
-<br>
-<p>The following graph shows the total
-<a href="about.html#advertised-bandwidth">advertised</a> and
-<a href="about.html#bandwidth-history">consumed bandwidth</a> of all
-<a href="about.html#relay">relays</a> in the network.
-<font color="red">Note that the unit has recently changed from MiB/s to
-Gbit/s.</font></p>
-<img src="bandwidth.png${bandwidth_url}"
-     width="576" height="360" alt="Relay bandwidth graph">
-<form action="bandwidth.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="bandwidth">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(bandwidth_start) == 0}">${default_start_date}</c:when><c:otherwise>${bandwidth_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(bandwidth_end) == 0}">${default_end_date}</c:when><c:otherwise>${bandwidth_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="bandwidth.pdf${bandwidth_url}">PDF</a> or
-<a href="bandwidth.svg${bandwidth_url}">SVG</a>.</p>
-<br>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="bandwidth-data.html">Data: Bandwidth provided and consumed by relays</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="bwhist-flags.html">Graph: Relay bandwidth by Exit and/or Guard flags</a></li>
-<li><a href="bandwidth-flags.html">Graph: Advertised bandwidth and bandwidth history by relay flags</a></li>
-<li><a href="dirbytes.html">Graph: Number of bytes spent on answering directory requests</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/banner.jsp b/website/web/WEB-INF/banner.jsp
deleted file mode 100644
index e69de29..0000000
diff --git a/website/web/WEB-INF/bubbles.jsp b/website/web/WEB-INF/bubbles.jsp
index a18e127..1fedbeb 100644
--- a/website/web/WEB-INF/bubbles.jsp
+++ b/website/web/WEB-INF/bubbles.jsp
@@ -4,17 +4,16 @@
 <head>
   <title>Tor Metrics — Network bubble graphs</title>
   <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-  <script src="/js/d3.min.js"></script>
-  <script src="/js/bubbles.js"></script>
+  <link href="css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
+  <link href="images/favicon.ico" type="image/x-icon" rel="shortcut icon">
+  <script src="js/d3.min.js"></script>
+  <script src="js/bubbles.js"></script>
 </head>
 <body>
   <div class="center">
-    <%@ include file="banner.jsp"%>
     <div class="main-column">
 
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Network bubble graphs</h2>
+<h2><a href="/"><img src="images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Network bubble graphs</h2>
 <br>
 <p>The following graph visualizes diversity of currently running
 <a href="about.html#relay">relays</a> in terms of their probability to be
diff --git a/website/web/WEB-INF/bwhist-flags.jsp b/website/web/WEB-INF/bwhist-flags.jsp
deleted file mode 100644
index 97c5c36..0000000
--- a/website/web/WEB-INF/bwhist-flags.jsp
+++ /dev/null
@@ -1,66 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Relay bandwidth by Exit and/or Guard flags</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Relay bandwidth by Exit and/or Guard flags</h2>
-<br>
-<p>The following graph shows the
-<a href="about.html#bandwidth-history">consumed bandwidth</a> reported by
-relays, subdivided into four distinct subsets by assigned "Exit" and/or
-"Guard" <a href="about.html#relay-flag">flags</a>.
-<font color="red">Note that the unit has recently changed from MiB/s to
-Gbit/s.</font></p>
-<img src="bwhist-flags.png${bwhist_flags_url}"
-     width="576" height="360" alt="Relay bandwidth by flags graph">
-<form action="bwhist-flags.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="bwhist-flags">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(bwhist_flags_start) == 0}">${default_start_date}</c:when><c:otherwise>${bwhist_flags_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(bwhist_flags_end) == 0}">${default_end_date}</c:when><c:otherwise>${bwhist_flags_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="bwhist-flags.pdf${bwhist_flags_url}">PDF</a> or
-<a href="bwhist-flags.svg${bwhist_flags_url}">SVG</a>.</p>
-<br>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="bandwidth-data.html">Data: Bandwidth provided and consumed by relays</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="relayflags.html">Graph: Relays with Exit, Fast, Guard, Stable, and HSDir flags</a></li>
-<li><a href="bandwidth.html">Graph: Total relay bandwidth in the network</a></li>
-<li><a href="bandwidth-flags.html">Graph: Advertised bandwidth and bandwidth history by relay flags</a></li>
-<li><a href="dirbytes.html">Graph: Number of bytes spent on answering directory requests</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/clients-data.jsp b/website/web/WEB-INF/clients-data.jsp
deleted file mode 100644
index 5f64d37..0000000
--- a/website/web/WEB-INF/clients-data.jsp
+++ /dev/null
@@ -1,96 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Estimated number of clients in the Tor network</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Estimated number of clients in the Tor network</h2>
-<br>
-<p>The following data file contains estimates on the number of
-<a href="about.html#client">clients</a> in the network.
-These numbers are derived from directory requests counted on
-<a href="about.html#directory-authority">directory authorities</a>,
-<a href="about.html#directory-mirror">directory mirrors</a>, and
-<a href="about.html#bridge">bridges</a>.
-Statistics are available for clients connecting directly relays and
-clients connecting via bridges.
-There are statistics available by country (for both directly-connecting
-clients and clients connecting via bridges), by transport protocol (only
-for clients connecting via bridges), and by IP version (only for clients
-connecting via bridges).
-Statistics also include predicted client numbers from past observations,
-which can be used to detect censorship events.</p>
-
-<p><b>Download as <a href="stats/clients.csv">CSV file</a>.</b></p>
-
-<p>The statistics file contains the following columns:</p>
-<ul>
-<li><b>date:</b> UTC date (YYYY-MM-DD) for which client numbers are
-estimated.</li>
-<li><b>node:</b> The node type to which clients connect first, which can
-be either <b>"relay"</b> or <b>"bridge"</b>.</li>
-<li><b>country:</b> Two-letter lower-case country code as found in a GeoIP
-database by resolving clients' IP addresses, or <b>"??"</b> if client IP
-addresses could not be resolved.
-If this column contains the empty string, all clients are included,
-regardless of their country code.</li>
-<li><b>transport:</b> Transport name used by clients to connect to the Tor
-network using bridges.
-Examples are <b>"obfs2"</b>, <b>"obfs3"</b>, <b>"websocket"</b>, or
-<b>"<OR>"</b> (original onion routing protocol).
-If this column contains the empty string, all clients are included,
-regardless of their transport.
-There are no statistics on the number of clients by transport that connect
-to the Tor network via relays.</li>
-<li><b>version:</b> IP version used by clients to connect to the Tor
-network using bridges.
-Examples are <b>"v4"</b> and <b>"v6"</b>.
-If this column contains the empty string, all clients are included,
-regardless of their IP version.
-There are no statistics on the number of clients by IP version that connect
-directly to the Tor network using relays.</li>
-<li><b>lower:</b> Lower number of expected clients under the assumption
-that there has been no censorship event.
-If this column contains the empty string, there are no expectations on the
-number of clients.</li>
-<li><b>upper:</b> Upper number of expected clients under the assumption
-that there has been no release of censorship.
-If this column contains the empty string, there are no expectations on the
-number of clients.</li>
-<li><b>clients:</b> Estimated number of clients.</li>
-<li><b>frac:</b> Fraction of relays or bridges in percent that the
-estimate is based on.
-The higher this value, the more reliable is the estimate.
-Values above 50 can be considered reliable enough for most purposes,
-lower values should be handled with more care.</li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="userstats-relay-country.html">Graph: Direct users by country</a></li>
-<li><a href="userstats-relay-table.html">Table: Top-10 countries by directly connecting users</a></li>
-<li><a href="userstats-censorship-events.html">Table: Top-10 countries by possible censorship events</a></li>
-<li><a href="userstats-bridge-country.html">Graph: Bridge users by country</a></li>
-<li><a href="userstats-bridge-table.html">Table: Top-10 countries by bridge users</a></li>
-<li><a href="userstats-bridge-transport.html">Graph: Bridge users by transport</a></li>
-<li><a href="userstats-bridge-version.html">Graph: Bridge users by IP version</a></li>
-<li><a href="oxford-anonymous-internet.html">Link: Tor users as percentage of larger Internet population</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
-
diff --git a/website/web/WEB-INF/cloudbridges.jsp b/website/web/WEB-INF/cloudbridges.jsp
deleted file mode 100644
index 2f362cb..0000000
--- a/website/web/WEB-INF/cloudbridges.jsp
+++ /dev/null
@@ -1,64 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Tor Cloud bridges</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<a name="cloudbridges"></a>
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Tor Cloud bridges</h2>
-<br>
-<p>The following graph shows the number of running
-<a href="about.html#bridge">bridges</a> that are deployed in
-<a href="http://aws.amazon.com/ec2/">Amazon's EC2 cloud</a> using images
-provided by <a href="https://cloud.torproject.org/">Tor Cloud</a>.
-These bridges have, by default, a nickname that starts with "ec2bridger"
-which bridge operators usually leave unchanged.</p>
-<img src="cloudbridges.png${cloudbridges_url}"
-     width="576" height="360" alt="Tor Cloud bridges graph">
-<form action="cloudbridges.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="cloudbridges">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(cloudbridges_start) == 0}">${default_start_date}</c:when><c:otherwise>${cloudbridges_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(cloudbridges_end) == 0}">${default_end_date}</c:when><c:otherwise>${cloudbridges_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="cloudbridges.pdf${cloudbridges_url}">PDF</a> or
-<a href="cloudbridges.svg${cloudbridges_url}">SVG</a>.</p>
-<br>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="servers-data.html">Data: Number of relays and bridges</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="networksize.html">Graph: Relays and bridges in the network</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/connbidirect-data.jsp b/website/web/WEB-INF/connbidirect-data.jsp
deleted file mode 100644
index 1be8831..0000000
--- a/website/web/WEB-INF/connbidirect-data.jsp
+++ /dev/null
@@ -1,70 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Fraction of connections used uni-/bidirectionally (deprecated)</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Fraction of connections used uni-/bidirectionally (deprecated)</h2>
-<br>
-<p><font color="red">As of August 25, 2015, this page and the linked data
-file have been replaced by <a href="connbidirect2-data.html">this page and
-the data file linked from there</a>.
-Starting on January 11, 2016, the linked data file is not updated anymore.
-This page and the linked data file will be removed in the
-future.</font></p>
-
-<p>The following data file contains statistics on the fraction of direct
-connections between a <a href="about.html#relay">relay</a> and other nodes
-in the network that are used uni- or bidirectionally.
-Every 10 seconds, relays determine for every direct connection whether
-they read and wrote less than a threshold of 20 KiB.
-For the remaining connections, relays determine whether they read/wrote at
-least 10 times as many bytes as they wrote/read.
-If so, they classify a connection as "mostly reading" or "mostly writing",
-respectively.
-All other connections are classified as "both reading and writing".
-After classifying connections, read and write counters are reset for the
-next 10-second interval.
-The data file contains the absolute number of 10-second intervals per
-relay, aggregated over 24-hour periods.</p>
-
-<p><b>Download as <a href="stats/connbidirect.csv">CSV file</a>.</b></p>
-
-<p>The statistics file contains the following columns:</p>
-<ul>
-<li><b>date:</b> UTC date (YYYY-MM-DD) for which statistics on
-uni-/bidirectional connection usage were reported.</li>
-<li><b>source:</b> Fingerprint of the relay reporting statistics.</li>
-<li><b>below:</b> Number of 10-second intervals of connections with less
-than 20 KiB read and written data.</li>
-<li><b>read:</b> Number of 10-second intervals of connections with 10
-times as many read bytes as written bytes.</li>
-<li><b>write:</b> Number of 10-second intervals of connections with 10
-times as many written bytes as read bytes.</li>
-<li><b>both:</b> Number of 10-second intervals of connections with less
-than 10 times as many written or read bytes as in the other
-direction.</li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="connbidirect.html">Graph: Fraction of connections used uni-/bidirectionally</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
-
diff --git a/website/web/WEB-INF/connbidirect.jsp b/website/web/WEB-INF/connbidirect.jsp
deleted file mode 100644
index 68f7348..0000000
--- a/website/web/WEB-INF/connbidirect.jsp
+++ /dev/null
@@ -1,67 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Fraction of connections used uni-/bidirectionally</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Fraction of connections used uni-/bidirectionally</h2>
-<br>
-<p>The following graph shows the fraction of direct connections between a
-<a href="about.html#relay">relay</a> and other nodes in the network that
-are used uni- or bi-directionally.
-Every 10 seconds, relays determine for every direct connection whether
-they read and wrote less than a threshold of 20 KiB.
-Connections below this threshold are excluded from the graph.
-For the remaining connections, relays determine whether they read/wrote at
-least 10 times as many bytes as they wrote/read.
-If so, they classify a connection as "Mostly reading" or "Mostly writing",
-respectively.
-All other connections are classified as "Both reading and writing".
-After classifying connections, read and write counters are reset for the
-next 10-second interval.
-The graph shows daily medians and inter-quartile ranges of reported
-fractions.</p>
-<img src="connbidirect.png${connbidirect_url}"
-     width="576" height="360"
-     alt="Fraction of direct connections used uni-/bidirectionally">
-<form action="connbidirect.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="connbidirect">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(connbidirect_start) == 0}">${default_start_date}</c:when><c:otherwise>${connbidirect_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(connbidirect_end) == 0}">${default_end_date}</c:when><c:otherwise>${connbidirect_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="connbidirect.pdf${connbidirect_url}">PDF</a> or
-<a href="connbidirect.svg${connbidirect_url}">SVG</a>.</p>
-<br>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="connbidirect2-data.html">Data: Fraction of connections used uni-/bidirectionally</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/connbidirect2-data.jsp b/website/web/WEB-INF/connbidirect2-data.jsp
deleted file mode 100644
index 0209f6a..0000000
--- a/website/web/WEB-INF/connbidirect2-data.jsp
+++ /dev/null
@@ -1,67 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Fraction of connections used uni-/bidirectionally</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Fraction of connections used uni-/bidirectionally</h2>
-<br>
-<p>The following data file contains statistics on the fraction of direct
-connections between a <a href="about.html#relay">relay</a> and other nodes
-in the network that are used uni- or bidirectionally.
-Every 10 seconds, relays determine for every direct connection whether
-they read and wrote less than a threshold of 20 KiB.
-Connections below this threshold are excluded from the statistics file.
-For the remaining connections, relays determine whether they read/wrote at
-least 10 times as many bytes as they wrote/read.
-If so, they classify a connection as "mostly reading" or "mostly writing",
-respectively.
-All other connections are classified as "both reading and writing".
-After classifying connections, read and write counters are reset for the
-next 10-second interval.
-The data file contains daily medians and quartiles of reported
-fractions.</p>
-
-<p><b>Download as <a href="stats/connbidirect2.csv">CSV file</a>.</b></p>
-
-<p>The statistics file contains the following columns:</p>
-<ul>
-<li><b>date:</b> UTC date (YYYY-MM-DD) for which statistics on
-uni-/bidirectional connection usage were reported.</li>
-<li><b>direction:</b> Direction of reported fraction, which can be
-<b>"read"</b>, <b>"write"</b>, or <b>"both"</b> for connections classified
-as "mostly reading", "mostly writing", or "both reading as writing".
-Connections below the threshold have been removed from this statistics
-file entirely.</li>
-<li><b>quantile:</b> Quantile of the reported fraction when considering
-all statistics reported for this date.
-Examples are <b>"0.5"</b> for the median and <b>"0.25"</b> and
-<b>"0.75"</b> for the lower and upper quartile.</li>
-<li><b>fraction:</b> Fraction of connections in percent for the given
-date, direction, and quantile.
-For each daily statistic reported by a relay, fractions for the three
-directions "read", "write", and "both" sum up to exactly 100.</li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="connbidirect.html">Graph: Fraction of connections used uni-/bidirectionally</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
-
diff --git a/website/web/WEB-INF/data.jsp b/website/web/WEB-INF/data.jsp
new file mode 100644
index 0000000..d75f4d7
--- /dev/null
+++ b/website/web/WEB-INF/data.jsp
@@ -0,0 +1,52 @@
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+  <title>Tor Metrics — ${title}</title>
+  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+  <link href="css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
+  <link href="images/favicon.ico" type="image/x-icon" rel="shortcut icon">
+</head>
+<body>
+  <div class="center">
+    <div class="main-column">
+
+<h2><a href="."><img src="images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — ${title}</h2>
+<br>
+${description}
+
+<p><b>Download as <a href="${data_file}">CSV file</a>.</b></p>
+
+<p>The statistics file contains the following columns:</p>
+<ul>
+<c:forEach var="row" items="${data_column_spec}">
+<li>${row}</li>
+</c:forEach>
+</ul>
+
+<c:if test="${fn:length(data) > 0}">
+<h4>Underlying data</h4>
+<ul>
+<c:forEach var="row" items="${data}">
+<li><a href="${row[0]}">${row[1]}</a></li>
+</c:forEach>
+</ul>
+</c:if>
+
+<c:if test="${fn:length(related) > 0}">
+<h4>Related metrics</h4>
+<ul>
+<c:forEach var="row" items="${related}">
+<li><a href="${row[0]}">${row[1]}</a></li>
+</c:forEach>
+</ul>
+</c:if>
+
+    </div>
+  </div>
+  <div class="bottom" id="bottom">
+    <%@ include file="footer.jsp"%>
+  </div>
+</body>
+</html>
diff --git a/website/web/WEB-INF/dirbytes.jsp b/website/web/WEB-INF/dirbytes.jsp
deleted file mode 100644
index d7e9b18..0000000
--- a/website/web/WEB-INF/dirbytes.jsp
+++ /dev/null
@@ -1,69 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Number of bytes spent on answering directory requests</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Number of bytes spent on answering directory requests</h2>
-<br>
-<p>The following graph shows the portion of
-<a href="about.html#bandwidth-history">consumed bandwidth</a> that
-<a href="about.html#directory-authority">directory authorities</a> and
-<a href="about.html#directory-mirror">mirrors</a> have spent on answering
-directory requests.
-Not all directories report these statistics, so the graph shows an
-estimation of total consumed bandwidth as it would be observed if all
-directories reported these statistics.
-<font color="red">Note that the unit has recently changed from MiB/s to
-Gbit/s.</font></p>
-<img src="dirbytes.png${dirbytes_url}"
-     width="576" height="360" alt="Dir bytes graph">
-<form action="dirbytes.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="dirbytes">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(dirbytes_start) == 0}">${default_start_date}</c:when><c:otherwise>${dirbytes_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(dirbytes_end) == 0}">${default_end_date}</c:when><c:otherwise>${dirbytes_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="dirbytes.pdf${dirbytes_url}">PDF</a> or
-<a href="dirbytes.svg${dirbytes_url}">SVG</a>.</p>
-<br>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="bandwidth-data.html">Data: Bandwidth provided and consumed by relays</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="bandwidth.html">Graph: Total relay bandwidth in the network</a></li>
-<li><a href="bwhist-flags.html">Graph: Relay bandwidth by Exit and/or Guard flags</a></li>
-<li><a href="bandwidth-flags.html">Graph: Advertised bandwidth and bandwidth history by relay flags</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/error.jsp b/website/web/WEB-INF/error.jsp
index b42ae99..3aedc8d 100644
--- a/website/web/WEB-INF/error.jsp
+++ b/website/web/WEB-INF/error.jsp
@@ -5,14 +5,13 @@
 <head>
   <title>Tor Metrics — Error</title>
   <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
+  <link href="css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
+  <link href="images/favicon.ico" type="image/x-icon" rel="shortcut icon">
 </head>
 <body>
   <div class="center">
-    <%@ include file="banner.jsp"%>
     <div class="main-column">
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Error</h2>
+<h2><a href="/"><img src="images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Error</h2>
 <br>
 <p>
 Oops! Something went wrong here! We encountered a
diff --git a/website/web/WEB-INF/graph.jsp b/website/web/WEB-INF/graph.jsp
new file mode 100644
index 0000000..92d44a1
--- /dev/null
+++ b/website/web/WEB-INF/graph.jsp
@@ -0,0 +1,159 @@
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+  <title>Tor Metrics — ${title}</title>
+  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+  <link href="css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
+  <link href="images/favicon.ico" type="image/x-icon" rel="shortcut icon">
+</head>
+<body>
+  <div class="center">
+    <div class="main-column">
+
+<h2><a href="."><img src="images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — ${title}</h2>
+<br>
+${description}
+<img src="${id}.png${parameters}"
+     width="576" height="360" alt="${title} graph">
+<form action="${id}.html">
+  <div class="formrow">
+    <c:if test="${fn:length(start) > 0}">
+      <p>
+        <label>Start date (yyyy-mm-dd):
+          <input type="text" name="start" size="10" value="${start[0]}">
+        </label>
+      </p>
+    </c:if>
+    <c:if test="${fn:length(end) > 0}">
+      <p>
+        <label>End date (yyyy-mm-dd):
+          <input type="text" name="end" size="10" value="${end[0]}">
+        </label>
+      </p>
+    </c:if>
+    <c:if test="${fn:length(p) > 0}">
+      <p>Percentiles:
+      <c:forEach var="row" items="${p}">
+        <label class="checkbox-label">
+          <input type="checkbox" name="p" value="${row[0]}"${row[1]}> ${row[2]}
+        </label>
+      </c:forEach>
+      </p>
+    </c:if>
+    <c:if test="${fn:length(n) > 0}">
+      <p>Percentiles:
+      <c:forEach var="row" items="${n}">
+        <label class="checkbox-label">
+          <input type="checkbox" name="n" value="${row[0]}"${row[1]}> ${row[0]}
+        </label>
+      </c:forEach>
+      </p>
+    </c:if>
+    <c:if test="${fn:length(flag) > 0}">
+      <p>Relay flags:
+      <c:forEach var="row" items="${flag}">
+        <label class="checkbox-label">
+          <input type="checkbox" name="flag" value="${row[0]}"${row[1]}> ${row[0]}
+        </label>
+      </c:forEach>
+      </p>
+    </c:if>
+    <c:if test="${fn:length(country) > 0}">
+      <p>
+        <label>Source: <select name="country">
+        <c:forEach var="row" items="${country}">
+          <option value="${row[0]}"${row[1]}>${row[2]}</option>
+        </c:forEach>
+        </select>
+        </label>
+      </p>
+    </c:if>
+    <c:if test="${fn:length(events) > 0}">
+      <p>
+        <label>Show possible censorship events if available (<a
+               href="http://research.torproject.org/techreports/detector-2011-09-09.pdf">BETA</a>)
+          <select name="events">
+          <c:forEach var="row" items="${events}">
+            <option value="${row[0]}"${row[1]}>${row[2]}</option>
+          </c:forEach>
+          </select>
+        </label>
+      </p>
+    </c:if>
+    <c:if test="${fn:length(transport) > 0}">
+      <p>Source:
+        <c:forEach var="row" items="${transport}">
+        <label class="checkbox-label">
+          <input type="checkbox" name="transport" value="${row[0]}"${row[1]}> ${row[2]}
+        </label>
+        </c:forEach>
+      </p>
+    </c:if>
+    <c:if test="${fn:length(version) > 0}">
+      <p>
+        <label>Source:
+          <select name="version">
+          <c:forEach var="row" items="${version}">
+            <option value="${row[0]}"${row[1]}>${row[2]}</option>
+          </c:forEach>
+          </select>
+        </label>
+      </p>
+    </c:if>
+    <c:if test="${fn:length(source) > 0}">
+      <p>Source:
+      <c:forEach var="row" items="${source}">
+        <label class="radio-label">
+          <input type="radio" name="source" value="${row[0]}"${row[1]}> ${row[0]}
+        </label>
+      </c:forEach>
+      </p>
+    </c:if>
+    <c:if test="${fn:length(filesize) > 0}">
+      <p>File size:
+      <c:forEach var="row" items="${filesize}">
+        <label class="radio-label">
+          <input type="radio" name="filesize" value="${row[0]}"${row[1]}> ${row[2]}
+        </label>
+      </c:forEach>
+      </p>
+    </c:if>
+    <p>
+    <input class="submit" type="submit" value="Update graph">
+    </p>
+  </div>
+</form>
+
+<p>Download graph as
+<a href="${id}.pdf${parameters}">PDF</a> or
+<a href="${id}.svg${parameters}">SVG</a>.</p>
+<br>
+
+<c:if test="${fn:length(data) > 0}">
+<h4>Underlying data</h4>
+<ul>
+<c:forEach var="row" items="${data}">
+<li><a href="${row[0]}">${row[1]}</a></li>
+</c:forEach>
+</ul>
+</c:if>
+
+<c:if test="${fn:length(related) > 0}">
+<h4>Related metrics</h4>
+<ul>
+<c:forEach var="row" items="${related}">
+<li><a href="${row[0]}">${row[1]}</a></li>
+</c:forEach>
+</ul>
+</c:if>
+
+    </div>
+  </div>
+  <div class="bottom" id="bottom">
+    <%@ include file="footer.jsp"%>
+  </div>
+</body>
+</html>
+
diff --git a/website/web/WEB-INF/hidserv-data.jsp b/website/web/WEB-INF/hidserv-data.jsp
deleted file mode 100644
index 44c2822..0000000
--- a/website/web/WEB-INF/hidserv-data.jsp
+++ /dev/null
@@ -1,66 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Hidden-service statistics</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Hidden-service statistics</h2>
-<br>
-<p>
-The following data file contains
-<a href="about.html#hidden-service">hidden-service</a> statistics
-gathered by a small subset of <a href="about.html#relay">relays</a> and
-extrapolated to network totals.
-Statistics include the amount of hidden-service traffic and the number
-of hidden-service addresses in the network per day.
-For more details on the extrapolation algorithm, see <a
-href="https://blog.torproject.org/blog/some-statistics-about-onions">this
-blog post</a> and <a
-href="https://research.torproject.org/techreports/extrapolating-hidserv-stats-2015-01-31.pdf">this
-technical report</a>.</p>
-
-<p><b>Download as <a href="stats/hidserv.csv">CSV file</a>.</b></p>
-
-<p>The statistics file contains the following columns:</p>
-<ul>
-<li><b>date:</b> UTC date (YYYY-MM-DD) when relays or bridges have been
-listed as running.</li>
-<li><b>type:</b> Type of hidden-service statistic reported by relays and
-extrapolated to network totals.
-Examples include <b>"rend-relayed-cells"</b> for the number of cells on
-rendezvous circuits observed by rendezvous points and
-<b>"dir-onions-seen"</b> for the number of unique .onion addresses
-observed by hidden-service directories.</li>
-<li><b>wmean:</b> Weighted mean of extrapolated network totals.</li>
-<li><b>wmedian:</b> Weighted median of extrapolated network totals.</li>
-<li><b>wiqm:</b> Weighted interquartile mean of extrapolated network
-totals.</li>
-<li><b>frac:</b> Total network fraction of reported statistics.</li>
-<li><b>stats:</b> Number of reported statistics with non-zero computed
-network fraction.</li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="hidserv-dir-onions-seen.html">Graph: Unique .onion addresses</a></li>
-<li><a href="hidserv-rend-relayed-cells.html">Graph: Hidden-service traffic</a></li>
-<li><a href="hidserv-frac-reporting.html">Graph: Fraction of relays reporting hidden-service statistics</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
-
diff --git a/website/web/WEB-INF/hidserv-dir-onions-seen.jsp b/website/web/WEB-INF/hidserv-dir-onions-seen.jsp
deleted file mode 100644
index 9551168..0000000
--- a/website/web/WEB-INF/hidserv-dir-onions-seen.jsp
+++ /dev/null
@@ -1,71 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Unique .onion addresses</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Unique .onion addresses</h2>
-<br>
-<p>The following graph shows the number of unique .onion addresses in
-the network per day.
-These numbers are extrapolated from aggregated statistics on unique
-.onion addresses reported by single <a href="about.html#relay">relays</a>
-acting as <a href="about.html#hidden-service">hidden-service</a>
-directories, if at least 1% of relays reported these statistics.
-For more details on the extrapolation algorithm, see <a
-href="https://blog.torproject.org/blog/some-statistics-about-onions">this
-blog post</a> and <a
-href="https://research.torproject.org/techreports/extrapolating-hidserv-stats-2015-01-31.pdf">this
-technical report</a>.</p>
-
-<img src="hidserv-dir-onions-seen.png${hidserv_dir_onions_seen_url}"
-     width="576" height="360" alt="Unique .onion addresses graph">
-<form action="hidserv-dir-onions-seen.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="hidserv-dir-onions-seen">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(hidserv_dir_onions_seen_start) == 0}">${default_start_date}</c:when><c:otherwise>${hidserv_dir_onions_seen_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(hidserv_dir_onions_seen_end) == 0}">${default_end_date}</c:when><c:otherwise>${hidserv_dir_onions_seen_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="hidserv-dir-onions-seen.pdf${hidserv_dir_onions_seen_url}">PDF</a> or
-<a href="hidserv-dir-onions-seen.svg${hidserv_dir_onions_seen_url}">SVG</a>.</p>
-<br>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="hidserv-data.html">Data: Hidden-service statistics</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="hidserv-rend-relayed-cells.html">Graph: Hidden-service traffic</a></li>
-<li><a href="hidserv-frac-reporting.html">Graph: Fraction of relays reporting hidden-service statistics</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
-
diff --git a/website/web/WEB-INF/hidserv-frac-reporting.jsp b/website/web/WEB-INF/hidserv-frac-reporting.jsp
deleted file mode 100644
index 57e6536..0000000
--- a/website/web/WEB-INF/hidserv-frac-reporting.jsp
+++ /dev/null
@@ -1,70 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Fraction of relays reporting hidden-service statistics</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Fraction of relays reporting hidden-service statistics</h2>
-<br>
-<p>The following graph shows the fraction of
-<a href="about.html#relay">relays</a> that report statistics on
-<a href="about.html#hidden-service">hidden service</a> usage.
-If at least 1% of relays report a statistic, it gets extrapolated towards
-a network total, where higher fractions are produce more accurate results.
-For more details on the extrapolation algorithm, see <a
-href="https://blog.torproject.org/blog/some-statistics-about-onions">this
-blog post</a> and <a
-href="https://research.torproject.org/techreports/extrapolating-hidserv-stats-2015-01-31.pdf">this
-technical report</a>.</p>
-
-<img src="hidserv-frac-reporting.png${hidserv_frac_reporting_url}"
-     width="576" height="360" alt="Fraction of relays reporting hidden-service statistics graph">
-<form action="hidserv-frac-reporting.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="hidserv-frac-reporting">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(hidserv_frac_reporting_start) == 0}">${default_start_date}</c:when><c:otherwise>${hidserv_frac_reporting_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(hidserv_frac_reporting_end) == 0}">${default_end_date}</c:when><c:otherwise>${hidserv_frac_reporting_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="hidserv-frac-reporting.pdf${hidserv_frac_reporting_url}">PDF</a> or
-<a href="hidserv-frac-reporting.svg${hidserv_frac_reporting_url}">SVG</a>.</p>
-<br>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="hidserv-data.html">Data: Hidden-service statistics</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="hidserv-dir-onions-seen.html">Graph: Unique .onion addresses</a></li>
-<li><a href="hidserv-rend-relayed-cells.html">Graph: Hidden-service traffic</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
-
diff --git a/website/web/WEB-INF/hidserv-rend-relayed-cells.jsp b/website/web/WEB-INF/hidserv-rend-relayed-cells.jsp
deleted file mode 100644
index 359b7b3..0000000
--- a/website/web/WEB-INF/hidserv-rend-relayed-cells.jsp
+++ /dev/null
@@ -1,72 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Hidden-service traffic</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Hidden-service traffic</h2>
-<br>
-<p>The following graph shows the amount of hidden-service traffic in the
-network per day.
-This number is extrapolated from aggregated statistics on hidden-service
-traffic reported by single <a href="about.html#relay">relays</a>
-acting as rendezvous points for
-<a href="about.html#hidden-service">hidden services</a>, if at least 1% of
-relays reported these statistics.
-For more details on the extrapolation algorithm, see <a
-href="https://blog.torproject.org/blog/some-statistics-about-onions">this
-blog post</a> and <a
-href="https://research.torproject.org/techreports/extrapolating-hidserv-stats-2015-01-31.pdf">this
-technical report</a>.</p>
-
-<img src="hidserv-rend-relayed-cells.png${hidserv_rend_relayed_cells_url}"
-     width="576" height="360" alt="Hidden-service traffic graph">
-<form action="hidserv-rend-relayed-cells.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="hidserv-rend-relayed-cells">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(hidserv_rend_relayed_cells_start) == 0}">${default_start_date}</c:when><c:otherwise>${hidserv_rend_relayed_cells_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(hidserv_rend_relayed_cells_end) == 0}">${default_end_date}</c:when><c:otherwise>${hidserv_rend_relayed_cells_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="hidserv-rend-relayed-cells.pdf${hidserv_rend_relayed_cells_url}">PDF</a> or
-<a href="hidserv-rend-relayed-cells.svg${hidserv_rend_relayed_cells_url}">SVG</a>.</p>
-<br>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="hidserv-data.html">Data: Hidden-service statistics</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="hidserv-dir-onions-seen.html">Graph: Unique .onion addresses</a></li>
-<li><a href="hidserv-frac-reporting.html">Graph: Fraction of relays reporting hidden-service statistics</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
-
diff --git a/website/web/WEB-INF/index.jsp b/website/web/WEB-INF/index.jsp
index 065f85a..6c72137 100644
--- a/website/web/WEB-INF/index.jsp
+++ b/website/web/WEB-INF/index.jsp
@@ -5,14 +5,13 @@
 <head>
   <title>Tor Metrics</title>
   <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
+  <link href="css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
+  <link href="images/favicon.ico" type="image/x-icon" rel="shortcut icon">
 </head>
 <body>
   <div class="center">
-    <%@ include file="banner.jsp"%>
     <div class="main-column">
-        <h2><a href="/"><img src="/images/metrics-logo.png" width="153" height="200" alt="Metrics logo"><img src="/images/metrics-wordmark.png" width="384" height="50" alt="Metrics wordmark"></a></h2>
+        <h2><a href="/"><img src="images/metrics-logo.png" width="153" height="200" alt="Metrics logo"><img src="images/metrics-wordmark.png" width="384" height="50" alt="Metrics wordmark"></a></h2>
         <br>
         <p>Welcome to Tor Metrics, the primary place to learn interesting
         facts about the Tor network, the largest deployed anonymity
diff --git a/website/web/WEB-INF/link.jsp b/website/web/WEB-INF/link.jsp
new file mode 100644
index 0000000..15a9234
--- /dev/null
+++ b/website/web/WEB-INF/link.jsp
@@ -0,0 +1,43 @@
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+  <title>Tor Metrics — ${title}</title>
+  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+  <link href="css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
+  <link href="images/favicon.ico" type="image/x-icon" rel="shortcut icon">
+</head>
+<body>
+  <div class="center">
+    <div class="main-column">
+
+<h2><a href="."><img src="images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — ${title}</h2>
+<br>
+${description}
+
+<c:if test="${fn:length(data) > 0}">
+<h4>Underlying data</h4>
+<ul>
+<c:forEach var="row" items="${data}">
+<li><a href="${row[0]}">${row[1]}</a></li>
+</c:forEach>
+</ul>
+</c:if>
+
+<c:if test="${fn:length(related) > 0}">
+<h4>Related metrics</h4>
+<ul>
+<c:forEach var="row" items="${related}">
+<li><a href="${row[0]}">${row[1]}</a></li>
+</c:forEach>
+</ul>
+</c:if>
+
+    </div>
+  </div>
+  <div class="bottom" id="bottom">
+    <%@ include file="footer.jsp"%>
+  </div>
+</body>
+</html>
diff --git a/website/web/WEB-INF/networksize.jsp b/website/web/WEB-INF/networksize.jsp
deleted file mode 100644
index d799118..0000000
--- a/website/web/WEB-INF/networksize.jsp
+++ /dev/null
@@ -1,63 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Relays and bridges in the network</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Relays and bridges in the network</h2>
-<br>
-<p>The following graph shows the number of running
-<a href="about.html#relay">relays</a> and
-<a href="about.html#bridge">bridges</a> in the network.</p>
-<img src="networksize.png${networksize_url}"
-     width="576" height="360" alt="Network size graph">
-<form action="networksize.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="networksize">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(networksize_start) == 0}">${default_start_date}</c:when><c:otherwise>${networksize_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(networksize_end) == 0}">${default_end_date}</c:when><c:otherwise>${networksize_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="networksize.pdf${networksize_url}">PDF</a> or
-<a href="networksize.svg${networksize_url}">SVG</a>.</p>
-<br>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="servers-data.html">Data: Number of relays and bridges</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="relayflags.html">Graph: Relays with Exit, Fast, Guard, Stable, and HSDir flags</a></li>
-<li><a href="versions.html">Graph: Relays by version</a></li>
-<li><a href="platforms.html">Graph: Relays by platform</a></li>
-<li><a href="cloudbridges.html">Graph: Tor Cloud bridges</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/oxford-anonymous-internet.jsp b/website/web/WEB-INF/oxford-anonymous-internet.jsp
deleted file mode 100644
index a81e84b..0000000
--- a/website/web/WEB-INF/oxford-anonymous-internet.jsp
+++ /dev/null
@@ -1,46 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Tor users as percentage of larger Internet population</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Tor users as percentage of larger Internet population</h2>
-<br>
-<p>The Oxford Internet Institute made a cartogram visualization of Tor
-users as compared to the overall Internet population.
-They used the average number of Tor <a href="about.html#client">users</a>
-per country from August 2012 to August 2013 and put it in relation to
-total Internet users per country.
-More details and conclusions can be found on the
-<a href="http://geography.oii.ox.ac.uk/?page=tor">Information Geographies
-website at the Oxford Internet Institute</a>.</p>
-
-<a href="http://geography.oii.ox.ac.uk/?page=tor">
-<img src="images/oxford-anonymous-internet.png"
-     alt="The anonymous Internet">
-</a>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="userstats-relay-country.html">Graph: Direct users by country</a></li>
-<li><a href="userstats-relay-table.html">Table: Top-10 countries by directly connecting users</a></li>
-<li><a href="clients-data.html">Data: Estimated number of clients in the Tor network</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/platforms.jsp b/website/web/WEB-INF/platforms.jsp
deleted file mode 100644
index cdcd82c..0000000
--- a/website/web/WEB-INF/platforms.jsp
+++ /dev/null
@@ -1,63 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Relays by platform</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Relays by platform</a></h2>
-<br>
-<p>The following graph shows the number of running
-<a href="about.html#relay">relays</a> by operating system.
-Relays report their operating system when they announce themselves in the
-network.</p>
-<img src="platforms.png${platforms_url}"
-     width="576" height="360" alt="Relay platforms graph">
-<form action="platforms.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="platforms">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(platforms_start) == 0}">${default_start_date}</c:when><c:otherwise>${platforms_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(platforms_end) == 0}">${default_end_date}</c:when><c:otherwise>${platforms_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="platforms.pdf${platforms_url}">PDF</a> or
-<a href="platforms.svg${platforms_url}">SVG</a>.</p>
-<br>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="servers-data.html">Data: Number of relays and bridges</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="networksize.html">Graph: Relays and bridges in the network</a></li>
-<li><a href="relayflags.html">Graph: Relays with Exit, Fast, Guard, Stable, and HSDir flags</a></li>
-<li><a href="versions.html">Graph: Relays by version</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/relayflags.jsp b/website/web/WEB-INF/relayflags.jsp
deleted file mode 100644
index 89ff179..0000000
--- a/website/web/WEB-INF/relayflags.jsp
+++ /dev/null
@@ -1,76 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Relays with Exit, Fast, Guard, Stable, and HSDir flags</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Relays with Exit, Fast, Guard, Stable, and HSDir flags</h2>
-<br>
-
-<p>The following graph shows the number of running
-<a href="about.html#relay">relays</a> that have had certain
-<a href="about.html#relay-flag">flags</a> assigned by the
-<a href="about.html#directory-authority">directory authorities</a>.
-These flags indicate that a relay should be preferred for either guard
-("Guard") or exit positions ("Exit"), that a relay is suitable for
-high-bandwidth ("Fast") or long-lived circuits ("Stable"), or that a relay
-is considered a hidden service directory ("HSDir").</p>
-<img src="relayflags.png${relayflags_url}"
-     width="576" height="360" alt="Relay flags graph">
-<form action="relayflags.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="relayflags">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(relayflags_start) == 0}">${default_start_date}</c:when><c:otherwise>${relayflags_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(relayflags_end) == 0}">${default_end_date}</c:when><c:otherwise>${relayflags_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-      <label>Relay flags: </label>
-      <label class="checkbox-label"><input type="checkbox" name="flag" value="Running"<c:if test="${fn:length(relayflags_flag) == 0 or fn:contains(fn:join(relayflags_flag, ','), 'Running')}"> checked</c:if>> Running</label>
-      <label class="checkbox-label"><input type="checkbox" name="flag" value="Exit"<c:if test="${fn:length(relayflags_flag) == 0 or fn:contains(fn:join(relayflags_flag, ','), 'Exit')}"> checked</c:if>> Exit</label>
-      <label class="checkbox-label"><input type="checkbox" name="flag" value="Fast"<c:if test="${fn:length(relayflags_flag) == 0 or fn:contains(fn:join(relayflags_flag, ','), 'Fast')}"> checked</c:if>> Fast</label>
-      <label class="checkbox-label"><input type="checkbox" name="flag" value="Guard"<c:if test="${fn:length(relayflags_flag) == 0 or fn:contains(fn:join(relayflags_flag, ','), 'Guard')}"> checked</c:if>> Guard</label>
-      <label class="checkbox-label"><input type="checkbox" name="flag" value="Stable"<c:if test="${fn:length(relayflags_flag) == 0 or fn:contains(fn:join(relayflags_flag, ','), 'Stable')}"> checked</c:if>> Stable</label>
-      <label class="checkbox-label"><input type="checkbox" name="flag" value="HSDir"<c:if test="${fn:length(relayflags_flag) > 0 and fn:contains(fn:join(relayflags_flag, ','), 'HSDir')}"> checked</c:if>> HSDir</label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="relayflags.pdf${relayflags_url}">PDF</a> or
-<a href="relayflags.svg${relayflags_url}">SVG</a>.</p>
-<br>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="servers-data.html">Data: Number of relays and bridges</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="networksize.html">Graph: Relays and bridges in the network</a></li>
-<li><a href="versions.html">Graph: Relays by version</a></li>
-<li><a href="platforms.html">Graph: Relays by platform</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/servers-data.jsp b/website/web/WEB-INF/servers-data.jsp
deleted file mode 100644
index 34ef552..0000000
--- a/website/web/WEB-INF/servers-data.jsp
+++ /dev/null
@@ -1,99 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Number of relays and bridges</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Number of relays and bridges</h2>
-<br>
-<p>The following data file contains the number of running
-<a href="about.html#relay">relays</a> and
-<a href="about.html#bridge">bridges</a> in the network.
-Statistics include subsets of relays or bridges by
-<a href="about.html#relay-flag">relay flag</a> (relays only), country code
-(relays only, and only until February 2013), tor software version (relays
-only), operating system (relays only), and by whether or not they are
-running in the EC2 cloud (bridges only).
-The data file contains daily (mean) averages of relay and bridge
-numbers.</p>
-
-<p><b>Download as <a href="stats/servers.csv">CSV file</a>.</b></p>
-
-<p>The statistics file contains the following columns:</p>
-<ul>
-<li><b>date:</b> UTC date (YYYY-MM-DD) when relays or bridges have been
-listed as running.</li>
-<li><b>flag:</b> Relay flag assigned by the directory authorities.
-Examples are <b>"Exit"</b>, <b>"Guard"</b>, <b>"Fast"</b>,
-<b>"Stable"</b>, and <b>"HSDir"</b>.
-Relays can have none, some, or all these relay flags assigned.
-Relays that don't have the <b>"Running"</b> flag are not included in these
-statistics regardless of their other flags.
-If this column contains the empty string, all running relays are included,
-regardless of assigned flags.
-There are no statistics on the number of bridges by relay flag.</li>
-<li><b>country:</b> Two-letter lower-case country code as found in a GeoIP
-database by resolving the relay's first onion-routing IP address, or
-<b>"??"</b> if an IP addresses could not be resolved.
-If this column contains the empty string, all running relays are included,
-regardless of their resolved country code.
-Statistics on relays by country code are only available until January 31,
-2013.
-There are no statistics on the number of bridges by country code.</li>
-<li><b>version:</b> First three dotted numbers of the Tor software version
-as reported by the relay.
-An example is <b>"0.2.5"</b>.
-If this column contains the empty string, all running relays are included,
-regardless of the Tor software version they run.
-There are no statistics on the number of bridges by Tor software
-version.</li>
-<li><b>platform:</b> Operating system as reported by the relay.
-Examples are <b>"Linux"</b>, <b>"Darwin"</b> (Mac OS X), <b>"BSD"</b>,
-<b>"Windows"</b>, and <b>"Other"</b>.
-If this column contains the empty string, all running relays are included,
-regardless of the operating system they run on.
-There are no statistics on the number of bridges by operating system.</li>
-<li><b>ec2bridge:</b> Whether bridges are running in the EC2 cloud or not.
-More precisely, bridges in the EC2 cloud running an image provided by Tor
-by default set their nickname to <b>"ec2bridger"</b> plus 8 random hex
-characters.
-This column either contains <b>"t"</b> for bridges matching this naming
-scheme, or the empty string for all bridges regardless of their nickname.
-There are no statistics on the number of relays running in the EC2
-cloud.</li>
-<li><b>relays:</b> The average number of relays matching the criteria in
-the previous columns.
-If the values in previous columns are specific to bridges only, this
-column contains the empty string.</li>
-<li><b>bridges:</b> The average number of bridges matching the criteria in
-the previous columns.
-If the values in previous columns are specific to relays only, this column
-contains the empty string.</li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="networksize.html">Graph: Relays and bridges in the network</a></li>
-<li><a href="relayflags.html">Graph: Relays with Exit, Fast, Guard, Stable, and HSDir flags</a></li>
-<li><a href="versions.html">Graph: Relays by version</a></li>
-<li><a href="platforms.html">Graph: Relays by platform</a></li>
-<li><a href="cloudbridges.html">Graph: Tor Cloud bridges</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
-
diff --git a/website/web/WEB-INF/table.jsp b/website/web/WEB-INF/table.jsp
new file mode 100644
index 0000000..1eca70f
--- /dev/null
+++ b/website/web/WEB-INF/table.jsp
@@ -0,0 +1,74 @@
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+  <title>Tor Metrics — ${title}</title>
+  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+  <link href="css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
+  <link href="images/favicon.ico" type="image/x-icon" rel="shortcut icon">
+</head>
+<body>
+  <div class="center">
+    <div class="main-column">
+
+<h2><a href="."><img src="images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — ${title}</h2>
+<br>
+${description}
+<form action="${id}.html">
+  <div class="formrow">
+    <p>
+    <label>Start date (yyyy-mm-dd):
+      <input type="text" name="start" size="10" value="${start[0]}">
+    </label>
+    </p><p>
+    <label>End date (yyyy-mm-dd):
+      <input type="text" name="end" size="10" value="${end[0]}">
+    </label>
+    </p><p>
+    <input class="submit" type="submit" value="Update table">
+    </p>
+  </div>
+</form>
+<br>
+<table>
+  <tr>
+  <c:forEach var="row" items="${tableheader}">
+    <th>${row}</th>
+  </c:forEach>
+  </tr>
+  <c:forEach var="row" items="${tabledata}">
+    <tr>
+    <c:forEach var="col" items="${row}">
+      <td>${col}</td>
+    </c:forEach>
+    </tr>
+  </c:forEach>
+</table>
+<p>
+
+<c:if test="${fn:length(data) > 0}">
+<h4>Underlying data</h4>
+<ul>
+<c:forEach var="row" items="${data}">
+<li><a href="${row[0]}">${row[1]}</a></li>
+</c:forEach>
+</ul>
+</c:if>
+
+<c:if test="${fn:length(related) > 0}">
+<h4>Related metrics</h4>
+<ul>
+<c:forEach var="row" items="${related}">
+<li><a href="${row[0]}">${row[1]}</a></li>
+</c:forEach>
+</ul>
+</c:if>
+
+    </div>
+  </div>
+  <div class="bottom" id="bottom">
+    <%@ include file="footer.jsp"%>
+  </div>
+</body>
+</html>
diff --git a/website/web/WEB-INF/torperf-data.jsp b/website/web/WEB-INF/torperf-data.jsp
deleted file mode 100644
index 3a7e605..0000000
--- a/website/web/WEB-INF/torperf-data.jsp
+++ /dev/null
@@ -1,68 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Performance of downloading static files over Tor</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Performance of downloading static files over Tor</h2>
-<br>
-<p>The following data file contains aggregate statistics on performance
-when downloading static files of different sizes over Tor.
-These statistics are generated by the
-<a href="https://gitweb.torproject.org/torperf.git">Torperf</a> tool,
-which periodically fetches static files over Tor and records several
-timestamps in the process.
-The data file contains daily medians and quartiles as well as total
-numbers of requests, timeouts, and failures.
-Raw Torperf measurement data are available on the
-<a href="https://collector.torproject.org/formats.html#torperf">CollecTor</a>
-website.</p>
-
-<p><b>Download as <a href="stats/torperf.csv">CSV file</a>.</b></p>
-
-<p>The statistics file contains the following columns:</p>
-<ul>
-<li><b>date:</b> UTC date (YYYY-MM-DD) when download performance was
-measured.</li>
-<li><b>size:</b> Size of the downloaded file in bytes.</li>
-<li><b>source:</b> Name of the Torperf service performing measurements.
-If this column contains the empty string, all measurements are included,
-regardless of which Torperf service performed them.
-Examples are <b>"moria"</b>, <b>"siv"</b>, and <b>"torperf"</b>.</li>
-<li><b>q1:</b> First quartile of time until receiving the last byte in
-milliseconds.</li>
-<li><b>md:</b> Median of time until receiving the last byte in
-milliseconds.</li>
-<li><b>q3:</b> Third quartile of time until receiving the last byte in
-milliseconds.</li>
-<li><b>timeouts:</b> Number of timeouts that occurred when attempting to
-download the static file over Tor.</li>
-<li><b>failures:</b> Number of failures that occurred when attempting to
-download the static file over Tor.</li>
-<li><b>requests:</b> Total number of requests made to download the static
-file over Tor.</li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="torperf.html">Graph: Time to download files over Tor</a></li>
-<li><a href="torperf-failures.html">Graph: Timeouts and failures of downloading files over Tor</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
-
diff --git a/website/web/WEB-INF/torperf-failures.jsp b/website/web/WEB-INF/torperf-failures.jsp
deleted file mode 100644
index 71f0716..0000000
--- a/website/web/WEB-INF/torperf-failures.jsp
+++ /dev/null
@@ -1,74 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Timeouts and failures of downloading files over Tor</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Timeouts and failures of downloading files over Tor</h2>
-<br>
-<p>The following graph shows the fraction of timeouts and failures when
-downloading static files of different sizes over Tor.
-A timeout occurs when a download does not complete within the scheduled
-time, in which case it is aborted in order not to overlap with the next
-scheduled download.
-A failure occurs when the download completes, but the response is smaller
-than expected.</p>
-<img src="torperf-failures.png${torperf_failures_url}"
-     width="576" height="360" alt="Torperf failures graph">
-<form action="torperf-failures.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="torperf-failures">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(torperf_failures_start) == 0}">${default_start_date}</c:when><c:otherwise>${torperf_failures_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(torperf_failures_end) == 0}">${default_end_date}</c:when><c:otherwise>${torperf_failures_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-      Source:
-      <label class="radio-label"><input type="radio" name="source" value="all"<c:if test="${fn:length(torperf_failures_source) == 0 or torperf_failures_source[0] eq 'all'}"> checked</c:if>> all</label>
-      <label class="radio-label"><input type="radio" name="source" value="torperf"<c:if test="${torperf_failures_source[0] eq 'torperf'}"> checked</c:if>> torperf</label>
-      <label class="radio-label"><input type="radio" name="source" value="moria"<c:if test="${torperf_failures_source[0] eq 'moria'}"> checked</c:if>> moria</label>
-      <label class="radio-label"><input type="radio" name="source" value="siv"<c:if test="${torperf_failures_source[0] eq 'siv'}"> checked</c:if>> siv</label>
-    </p><p>
-      File size:
-      <label class="radio-label"><input type="radio" name="filesize" value="50kb"<c:if test="${fn:length(torperf_failures_filesize) == 0 or torperf_failures_filesize[0] eq '50kb'}"> checked</c:if>> 50 KiB</label>
-      <label class="radio-label"><input type="radio" name="filesize" value="1mb"<c:if test="${torperf_failures_filesize[0] eq '1mb'}"> checked</c:if>> 1 MiB</label>
-      <label class="radio-label"><input type="radio" name="filesize" value="5mb"<c:if test="${torperf_failures_filesize[0] eq '5mb'}"> checked</c:if>> 5 MiB</label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="torperf-failures.pdf${torperf_failures_url}">PDF</a> or
-<a href="torperf-failures.svg${torperf_failures_url}">SVG</a>.</p>
-<br>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="torperf-data.html">Data: Performance of downloading static files over Tor</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="torperf.html">Graph: Time to download files over Tor</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/torperf.jsp b/website/web/WEB-INF/torperf.jsp
deleted file mode 100644
index 1171b76..0000000
--- a/website/web/WEB-INF/torperf.jsp
+++ /dev/null
@@ -1,73 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Time to download files over Tor</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Time to download files over Tor</h2>
-<br>
-<p>The following graph shows overall performance when downloading static
-files of different sizes over Tor.
-The graph shows the range of measurements from first to third quartile,
-and highlights the median.
-The slowest and fastest quarter of measurements are omitted from the
-graph.</p>
-<img src="torperf.png${torperf_url}"
-     width="576" height="360" alt="Torperf graph">
-<form action="torperf.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="torperf">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(torperf_start) == 0}">${default_start_date}</c:when><c:otherwise>${torperf_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(torperf_end) == 0}">${default_end_date}</c:when><c:otherwise>${torperf_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-      Source:
-      <label class="radio-label"><input type="radio" name="source" value="all"<c:if test="${fn:length(torperf_source) == 0 or torperf_source[0] eq 'all'}"> checked</c:if>> all</label>
-      <label class="radio-label"><input type="radio" name="source" value="torperf"<c:if test="${torperf_source[0] eq 'torperf'}"> checked</c:if>> torperf</label>
-      <label class="radio-label"><input type="radio" name="source" value="moria"<c:if test="${torperf_source[0] eq 'moria'}"> checked</c:if>> moria</label>
-      <label class="radio-label"><input type="radio" name="source" value="siv"<c:if test="${torperf_source[0] eq 'siv'}"> checked</c:if>> siv</label>
-    </p><p>
-      File size:
-      <label class="radio-label"><input type="radio" name="filesize" value="50kb"<c:if test="${fn:length(torperf_filesize) == 0 or torperf_filesize[0] eq '50kb'}"> checked</c:if>> 50 KiB</label>
-      <label class="radio-label"><input type="radio" name="filesize" value="1mb"<c:if test="${torperf_filesize[0] eq '1mb'}"> checked</c:if>> 1 MiB</label>
-      <label class="radio-label"><input type="radio" name="filesize" value="5mb"<c:if test="${torperf_filesize[0] eq '5mb'}"> checked</c:if>> 5 MiB</label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="torperf.pdf${torperf_url}">PDF</a> or
-<a href="torperf.svg${torperf_url}">SVG</a>.</p>
-<br>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="torperf-data.html">Data: Performance of downloading static files over Tor</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="torperf-failures.html">Graph: Timeouts and failures of downloading files over Tor</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/uncharted-data-flow.jsp b/website/web/WEB-INF/uncharted-data-flow.jsp
deleted file mode 100644
index 2e0febb..0000000
--- a/website/web/WEB-INF/uncharted-data-flow.jsp
+++ /dev/null
@@ -1,36 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Data flow in the Tor network</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Data flow in the Tor network</h2>
-<br>
-<p>Uncharted made a visualization of data flow in the Tor network where
-they place each <a href="about.html#relay">relay</a> on a world map and
-illustrate traffic exchanged between relays as animated dots. More details
-can be found on the <a
-href="https://torflow.uncharted.software/">Uncharted website</a>.</p>
-
-<a href="https://torflow.uncharted.software/">
-<img src="images/uncharted-data-flow.png"
-     alt="Data flow in the Tor network">
-</a>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/userstats-bridge-country.jsp b/website/web/WEB-INF/userstats-bridge-country.jsp
deleted file mode 100644
index 430241f..0000000
--- a/website/web/WEB-INF/userstats-bridge-country.jsp
+++ /dev/null
@@ -1,73 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Bridge users by country</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Bridge users by country</h2>
-<br>
-<p>The following graph shows the estimated number of
-<a href="about.html#client">clients</a> connecting via
-<a href="about.html#bridge">bridges</a>.
-These numbers are derived from directory requests counted on bridges.
-Bridges resolve client IP addresses of incoming directory requests to
-country codes, so that graphs are available for most countries.</p>
-<img src="userstats-bridge-country.png${userstats_bridge_country_url}"
-     width="576" height="360" alt="Bridge users by country graph">
-<form action="userstats-bridge-country.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="userstats-bridge-country">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(userstats_bridge_country_start) == 0}">${default_start_date}</c:when><c:otherwise>${userstats_bridge_country_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(userstats_bridge_country_end) == 0}">${default_end_date}</c:when><c:otherwise>${userstats_bridge_country_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-      <label>Source: <select name="country">
-        <option value="all"<c:if test="${userstats_bridge_country_country[0] eq 'all'}"> selected</c:if>>All users</option>
-        <c:forEach var="country" items="${countries}" >
-          <option value="${country[0]}"<c:if test="${userstats_bridge_country_country[0] eq country[0]}"> selected</c:if>>${country[1]}</option>
-        </c:forEach>
-      </select></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="userstats-bridge-country.pdf${userstats_bridge_country_url}">PDF</a> or
-<a href="userstats-bridge-country.svg${userstats_bridge_country_url}">SVG</a>.
-<a href="https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt">Questions
-and answers about users statistics</a></p>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="clients-data.html">Data: Estimated number of clients in the Tor network</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="userstats-relay-country.html">Graph: Direct users by country</a></li>
-<li><a href="userstats-relay-table.html">Table: Top-10 countries by directly connecting users</a></li>
-<li><a href="userstats-bridge-table.html">Table: Top-10 countries by bridge users</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/userstats-bridge-table.jsp b/website/web/WEB-INF/userstats-bridge-table.jsp
deleted file mode 100644
index 06ce6dc..0000000
--- a/website/web/WEB-INF/userstats-bridge-table.jsp
+++ /dev/null
@@ -1,77 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Top-10 countries by bridge users</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Top-10 countries by bridge users</h2>
-<br>
-<p>The following table shows the top-10 countries by estimated number of
-<a href="about.html#client">clients</a> connecting via
-<a href="about.html#bridge">bridges</a>.
-These numbers are derived from directory requests counted on bridges.
-Bridges resolve client IP addresses of incoming directory requests to
-country codes, so that numbers are available for most countries.</p>
-<form action="userstats-bridge-table.html">
-  <div class="formrow">
-    <input type="hidden" name="table" value="userstats-bridge">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(userstats_bridge_start) == 0}">${default_start_date}</c:when><c:otherwise>${userstats_bridge_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(userstats_bridge_end) == 0}">${default_end_date}</c:when><c:otherwise>${userstats_bridge_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update table">
-    </p>
-  </div>
-</form>
-<br>
-<table>
-  <tr>
-    <th>Country</th>
-    <th>Mean daily users</th>
-  </tr>
-  <c:forEach var="row" items="${userstats_bridge_tabledata}">
-    <tr>
-      <td><a href="userstats-bridge-country.html?graph=userstats-bridge-country&country=${row['cc']}">${row['country']}</a> </td>
-      <td>${row['abs']} <c:if test="${row['rel'] != 'NA'}"> (<fmt:formatNumber type="number" minFractionDigits="2" value="${row['rel']}" /> %)</c:if></td>
-    </tr>
-  </c:forEach>
-</table>
-<p>
-<a href="https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt">Questions
-and answers about users statistics</a></p>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="clients-data.html">Data: Estimated number of clients in the Tor network</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="userstats-relay-country.html">Graph: Direct users by country</a></li>
-<li><a href="userstats-relay-table.html">Table: Top-10 countries by directly connecting users</a></li>
-<li><a href="userstats-bridge-country.html">Graph: Bridge users by country</a></li>
-<li><a href="userstats-bridge-transport.html">Graph: Bridge users by transport</a></li>
-<li><a href="userstats-bridge-version.html">Graph: Bridge users by IP version</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/userstats-bridge-transport.jsp b/website/web/WEB-INF/userstats-bridge-transport.jsp
deleted file mode 100644
index 8ec6cd5..0000000
--- a/website/web/WEB-INF/userstats-bridge-transport.jsp
+++ /dev/null
@@ -1,79 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Bridge users by transport</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Bridge users by transport</h2>
-<br>
-<p>The following graph shows the estimated number of
-<a href="about.html#client">clients</a> connecting via
-<a href="about.html#bridge">bridges</a>.
-These numbers are derived from directory requests counted on bridges.
-Bridges distinguish connecting clients by transport protocol, which may
-include <a href="about.html#pluggable-transport">pluggable transports</a>,
-so that graphs are available for different transports.</p>
-<img src="userstats-bridge-transport.png${userstats_bridge_transport_url}"
-     width="576" height="360" alt="Bridge users by transport graph">
-<form action="userstats-bridge-transport.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="userstats-bridge-transport">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(userstats_bridge_transport_start) == 0}">${default_start_date}</c:when><c:otherwise>${userstats_bridge_transport_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(userstats_bridge_transport_end) == 0}">${default_end_date}</c:when><c:otherwise>${userstats_bridge_transport_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-      Source:
-      <label class="checkbox-label"><input type="checkbox" name="transport" value="!<OR>"<c:if test="${fn:length(userstats_bridge_transport_transport) == 0 or fn:contains(fn:join(userstats_bridge_transport_transport, ','), '!<OR>')}"> checked</c:if>> Any pluggable transport</label>
-      <label class="checkbox-label"><input type="checkbox" name="transport" value="obfs2"<c:if test="${fn:length(userstats_bridge_transport_transport) > 0 and fn:contains(fn:join(userstats_bridge_transport_transport, ','), 'obfs2')}"> checked</c:if>> obfs2</label>
-      <label class="checkbox-label"><input type="checkbox" name="transport" value="obfs3"<c:if test="${fn:length(userstats_bridge_transport_transport) > 0 and fn:contains(fn:join(userstats_bridge_transport_transport, ','), 'obfs3')}"> checked</c:if>> obfs3</label>
-      <label class="checkbox-label"><input type="checkbox" name="transport" value="obfs4"<c:if test="${fn:length(userstats_bridge_transport_transport) > 0 and fn:contains(fn:join(userstats_bridge_transport_transport, ','), 'obfs4')}"> checked</c:if>> obfs4</label>
-      <label class="checkbox-label"><input type="checkbox" name="transport" value="websocket"<c:if test="${fn:length(userstats_bridge_transport_transport) > 0 and fn:contains(fn:join(userstats_bridge_transport_transport, ','), 'websocket')}"> checked</c:if>> Flash proxy/websocket</label>
-      <label class="checkbox-label"><input type="checkbox" name="transport" value="fte"<c:if test="${fn:length(userstats_bridge_transport_transport) > 0 and fn:contains(fn:join(userstats_bridge_transport_transport, ','), 'fte')}"> checked</c:if>> FTE</label>
-      <label class="checkbox-label"><input type="checkbox" name="transport" value="meek"<c:if test="${fn:length(userstats_bridge_transport_transport) > 0 and fn:contains(fn:join(userstats_bridge_transport_transport, ','), 'meek')}"> checked</c:if>> meek</label>
-      <label class="checkbox-label"><input type="checkbox" name="transport" value="scramblesuit"<c:if test="${fn:length(userstats_bridge_transport_transport) > 0 and fn:contains(fn:join(userstats_bridge_transport_transport, ','), 'scramblesuit')}"> checked</c:if>> scramblesuit</label>
-      <label class="checkbox-label"><input type="checkbox" name="transport" value="<??>"<c:if test="${fn:length(userstats_bridge_transport_transport) > 0 and fn:contains(fn:join(userstats_bridge_transport_transport, ','), '<??>')}"> checked</c:if>> Unknown pluggable transport(s)</label>
-      <label class="checkbox-label"><input type="checkbox" name="transport" value="<OR>"<c:if test="${fn:length(userstats_bridge_transport_transport) > 0 and fn:contains(fn:join(userstats_bridge_transport_transport, ','), '<OR>')}"> checked</c:if>> Default OR protocol</label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="userstats-bridge-transport.pdf${userstats_bridge_transport_url}">PDF</a> or
-<a href="userstats-bridge-transport.svg${userstats_bridge_transport_url}">SVG</a>.
-<a href="https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt">Questions
-and answers about users statistics</a></p>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="clients-data.html">Data: Estimated number of clients in the Tor network</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="userstats-bridge-country.html">Graph: Bridge users by country</a></li>
-<li><a href="userstats-bridge-table.html">Table: Top-10 countries by bridge users</a></li>
-<li><a href="userstats-bridge-version.html">Graph: Bridge users by IP version</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/userstats-bridge-version.jsp b/website/web/WEB-INF/userstats-bridge-version.jsp
deleted file mode 100644
index b8976df..0000000
--- a/website/web/WEB-INF/userstats-bridge-version.jsp
+++ /dev/null
@@ -1,71 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Bridge users by IP version</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Bridge users by IP version</h2>
-<br>
-<p>The following graph shows the estimated number of
-<a href="about.html#client">clients</a> connecting via
-<a href="about.html#bridge">bridges</a>.
-These numbers are derived from directory requests counted on bridges.
-Bridges distinguish connecting clients by IP version, so that graphs are
-available for both IP versions 4 and 6.</p>
-<img src="userstats-bridge-version.png${userstats_bridge_version_url}"
-     width="576" height="360" alt="Bridge users by IP version graph">
-<form action="userstats-bridge-version.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="userstats-bridge-version">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(userstats_bridge_version_start) == 0}">${default_start_date}</c:when><c:otherwise>${userstats_bridge_version_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(userstats_bridge_version_end) == 0}">${default_end_date}</c:when><c:otherwise>${userstats_bridge_version_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-      <label>Source: <select name="version">
-        <option value="v4"<c:if test="${userstats_bridge_version_version[0] eq 'v4'}"> selected</c:if>>IPv4</option>
-        <option value="v6"<c:if test="${userstats_bridge_version_version[0] eq 'v6'}"> selected</c:if>>IPv6</option>
-      </select></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="userstats-bridge-version.pdf${userstats_bridge_version_url}">PDF</a> or
-<a href="userstats-bridge-version.svg${userstats_bridge_version_url}">SVG</a>.
-<a href="https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt">Questions
-and answers about users statistics</a></p>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="clients-data.html">Data: Estimated number of clients in the Tor network</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="userstats-bridge-country.html">Graph: Bridge users by country</a></li>
-<li><a href="userstats-bridge-table.html">Table: Top-10 countries by bridge users</a></li>
-<li><a href="userstats-bridge-transport.html">Graph: Bridge users by transport</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/userstats-censorship-events.jsp b/website/web/WEB-INF/userstats-censorship-events.jsp
deleted file mode 100644
index 6b7e21f..0000000
--- a/website/web/WEB-INF/userstats-censorship-events.jsp
+++ /dev/null
@@ -1,75 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Top-10 countries by possible censorship events</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Top-10 countries by possible censorship events</h2>
-<br>
-<p>The following table shows the top-10 countries by possible censorship
-events, as obtained from an anomaly-based censorship-detection system (for
-more details, see this
-<a href="https://research.torproject.org/techreports/detector-2011-09-09.pdf">technical report</a>).</p>
-<form action="userstats-censorship-events.html">
-  <div class="formrow">
-    <input type="hidden" name="table" value="userstats-censorship-events">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(userstats_censorship_events_start) == 0}">${default_start_date}</c:when><c:otherwise>${userstats_censorship_events_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(userstats_censorship_events_end) == 0}">${default_end_date}</c:when><c:otherwise>${userstats_censorship_events_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update table">
-    </p>
-  </div>
-</form>
-<br>
-<table>
-  <tr>
-    <th>Country</th>
-    <th>Downturns</th>
-    <th>Upturns</th>
-  </tr>
-  <c:forEach var="row" items="${userstats_censorship_events_tabledata}">
-    <tr>
-      <td><a href="userstats-relay-country.html?graph=userstats-relay-country&country=${row['cc']}&events=on">${row['country']}</a> </td>
-      <td>${row['downturns']}</td>
-      <td>${row['upturns']}</td>
-    </tr>
-  </c:forEach>
-</table>
-<p>
-<a href="https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt">Questions
-and answers about users statistics</a></p>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="clients-data.html">Data: Estimated number of clients in the Tor network</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="userstats-relay-country.html">Graph: Direct users by country</a></li>
-<li><a href="userstats-relay-table.html">Table: Top-10 countries by directly connecting users</a></li>
-<li><a href="userstats-censorship-events.html">Table: Top-10 countries by possible censorship events</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/userstats-relay-country.jsp b/website/web/WEB-INF/userstats-relay-country.jsp
deleted file mode 100644
index c7f8525..0000000
--- a/website/web/WEB-INF/userstats-relay-country.jsp
+++ /dev/null
@@ -1,89 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Direct users by country</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Direct users by country</h2>
-<br>
-<p>The following graph shows the estimated number of directly-connecting
-<a href="about.html#client">clients</a>; that is, it excludes clients
-connecting via <a href="about.html#bridge">bridges</a>.
-These estimates are derived from the number of directory requests counted
-on <a href="about.html#directory-authority">directory authorities</a> and
-<a href="about.html#directory-mirror">mirrors</a>.
-Relays resolve client IP addresses to country codes, so that graphs are
-available for most countries.
-Furthermore, it is possible to display indications of censorship events as
-obtained from an anomaly-based censorship-detection system (for more
-details, see this
-<a href="https://research.torproject.org/techreports/detector-2011-09-09.pdf">technical report</a>).</p>
-<img src="userstats-relay-country.png${userstats_relay_country_url}"
-     width="576" height="360" alt="Direct users by country graph">
-<form action="userstats-relay-country.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="userstats-relay-country">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(userstats_relay_country_start) == 0}">${default_start_date}</c:when><c:otherwise>${userstats_relay_country_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(userstats_relay_country_end) == 0}">${default_end_date}</c:when><c:otherwise>${userstats_relay_country_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-      <label>Source: <select name="country">
-        <option value="all"<c:if test="${userstats_relay_country_country[0] eq 'all'}"> selected</c:if>>All users</option>
-        <c:forEach var="country" items="${countries}" >
-          <option value="${country[0]}"<c:if test="${userstats_relay_country_country[0] eq country[0]}"> selected</c:if>>${country[1]}</option>
-        </c:forEach>
-      </select></label>
-    </p><p>
-      <label>Show possible censorship events if available (<a
-      href="http://research.torproject.org/techreports/detector-2011-09-09.pdf">BETA</a>)
-      <select name="events">
-        <option value="off">Off</option>
-        <option value="on"<c:if test="${userstats_relay_country_events[0] eq 'on'}"> selected</c:if>>On: both points and expected range</option>
-        <option value="points"<c:if test="${userstats_relay_country_events[0] eq 'points'}"> selected</c:if>>On: points only, no expected range</option>
-      </select></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="userstats-relay-country.pdf${userstats_relay_country_url}">PDF</a> or
-<a href="userstats-relay-country.svg${userstats_relay_country_url}">SVG</a>.
-<a href="https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt">Questions
-and answers about users statistics</a></p>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="clients-data.html">Data: Estimated number of clients in the Tor network</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="userstats-relay-table.html">Table: Top-10 countries by directly connecting users</a></li>
-<li><a href="userstats-censorship-events.html">Table: Top-10 countries by possible censorship events</a></li>
-<li><a href="userstats-bridge-country.html">Graph: Bridge users by country</a></li>
-<li><a href="userstats-bridge-table.html">Table: Top-10 countries by bridge users</a></li>
-<li><a href="oxford-anonymous-internet.html">Link: Tor users as percentage of larger Internet population</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/userstats-relay-table.jsp b/website/web/WEB-INF/userstats-relay-table.jsp
deleted file mode 100644
index 07138c0..0000000
--- a/website/web/WEB-INF/userstats-relay-table.jsp
+++ /dev/null
@@ -1,78 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Top-10 countries by directly connecting users</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="/css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="/images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="/images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Top-10 countries by directly connecting users</h2>
-<br>
-<p>The following table shows the top-10 countries by estimated number of
-directly-connecting <a href="about.html#client">clients</a>.
-These numbers are derived from directory requests counted on
-<a href="about.html#directory-authority">directory authorities</a> and
-<a href="about.html#directory-mirror">mirrors</a>.
-Relays resolve client IP addresses to country codes, so that numbers are
-available for most countries.</p>
-<form action="userstats-relay-table.html">
-  <div class="formrow">
-    <input type="hidden" name="table" value="userstats-relay">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(userstats_relay_start) == 0}">${default_start_date}</c:when><c:otherwise>${userstats_relay_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(userstats_relay_end) == 0}">${default_end_date}</c:when><c:otherwise>${userstats_relay_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update table">
-    </p>
-  </div>
-</form>
-<br>
-<table>
-  <tr>
-    <th>Country</th>
-    <th>Mean daily users</th>
-  </tr>
-  <c:forEach var="row" items="${userstats_relay_tabledata}">
-    <tr>
-      <td><a href="userstats-relay-country.html?graph=userstats-relay-country&country=${row['cc']}">${row['country']}</a> </td>
-      <td>${row['abs']} <c:if test="${row['rel'] != 'NA'}"> (<fmt:formatNumber type="number" minFractionDigits="2" value="${row['rel']}" /> %)</c:if></td>
-    </tr>
-  </c:forEach>
-</table>
-<p>
-<a href="https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt">Questions
-and answers about users statistics</a></p>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="clients-data.html">Data: Estimated number of clients in the Tor network</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="userstats-relay-country.html">Graph: Direct users by country</a></li>
-<li><a href="userstats-censorship-events.html">Table: Top-10 countries by possible censorship events</a></li>
-<li><a href="userstats-bridge-country.html">Graph: Bridge users by country</a></li>
-<li><a href="userstats-bridge-table.html">Table: Top-10 countries by bridge users</a></li>
-<li><a href="oxford-anonymous-internet.html">Link: Tor users as percentage of larger Internet population</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
diff --git a/website/web/WEB-INF/versions.jsp b/website/web/WEB-INF/versions.jsp
deleted file mode 100644
index 02f9173..0000000
--- a/website/web/WEB-INF/versions.jsp
+++ /dev/null
@@ -1,68 +0,0 @@
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — Relays by version</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="../css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="../images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <%@ include file="banner.jsp"%>
-    <div class="main-column">
-
-<h2><a href="/"><img src="../images/metrics-wordmark-small.png" width="138" height="18" alt="Metrics wordmark"></a> — Relays by version</h2>
-<br>
-<p>The following graph shows the number of running
-<a href="about.html#relay">relays</a> by tor software version.
-Relays report their tor software version when they announce themselves in
-the network.
-More details on when these versions were declared stable or unstable can
-be found on the
-<a href="https://www.torproject.org/download/download.html">download page</a>
-and in the
-<a href="https://gitweb.torproject.org/tor.git/tree/ChangeLog">changes file</a>.</p>
-<img src="versions.png${versions_url}"
-     width="576" height="360" alt="Relay versions graph">
-<form action="versions.html">
-  <div class="formrow">
-    <input type="hidden" name="graph" value="versions">
-    <p>
-    <label>Start date (yyyy-mm-dd):
-      <input type="text" name="start" size="10"
-             value="<c:choose><c:when test="${fn:length(versions_start) == 0}">${default_start_date}</c:when><c:otherwise>${versions_start[0]}</c:otherwise></c:choose>"></label>
-    <label>End date (yyyy-mm-dd):
-      <input type="text" name="end" size="10"
-             value="<c:choose><c:when test="${fn:length(versions_end) == 0}">${default_end_date}</c:when><c:otherwise>${versions_end[0]}</c:otherwise></c:choose>"></label>
-    </p><p>
-    <input class="submit" type="submit" value="Update graph">
-    </p>
-  </div>
-</form>
-<p>Download graph as
-<a href="versions.pdf${versions_url}">PDF</a> or
-<a href="versions.svg${versions_url}">SVG</a>.</p>
-<br>
-
-<h4>Underlying data</h4>
-<ul>
-<li><a href="servers-data.html">Data: Number of relays and bridges</a></li>
-</ul>
-
-<h4>Related metrics</h4>
-<ul>
-<li><a href="networksize.html">Graph: Relays and bridges in the network</a></li>
-<li><a href="relayflags.html">Graph: Relays with Exit, Fast, Guard, Stable, and HSDir flags</a></li>
-<li><a href="platforms.html">Graph: Relays by platform</a></li>
-</ul>
-
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>



More information about the tor-commits mailing list