[tor-commits] [metrics-web/master] Provide advertised bandwidth distribution stats.

karsten at torproject.org karsten at torproject.org
Fri Jan 31 14:34:59 UTC 2014


commit 7509b48d04733c8ab4e3163a871eb6459de1c956
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Fri Jan 31 13:39:48 2014 +0100

    Provide advertised bandwidth distribution stats.
    
    Implements #10460.
---
 modules/advbwdist/.gitignore                       |    4 +
 modules/advbwdist/aggregate.R                      |   16 +++
 modules/advbwdist/build.xml                        |   44 ++++++
 .../src/org/torproject/metrics/advbwdist/Main.java |  152 ++++++++++++++++++++
 shared/bin/60-run-advbwdist-stats.sh               |    6 +
 shared/bin/99-copy-stats-files.sh                  |    1 +
 website/etc/web.xml                                |    6 +
 website/rserve/graphs.R                            |   54 +++++++
 .../metrics/web/graphs/GraphParameterChecker.java  |   40 ++++++
 .../metrics/web/graphs/RObjectGenerator.java       |    3 +
 .../metrics/web/research/ResearchStatsServlet.java |    1 +
 website/web/WEB-INF/network.jsp                    |  104 ++++++++++++++
 website/web/WEB-INF/stats.jsp                      |   29 ++++
 13 files changed, 460 insertions(+)

