commit 7509b48d04733c8ab4e3163a871eb6459de1c956 Author: Karsten Loesing karsten.loesing@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>
tor-commits@lists.torproject.org