commit a364fca14475272be7b9572f900fe3fb8264521d Author: Karsten Loesing karsten.loesing@gmx.net Date: Mon Mar 16 16:46:10 2015 +0100
Turn home page into table of metrics. --- .../org/torproject/metrics/web/IndexServlet.java | 326 +++++++++++++++++++- website/web/WEB-INF/index.jsp | 133 ++++---- 2 files changed, 384 insertions(+), 75 deletions(-)
diff --git a/website/src/org/torproject/metrics/web/IndexServlet.java b/website/src/org/torproject/metrics/web/IndexServlet.java index c42b0e9..7b2c91c 100644 --- a/website/src/org/torproject/metrics/web/IndexServlet.java +++ b/website/src/org/torproject/metrics/web/IndexServlet.java @@ -1,24 +1,340 @@ -/* Copyright 2011, 2012 The Tor Project +/* Copyright 2011--2015 The Tor Project * See LICENSE for licensing information */ package org.torproject.metrics.web;
import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.Comparator; +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; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
+@SuppressWarnings("serial") public class IndexServlet extends HttpServlet {
- private static final long serialVersionUID = 7871368999788994664L; + private static final String[][] knownTags = new String[][] { + { "cl", "Clients" }, + { "rl", "Relays" }, + { "br", "Bridges" }, + { "pt", "Pluggable transports" }, + { "hs", "Hidden services" }, + { "bw", "Bandwidth" }, + { "pf", "Performance" }, + { "dv", "Diversity" } + }; + private static final String[] defaultTags = + new String[] { "cl", "rl", "br", "pt", "hs", "bw", "pf", "dv" }; + + private static final String[][] knownTypes = new String[][] { + { "gr", "Graph" }, + { "tb", "Table" }, + { "ln", "Link" }, + { "dt", "Data" } + }; + private static final String[] defaultTypes = + new String[] { "gr", "tb", "ln", "dt" }; + + private static final String[][] knownLevels = new String[][] { + { "bs", "Basic" }, + { "ad", "Advanced" } + }; + private static final String[] defaultLevels = new String[] { "bs" }; + + private static final String[][] knownOrders = new String[][] { + { "name", "Name" }, + { "tags", "Tags" }, + { "type", "Type" }, + { "level", "Level" }, + { "shuffle", "None (shuffle)" } + }; + 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", + 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-data.html", + "Hidden-service statistics", new String[] { "Hidden services" }, + "Data", "Advanced")); + }
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - - /* Forward the request to the JSP that does all the hard work. */ + @SuppressWarnings("rawtypes") + Map parameterMap = request.getParameterMap(); + BitSet requestedTags = this.parseParameter( + (String[]) parameterMap.get("tag"), knownTags, defaultTags); + BitSet requestedTypes = this.parseParameter( + (String[]) parameterMap.get("type"), knownTypes, defaultTypes); + BitSet requestedLevels = this.parseParameter( + (String[]) parameterMap.get("level"), knownLevels, defaultLevels); + BitSet requestedOrder = this.parseParameter( + (String[]) parameterMap.get("order"), knownOrders, defaultOrders); + request.setAttribute("tags", this.formatParameter(knownTags, + requestedTags)); + request.setAttribute("types", this.formatParameter(knownTypes, + requestedTypes)); + request.setAttribute("levels", this.formatParameter(knownLevels, + requestedLevels)); + request.setAttribute("order", this.formatParameter(knownOrders, + requestedOrder)); + List<Metric> filteredAndOrderedMetrics = this.filterMetrics( + requestedTags, requestedTypes, requestedLevels); + this.orderMetrics(filteredAndOrderedMetrics, requestedOrder); + request.setAttribute("results", this.formatMetrics( + filteredAndOrderedMetrics)); request.getRequestDispatcher("WEB-INF/index.jsp").forward(request, response); } -}
+ private BitSet parseParameter(String[] unparsedValues, + String[][] knownValues, String[] defaultValues) { + BitSet result = new BitSet(); + if (unparsedValues == null || unparsedValues.length == 0 || + unparsedValues.length > knownValues.length) { + unparsedValues = defaultValues; + } + Set<String> requestedValues = + new HashSet<String>(Arrays.asList(unparsedValues)); + for (int i = 0; i < knownValues.length; i++) { + if (requestedValues.contains(knownValues[i][0])) { + result.set(i); + } + } + return result; + } + + private String[][] formatParameter(String[][] strings, BitSet bitSet) { + String[][] formattedParameter = new String[strings.length][]; + for (int i = 0; i < formattedParameter.length; i++) { + String[] formatted = new String[] { strings[i][0], strings[i][1], + "" }; + if (bitSet.get(i)) { + formatted[2] = " checked"; + } + formattedParameter[i] = formatted; + } + return formattedParameter; + } + + private static class Metric { + private String url; + private String name; + private BitSet tags; + private BitSet type; + private BitSet level; + private Metric(String url, String name, String[] tagStrings, + String typeString, String levelString) { + this.url = url; + this.name = name; + this.tags = this.convertStringsToBitSet(knownTags, tagStrings); + this.type = this.convertStringToBitSet(knownTypes, typeString); + this.level = this.convertStringToBitSet(knownLevels, levelString); + } + private BitSet convertStringsToBitSet(String[][] knownKeysAndValues, + String[] givenKeyStrings) { + BitSet result = new BitSet(knownKeysAndValues.length); + Set<String> keys = new HashSet<String>(Arrays.asList( + givenKeyStrings)); + for (int i = 0; i < knownKeysAndValues.length; i++) { + if (keys.contains(knownKeysAndValues[i][1])) { + result.set(i); + } + } + if (result.cardinality() != givenKeyStrings.length) { + throw new RuntimeException("Unknown key(s): " + keys); + } + return result; + } + private BitSet convertStringToBitSet(String[][] knownKeysAndValues, + String givenKeyString) { + return this.convertStringsToBitSet(knownKeysAndValues, + new String[] { givenKeyString }); + } + private String[] toStrings() { + return new String[] { this.url, this.name, + this.convertBitSetToString(knownTags, this.tags), + this.convertBitSetToString(knownTypes, this.type), + this.convertBitSetToString(knownLevels, this.level) }; + } + private String convertBitSetToString(String[][] knownKeysAndValues, + BitSet bitSet) { + StringBuilder sb = new StringBuilder(); + int i = -1; + while ((i = bitSet.nextSetBit(i + 1)) >= 0) { + sb.append(", " + knownKeysAndValues[i][1]); + } + return sb.substring(Math.min(sb.length(), 2)); + } + } + + private List<Metric> filterMetrics(BitSet requestedTags, + BitSet requestedTypes, BitSet requestedLevels) { + List<Metric> filteredMetrics = new ArrayList<Metric>(); + for (Metric metric : availableMetrics) { + if (requestedTags.intersects(metric.tags) && + requestedTypes.intersects(metric.type) && + requestedLevels.intersects(metric.level)) { + filteredMetrics.add(metric); + } + } + return filteredMetrics; + } + + private void orderMetrics(List<Metric> resultMetrics, + BitSet requestedOrder) { + switch (requestedOrder.nextSetBit(0)) { + case 0: + Collections.sort(resultMetrics, new Comparator<Metric>() { + public int compare(Metric a, Metric b) { + return a.name.compareTo(b.name); + } + }); + break; + case 1: + Collections.sort(resultMetrics, new Comparator<Metric>() { + public int compare(Metric a, Metric b) { + return compareTwoBitSets(a.tags, b.tags); + } + }); + break; + case 2: + Collections.sort(resultMetrics, new Comparator<Metric>() { + public int compare(Metric a, Metric b) { + return compareTwoBitSets(a.type, b.type); + } + }); + break; + case 3: + Collections.sort(resultMetrics, new Comparator<Metric>() { + public int compare(Metric a, Metric b) { + return compareTwoBitSets(a.level, b.level); + } + }); + break; + default: + Collections.shuffle(resultMetrics); + break; + } + } + + private int compareTwoBitSets(BitSet a, BitSet b) { + if (a.equals(b)) { + return 0; + } + BitSet xor = (BitSet) a.clone(); + xor.xor(b); + return xor.length() == b.length() ? -1 : 1; + } + + private String[][] formatMetrics( + List<Metric> filteredAndOrderedMetrics) { + String[][] formattedMetrics = + new String[filteredAndOrderedMetrics.size()][]; + for (int i = 0; i < formattedMetrics.length; i++) { + formattedMetrics[i] = filteredAndOrderedMetrics.get(i).toStrings(); + } + return formattedMetrics; + } +} diff --git a/website/web/WEB-INF/index.jsp b/website/web/WEB-INF/index.jsp index 182935b..9ea7e00 100644 --- a/website/web/WEB-INF/index.jsp +++ b/website/web/WEB-INF/index.jsp @@ -1,3 +1,5 @@ +<%@ 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> @@ -21,77 +23,68 @@ <a href="https://www.torproject.org/about/contact.html.en">let us know</a>.</small></p>
- <h3>Servers</h3> - <p>How many relays and bridges are in the network? - How many of them permit exiting?</p> -<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> -<li><a href="servers-data.html">Data: Number of relays and bridges</a></li> -</ul> +<div> +<div style="border:1px solid gray;border-radius:10px;padding:10px;float:left;overflow:hidden;margin-right:20px;"> +<form action="/"> +<p> +<label for="tag"><b>Tags</b></label><br> +<c:forEach var="row" items="${tags}"> +<input name="tag" type="checkbox" value="${row[0]}" <c:if test="${fn:length(row[2]) > 0}"> checked</c:if>> ${row[1]}</br> +</c:forEach> +</p> +<p> +<label for="type"><b>Type</b></label></br> +<c:forEach var="row" items="${types}"> +<input name="type" type="checkbox" value="${row[0]}" <c:if test="${fn:length(row[2]) > 0}"> checked</c:if>> ${row[1]}</br> +</c:forEach> +</p> +<p> +<label for="level"><b>Level</b></label></br> +<c:forEach var="row" items="${levels}"> +<input name="level" type="checkbox" value="${row[0]}" <c:if test="${fn:length(row[2]) > 0}"> checked</c:if>> ${row[1]}</br> +</c:forEach> +</p> +<p> +<label for="sort"><b>Order</b></label></br> +<c:forEach var="row" items="${order}"> +<input name="order" type="radio" value="${row[0]}" <c:if test="${fn:length(row[2]) > 0}"> checked</c:if>> ${row[1]}</br> +</c:forEach> +</p> +<p> +<input type="reset" value="Reset"> +<input type="submit" value="Update"> +</p> +</form> +</div>
- <h3>Bandwidth</h3> - <p>How much bandwidth do relays advertise? - And how much of that is actually consumed?</p> - -<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="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> -<li><a href="bandwidth-data.html">Data: Bandwidth provided and consumed by relays</a></li> -<li><a href="advbwdist-data.html">Data: Advertised bandwidth distribution and n-th fastest relays</a></li> -</ul> - - <h3>Diversity</h3> - <p>How diverse is the network? - In which countries are relays located?</p> - -<ul> -<li><a href="bubbles.html">Graph: Network bubble graphs</a></li> -</ul> - - <h3>Users</h3> - <p>Where do users come from? - What transports and IP versions are they using?</p> - -<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> -<li><a href="clients-data.html">Data: Estimated number of clients in the Tor network</a></li> -</ul> - - <h3>Performance</h3> - <p>How long does it take to download a megabyte of data over Tor? - How about five?</p> - -<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> -<li><a href="connbidirect.html">Graph: Fraction of connections used uni-/bidirectionally</a></li> -<li><a href="torperf-data.html">Data: Performance of downloading static files over Tor</a></li> -<li><a href="connbidirect-data.html">Data: Fraction of connections used uni-/bidirectionally</a></li> -</ul> - - <h3>Hidden services</h3> - <p>How many hidden services are there in the network, and how much - traffic do they handle?</p> - -<ul> -<li><a href="hidserv-dir-onions-seen.html">Graph: Unique .onion addresses</a></li> -<li><a href="hidserv-data.html">Data: Hidden-service statistics</a></li> -</ul> +<div style="overflow:hidden;"> +<style> +table { + border-spacing: 10px; +} +</style> +<table> +<thead> +<tr> +<th>Name</th> +<th>Tags</th> +<th>Type</th> +<th>Level</th> +</tr> +</thead> +<tbody> +<c:forEach var="row" items="${results}"> +<tr> +<td><a href="${row[0]}">${row[1]}</a></td> +<td>${row[2]}</td> +<td>${row[3]}</td> +<td>${row[4]}</td> +</tr> +</c:forEach> +</tbody> +</table> +</div> +</div>
</div> </div>