diff --git a/modules/advbwdist/.gitignore b/modules/advbwdist/.gitignore
new file mode 100644
index 0000000..4bb76a5
--- /dev/null
+++ b/modules/advbwdist/.gitignore
@@ -0,0 +1,4 @@
+classes/
+stats/
+status/
+
diff --git a/modules/advbwdist/aggregate.R b/modules/advbwdist/aggregate.R
new file mode 100644
index 0000000..ee52a64
--- /dev/null
+++ b/modules/advbwdist/aggregate.R
@@ -0,0 +1,16 @@
+require(reshape)
+t <- read.csv("stats/advbwdist-validafter.csv", stringsAsFactors = FALSE)
+t <- t[t$valid_after < paste(Sys.Date() - 1, "23:59:59"), ]
+t <- aggregate(list(advbw = as.numeric(t$advbw)),
+  by = list(date = as.Date(cut.Date(as.Date(t$valid_after), "day")),
+  isexit = !is.na(t$isexit), relay = ifelse(is.na(t$relay), -1, t$relay),
+  percentile = ifelse(is.na(t$percentile), -1, t$percentile)),
+  FUN = median)
+t <- data.frame(date = t$date, isexit = ifelse(t$isexit, "t", ""),
+  relay = ifelse(t$relay < 0, NA, t$relay),
+  percentile = ifelse(t$percentile < 0, NA, t$percentile),
+  advbw = floor(t$advbw))
+t <- t[order(t$date, t$isexit, t$relay, t$percentile), ]
+write.csv(t, "stats/advbwdist.csv", quote = FALSE, row.names = FALSE,
+  na = "")
+
diff --git a/modules/advbwdist/build.xml b/modules/advbwdist/build.xml
new file mode 100644
index 0000000..16845ee
--- /dev/null
+++ b/modules/advbwdist/build.xml
@@ -0,0 +1,44 @@
+<project default="run" name="advbwdist" basedir=".">
+
+  <property name="sources" value="src"/>
+  <property name="classes" value="classes"/>
+  <path id="classpath">
+    <pathelement path="${classes}"/>
+    <fileset dir="/usr/share/java">
+      <include name="commons-codec.jar"/>
+      <include name="commons-compress.jar"/>
+      <include name="commons-lang.jar"/>
+    </fileset>
+    <fileset dir="../../deps/metrics-lib">
+      <include name="descriptor.jar"/>
+    </fileset>
+  </path>
+
+  <target name="metrics-lib">
+    <ant dir="../../deps/metrics-lib"/>
+  </target>
+
+  <target name="compile" depends="metrics-lib">
+    <mkdir dir="${classes}"/>
+    <javac destdir="${classes}"
+           srcdir="${sources}"
+           source="1.5"
+           target="1.5"
+           debug="true"
+           deprecation="true"
+           optimize="false"
+           failonerror="true"
+           includeantruntime="false">
+      <classpath refid="classpath"/>
+    </javac>
+  </target>
+
+  <target name="run" depends="compile">
+    <java fork="true"
+          maxmemory="1024m"
+          classname="org.torproject.metrics.advbwdist.Main">
+      <classpath refid="classpath"/>
+    </java>
+  </target>
+</project>
+
diff --git a/modules/advbwdist/src/org/torproject/metrics/advbwdist/Main.java b/modules/advbwdist/src/org/torproject/metrics/advbwdist/Main.java
new file mode 100644
index 0000000..09eaab0
--- /dev/null
+++ b/modules/advbwdist/src/org/torproject/metrics/advbwdist/Main.java
@@ -0,0 +1,152 @@
+package org.torproject.metrics.advbwdist;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.torproject.descriptor.Descriptor;
+import org.torproject.descriptor.DescriptorFile;
+import org.torproject.descriptor.DescriptorReader;
+import org.torproject.descriptor.DescriptorSourceFactory;
+import org.torproject.descriptor.NetworkStatusEntry;
+import org.torproject.descriptor.RelayNetworkStatusConsensus;
+import org.torproject.descriptor.ServerDescriptor;
+
+public class Main {
+  public static void main(String[] args) throws IOException {
+
+    /* Parse server descriptors, not keeping a parse history, and memorize
+     * the advertised bandwidth for every server descriptor. */
+    DescriptorReader descriptorReader =
+        DescriptorSourceFactory.createDescriptorReader();
+    descriptorReader.addDirectory(
+        new File("../../shared/in/relay-descriptors/server-descriptors"));
+    Iterator<DescriptorFile> descriptorFiles =
+        descriptorReader.readDescriptors();
+    Map<String, Long> serverDescriptors =
+        new HashMap<String, Long>();
+    while (descriptorFiles.hasNext()) {
+      DescriptorFile descriptorFile = descriptorFiles.next();
+      for (Descriptor descriptor : descriptorFile.getDescriptors()) {
+        if (!(descriptor instanceof ServerDescriptor)) {
+          continue;
+        }
+        ServerDescriptor serverDescriptor = (ServerDescriptor) descriptor;
+        String digest = serverDescriptor.getServerDescriptorDigest();
+        long advertisedBandwidth = Math.min(Math.min(
+            serverDescriptor.getBandwidthRate(),
+            serverDescriptor.getBandwidthBurst()),
+            serverDescriptor.getBandwidthObserved());
+        serverDescriptors.put(digest.toUpperCase(), advertisedBandwidth);
+      }
+    }
+
+    /* Parse consensuses, keeping a parse history. */
+    descriptorReader = DescriptorSourceFactory.createDescriptorReader();
+    descriptorReader.addDirectory(
+        new File("../../shared/in/relay-descriptors/consensuses"));
+    descriptorReader.setExcludeFiles(
+        new File("status/parsed-consensuses"));
+    descriptorFiles = descriptorReader.readDescriptors();
+    File resultsFile = new File("stats/advbwdist-validafter.csv");
+    resultsFile.getParentFile().mkdirs();
+    boolean writeHeader = !resultsFile.exists();
+    BufferedWriter bw = new BufferedWriter(new FileWriter(resultsFile,
+        true));
+    if (writeHeader) {
+      bw.write("valid_after,isexit,relay,percentile,advbw\n");
+    }
+    SimpleDateFormat dateTimeFormat = new SimpleDateFormat(
+        "yyyy-MM-dd HH:mm:ss");
+    dateTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+    while (descriptorFiles.hasNext()) {
+      DescriptorFile descriptorFile = descriptorFiles.next();
+      for (Descriptor descriptor : descriptorFile.getDescriptors()) {
+        if (!(descriptor instanceof RelayNetworkStatusConsensus)) {
+          continue;
+        }
+
+        /* Parse server descriptor digests from consensus and look up
+         * advertised bandwidths. */
+        RelayNetworkStatusConsensus consensus =
+            (RelayNetworkStatusConsensus) descriptor;
+        String validAfter = dateTimeFormat.format(
+            consensus.getValidAfterMillis());
+        List<Long> advertisedBandwidthsAllRelays = new ArrayList<Long>(),
+            advertisedBandwidthsExitsOnly = new ArrayList<Long>();
+        for (NetworkStatusEntry relay :
+            consensus.getStatusEntries().values()) {
+          if (!relay.getFlags().contains("Running")) {
+            continue;
+          }
+          String serverDescriptorDigest = relay.getDescriptor().
+              toUpperCase();
+          if (!serverDescriptors.containsKey(serverDescriptorDigest)) {
+            continue;
+          }
+          long advertisedBandwidth = serverDescriptors.get(
+              serverDescriptorDigest);
+          advertisedBandwidthsAllRelays.add(advertisedBandwidth);
+          if (relay.getFlags().contains("Exit") &&
+              !relay.getFlags().contains("BadExit")) {
+            advertisedBandwidthsExitsOnly.add(advertisedBandwidth);
+          }
+        }
+
+        /* Write advertised bandwidths of n-th fastest relays/exits. */
+        Collections.sort(advertisedBandwidthsAllRelays,
+            Collections.reverseOrder());
+        Collections.sort(advertisedBandwidthsExitsOnly,
+            Collections.reverseOrder());
+        int[] fastestRelays = new int[] { 1, 2, 3, 5, 10, 20, 30, 50, 100,
+            200, 300, 500, 1000, 2000, 3000, 5000 };
+        for (int fastestRelay : fastestRelays) {
+          if (advertisedBandwidthsAllRelays.size() >= fastestRelay) {
+            bw.write(String.format("%s,,%d,,%d%n", validAfter,
+                fastestRelay,
+                advertisedBandwidthsAllRelays.get(fastestRelay - 1)));
+          }
+        }
+        for (int fastestRelay : fastestRelays) {
+          if (advertisedBandwidthsExitsOnly.size() >= fastestRelay) {
+            bw.write(String.format("%s,TRUE,%d,,%d%n", validAfter,
+                fastestRelay,
+                advertisedBandwidthsExitsOnly.get(fastestRelay - 1)));
+          }
+        }
+
+        /* Write advertised bandwidth percentiles of relays/exits. */
+        Collections.sort(advertisedBandwidthsAllRelays);
+        Collections.sort(advertisedBandwidthsExitsOnly);
+        int[] percentiles = new int[] { 0, 1, 2, 3, 5, 9, 10, 20, 25, 30,
+            40, 50, 60, 70, 75, 80, 90, 91, 95, 97, 98, 99, 100 };
+        if (!advertisedBandwidthsAllRelays.isEmpty()) {
+          for (int percentile : percentiles) {
+            bw.write(String.format("%s,,,%d,%d%n", validAfter,
+                percentile, advertisedBandwidthsAllRelays.get(
+                ((advertisedBandwidthsAllRelays.size() - 1) *
+                percentile) / 100)));
+          }
+        }
+        if (!advertisedBandwidthsExitsOnly.isEmpty()) {
+          for (int percentile : percentiles) {
+            bw.write(String.format("%s,TRUE,,%d,%d%n", validAfter,
+                percentile, advertisedBandwidthsExitsOnly.get(
+                ((advertisedBandwidthsExitsOnly.size() - 1) *
+                percentile) / 100)));
+          }
+        }
+      }
+    }
+    bw.close();
+  }
+}
+
diff --git a/shared/bin/60-run-advbwdist-stats.sh b/shared/bin/60-run-advbwdist-stats.sh
new file mode 100755
index 0000000..a06e848
--- /dev/null
+++ b/shared/bin/60-run-advbwdist-stats.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+cd modules/advbwdist/
+ant | grep "\[java\]"
+R --slave -f aggregate.R
+cd ../../
+
diff --git a/shared/bin/99-copy-stats-files.sh b/shared/bin/99-copy-stats-files.sh
index 6dce205..2292da2 100755
--- a/shared/bin/99-copy-stats-files.sh
+++ b/shared/bin/99-copy-stats-files.sh
@@ -1,4 +1,5 @@
 #!/bin/sh
 mkdir -p shared/stats
 cp -a modules/legacy/stats/*.csv shared/stats/
+cp -a modules/advbwdist/stats/advbwdist.csv shared/stats/
 
diff --git a/website/etc/web.xml b/website/etc/web.xml
index fa446bc..1a0e372 100644
--- a/website/etc/web.xml
+++ b/website/etc/web.xml
@@ -228,6 +228,12 @@
     <url-pattern>/userstats-bridge-version.png</url-pattern>
     <url-pattern>/userstats-bridge-version.pdf</url-pattern>
     <url-pattern>/userstats-bridge-version.svg</url-pattern>
+    <url-pattern>/advbwdist-perc.png</url-pattern>
+    <url-pattern>/advbwdist-perc.pdf</url-pattern>
+    <url-pattern>/advbwdist-perc.svg</url-pattern>
+    <url-pattern>/advbwdist-relay.png</url-pattern>
+    <url-pattern>/advbwdist-relay.pdf</url-pattern>
+    <url-pattern>/advbwdist-relay.svg</url-pattern>
   </servlet-mapping>
 
   <servlet>
diff --git a/website/rserve/graphs.R b/website/rserve/graphs.R
index 614bcea..bdeebe0 100644
--- a/website/rserve/graphs.R
+++ b/website/rserve/graphs.R
@@ -927,3 +927,57 @@ plot_userstats_bridge_version <- function(start, end, version, path) {
   plot_userstats(start, end, 'bridge', 'version', version, 'off', path)
 }
 
+plot_advbwdist_perc <- function(start, end, p, path) {
+  end <- min(end, as.character(Sys.Date() - 2))
+  t <- read.csv(paste("/srv/metrics.torproject.org/web/shared/stats/",
+                "advbwdist.csv", sep = ""), stringsAsFactors = FALSE)
+  t <- t[t$date >= start & t$date <= end &
+         t$percentile %in% as.numeric(p), ]
+  t <- data.frame(date = t$date, advbw = t$advbw / 2^20,
+                  variable = ifelse(t$isexit != "t", "All relays",
+                                    "Exits only"),
+                  percentile = as.factor(t$percentile))
+  date_breaks <- date_breaks(
+    as.numeric(max(as.Date(t$date, "%Y-%m-%d")) -
+    min(as.Date(t$date, "%Y-%m-%d"))))
+  ggplot(t, aes(x = as.Date(date), y = advbw, colour = percentile)) +
+    facet_grid(variable ~ .) +
+    geom_line(size = 0.75) +
+    scale_x_date(name = paste("\nThe Tor Project - ",
+        "https://metrics.torproject.org/", sep = ""),
+        format = date_breaks$format, major = date_breaks$major,
+        minor = date_breaks$minor) +
+    scale_y_continuous(name = "Advertised bandwidth in MiB/s\n",
+        limits = c(0, max(t$advbw, na.rm = TRUE))) +
+    scale_colour_hue(name = "Percentile",
+        breaks = rev(levels(t$percentile))) +
+    opts(title = "Advertised bandwidth distribution\n")
+  ggsave(filename = path, width = 8, height = 5, dpi = 72)
+}
+
+plot_advbwdist_relay <- function(start, end, n, path) {
+  end <- min(end, as.character(Sys.Date() - 2))
+  t <- read.csv(paste("/srv/metrics.torproject.org/web/shared/stats/",
+                "advbwdist.csv", sep = ""), stringsAsFactors = FALSE)
+  t <- t[t$date >= start & t$date <= end & t$relay %in% as.numeric(n), ]
+  t <- data.frame(date = t$date, advbw = t$advbw / 2^20,
+                  variable = ifelse(t$isexit != "t", "All relays",
+                                    "Exits only"),
+                  relay = as.factor(t$relay))
+  date_breaks <- date_breaks(
+    as.numeric(max(as.Date(t$date, "%Y-%m-%d")) -
+    min(as.Date(t$date, "%Y-%m-%d"))))
+  ggplot(t, aes(x = as.Date(date), y = advbw, colour = relay)) +
+    facet_grid(variable ~ .) +
+    geom_line(size = 0.75) +
+    scale_x_date(name = paste("\nThe Tor Project - ",
+        "https://metrics.torproject.org/", sep = ""),
+        format = date_breaks$format, major = date_breaks$major,
+        minor = date_breaks$minor) +
+    scale_y_continuous(name = "Advertised bandwidth in MiB/s\n",
+        limits = c(0, max(t$advbw, na.rm = TRUE))) +
+    scale_colour_hue(name = "n", breaks = levels(t$relay)) +
+    opts(title = "Advertised bandwidth of n-th fastest relays\n")
+  ggsave(filename = path, width = 8, height = 5, dpi = 72)
+}
+
diff --git a/website/src/org/torproject/metrics/web/graphs/GraphParameterChecker.java b/website/src/org/torproject/metrics/web/graphs/GraphParameterChecker.java
index 098d908..3e04bea 100644
--- a/website/src/org/torproject/metrics/web/graphs/GraphParameterChecker.java
+++ b/website/src/org/torproject/metrics/web/graphs/GraphParameterChecker.java
@@ -61,6 +61,10 @@ public class GraphParameterChecker {
     this.knownParameterValues.put("transport",
         "obfs2,obfs3,websocket,<OR>,<??>");
     this.knownParameterValues.put("version", "v4,v6");
+    this.knownParameterValues.put("p", "100,99,98,97,95,91,90,80,75,70,"
+        + "60,50,40,30,25,20,10,9,5,3,2,1,0");
+    this.knownParameterValues.put("n", "1,2,3,5,10,20,30,50,100,200,300,"
+        + "500,1000,2000,3000,5000");
   }
 
   public void setAvailableGraphs(Map<String, String> availableGraphs) {
@@ -273,6 +277,42 @@ public class GraphParameterChecker {
       recognizedGraphParameters.put("version", versionParameters);
     }
 
+    /* Parse p if supported by the graph type. If no p's are passed, use
+     * "100" as default. */
+    if (supportedGraphParameters.contains("p")) {
+      String[] pParameters = (String[]) requestParameters.get("p");
+      if (pParameters != null) {
+        List<String> knownPs = Arrays.asList(
+            this.knownParameterValues.get("p").split(","));
+        for (String p : pParameters) {
+          if (p == null || p.length() == 0 || !knownPs.contains(p)) {
+            return null;
+          }
+        }
+      } else {
+        pParameters = new String[] { "100" };
+      }
+      recognizedGraphParameters.put("p", pParameters);
+    }
+
+    /* Parse n if supported by the graph type. If no n's are passed, use
+     * "1" as default. */
+    if (supportedGraphParameters.contains("n")) {
+      String[] nParameters = (String[]) requestParameters.get("n");
+      if (nParameters != null) {
+        List<String> knownNs = Arrays.asList(
+            this.knownParameterValues.get("n").split(","));
+        for (String n : nParameters) {
+          if (n == null || n.length() == 0 || !knownNs.contains(n)) {
+            return null;
+          }
+        }
+      } else {
+        nParameters = new String[] { "1" };
+      }
+      recognizedGraphParameters.put("n", nParameters);
+    }
+
     /* We now have a map with all required graph parameters. Return it. */
     return recognizedGraphParameters;
   }
diff --git a/website/src/org/torproject/metrics/web/graphs/RObjectGenerator.java b/website/src/org/torproject/metrics/web/graphs/RObjectGenerator.java
index 8b64ff7..314f38a 100644
--- a/website/src/org/torproject/metrics/web/graphs/RObjectGenerator.java
+++ b/website/src/org/torproject/metrics/web/graphs/RObjectGenerator.java
@@ -109,6 +109,9 @@ public class RObjectGenerator implements ServletContextListener {
         "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.availableGraphFileTypes = new HashSet<String>(Arrays.asList(
         "png,pdf,svg".split(",")));
     GraphParameterChecker.getInstance().setAvailableGraphs(
diff --git a/website/src/org/torproject/metrics/web/research/ResearchStatsServlet.java b/website/src/org/torproject/metrics/web/research/ResearchStatsServlet.java
index e9eaa38..d1f9cfb 100644
--- a/website/src/org/torproject/metrics/web/research/ResearchStatsServlet.java
+++ b/website/src/org/torproject/metrics/web/research/ResearchStatsServlet.java
@@ -34,6 +34,7 @@ public class ResearchStatsServlet extends HttpServlet {
     this.availableStatisticsFiles.add("clients");
     this.availableStatisticsFiles.add("torperf");
     this.availableStatisticsFiles.add("connbidirect");
+    this.availableStatisticsFiles.add("advbwdist");
   }
 
   public long getLastModified(HttpServletRequest request) {
diff --git a/website/web/WEB-INF/network.jsp b/website/web/WEB-INF/network.jsp
index e0b297a..66b878f 100644
--- a/website/web/WEB-INF/network.jsp
+++ b/website/web/WEB-INF/network.jsp
@@ -296,6 +296,110 @@ the number of written and read dir bytes by all relays.</p>
 <a href="dirbytes.svg${dirbytes_url}">SVG</a>.</p>
 <p><a href="stats/bandwidth.csv">CSV</a> file containing all data.</p>
 <br>
+
+<a name="advbwdist-perc"></a>
+<h3><a href="#advbwdist-perc" class="anchor">Advertised bandwidth
+distribution</a></h3>
+<br>
+<p>The following graph shows the distribution of advertised bandwidth in
+the network. In contrast to the graphs above, the following graph contains
+no sums of advertised bandwidths, but bandwidths of single relays.</p>
+<img src="advbwdist-perc.png${advbwdist_perc_url}"
+     width="576" height="360"
+     alt="Advertised bandwidth distribution graph">
+<form action="network.html#advbwdist-perc">
+  <div class="formrow">
+    <input type="hidden" name="graph" value="advbwdist-perc">
+    <p>
+    <label>Start date (yyyy-mm-dd):</label>
+      <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>End date (yyyy-mm-dd):</label>
+      <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>">
+    </p><p>
+      <label>Percentiles: </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)
+      <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
+      <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
+      <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
+      <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
+      <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
+      <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
+      <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
+      <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)
+      <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
+      <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
+      <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)
+      <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
+      <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
+      <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)
+      <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
+      <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
+      <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
+      <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
+      <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
+      <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
+      <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
+      <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)
+    </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>
+<p><a href="stats/advbwdist.csv">CSV</a> file containing all data.</p>
+<br>
+
+<a name="advbwdist-relay"></a>
+<h3><a href="#advbwdist-relay" class="anchor">Advertised bandwidth of
+n-th fastest relays</a></h3>
+<br>
+<p>The following graph shows the advertised bandwidth of the n-th fastest
+relays in the network.</p>
+<img src="advbwdist-relay.png${advbwdist_relay_url}"
+     width="576" height="360"
+     alt="Advertised bandwidth of n-th fastest relays graph">
+<form action="network.html#advbwdist-relay">
+  <div class="formrow">
+    <input type="hidden" name="graph" value="advbwdist-relay">
+    <p>
+    <label>Start date (yyyy-mm-dd):</label>
+      <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>End date (yyyy-mm-dd):</label>
+      <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>">
+    </p><p>
+      <label>n-th fastest relays: </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
+      <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
+      <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
+      <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
+      <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
+      <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
+      <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
+      <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
+      <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
+      <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
+      <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
+      <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
+      <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
+      <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
+      <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
+      <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
+    </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>
+<p><a href="stats/advbwdist.csv">CSV</a> file containing all data.</p>
+<br>
     </div>
   </div>
   <div class="bottom" id="bottom">
diff --git a/website/web/WEB-INF/stats.jsp b/website/web/WEB-INF/stats.jsp
index ccc2540..bdbe4d9 100644
--- a/website/web/WEB-INF/stats.jsp
+++ b/website/web/WEB-INF/stats.jsp
@@ -136,6 +136,35 @@ relays when serving directory data.</li>
 <hr>
 <br>
 
+<a name="advbwdist"></a>
+<h3><a href="#advbwdist" class="anchor">Advertised bandwidth distribution
+and n-th fastest relays</a></h3>
+<br>
+<p>Statistics file <a href="stats/advbwdist.csv">advbwdist.csv</a>
+contains statistics on the advertised bandwidth of relays in the network.
+These statistics include advertised bandwidth percentiles and advertised
+bandwidth values of the n-th fastest relays.
+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>
+<hr>
+<br>
+
 <a name="fast-exits"></a>
 <h3><a href="#fast-exits" class="anchor">Relays meeting or almost meeting
 fast-exit requirements</a></h3>



More information about the tor-commits mailing list