tor-commits
Threads by month
- ----- 2025 -----
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
November 2019
- 20 participants
- 2923 discussions
commit 0f30892d8e07f3fe4796b7fdc532664ed6fad403
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Wed Sep 26 17:21:00 2018 +0200
Bump version to 1.2.0-dev.
---
build.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/build.xml b/build.xml
index 759901e..e5a9794 100644
--- a/build.xml
+++ b/build.xml
@@ -8,7 +8,7 @@
<property name="javadoc-title" value="MetricsWeb API Documentation"/>
<property name="implementation-title" value="metrics-web" />
- <property name="release.version" value="1.2.0" />
+ <property name="release.version" value="1.2.0-dev" />
<property name="metricslibversion" value="2.4.0" />
<property name="exoneratorversion" value="4.0.0" />
<property name="jetty.version" value="-9.2.21.v20170120" />
1
0
commit 50a4315b5fef9bc79aee881b1709171d27a0f04f
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Mon Oct 8 16:26:37 2018 +0200
Update to latest metrics-lib.
---
src/submods/metrics-lib | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/submods/metrics-lib b/src/submods/metrics-lib
index 7c26323..603a439 160000
--- a/src/submods/metrics-lib
+++ b/src/submods/metrics-lib
@@ -1 +1 @@
-Subproject commit 7c26323811d733643f9042611c55cdaeec4e3cc4
+Subproject commit 603a439f802c6d4a8b29367ce13b345ae8cf02bc
1
0

[metrics-web/release] Extend Tor Browser graphs to include update pings.
by karsten@torproject.org 09 Nov '19
by karsten@torproject.org 09 Nov '19
09 Nov '19
commit 49d1198d553c627321cfaee41b142dc7f271dc01
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Thu Oct 11 12:24:52 2018 +0200
Extend Tor Browser graphs to include update pings.
Implements the first half of #27931 by announcing changes to CSV file
formats.
---
src/main/resources/web/jsps/stats.jsp | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/src/main/resources/web/jsps/stats.jsp b/src/main/resources/web/jsps/stats.jsp
index 3559994..bb038e4 100644
--- a/src/main/resources/web/jsps/stats.jsp
+++ b/src/main/resources/web/jsps/stats.jsp
@@ -47,6 +47,7 @@ https://metrics.torproject.org/identifier.csv
<li><b>July 31, 2018:</b> Announced pending changes to per-graph CSV files to become effective on August 15 and pre-aggregated CSV files to be removed by September 15.</li>
<li><b>August 15, 2018:</b> Made the first batch of changes to per-graph CSV files.</li>
<li><b>September 15, 2018:</b> Removed all pre-aggregated CSV files.</li>
+<li><b>October 28, 2018:</b> Add and/or remove columns to <a href="#webstats-tb-platform">Tor Browser downloads by platform</a> and <a href="#webstats-tb-locale">Tor Browser downloads by locale</a> graphs.</li>
</ul>
</div>
@@ -680,9 +681,12 @@ Applications <a href="#applications" name="applications" class="anchor">#</a></h
<ul>
<li><b>date:</b> UTC date (YYYY-MM-DD) when requests to <code>torproject.org</code> web servers have been logged.</li>
-<li><b>linux:</b> Number of Tor Browser initial downloads for Linux.</li>
-<li><b>macos:</b> Number of Tor Browser initial downloads for macOS.</li>
-<li><b>windows:</b> Number of Tor Browser initial downloads for Windows.</li>
+<li><b>linux:</b> Number of Tor Browser initial downloads for Linux. <span class="red">This column is going to be removed after October 28, 2018.</span></li>
+<li><b>macos:</b> Number of Tor Browser initial downloads for macOS. <span class="red">This column is going to be removed after October 28, 2018.</span></li>
+<li><b>windows:</b> Number of Tor Browser initial downloads for Windows. <span class="red">This column is going to be removed after October 28, 2018.</span></li>
+<li><b>platform:</b> Platform, like "Linux", "macOS", or "Windows". <span class="blue">This column is going to be added after October 28, 2018.</span></li>
+<li><b>initial_downloads:</b> Number of Tor Browser initial downloads. <span class="blue">This column is going to be added after October 28, 2018.</span></li>
+<li><b>update_pings:</b> Number of Tor Browser update pings. <span class="blue">This column is going to be added after October 28, 2018.</span></li>
</ul>
<h3>Tor Browser downloads by locale
@@ -703,6 +707,7 @@ Applications <a href="#applications" name="applications" class="anchor">#</a></h
<li><b>date:</b> UTC date (YYYY-MM-DD) when requests to <code>torproject.org</code> web servers have been logged.</li>
<li><b>locale:</b> Locale, like "en-US" for English (United States), "de" for German, etc., and "??" for unrecognized locales.</li>
<li><b>initial_downloads:</b> Number of Tor Browser initial downloads.</li>
+<li><b>update_pings:</b> Number of Tor Browser update pings. <span class="blue">This column is going to be added after October 28, 2018.</span></li>
</ul>
<h3>Tor Messenger downloads and updates
1
0

09 Nov '19
commit 285be4447deb0bd35e28dd9aab74567c1c6541b8
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Wed Oct 10 15:48:12 2018 +0200
Add graph with total cw across bwauths.
Implements #25459.
---
CHANGELOG.md | 7 +
build.xml | 11 +
src/main/R/rserver/graphs.R | 32 +++
.../metrics/stats/collectdescs/Main.java | 1 +
.../metrics/stats/totalcw/Configuration.java | 18 ++
.../torproject/metrics/stats/totalcw/Database.java | 184 ++++++++++++++++
.../org/torproject/metrics/stats/totalcw/Main.java | 79 +++++++
.../metrics/stats/totalcw/OutputLine.java | 41 ++++
.../torproject/metrics/stats/totalcw/Parser.java | 62 ++++++
.../totalcw/TotalcwRelayNetworkStatusVote.java | 50 +++++
.../torproject/metrics/stats/totalcw/Writer.java | 36 +++
src/main/resources/web.xml | 4 +
src/main/resources/web/json/categories.json | 1 +
src/main/resources/web/json/metrics.json | 11 +
.../resources/web/jsps/reproducible-metrics.jsp | 36 ++-
src/main/resources/web/jsps/stats.jsp | 20 ++
src/main/sql/totalcw/init-totalcw.sql | 74 +++++++
.../totalcw/TotalcwRelayNetworkStatusVoteTest.java | 126 +++++++++++
...E-55A38ED50848BE1F13C6A35C3CA637B0D962C2EF.part | 239 ++++++++++++++++++++
...1-049AB3179B12DACC391F06A10C2A8904E4339D33.part | 239 ++++++++++++++++++++
...6-2669AD153408F88E416CE6206D1A75EC3324A2F4.part | 241 +++++++++++++++++++++
...7-38C6A19F78948B689345EE41D7119D76246C4D3E.part | 239 ++++++++++++++++++++
22 files changed, 1747 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7890d2d..f604ff9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+# Changes in version 1.3.0 - 2018-??-??
+
+ * Medium changes
+ - Start downloading and processing votes.
+ - Add Apache Commons Math 3.6.1 as dependency.
+
+
# Changes in version 1.2.0 - 2018-08-25
* Medium changes
diff --git a/build.xml b/build.xml
index e5a9794..3614d78 100644
--- a/build.xml
+++ b/build.xml
@@ -45,6 +45,7 @@
<patternset id="common" >
<include name="commons-codec-1.10.jar"/>
<include name="commons-lang3-3.5.jar"/>
+ <include name="commons-math3-3.6.1.jar"/>
<include name="jackson-annotations-2.8.6.jar"/>
<include name="jackson-core-2.8.6.jar"/>
<include name="jackson-databind-2.8.6.jar"/>
@@ -319,6 +320,7 @@
<antcall target="clients" />
<antcall target="ipv6servers" />
<antcall target="webstats" />
+ <antcall target="totalcw" />
<antcall target="make-data-available" />
</target>
@@ -470,6 +472,14 @@
</antcall>
</target>
+ <target name="totalcw" >
+ <property name="module.name" value="totalcw" />
+ <antcall target="run-java" >
+ <param name="module.main"
+ value="org.torproject.metrics.stats.totalcw.Main" />
+ </antcall>
+ </target>
+
<!--
The run-rserver target documents a working option of
configuring an R server for running metrics-web.
@@ -506,6 +516,7 @@
includes="clients*.csv userstats-combined.csv" />
<fileset dir="${modulebase}/ipv6servers/stats" includes="ipv6servers.csv" />
<fileset dir="${modulebase}/webstats/stats" includes="webstats.csv" />
+ <fileset dir="${modulebase}/totalcw/stats" includes="totalcw.csv" />
</copy>
<copy todir="${rdatadir}" >
<fileset dir="${modulebase}/clients/RData" includes="*.RData" />
diff --git a/src/main/R/rserver/graphs.R b/src/main/R/rserver/graphs.R
index 66a1414..12915fa 100644
--- a/src/main/R/rserver/graphs.R
+++ b/src/main/R/rserver/graphs.R
@@ -1572,3 +1572,35 @@ write_advbw_ipv6 <- function(start_p = NULL, end_p = NULL, path_p) {
write.csv(path_p, quote = FALSE, row.names = FALSE, na = "")
}
+prepare_totalcw <- function(start_p, end_p) {
+ read.csv(paste(stats_dir, "totalcw.csv", sep = ""),
+ colClasses = c("valid_after_date" = "Date")) %>%
+ filter(if (!is.null(start_p))
+ valid_after_date >= as.Date(start_p) else TRUE) %>%
+ filter(if (!is.null(end_p))
+ valid_after_date <= as.Date(end_p) else TRUE)
+}
+
+plot_totalcw <- function(start_p, end_p, path_p) {
+ prepare_totalcw(start_p, end_p) %>%
+ complete(valid_after_date = full_seq(valid_after_date, period = 1),
+ nesting(nickname)) %>%
+ ggplot(aes(x = valid_after_date, y = measured_sum_avg,
+ colour = nickname)) +
+ geom_line(na.rm = TRUE) +
+ scale_x_date(name = "", breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
+ scale_y_continuous(name = "", labels = formatter, limits = c(0, NA)) +
+ scale_colour_hue(name = "") +
+ ggtitle("Total consensus weights across bandwidth authorities") +
+ labs(caption = copyright_notice)
+ ggsave(filename = path_p, width = 8, height = 5, dpi = 150)
+}
+
+write_totalcw <- function(start_p = NULL, end_p = NULL, path_p) {
+ prepare_totalcw(start_p, end_p) %>%
+ rename(date = valid_after_date, totalcw = measured_sum_avg) %>%
+ arrange(date, nickname) %>%
+ write.csv(path_p, quote = FALSE, row.names = FALSE, na = "")
+}
+
diff --git a/src/main/java/org/torproject/metrics/stats/collectdescs/Main.java b/src/main/java/org/torproject/metrics/stats/collectdescs/Main.java
index 4c64425..a9620f5 100644
--- a/src/main/java/org/torproject/metrics/stats/collectdescs/Main.java
+++ b/src/main/java/org/torproject/metrics/stats/collectdescs/Main.java
@@ -24,6 +24,7 @@ public class Main {
"/recent/relay-descriptors/consensuses/",
"/recent/relay-descriptors/extra-infos/",
"/recent/relay-descriptors/server-descriptors/",
+ "/recent/relay-descriptors/votes/",
"/recent/torperf/",
"/recent/webstats/"
}, 0L, new File("../../shared/in"), true);
diff --git a/src/main/java/org/torproject/metrics/stats/totalcw/Configuration.java b/src/main/java/org/torproject/metrics/stats/totalcw/Configuration.java
new file mode 100644
index 0000000..a424f84
--- /dev/null
+++ b/src/main/java/org/torproject/metrics/stats/totalcw/Configuration.java
@@ -0,0 +1,18 @@
+/* Copyright 2018 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.stats.totalcw;
+
+/** Configuration options parsed from Java properties with reasonable hard-coded
+ * defaults. */
+class Configuration {
+ static String descriptors = System.getProperty("totalcw.descriptors",
+ "../../shared/in/");
+ static String database = System.getProperty("totalcw.database",
+ "jdbc:postgresql:totalcw");
+ static String history = System.getProperty("totalcw.history",
+ "status/read-descriptors");
+ static String output = System.getProperty("totalcw.output",
+ "stats/totalcw.csv");
+}
+
diff --git a/src/main/java/org/torproject/metrics/stats/totalcw/Database.java b/src/main/java/org/torproject/metrics/stats/totalcw/Database.java
new file mode 100644
index 0000000..be4cad3
--- /dev/null
+++ b/src/main/java/org/torproject/metrics/stats/totalcw/Database.java
@@ -0,0 +1,184 @@
+/* Copyright 2018 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.stats.totalcw;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Timestamp;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/** Database wrapper to connect to the database, insert data, run the stored
+ * procedure for aggregating data, and query aggregated data as output. */
+class Database implements AutoCloseable {
+
+ /** Database connection string. */
+ private String jdbcString;
+
+ /** Connection object for all interactions with the database. */
+ private Connection connection;
+
+ /** Prepared statement for finding out whether a given authority is already
+ * contained in the authority table. */
+ private PreparedStatement psAuthoritySelect;
+
+ /** Prepared statement for inserting an authority into the authority table. */
+ private PreparedStatement psAuthorityInsert;
+
+ /** Prepared statement for checking whether a vote has been inserted into the
+ * vote table before. */
+ private PreparedStatement psVoteSelect;
+
+ /** Prepared statement for inserting a vote into the vote table. */
+ private PreparedStatement psVoteInsert;
+
+ /** Create a new Database instance and prepare for inserting or querying
+ * data. */
+ Database(String jdbcString) throws SQLException {
+ this.jdbcString = jdbcString;
+ this.connect();
+ this.prepareStatements();
+ }
+
+ private void connect() throws SQLException {
+ this.connection = DriverManager.getConnection(this.jdbcString);
+ this.connection.setAutoCommit(false);
+ }
+
+ private void prepareStatements() throws SQLException {
+ this.psAuthoritySelect = this.connection.prepareStatement(
+ "SELECT authority_id FROM authority "
+ + "WHERE nickname = ? AND identity_hex = ?");
+ this.psAuthorityInsert = this.connection.prepareStatement(
+ "INSERT INTO authority (nickname, identity_hex) VALUES (?, ?)",
+ Statement.RETURN_GENERATED_KEYS);
+ this.psVoteSelect = this.connection.prepareStatement(
+ "SELECT EXISTS (SELECT 1 FROM vote "
+ + "WHERE valid_after = ? AND authority_id = ?)");
+ this.psVoteInsert = this.connection.prepareStatement(
+ "INSERT INTO vote (valid_after, authority_id, measured_count, "
+ + "measured_sum, measured_mean, measured_min, measured_q1, "
+ + "measured_median, measured_q3, measured_max) "
+ + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ Statement.RETURN_GENERATED_KEYS);
+ }
+
+ /** Insert a parsed vote into the vote table. */
+ void insertVote(TotalcwRelayNetworkStatusVote vote) throws SQLException {
+ if (null == vote) {
+ /* Nothing to insert. */
+ return;
+ }
+ int authorityId = -1;
+ this.psAuthoritySelect.clearParameters();
+ this.psAuthoritySelect.setString(1, vote.nickname);
+ this.psAuthoritySelect.setString(2, vote.identityHex);
+ try (ResultSet rs = this.psAuthoritySelect.executeQuery()) {
+ if (rs.next()) {
+ authorityId = rs.getInt(1);
+ }
+ }
+ if (authorityId < 0) {
+ this.psAuthorityInsert.clearParameters();
+ this.psAuthorityInsert.setString(1, vote.nickname);
+ this.psAuthorityInsert.setString(2, vote.identityHex);
+ this.psAuthorityInsert.execute();
+ try (ResultSet rs = this.psAuthorityInsert.getGeneratedKeys()) {
+ if (rs.next()) {
+ authorityId = rs.getInt(1);
+ }
+ }
+ if (authorityId < 0) {
+ throw new SQLException("Could not retrieve auto-generated key for new "
+ + "authority entry.");
+ }
+ }
+ Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"),
+ Locale.US);
+ this.psVoteSelect.clearParameters();
+ this.psVoteSelect.setTimestamp(1,
+ Timestamp.from(ZonedDateTime.of(vote.validAfter,
+ ZoneId.of("UTC")).toInstant()), calendar);
+ this.psVoteSelect.setInt(2, authorityId);
+ try (ResultSet rs = this.psVoteSelect.executeQuery()) {
+ if (rs.next()) {
+ if (rs.getBoolean(1)) {
+ /* Vote is already contained. */
+ return;
+ }
+ }
+ }
+ int voteId = -1;
+ this.psVoteInsert.clearParameters();
+ this.psVoteInsert.setTimestamp(1,
+ Timestamp.from(ZonedDateTime.of(vote.validAfter,
+ ZoneId.of("UTC")).toInstant()), calendar);
+ this.psVoteInsert.setInt(2, authorityId);
+ this.psVoteInsert.setLong(3, vote.measuredCount);
+ this.psVoteInsert.setLong(4, vote.measuredSum);
+ this.psVoteInsert.setLong(5, vote.measuredMean);
+ this.psVoteInsert.setLong(6, vote.measuredMin);
+ this.psVoteInsert.setLong(7, vote.measuredQ1);
+ this.psVoteInsert.setLong(8, vote.measuredMedian);
+ this.psVoteInsert.setLong(9, vote.measuredQ3);
+ this.psVoteInsert.setLong(10, vote.measuredMax);
+ this.psVoteInsert.execute();
+ try (ResultSet rs = this.psVoteInsert.getGeneratedKeys()) {
+ if (rs.next()) {
+ voteId = rs.getInt(1);
+ }
+ }
+ if (voteId < 0) {
+ throw new SQLException("Could not retrieve auto-generated key for new "
+ + "vote entry.");
+ }
+ }
+
+ /** Roll back any changes made in this execution. */
+ void rollback() throws SQLException {
+ this.connection.rollback();
+ }
+
+ /** Commit all changes made in this execution. */
+ void commit() throws SQLException {
+ this.connection.commit();
+ }
+
+ /** Query the totalcw view to obtain aggregated statistics. */
+ Iterable<OutputLine> queryTotalcw() throws SQLException {
+ List<OutputLine> statistics = new ArrayList<>();
+ Statement st = this.connection.createStatement();
+ Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"),
+ Locale.US);
+ String queryString = "SELECT " + OutputLine.columnHeadersDelimitedBy(", ")
+ + " FROM totalcw";
+ try (ResultSet rs = st.executeQuery(queryString)) {
+ while (rs.next()) {
+ OutputLine outputLine = new OutputLine();
+ outputLine.validAfterDate = rs.getDate(
+ OutputLine.Column.VALID_AFTER_DATE.name(), calendar).toLocalDate();
+ outputLine.nickname = rs.getString(OutputLine.Column.NICKNAME.name());
+ outputLine.measuredSumAvg = rs.getLong(
+ OutputLine.Column.MEASURED_SUM_AVG.name());
+ statistics.add(outputLine);
+ }
+ }
+ return statistics;
+ }
+
+ /** Release database connection. */
+ public void close() throws SQLException {
+ this.connection.close();
+ }
+}
+
diff --git a/src/main/java/org/torproject/metrics/stats/totalcw/Main.java b/src/main/java/org/torproject/metrics/stats/totalcw/Main.java
new file mode 100644
index 0000000..7c77160
--- /dev/null
+++ b/src/main/java/org/torproject/metrics/stats/totalcw/Main.java
@@ -0,0 +1,79 @@
+/* Copyright 2018 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.stats.totalcw;
+
+import org.torproject.descriptor.Descriptor;
+import org.torproject.descriptor.DescriptorReader;
+import org.torproject.descriptor.DescriptorSourceFactory;
+import org.torproject.descriptor.RelayNetworkStatusVote;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.nio.file.Paths;
+import java.sql.SQLException;
+import java.util.Arrays;
+
+/** Main class of the totalcw module that imports bandwidth measurement
+ * statistics from votes into a database and exports aggregate statistics to a
+ * CSV file. */
+public class Main {
+
+ private static Logger log = LoggerFactory.getLogger(Main.class);
+
+ private static String[][] paths = {
+ {"recent", "relay-descriptors", "votes"},
+ {"archive", "relay-descriptors", "votes"}};
+
+ /** Run the module. */
+ public static void main(String[] args) throws Exception {
+
+ log.info("Starting totalcw module.");
+
+ log.info("Reading votes and inserting relevant parts into the database.");
+ DescriptorReader reader = DescriptorSourceFactory.createDescriptorReader();
+ File historyFile = new File(Configuration.history);
+ reader.setHistoryFile(historyFile);
+ Parser parser = new Parser();
+ try (Database database = new Database(Configuration.database)) {
+ try {
+ for (Descriptor descriptor : reader.readDescriptors(
+ Arrays.stream(paths).map((String[] path)
+ -> Paths.get(Configuration.descriptors, path).toFile())
+ .toArray(File[]::new))) {
+ if (descriptor instanceof RelayNetworkStatusVote) {
+ database.insertVote(parser.parseRelayNetworkStatusVote(
+ (RelayNetworkStatusVote) descriptor));
+ } else {
+ log.debug("Skipping unknown descriptor of type {}.",
+ descriptor.getClass());
+ }
+ }
+
+ log.info("Committing all updated parts in the database.");
+ database.commit();
+ } catch (SQLException sqle) {
+ log.error("Cannot recover from SQL exception while inserting data. "
+ + "Rolling back and exiting.", sqle);
+ database.rollback();
+ return;
+ }
+ reader.saveHistoryFile(historyFile);
+
+ log.info("Querying aggregated statistics from the database.");
+ Iterable<OutputLine> output = database.queryTotalcw();
+ log.info("Writing aggregated statistics to {}.", Configuration.output);
+ if (null != output) {
+ new Writer().write(Paths.get(Configuration.output), output);
+ }
+
+ log.info("Terminating totalcw module.");
+ } catch (SQLException sqle) {
+ log.error("Cannot recover from SQL exception while querying. Not writing "
+ + "output file.", sqle);
+ }
+ }
+}
+
diff --git a/src/main/java/org/torproject/metrics/stats/totalcw/OutputLine.java b/src/main/java/org/torproject/metrics/stats/totalcw/OutputLine.java
new file mode 100644
index 0000000..450dbac
--- /dev/null
+++ b/src/main/java/org/torproject/metrics/stats/totalcw/OutputLine.java
@@ -0,0 +1,41 @@
+/* Copyright 2018 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.stats.totalcw;
+
+import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+/** Data object holding all parts of an output line. */
+class OutputLine {
+
+ /** Column names used in the database and in the first line of the output
+ * file. */
+ enum Column {
+ VALID_AFTER_DATE, NICKNAME, MEASURED_SUM_AVG
+ }
+
+ /** Column headers joined together with the given delimiter. */
+ static String columnHeadersDelimitedBy(String delimiter) {
+ return Arrays.stream(Column.values()).map(c -> c.toString().toLowerCase())
+ .collect(Collectors.joining(delimiter));
+ }
+
+ /** Date. */
+ LocalDate validAfterDate;
+
+ /** Server type, which can be "relay" or "bridge". */
+ String nickname;
+
+ /** Mean value of total measured bandwidths of all relays over the day. */
+ Long measuredSumAvg;
+
+ /** Format all fields in a single output line for inclusion in a CSV
+ * file. */
+ @Override
+ public String toString() {
+ return String.format("%s,%s,%d", validAfterDate, nickname, measuredSumAvg);
+ }
+}
+
diff --git a/src/main/java/org/torproject/metrics/stats/totalcw/Parser.java b/src/main/java/org/torproject/metrics/stats/totalcw/Parser.java
new file mode 100644
index 0000000..155b6b0
--- /dev/null
+++ b/src/main/java/org/torproject/metrics/stats/totalcw/Parser.java
@@ -0,0 +1,62 @@
+/* Copyright 2018 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.stats.totalcw;
+
+import org.apache.commons.math3.stat.descriptive.rank.Percentile;
+import org.torproject.descriptor.NetworkStatusEntry;
+import org.torproject.descriptor.RelayNetworkStatusVote;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** Parser that extracts bandwidth measurement statistics from votes and creates
+ * data objects for them. */
+class Parser {
+
+ /** Parse and return a vote, but return <code>null</code> if the vote did not
+ * contain any bandwidth measurements. */
+ TotalcwRelayNetworkStatusVote parseRelayNetworkStatusVote(
+ RelayNetworkStatusVote vote) {
+ List<Long> measuredBandwidths = new ArrayList<>();
+ for (NetworkStatusEntry entry : vote.getStatusEntries().values()) {
+ if (entry.getMeasured() >= 0L) {
+ measuredBandwidths.add(entry.getMeasured());
+ }
+ }
+ if (measuredBandwidths.isEmpty()) {
+ /* Return null, because we wouldn't want to add this vote to the database
+ * anyway. */
+ return null;
+ }
+ TotalcwRelayNetworkStatusVote parsedVote
+ = new TotalcwRelayNetworkStatusVote();
+ parsedVote.validAfter = Instant.ofEpochMilli(vote.getValidAfterMillis())
+ .atZone(ZoneId.of("UTC")).toLocalDateTime();
+ parsedVote.identityHex = vote.getIdentity();
+ parsedVote.nickname = vote.getNickname();
+ Collections.sort(measuredBandwidths);
+ long totalValue = 0L;
+ double[] values = new double[measuredBandwidths.size()];
+ for (int i = 0; i < measuredBandwidths.size(); i++) {
+ values[i] = (double) measuredBandwidths.get(i);
+ totalValue += measuredBandwidths.get(i);
+ }
+ parsedVote.measuredCount = values.length;
+ parsedVote.measuredSum = totalValue;
+ parsedVote.measuredMean = totalValue / values.length;
+ parsedVote.measuredMin = (long) Math.floor(values[0]);
+ parsedVote.measuredMax = (long) Math.floor(values[values.length - 1]);
+ Percentile percentile = new Percentile().withEstimationType(
+ Percentile.EstimationType.R_7);
+ percentile.setData(values);
+ parsedVote.measuredQ1 = (long) Math.floor(percentile.evaluate(25.0));
+ parsedVote.measuredMedian = (long) Math.floor(percentile.evaluate(50.0));
+ parsedVote.measuredQ3 = (long) Math.floor(percentile.evaluate(75.0));
+ return parsedVote;
+ }
+}
+
diff --git a/src/main/java/org/torproject/metrics/stats/totalcw/TotalcwRelayNetworkStatusVote.java b/src/main/java/org/torproject/metrics/stats/totalcw/TotalcwRelayNetworkStatusVote.java
new file mode 100644
index 0000000..c139cdc
--- /dev/null
+++ b/src/main/java/org/torproject/metrics/stats/totalcw/TotalcwRelayNetworkStatusVote.java
@@ -0,0 +1,50 @@
+/* Copyright 2018 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.stats.totalcw;
+
+import java.time.LocalDateTime;
+
+/** Data object holding all relevant parts parsed from a vote. */
+class TotalcwRelayNetworkStatusVote {
+
+ /** Valid-after time of the vote. */
+ LocalDateTime validAfter;
+
+ /** The 1 to 19 character long alphanumeric nickname assigned to the authority
+ * by its operator. */
+ String nickname;
+
+ /** Uppercase hex fingerprint of the authority's (v3 authority) identity
+ * key. */
+ String identityHex;
+
+ /** Count of status entries containing bandwidth measurements. */
+ long measuredCount;
+
+ /** Sum of bandwidth measurements of all contained status entries. */
+ long measuredSum;
+
+ /** Mean value of bandwidth measurements of all contained status entries. */
+ long measuredMean;
+
+ /** Minimum value of bandwidth measurements of all contained status
+ * entries. */
+ long measuredMin;
+
+ /** First quartile value of bandwidth measurements of all contained status
+ * entries. */
+ long measuredQ1;
+
+ /** Median value of bandwidth measurements of all contained status entries. */
+ long measuredMedian;
+
+ /** Third quartile value of bandwidth measurements of all contained status
+ * entries. */
+ long measuredQ3;
+
+ /** Maximum value of bandwidth measurements of all contained status
+ * entries. */
+ long measuredMax;
+}
+
diff --git a/src/main/java/org/torproject/metrics/stats/totalcw/Writer.java b/src/main/java/org/torproject/metrics/stats/totalcw/Writer.java
new file mode 100644
index 0000000..6688eae
--- /dev/null
+++ b/src/main/java/org/torproject/metrics/stats/totalcw/Writer.java
@@ -0,0 +1,36 @@
+/* Copyright 2018 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.stats.totalcw;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Writer that takes output line objects and writes them to a file, preceded
+ * by a column header line. */
+class Writer {
+
+ /** Write output lines to the given file. */
+ void write(Path filePath, Iterable<OutputLine> outputLines)
+ throws IOException {
+ File parentFile = filePath.toFile().getParentFile();
+ if (null != parentFile && !parentFile.exists()) {
+ if (!parentFile.mkdirs()) {
+ throw new IOException("Unable to create parent directory of output "
+ + "file. Not writing this file.");
+ }
+ }
+ List<String> formattedOutputLines = new ArrayList<>();
+ formattedOutputLines.add(OutputLine.columnHeadersDelimitedBy(","));
+ for (OutputLine line : outputLines) {
+ formattedOutputLines.add(line.toString());
+ }
+ Files.write(filePath, formattedOutputLines, StandardCharsets.UTF_8);
+ }
+}
+
diff --git a/src/main/resources/web.xml b/src/main/resources/web.xml
index 0c79916..6ff82ad 100644
--- a/src/main/resources/web.xml
+++ b/src/main/resources/web.xml
@@ -55,6 +55,7 @@
<url-pattern>/relays-ipv6.html</url-pattern>
<url-pattern>/bridges-ipv6.html</url-pattern>
<url-pattern>/advbw-ipv6.html</url-pattern>
+ <url-pattern>/totalcw.html</url-pattern>
</servlet-mapping>
<servlet>
@@ -193,6 +194,9 @@
<url-pattern>/advbw-ipv6.png</url-pattern>
<url-pattern>/advbw-ipv6.pdf</url-pattern>
<url-pattern>/advbw-ipv6.csv</url-pattern>
+ <url-pattern>/totalcw.png</url-pattern>
+ <url-pattern>/totalcw.pdf</url-pattern>
+ <url-pattern>/totalcw.csv</url-pattern>
</servlet-mapping>
<servlet>
diff --git a/src/main/resources/web/json/categories.json b/src/main/resources/web/json/categories.json
index af05085..73d2f01 100644
--- a/src/main/resources/web/json/categories.json
+++ b/src/main/resources/web/json/categories.json
@@ -30,6 +30,7 @@
"platforms",
"relays-ipv6",
"bridges-ipv6",
+ "totalcw",
"uptimes",
"networkchurn",
"bubbles"
diff --git a/src/main/resources/web/json/metrics.json b/src/main/resources/web/json/metrics.json
index ab5d98d..ed74959 100644
--- a/src/main/resources/web/json/metrics.json
+++ b/src/main/resources/web/json/metrics.json
@@ -427,5 +427,16 @@
"start",
"end"
]
+ },
+ {
+ "id": "totalcw",
+ "title": "Total consensus weights across bandwidth authorities",
+ "type": "Graph",
+ "description": "<p>This graph shows total consensus weights across bandwidth authorities. This graph may be useful for comparing bandwidth authority implementations by comparing their total bandwidth weights.</p>",
+ "function": "totalcw",
+ "parameters": [
+ "start",
+ "end"
+ ]
}
]
diff --git a/src/main/resources/web/jsps/reproducible-metrics.jsp b/src/main/resources/web/jsps/reproducible-metrics.jsp
index c590142..4bed5cc 100644
--- a/src/main/resources/web/jsps/reproducible-metrics.jsp
+++ b/src/main/resources/web/jsps/reproducible-metrics.jsp
@@ -242,11 +242,9 @@ We therefore refer to Step 4 of the <a href="#relay-users">Relay users</a> descr
<h2><i class="fa fa-server fa-fw" aria-hidden="true"></i>
Servers <a href="#servers" name="servers" class="anchor">#</a></h2>
-<p>The following description applies to the following graphs and table:</p>
-
<p>Statistics on the number of servers—<a href="/glossary.html#relay">relays</a> and <a href="/glossary.html#bridge">bridges</a>—were among the first to appear on Tor Metrics.
-These statistics have one thing in common: they use the number of running servers as their metric.
-Possible alternatives would be to use <a href="/glossary.html#consensus-weight">consensus weight</a> fractions or guard/middle/exit probabilities as metrics, but we're not doing that yet.
+Most of these statistics have one thing in common: they use the number of running servers as their metric.
+Possible alternatives are to use <a href="/glossary.html#consensus-weight">consensus weight</a> totals/fractions or guard/middle/exit probabilities as metrics, but we only recently started doing that.
In the following, we describe how exactly we count servers.</p>
<h3 id="running-relays" class="hover">Running relays
@@ -394,6 +392,36 @@ However, a small number of missing server descriptors per status is acceptable a
For the <a href="/bridges-ipv6.html">Bridges by IP version</a> graph we further skip days for which fewer than 12 statuses are known.
The goal is to avoid over-representing a few statuses during periods when the bridge directory authority had trouble producing a status for at least half of the day.</p>
+<h3 id="consensus-weight" class="hover">Consensus weight
+<a href="#consensus-weight" class="anchor">#</a>
+</h3>
+
+<p>The following statistic uses measured bandwidth, also known as <a href="/glossary.html#consensus-weight">consensus weight</a>, as metric for relay statistics, rather than absolute relay counts.</p>
+
+<p>The following description applies to the following graph:</p>
+
+<ul>
+<li>Total consensus weights across bandwidth authorities <a href="/totalcw.html" class="btn btn-primary btn-xs"><i class="fa fa-chevron-right" aria-hidden="true"></i> graph</a></li>
+</ul>
+
+<h4>Step 1: Parse votes.</h4>
+
+<p>Obtain votes from <a href="/collector.html#type-network-status-vote-3">CollecTor</a>.
+Refer to the <a href="https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt">Tor directory protocol, version 3</a> for details on the descriptor format.</p>
+
+<p>Parse and memorize the <code>"valid-after"</code> time from the vote header. We use this UTC timestamp to aggregate by the UTC date.</p>
+
+<p>Also parse the <code>"nickname"</code> and <code>"identity"</code> fields from the <code>"dir-source"</code> line. We use the identity to aggregate by authority and the nickname for display purposes.</p>
+
+<p>Parse the (optional) <code>"w"</code> lines of all status entries and compute the total of all measured bandwidth values denoted by the <code>"Measured="</code> keyword. If an entry does not contain such a value, skip the entry. If a vote does not contain a single measured bandwidth value, skip the vote.</code>
+
+<h4>Step 2: Compute daily averages</h4>
+
+<p>Go through all previously processed votes by valid-after UTC date and authority.
+If an authority published less than 12 votes on a given UTC date, skip this date and authority.
+Also skip the last date of the results, because those averages may still change throughout the day.
+For all remaining combinations of date and authority, compute the arithmetic mean of total measured bandwidth, rounded down to the next-smaller integer number.</p>
+
</div>
<div class="container">
diff --git a/src/main/resources/web/jsps/stats.jsp b/src/main/resources/web/jsps/stats.jsp
index bb038e4..c6da19f 100644
--- a/src/main/resources/web/jsps/stats.jsp
+++ b/src/main/resources/web/jsps/stats.jsp
@@ -304,6 +304,26 @@ Servers <a href="#servers" name="servers" class="anchor">#</a></h2>
<li><b>total:</b> Average number of bridges.</li>
</ul>
+<h3>Total consensus weights across bandwidth authorities
+<a href="/totalcw.html" class="btn btn-primary btn-xs"><i class="fa fa-chevron-right" aria-hidden="true"></i> graph</a>
+<a href="/totalcw.csv" class="btn btn-primary btn-xs"><i class="fa fa-chevron-right" aria-hidden="true"></i> data</a>
+<a href="#totalcw" name="totalcw" class="anchor">#</a></h3>
+
+<h4>Parameters</h4>
+
+<ul>
+<li><b>start:</b> First UTC date (YYYY-MM-DD) to include in the file.</li>
+<li><b>end:</b> Last UTC date (YYYY-MM-DD) to include in the file.</li>
+</ul>
+
+<h4>Columns</h4>
+
+<ul>
+<li><b>date:</b> UTC date (YYYY-MM-DD) when bridges have been listed as running.</li>
+<li><b>nickname:</b> Bandwidth authority nickname.</li>
+<li><b>totalcw:</b> Total consensus weight of all relays measured by the bandwidth authority.</li>
+</ul>
+
</div>
<div class="container">
diff --git a/src/main/sql/totalcw/init-totalcw.sql b/src/main/sql/totalcw/init-totalcw.sql
new file mode 100644
index 0000000..bbb6cac
--- /dev/null
+++ b/src/main/sql/totalcw/init-totalcw.sql
@@ -0,0 +1,74 @@
+-- Copyright 2018 The Tor Project
+-- See LICENSE for licensing information
+
+-- Table of v3 authorities that stores nicknames and identity fingerprints and
+-- assigns much shorter numeric identifiers for internal-only use.
+CREATE TABLE authority (
+
+ -- The auto-incremented numeric identifier for an authority.
+ authority_id SERIAL PRIMARY KEY,
+
+ -- The 1 to 19 character long alphanumeric nickname assigned to the authority by
+ -- its operator.
+ nickname CHARACTER VARYING(19) NOT NULL,
+
+ -- Uppercase hex fingerprint of the authority's (v3 authority) identity key.
+ identity_hex CHARACTER(40) NOT NULL,
+
+ UNIQUE (nickname, identity_hex)
+);
+
+-- Table of all votes with statistics on contained bandwidth measurements. Only
+-- contains votes containing bandwidth measurements.
+CREATE TABLE vote (
+
+ -- The auto-incremented numeric identifier for a vote.
+ vote_id SERIAL PRIMARY KEY,
+
+ -- Timestamp at which the consensus is supposed to become valid.
+ valid_after TIMESTAMP WITHOUT TIME ZONE NOT NULL,
+
+ -- Numeric identifier uniquely identifying the authority generating this vote.
+ authority_id INTEGER REFERENCES authority (authority_id),
+
+ -- Count of status entries containing bandwidth measurements.
+ measured_count BIGINT NOT NULL,
+
+ -- Sum of bandwidth measurements of all contained status entries.
+ measured_sum BIGINT NOT NULL,
+
+ -- Mean value of bandwidth measurements of all contained status entries.
+ measured_mean BIGINT NOT NULL,
+
+ -- Minimum value of bandwidth measurements of all contained status entries.
+ measured_min BIGINT NOT NULL,
+
+ -- First quartile value of bandwidth measurements of all contained status
+ -- entries.
+ measured_q1 BIGINT NOT NULL,
+
+ -- Median value of bandwidth measurements of all contained status entries.
+ measured_median BIGINT NOT NULL,
+
+ -- Third quartile value of bandwidth measurements of all contained status
+ -- entries.
+ measured_q3 BIGINT NOT NULL,
+
+ -- Maximum value of bandwidth measurements of all contained status entries.
+ measured_max BIGINT NOT NULL,
+
+ UNIQUE (valid_after, authority_id)
+);
+
+-- View on aggregated total consensus weight statistics in a format that is
+-- compatible for writing to an output CSV file. Votes are only included in the
+-- output if at least 12 votes are known for a given authority and day.
+CREATE OR REPLACE VIEW totalcw AS
+SELECT DATE(valid_after) AS valid_after_date, nickname,
+ FLOOR(AVG(measured_sum)) AS measured_sum_avg
+FROM vote NATURAL JOIN authority
+GROUP BY DATE(valid_after), nickname
+HAVING COUNT(vote_id) >= 12
+ AND DATE(valid_after) < (SELECT MAX(DATE(valid_after)) FROM vote)
+ORDER BY DATE(valid_after), nickname;
+
diff --git a/src/test/java/org/torproject/metrics/stats/totalcw/TotalcwRelayNetworkStatusVoteTest.java b/src/test/java/org/torproject/metrics/stats/totalcw/TotalcwRelayNetworkStatusVoteTest.java
new file mode 100644
index 0000000..085a976
--- /dev/null
+++ b/src/test/java/org/torproject/metrics/stats/totalcw/TotalcwRelayNetworkStatusVoteTest.java
@@ -0,0 +1,126 @@
+/* Copyright 2018 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.stats.totalcw;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.torproject.descriptor.Descriptor;
+import org.torproject.descriptor.DescriptorSourceFactory;
+import org.torproject.descriptor.RelayNetworkStatusVote;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
+import java.util.Arrays;
+import java.util.Collection;
+
+(a)RunWith(Parameterized.class)
+public class TotalcwRelayNetworkStatusVoteTest {
+
+ @Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ { "2018-10-15-00-00-00-vote-0232AF901C31A04EE9848595AF9BB7620D4C5B2E-"
+ + "55A38ED50848BE1F13C6A35C3CA637B0D962C2EF.part",
+ ZonedDateTime.parse("2018-10-15T00:00:00Z").toLocalDateTime(),
+ "dannenberg", "0232AF901C31A04EE9848595AF9BB7620D4C5B2E",
+ 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L },
+ { "2018-10-15-00-00-00-vote-27102BC123E7AF1D4741AE047E160C91ADC76B21-"
+ + "049AB3179B12DACC391F06A10C2A8904E4339D33.part",
+ ZonedDateTime.parse("2018-10-15T00:00:00Z").toLocalDateTime(),
+ "bastet", "27102BC123E7AF1D4741AE047E160C91ADC76B21",
+ 20L, 138803L, 6940L, 5L, 76L, 2490L, 9732L, 34800L },
+ { "2018-10-15-00-00-00-vote-ED03BB616EB2F60BEC80151114BB25CEF515B226-"
+ + "2669AD153408F88E416CE6206D1A75EC3324A2F4.part",
+ ZonedDateTime.parse("2018-10-15T00:00:00Z").toLocalDateTime(),
+ "gabelmoo", "ED03BB616EB2F60BEC80151114BB25CEF515B226",
+ 19, 133441L, 7023L, 2L, 153L, 3920L, 11030L, 31600L },
+ { "2018-10-15-00-00-00-vote-EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97-"
+ + "38C6A19F78948B689345EE41D7119D76246C4D3E.part",
+ ZonedDateTime.parse("2018-10-15T00:00:00Z").toLocalDateTime(),
+ "Faravahar", "EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97",
+ 20, 158534L, 7926L, 6L, 109L, 3215L, 9582L, 40700L }
+ });
+ }
+
+ @Parameter
+ public String fileName;
+
+ @Parameter(1)
+ public LocalDateTime expectedValidAfter;
+
+ @Parameter(2)
+ public String expectedNickname;
+
+ @Parameter(3)
+ public String expectedIdentityHex;
+
+ @Parameter(4)
+ public long expectedMeasuredCount;
+
+ @Parameter(5)
+ public long expectedMeasuredSum;
+
+ @Parameter(6)
+ public long expectedMeasuredMean;
+
+ @Parameter(7)
+ public long expectedMeasuredMin;
+
+ @Parameter(8)
+ public long expectedMeasuredQ1;
+
+ @Parameter(9)
+ public long expectedMeasuredMedian;
+
+ @Parameter(10)
+ public long expectedMeasuredQ3;
+
+ @Parameter(11)
+ public long expectedMeasuredMax;
+
+ @Test
+ public void testParseVote() throws Exception {
+ InputStream is = getClass().getClassLoader().getResourceAsStream(
+ "totalcw/" + this.fileName);
+ StringBuilder sb = new StringBuilder();
+ BufferedReader br = new BufferedReader(new InputStreamReader(is));
+ String line = br.readLine();
+ while (null != line) {
+ sb.append(line).append('\n');
+ line = br.readLine();
+ }
+ for (Descriptor descriptor
+ : DescriptorSourceFactory.createDescriptorParser().parseDescriptors(
+ sb.toString().getBytes(), new File(this.fileName), this.fileName)) {
+ TotalcwRelayNetworkStatusVote parsedVote = new Parser()
+ .parseRelayNetworkStatusVote((RelayNetworkStatusVote) descriptor);
+ if (0L == expectedMeasuredCount) {
+ assertNull(parsedVote);
+ } else {
+ assertEquals(this.expectedValidAfter, parsedVote.validAfter);
+ assertEquals(this.expectedNickname, parsedVote.nickname);
+ assertEquals(this.expectedIdentityHex, parsedVote.identityHex);
+ assertEquals(this.expectedMeasuredCount, parsedVote.measuredCount);
+ assertEquals(this.expectedMeasuredSum, parsedVote.measuredSum);
+ assertEquals(this.expectedMeasuredMean, parsedVote.measuredMean);
+ assertEquals(this.expectedMeasuredMin, parsedVote.measuredMin);
+ assertEquals(this.expectedMeasuredQ1, parsedVote.measuredQ1);
+ assertEquals(this.expectedMeasuredMedian, parsedVote.measuredMedian);
+ assertEquals(this.expectedMeasuredQ3, parsedVote.measuredQ3);
+ assertEquals(this.expectedMeasuredMax, parsedVote.measuredMax);
+ }
+ }
+ }
+}
diff --git a/src/test/resources/totalcw/2018-10-15-00-00-00-vote-0232AF901C31A04EE9848595AF9BB7620D4C5B2E-55A38ED50848BE1F13C6A35C3CA637B0D962C2EF.part b/src/test/resources/totalcw/2018-10-15-00-00-00-vote-0232AF901C31A04EE9848595AF9BB7620D4C5B2E-55A38ED50848BE1F13C6A35C3CA637B0D962C2EF.part
new file mode 100644
index 0000000..827fcfd
--- /dev/null
+++ b/src/test/resources/totalcw/2018-10-15-00-00-00-vote-0232AF901C31A04EE9848595AF9BB7620D4C5B2E-55A38ED50848BE1F13C6A35C3CA637B0D962C2EF.part
@@ -0,0 +1,239 @@
+@type network-status-vote-3 1.0
+network-status-version 3
+vote-status vote
+consensus-methods 25 26 27 28
+published 2018-10-14 23:50:00
+valid-after 2018-10-15 00:00:00
+fresh-until 2018-10-15 01:00:00
+valid-until 2018-10-15 03:00:00
+voting-delay 300 300
+recommended-relay-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=4 Microdesc=1-2 Relay=2
+recommended-client-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=4 Microdesc=1-2 Relay=2
+required-relay-protocols Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=3-4 Microdesc=1 Relay=1-2
+required-client-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=4 Microdesc=1-2 Relay=2
+known-flags Authority Exit Fast Guard HSDir Running Stable V2Dir Valid
+flag-thresholds stable-uptime=1216200 stable-mtbf=3386731 fast-speed=102000 guard-wfu=98.000% guard-tk=691200 guard-bw-inc-exits=7752000 guard-bw-exc-exits=6436000 enough-mtbf=1 ignoring-advertised-bws=0
+params CircuitPriorityHalflifeMsec=30000 DoSCircuitCreationEnabled=1 DoSConnectionEnabled=1 DoSConnectionMaxConcurrentCount=50 DoSRefuseSingleHopClientRendezvous=1 NumDirectoryGuards=3 NumEntryGuards=1 NumNTorsPerTAP=100 Support022HiddenServices=0 UseNTorHandshake=1 UseOptimisticData=1 bwauthpid=1 cbttestfreq=10 hs_service_max_rdv_failures=1 hsdir_spread_store=4 pb_disablepct=0 usecreatefast=0
+dir-source dannenberg 0232AF901C31A04EE9848595AF9BB7620D4C5B2E dannenberg.torauth.de 193.23.244.244 80 443
+contact Andreas Lehner
+shared-rand-participate
+shared-rand-commit 1 sha3-256 0232AF901C31A04EE9848595AF9BB7620D4C5B2E AAAAAFvD2IC9EM7ZZhuBysL9TCuY3vE/XuQsscEXKDtk9ATYpyGJSA==
+shared-rand-previous-value 9 Vd2znClwwth89jp91diG/Bs1AH+0ExSgRFmyVOMJwwE=
+shared-rand-current-value 9 oiXRUZGkT26O9aQmu/A52utoBF3gp27h0TvJ4gkDHkw=
+dir-key-certificate-version 3
+fingerprint 0232AF901C31A04EE9848595AF9BB7620D4C5B2E
+dir-key-published 2018-06-05 08:39:04
+dir-key-expires 2018-12-05 08:39:04
+dir-identity-key
+-----BEGIN RSA PUBLIC KEY-----
+MIIBigKCAYEAu9O0Pueesn0+29BlxZs60mBqehjdQtgSnKOm9QZxbQ0xrMQgbFnR
+hWbKD8erenyeFk2SF6AJkbyzgYC89hyPW+8GBDmg5bE8fRKjgV/nI3tY2m4rkY3u
+zSmYIdwqHUUc98Xzt9PaQ8IJAlDBY4XLKrWmJMxSyhBlVEept7+9Tj23qowW44Mz
+xPJZ1aFkB1FpkD6qmoCzVZbhXy3cGt1nDwdJK7KqlaXziz9pFiw8PzTVU2xFgJNy
++nEcT72DBtk3G5K2Riu/aXY/D541Cioj9KMV4Nv4g8aBKx58Xq2tq1pFkc1Bqj1y
+2MomVR3iskFzlqC8yKWGVe4OP2IaOhtcQJYp5GR9q+dWnr53WWNVxNu3sA9iMal3
+PJUk5pIYrsmArGew5gmlCe+Al46nPINxc7ouztmStAV+2F6SpZlKOcstnT+KJ52O
+1xnOSaj/WnzG2o4KZ9UrFQoUNOLQJcelPcC+vrinMk9BQPcB072l9NjpUBC9brsW
+qTCMStn1jfDDAgMBAAE=
+-----END RSA PUBLIC KEY-----
+dir-signing-key
+-----BEGIN RSA PUBLIC KEY-----
+MIIBCgKCAQEAy5TDIInDMMHEMdDoEgpmqGJDZ2EwtLjOeUxQD7TEy4uYd5XcsJP0
+cjkJDm1FAWU02AJFMtoT+Pg7Ge4+30jRytySp+mibQLs6s8O2xLV7mwH8if9oHZ5
+xl8aPcx7mOG6J9Ak3k610bMDkEDrxmNqg0pl+krtpWrxuh6GcA5bbfPqf0ZPyUaE
+MKKcFp/XxhLjvCOnryrQbeDYZySy2GkIx28A+u15prQnKm+6KskMC6SQIbo5QcBq
+R0bumjdl7OcuTvQsP5h/7jHovFHhA/j/5b2osbHWbG9kqn3fN50anJBNaGHnYbFQ
+gFSLZHfm8vSb5HnfzmsaDxYO/4xtfEi85wIDAQAB
+-----END RSA PUBLIC KEY-----
+dir-key-crosscert
+-----BEGIN ID SIGNATURE-----
+a0fX/ln8gMXpNGpC9ZXujMZep616AKu5DuECcKoqtAeoUeIIaAMdvMS/5RWUKDl7
+L8LeTjkUdhUpbodOtaRS+IGy9dXjSMOGUOgyLlvUSZ29jIAcTa5xBcBNgpUPkC6Y
+AbdTmNjvaq+oAkWooPnER58/iivSRrBbh8G9Cqe22AKlQUTRH87L/b5tpKDaxg3W
+89CmcsUulUVNt+Sj++kS2PqXMIySRwCv3xXQ/OLAsKJZvqByBBIY7y7TLkewiHcq
+8KdpxmvFdE/bjPnErzd3Dcdv6FW+QIgt+3XEqamXD06lTw6xUsKlX2IP+3Cei00r
+mQnm434h3FaGocvXuijjXw==
+-----END ID SIGNATURE-----
+dir-key-certification
+-----BEGIN SIGNATURE-----
+OyofWQWvPhRHhaMck1a8msuFf3AmZfLWBc/GOp8sxOvRU08pk+4hvjGGE8OpBqyF
+Lps5387U6bfqlppCvC/qvManCuViyEYqCpn1iiClq+VmcgfL9GgvjsAc8V0G6INy
+I8N19D3r/Htg44HNdW9yNgAeyv7PHzbQ9P6RyxXLawjFu4Mlho+a1IABeeFjE2f/
+PC6bDA5ix5K11AcVWAJKuTRAhQ0zOyLqnwD5UvjmqvO9jQrtK3kgrqvQ6ZJEakc9
+wDOB7+1I14gee2rvSD8rpyrPi3ystat1I4Tt2WlUGz9GX32Q7Mngq4muMVAY6bPS
+6ujDrCgTKP+bF0+WhlVO9v71NMpi+uNEwnd4FO2Tt06XM2GKbEzty5yjMPhy9b12
+GR9BaI2wMnwQ7m0Y/57Nh1w90u+GbjgBYgRVEldk7WDB5wxjBVxcfKFDzU18kLcf
+t94cSxk6CFPAPAr/LhCop7fU+4fkH0bveFm3dj0z8t6KMf5fMQvWVu5VFyUetSlV
+-----END SIGNATURE-----
+r seele AAoQ1DAR6kkoo19hBAX5K0QztNw xlbC5aW8ovDVh2t6VcKF/phheSg 2018-10-14 21:41:22 67.174.243.193 9001 0
+s Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=60
+p reject 1-65535
+id ed25519 ZtzhbIWHJpGQG+5N7hbRTtenyzq2RNJrx0QegtoY+bY
+m 25,26,27,28 sha256=4l7u4Oy1sCG/08MtsHimtWtaC1ydC/GOtCqRD/5ewgs
+r PutoElQueLee293884 AAwffNL+oHO5EdyUoWAOwvEX3ws Ay3cnaaHjnolSRe3ZjKcGlY17Q8 2018-10-14 08:06:47 174.127.217.73 55554 0
+s Fast Guard Running Stable V2Dir Valid
+v Tor 0.3.3.9
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=4950
+p reject 1-65535
+id ed25519 /58lPWzxxQfwoxed8I9l+D3+hTdOQ6RpgVgM9FCxil4
+m 25,26,27,28 sha256=0/b37HV9enGTzbwrxxg/0R5PfwRtRosXgtU5+vi/gBk
+r CalyxInstitute14 ABG9JIWtRdmE7EFZyI/AZuXjMA4 +8xNQyAVPkKgtLH0AISVZFNuAq0 2018-10-14 11:10:20 162.247.74.201 443 80
+s Exit Fast Guard HSDir Running Stable V2Dir Valid
+v Tor 0.3.3.7
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=10000
+p accept 20-23,43,53,79-81,88,110,143,194,220,389,443,464,531,543-544,554,563,636,706,749,873,902-904,981,989-995,1194,1220,1293,1500,1533,1677,1723,1755,1863,2082-2083,2086-2087,2095-2096,2102-2104,3128,3389,3690,4321,4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000,8008,8074,8080,8087-8088,8332-8333,8443,8888,9418,9999-10000,11371,12350,19294,19638,23456,33033,64738
+id ed25519 /AcSvVFxaIBxaB5I/7mwdVbMdP6JjDsEPVLU6Hj22a0
+m 25,26,27,28 sha256=7nBT4mYr4H5Jj71Qq7Nan7R7KqWPTRG89Oq1/5m4c1M
+r Neldoreth ABUk3UA9cp8I9+XXeBPvEnVs+o0 nms8ZM18C/K5XzLmgO5fHchjhFc 2018-10-14 19:09:56 185.13.39.197 443 80
+s Fast Guard Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=5242
+p reject 1-65535
+id ed25519 eZ49J/mRCMtMbD2GDZsjZm/gqqG9z5S6M5GykZmd2ro
+m 25,26,27,28 sha256=IxCWEdRM6Hqk1fiYx+DK9ufUPI49SE6Z4C8onbsBVXo
+r UbuntuCore246 ACenhGJF+i26Aj46wfqAaI8dc7s EmwRg8iMnKJc8GEusZxhsVf9PJI 2018-10-14 23:13:35 212.199.61.38 43511 0
+s Fast V2Dir Valid
+v Tor 0.3.3.10
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=121
+p reject 1-65535
+id ed25519 crWebog3RcKMV49zyu6pF5A/MxQM89x2cKNRK1wbTjM
+m 25,26,27,28 sha256=98nmfhxVBZqk7VUfyXQWpJmLAfWloZtMXu5H9FVt+p8
+r rotor25 ADQsDhVdRULlU5F4iy13nxRXjes m/3hkTP+ETmoYWZ3JrniJAeRHho 2018-10-14 17:19:08 188.24.22.193 9001 9030
+s Fast Running V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=3987
+p reject 1-65535
+id ed25519 8ybCWdul+E3HdzEb1YR3Fbp6EIwnGGPsUhsSh7FUnfs
+m 25,26,27,28 sha256=ZeMCL1T4SogPYK0iyuzYZoyFx+mp9/Ng8vGmE8wI9rU
+r torbogen AEHgFQsKMHUGwoY+/J8rfjpSOzY DXa3G+iPbGQo4AK2iazVY6R4eKs 2018-10-14 20:19:33 178.142.72.49 9001 9030
+s Fast Running V2Dir Valid
+v Tor 0.3.3.9
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=1275
+p reject 1-65535
+id ed25519 NzIv9KWavK4f+6426ctrr9UnsFl/Yt8ixEAP6OO/kVo
+m 25,26,27,28 sha256=lFXLHMP6Map0qD298F+eeybL4pqtXDRG7a7r4R7knlw
+r nicolass67atoll AES/YhfxWxj3ZvK1O85bPg4BV+k iK+QhopNtEipMSL+EeI6b/DitVc 2018-10-14 15:48:15 163.172.10.89 9001 0
+s Fast Running Stable V2Dir Valid
+v Tor 0.2.9.16
+pr Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1-2 Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2
+w Bandwidth=1048
+p reject 1-65535
+id ed25519 ++ezzEs6r+0m0YDwFAjwpIIbNKtzMbKB13osAAoGQ/c
+m 25,26,27,28 sha256=kGlToMt+rD0tL3N+5Plqj8Nav1FVQMiSYpz0adtzs0I
+r zzzzzzzzzzzzzzzzzzz AEVz/pNLpV0H2ucjF3k69OQbdbY 1FUGYNQtQo1MgV4jRP2XJapWemI 2018-10-14 23:43:10 77.12.174.141 9002 9031
+s Fast Running V2Dir Valid
+v Tor 0.3.2.10
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=565
+p reject 1-65535
+id ed25519 QnM3RTCh53wbKiC6N6IsCQHs3pj9J547xUNtrk5FnYE
+m 25,26,27,28 sha256=+1jqHQZNEebT5DFJHZIPE35at9TzFCB5hzqDWAxrXGk
+r helga AFnZKULbO4TlLrTYfp97GVz00AU ngp++P8hEV+Pj/j9OvvDCyuDqKA 2018-10-14 17:09:42 88.99.216.194 9001 9030
+s Fast HSDir Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=409
+p reject 1-65535
+id ed25519 M1nVVuHebCChHayS1hsajAkzpe4c8bEFKP499V3yBG4
+m 25,26,27,28 sha256=//iWXvWzIkvqFqwTf367uZJ2UoNwcrCrLcOs7DY5xvE
+r Torpi AHJ/OiwdDxcxqRJnxBFQecFrILQ bLi/i6JuuPmoWm1aGd73dy2tYlA 2018-10-14 15:51:48 110.146.4.151 9001 9030
+s Running Stable V2Dir Valid
+v Tor 0.2.4.29
+pr Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=1-4 LinkAuth=1 Microdesc=1 Relay=1-2
+w Bandwidth=92
+p reject 1-65535
+id ed25519 none
+m 25,26,27,28 sha256=TiQfeDGlZq6VrCEKFDCA0uvS6hiv8CVMdkLyRxuRZpU
+r VeespRU2 AHTsqCvVi4uxkJycTyN/2XebI/w r3AsIyXKvSFzC+5eDuiUgPTmG9g 2018-10-14 09:25:26 185.22.172.237 443 80
+s Fast Guard HSDir Running Stable V2Dir Valid
+v Tor 0.3.3.7
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=10000
+p reject 1-65535
+id ed25519 obR35oXyInFbtgitDsbasslq2SNFSTZeI1+2bJfK6HE
+m 25,26,27,28 sha256=+tNKyFkcOSA/CufXMtSfprtve7qkoDx4yjGG/XdH6Ec
+r Quintex13 AHe8unJE2z5qXtJ0boYXAGZoSIc GwoBS5ddT7ktxwqHjHYNc2u53xU 2018-10-14 07:38:44 199.249.223.62 443 80
+s Exit Fast Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=9426
+p accept 20-23,43,53,79-81,88,110,143,194,220,389,443,464-465,531,543-544,554,563,587,636,706,749,873,902-904,981,989-995,1194,1220,1293,1500,1533,1677,1723,1755,1863,2082-2083,2086-2087,2095-2096,2102-2104,3128,3389,3690,4321,4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000,8008,8074,8080,8082,8087-8088,8232-8233,8332-8333,8443,8888,9418,9999-10000,11371,19294,19638,50002,64738
+id ed25519 cHUct3VAkb+T4KQQQg9fLuH5feoGyjxang+Jihs0E80
+m 25,26,27,28 sha256=/JQIKi/L0aSZ8uMKc0DsHBeH2OmsvAj9GbXq8i8QQcE
+r UbuntuCore246 AH4Yastcf3fGnzddzt9ZT/1r9pk 9kJql4jbylNMYXhvn4OasP55fck 2018-10-14 11:03:40 77.146.180.194 40523 0
+s Fast V2Dir Valid
+v Tor 0.3.3.10
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=382
+p reject 1-65535
+id ed25519 rSPYL+kdnVmyJ9/Ni96SNABxuvE4DDLwO9l9tqeicoA
+m 25,26,27,28 sha256=hGcbcAmK7x/tNRe7iBBqNlj6sZByhmhTSwWw/wYZo3s
+r torrelay04 AH/ZCOnPz1nmT7pCt1cdLPQBvF0 n5XUymNehFnO8hTftwd4NIozrjQ 2018-10-14 18:52:54 159.69.153.73 9000 0
+a [2a01:4f8:1c1c:5cec::1]:9000
+s Fast Running Stable Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=8030
+p reject 1-65535
+id ed25519 OJfT1k1R4UB3eNZ7+Mx9MCh9a5RBXqCcqtDfqQw4UvA
+m 25,26,27 sha256=R60ONtNwXwpVOajWfWlH4yQ7a6rBYwgLJ87p5wH55fk
+m 28 sha256=8oP/xai6UYcjJY5gx9fgrLTYY66vgdAr4y2F+R7tqUQ
+r bigamy AH/cDMglli4ShKYaaMk4pmr75XA dMrr7wRTfKoVfCRHLuR0IZFSWsI 2018-10-14 13:28:57 185.24.218.171 9001 8080
+s Exit Fast Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=10000
+p accept 20-21,23,43,53,79-81,88,110,143,194,220,389,443,464-465,531,543-544,554,563,587,636,706,749,853,873,902-904,981,989-995,1220,1293,1500,1533,1677,1723,1755,1863,2082-2087,2095-2096,2102-2104,2374-2382,3128,3389,3690,4321,4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000-8100,8232-8233,8332-8333,8443,8888,9418,9999-10000,11371,19294,19638,50002,64738
+id ed25519 fTOvB48qkzbEyLpBemkuyBv9UieAUeM8RNfCypusrs0
+m 25,26,27,28 sha256=GpLj/6GejSGtvDxUAUrZgarLCVjbcZVDduWCqvShOBk
+r zech1989 AI57cMO0p1ILW+q4Bnq83I5j8f0 boK7UYxCaZy4lpe8irfYPFsX6SY 2018-10-14 21:18:08 185.243.53.99 9001 9030
+s Fast Guard HSDir Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=9726
+p reject 1-65535
+id ed25519 wPhoSjgH8xrZ/K2wKhYKd2IR6iQ2/pO4V9rIObVIZ30
+m 25,26,27,28 sha256=hx94DElvCTLaJLuCbKr5rShGxcpKpz8f5eZdYhXwfEQ
+r paris AJf70aisEQPfmPHoXdUsUPAdOtY 9x4Jiv45IDG4EDSSXfOd+Ss9qtc 2018-10-14 22:06:20 178.19.111.147 9001 8000
+s Exit Fast Guard HSDir Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=10000
+p accept 20-21,23,43,53,79-81,88,110,143,194,220,389,443,464-465,531,543-544,554,563,587,636,706,749,853,873,902-904,981,989-995,1220,1293,1500,1533,1677,1723,1755,1863,2082-2087,2095-2096,2102-2104,2374-2382,3128,3389,3690,4321,4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000-8100,8232-8233,8332-8333,8443,8888,9418,9999-10000,11371,19294,19638,50002,64738
+id ed25519 s49ps0kX/BuncEzm6dG6ZmLs7DCanj/WcXlPVt0D7Tw
+m 25,26,27,28 sha256=L9vWM3tYYI2RLlnofu1m9nMTckLVSKMXftVTDUwidhw
+r jactr AJhR35M3VLAN3odvzkCIzhtJQME RLckbaDnNUH4qGCkOmDo20UwE28 2018-10-14 23:26:36 84.40.112.70 9001 9030
+s Fast HSDir Running Stable V2Dir Valid
+v Tor 0.2.9.15
+pr Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1-2 Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2
+w Bandwidth=131
+p reject 1-65535
+id ed25519 +Jc2Ppp+/hmdtaE8T2uxkZIhOD5/kCEfz8gyDCMiDpg
+m 25,26,27,28 sha256=tD+2RMOICoOQRFeZH6D4Kx8mRN4JzhfrtcQs8q78hPA
+r IsThisAGoodIdea AJ2M8WzPcSMatc5VyCoDRG2j+Us Gce3qKSpkK8w2D+9k75V7vNdWzI 2018-10-14 16:51:45 220.233.27.93 9001 0
+s Running Valid
+v Tor 0.3.3.9
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=80
+p reject 1-65535
+id ed25519 peXjh3W6mWzg9iZALqNEDyP3ONQO7Vp9uSzqSszVHOA
+m 25,26,27,28 sha256=1jmLpMLTUMJr2IoHyDpGCrc/ZuBakhVUWivZ7NKwAz4
+directory-footer
+directory-signature 0232AF901C31A04EE9848595AF9BB7620D4C5B2E CD1FD971855430880D3C31E0331C5C55800C2F79
+-----BEGIN SIGNATURE-----
+CS6wJMEd74jA1bBTBt0CFPZXivvrvB9c8jf80z2TgHPo9WgUByCMx1+Gp5dHEKqy
+r3C2fgqF+1v8cMDy6vGGEkHTGWiyYt1Ge0mztr/tUcR7+sqqZychdUcVA9+abJ8P
+iI5whpWDieH+njn1BvphBVo2Hpzd/wCIqDanwaz6xHPxToINdGnA830SHGbs1b8H
+lLXWHuUjVSbkjHgN3NLd8nPjw1uOVIZBsWJa2dT6SCDkQCgbj5+g73kyk+OKJw4r
+ue8POxm8SOQT4BHTUfQH4Cgrje33nDJibTSymBOPAYkF6tIZsK7ujWEhnXbyciTQ
+dYM8WH+IZ9Iu31hyg277bQ==
+-----END SIGNATURE-----
diff --git a/src/test/resources/totalcw/2018-10-15-00-00-00-vote-27102BC123E7AF1D4741AE047E160C91ADC76B21-049AB3179B12DACC391F06A10C2A8904E4339D33.part b/src/test/resources/totalcw/2018-10-15-00-00-00-vote-27102BC123E7AF1D4741AE047E160C91ADC76B21-049AB3179B12DACC391F06A10C2A8904E4339D33.part
new file mode 100644
index 0000000..c73c6f5
--- /dev/null
+++ b/src/test/resources/totalcw/2018-10-15-00-00-00-vote-27102BC123E7AF1D4741AE047E160C91ADC76B21-049AB3179B12DACC391F06A10C2A8904E4339D33.part
@@ -0,0 +1,239 @@
+@type network-status-vote-3 1.0
+network-status-version 3
+vote-status vote
+consensus-methods 25 26 27 28
+published 2018-10-14 23:50:00
+valid-after 2018-10-15 00:00:00
+fresh-until 2018-10-15 01:00:00
+valid-until 2018-10-15 03:00:00
+voting-delay 300 300
+recommended-relay-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=4 Microdesc=1-2 Relay=2
+recommended-client-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=4 Microdesc=1-2 Relay=2
+required-relay-protocols Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=3-4 Microdesc=1 Relay=1-2
+required-client-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=4 Microdesc=1-2 Relay=2
+known-flags Authority Exit Fast Guard HSDir Running Stable V2Dir Valid
+flag-thresholds stable-uptime=1219572 stable-mtbf=3460952 fast-speed=90000 guard-wfu=98.000% guard-tk=691200 guard-bw-inc-exits=10400000 guard-bw-exc-exits=8880000 enough-mtbf=1 ignoring-advertised-bws=1
+params CircuitPriorityHalflifeMsec=30000 DoSCircuitCreationEnabled=1 DoSConnectionEnabled=1 DoSConnectionMaxConcurrentCount=50 DoSRefuseSingleHopClientRendezvous=1 NumDirectoryGuards=3 NumEntryGuards=1 NumNTorsPerTAP=100 Support022HiddenServices=0 UseNTorHandshake=1 UseOptimisticData=1 bwauthpid=1 cbttestfreq=10 hs_service_max_rdv_failures=1 hsdir_spread_store=4 pb_disablepct=0 usecreatefast=0
+dir-source bastet 27102BC123E7AF1D4741AE047E160C91ADC76B21 204.13.164.118 204.13.164.118 80 443
+contact stefani <nocat at readthefinemanual dot net>
+shared-rand-participate
+shared-rand-commit 1 sha3-256 27102BC123E7AF1D4741AE047E160C91ADC76B21 AAAAAFvD2IDJPppI/HhEtjCnUnd+nCJlpUZYS1f6jHZvFxFgnFtQXQ==
+shared-rand-previous-value 9 Vd2znClwwth89jp91diG/Bs1AH+0ExSgRFmyVOMJwwE=
+shared-rand-current-value 9 oiXRUZGkT26O9aQmu/A52utoBF3gp27h0TvJ4gkDHkw=
+dir-key-certificate-version 3
+fingerprint 27102BC123E7AF1D4741AE047E160C91ADC76B21
+dir-key-published 2018-10-10 00:02:19
+dir-key-expires 2019-10-10 00:02:19
+dir-identity-key
+-----BEGIN RSA PUBLIC KEY-----
+MIIBigKCAYEAuxgnMVH4vwBjMeGvrEODOYcjbCS4N+Wt0SZ6XA5I08HyMf5AbaaF
+MDscJBRIUOp7DyLmUwK+jp+QI8pUjjKsB8S0ctb/J3Im2T6CXnP2KgEfVmpNVQmV
+XdMm8cRZl1uIZDDBAXizSQ51f9A17TJh7pF/5khYp/SAzl6aO5ETn7ry0ITiJnNa
+6cY+400F7ZBA8NuXnCHVGfmpFFsiJKFrS1Kve629eeaNEd3mynRviBXJy5a4NEGf
+y42Ev8on6SxEnF9OG0NMJ081/+mP+j8Dsl3+Uehzr9B42MQQfDo4RdYGrt9XolBm
+L4eay1ieZEsFeDy0TMfiGGbr90wo1fgGLHIRSfTNLhhPJ/f9cTZPe98rhSgGWiAd
+RvK5SljoIOR4qdS9/aiZkj1P+etvh1rIQUcG4/xCOBnouEBK+DDHZFqyMtpMPtV0
+Bxi20DVaMJcyhdfjVqcRSyuR8tlOnTid6QwBj6kgIIfMaC+4Ht6yO/SYquCWlaZl
+y7Pu7li8WyW9AgMBAAE=
+-----END RSA PUBLIC KEY-----
+dir-signing-key
+-----BEGIN RSA PUBLIC KEY-----
+MIIBCgKCAQEAp/+6CgR/ptxWIt6uLCLt0t4e+yCq9i7MOcmcZlWHzIsP+sJxsPJO
+jqg5MycQsWt4wMUGCWj5ag+cozTGClfZ7dSxEzee5DoLa65RFK9ymFAnhUNs97sx
+F5CionrnUdjMO4MShALJgr7tXZ7I2/mwAG8foszEcl+dWhcaQio3WaT5rK/hLpiT
+URgYmieXaZWm1kLraI5DWnRY7GdFyWdBJKboStYpoJuYexUL7lbG7u7wqEG25N4+
+yFt8fiS7yjdlCf0WJRZlwmTLz87nn4Sqb/nYeqXKmT88LBs9D1csd8KgwOgyTkfb
+e7v+dTZSkqZYd0QXsqqxynZVKk63vWSXHwIDAQAB
+-----END RSA PUBLIC KEY-----
+dir-key-crosscert
+-----BEGIN ID SIGNATURE-----
+BNgMpEDYtEohkX1c0imNg4ATBxwPFeO01t1mgEQqyRZkihcfFi6Eb9stAqXto7Xs
+03QxovdfUP3kQvsidBBpt2WHBpW8CpARhwK8LvIPzu3q7SkLcW0d82qY29cRadfx
+yX2jBJVV4faH41IY5rt/EN6BAATFSnJ+IORFAX8A/75ZQ2H9EIOhdufkmA4zMGUx
+qKzU/L5LDyfTwWUJmsDYb492d1luws3EBkD5cDFyo5ab1rV7NwJw0al1m0pUF4Dm
+GSNtWlY1Lr/IUnPBksxY98TCkapgHKyq2FsnPJeSiTAHDIo1gpl2oJ0Lg5y2SBxZ
+FMmLq/Wt9rqbS216qVxX7w==
+-----END ID SIGNATURE-----
+dir-key-certification
+-----BEGIN SIGNATURE-----
+VEmK8S0LdEXDEGuF4QxLNQBtkQYEAParbBNLDT83XsLhXUfHja1MmkWJUemMpO0n
+R5eOVuHEegIbZzaIztYhgc0+4PKovx7q79rmPADwPACQxdB9dbzGhfgnxV1gfvQC
+CM6gfkruUDUqLoAtjeM++CMd8LgIOZBKmq4T4cQSUH2Mx7r+HYt/h2oqpWLGVufv
+wlE388tksfTdK9gVVht0MFBSTYjB5XEVLfF0LdxuOkHaiFJTUgJdGSecl8rtRSbq
+npy+Ex8Mjzsm//6asIdl2pWqkkmYNyqNeLxUl1r9xn1EOg8ZEWZ/L9L5EHkZvH1j
+YUS3ucYipsaT5QYOCmniLjLtQ6rkDABwn8F5co0RdIIrlIYjKMPGNUmwWz9rcYnC
+6L1DBttnMeLFC8zrn6OD3nI7Q7vrxj73Yl+gZhx2PIriHO01supq3+NGk743lHQu
+jF33Pili9eZU0J4bJzE/LLeVcEnlZPV3CRRvPuG/PTl/O2OCCyI2pM1IHVmp06SQ
+-----END SIGNATURE-----
+r seele AAoQ1DAR6kkoo19hBAX5K0QztNw xlbC5aW8ovDVh2t6VcKF/phheSg 2018-10-14 21:41:22 67.174.243.193 9001 0
+s Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=60 Measured=23
+p reject 1-65535
+id ed25519 ZtzhbIWHJpGQG+5N7hbRTtenyzq2RNJrx0QegtoY+bY
+m 25,26,27,28 sha256=4l7u4Oy1sCG/08MtsHimtWtaC1ydC/GOtCqRD/5ewgs
+r PutoElQueLee293884 AAwffNL+oHO5EdyUoWAOwvEX3ws Ay3cnaaHjnolSRe3ZjKcGlY17Q8 2018-10-14 08:06:47 174.127.217.73 55554 0
+s Fast Guard Running Stable V2Dir Valid
+v Tor 0.3.3.9
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=4950 Measured=5620
+p reject 1-65535
+id ed25519 /58lPWzxxQfwoxed8I9l+D3+hTdOQ6RpgVgM9FCxil4
+m 25,26,27,28 sha256=0/b37HV9enGTzbwrxxg/0R5PfwRtRosXgtU5+vi/gBk
+r CalyxInstitute14 ABG9JIWtRdmE7EFZyI/AZuXjMA4 +8xNQyAVPkKgtLH0AISVZFNuAq0 2018-10-14 11:10:20 162.247.74.201 443 80
+s Exit Fast Guard HSDir Running Stable V2Dir Valid
+v Tor 0.3.3.7
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=10000 Measured=25900
+p accept 20-23,43,53,79-81,88,110,143,194,220,389,443,464,531,543-544,554,563,636,706,749,873,902-904,981,989-995,1194,1220,1293,1500,1533,1677,1723,1755,1863,2082-2083,2086-2087,2095-2096,2102-2104,3128,3389,3690,4321,4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000,8008,8074,8080,8087-8088,8332-8333,8443,8888,9418,9999-10000,11371,12350,19294,19638,23456,33033,64738
+id ed25519 /AcSvVFxaIBxaB5I/7mwdVbMdP6JjDsEPVLU6Hj22a0
+m 25,26,27,28 sha256=7nBT4mYr4H5Jj71Qq7Nan7R7KqWPTRG89Oq1/5m4c1M
+r Neldoreth ABUk3UA9cp8I9+XXeBPvEnVs+o0 nms8ZM18C/K5XzLmgO5fHchjhFc 2018-10-14 19:09:56 185.13.39.197 443 80
+s Fast Guard Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=5242 Measured=10400
+p reject 1-65535
+id ed25519 eZ49J/mRCMtMbD2GDZsjZm/gqqG9z5S6M5GykZmd2ro
+m 25,26,27,28 sha256=IxCWEdRM6Hqk1fiYx+DK9ufUPI49SE6Z4C8onbsBVXo
+r UbuntuCore246 ACenhGJF+i26Aj46wfqAaI8dc7s EmwRg8iMnKJc8GEusZxhsVf9PJI 2018-10-14 23:13:35 212.199.61.38 43511 0
+s V2Dir Valid
+v Tor 0.3.3.10
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=121 Measured=80
+p reject 1-65535
+id ed25519 crWebog3RcKMV49zyu6pF5A/MxQM89x2cKNRK1wbTjM
+m 25,26,27,28 sha256=98nmfhxVBZqk7VUfyXQWpJmLAfWloZtMXu5H9FVt+p8
+r rotor25 ADQsDhVdRULlU5F4iy13nxRXjes m/3hkTP+ETmoYWZ3JrniJAeRHho 2018-10-14 17:19:08 188.24.22.193 9001 9030
+s Fast Running V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=3987 Measured=3910
+p reject 1-65535
+id ed25519 8ybCWdul+E3HdzEb1YR3Fbp6EIwnGGPsUhsSh7FUnfs
+m 25,26,27,28 sha256=ZeMCL1T4SogPYK0iyuzYZoyFx+mp9/Ng8vGmE8wI9rU
+r torbogen AEHgFQsKMHUGwoY+/J8rfjpSOzY DXa3G+iPbGQo4AK2iazVY6R4eKs 2018-10-14 20:19:33 178.142.72.49 9001 9030
+s Fast Running V2Dir Valid
+v Tor 0.3.3.9
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=1275 Measured=1070
+p reject 1-65535
+id ed25519 NzIv9KWavK4f+6426ctrr9UnsFl/Yt8ixEAP6OO/kVo
+m 25,26,27,28 sha256=lFXLHMP6Map0qD298F+eeybL4pqtXDRG7a7r4R7knlw
+r nicolass67atoll AES/YhfxWxj3ZvK1O85bPg4BV+k iK+QhopNtEipMSL+EeI6b/DitVc 2018-10-14 15:48:15 163.172.10.89 9001 0
+s Fast Running Stable V2Dir Valid
+v Tor 0.2.9.16
+pr Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1-2 Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2
+w Bandwidth=1048 Measured=320
+p reject 1-65535
+id ed25519 ++ezzEs6r+0m0YDwFAjwpIIbNKtzMbKB13osAAoGQ/c
+m 25,26,27,28 sha256=kGlToMt+rD0tL3N+5Plqj8Nav1FVQMiSYpz0adtzs0I
+r zzzzzzzzzzzzzzzzzzz AEVz/pNLpV0H2ucjF3k69OQbdbY 1FUGYNQtQo1MgV4jRP2XJapWemI 2018-10-14 23:43:10 77.12.174.141 9002 9031
+s Fast Running V2Dir Valid
+v Tor 0.3.2.10
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=565 Measured=163
+p reject 1-65535
+id ed25519 QnM3RTCh53wbKiC6N6IsCQHs3pj9J547xUNtrk5FnYE
+m 25,26,27,28 sha256=+1jqHQZNEebT5DFJHZIPE35at9TzFCB5hzqDWAxrXGk
+r helga AFnZKULbO4TlLrTYfp97GVz00AU ngp++P8hEV+Pj/j9OvvDCyuDqKA 2018-10-14 17:09:42 88.99.216.194 9001 9030
+s Fast HSDir Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=409 Measured=245
+p reject 1-65535
+id ed25519 M1nVVuHebCChHayS1hsajAkzpe4c8bEFKP499V3yBG4
+m 25,26,27,28 sha256=//iWXvWzIkvqFqwTf367uZJ2UoNwcrCrLcOs7DY5xvE
+r Torpi AHJ/OiwdDxcxqRJnxBFQecFrILQ bLi/i6JuuPmoWm1aGd73dy2tYlA 2018-10-14 15:51:48 110.146.4.151 9001 9030
+s Running Stable V2Dir Valid
+v Tor 0.2.4.29
+pr Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=1-4 LinkAuth=1 Microdesc=1 Relay=1-2
+w Bandwidth=92 Measured=18
+p reject 1-65535
+id ed25519 none
+m 25,26,27,28 sha256=TiQfeDGlZq6VrCEKFDCA0uvS6hiv8CVMdkLyRxuRZpU
+r VeespRU2 AHTsqCvVi4uxkJycTyN/2XebI/w r3AsIyXKvSFzC+5eDuiUgPTmG9g 2018-10-14 09:25:26 185.22.172.237 443 80
+s Fast Guard HSDir Running Stable V2Dir Valid
+v Tor 0.3.3.7
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=10000 Measured=20500
+p reject 1-65535
+id ed25519 obR35oXyInFbtgitDsbasslq2SNFSTZeI1+2bJfK6HE
+m 25,26,27,28 sha256=+tNKyFkcOSA/CufXMtSfprtve7qkoDx4yjGG/XdH6Ec
+r Quintex13 AHe8unJE2z5qXtJ0boYXAGZoSIc GwoBS5ddT7ktxwqHjHYNc2u53xU 2018-10-14 07:38:44 199.249.223.62 443 80
+s Exit Fast HSDir Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=9426 Measured=7570
+p accept 20-23,43,53,79-81,88,110,143,194,220,389,443,464-465,531,543-544,554,563,587,636,706,749,873,902-904,981,989-995,1194,1220,1293,1500,1533,1677,1723,1755,1863,2082-2083,2086-2087,2095-2096,2102-2104,3128,3389,3690,4321,4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000,8008,8074,8080,8082,8087-8088,8232-8233,8332-8333,8443,8888,9418,9999-10000,11371,19294,19638,50002,64738
+id ed25519 cHUct3VAkb+T4KQQQg9fLuH5feoGyjxang+Jihs0E80
+m 25,26,27,28 sha256=/JQIKi/L0aSZ8uMKc0DsHBeH2OmsvAj9GbXq8i8QQcE
+r UbuntuCore246 AH4Yastcf3fGnzddzt9ZT/1r9pk 9kJql4jbylNMYXhvn4OasP55fck 2018-10-14 11:03:40 77.146.180.194 40523 0
+s V2Dir Valid
+v Tor 0.3.3.10
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=382 Measured=23
+p reject 1-65535
+id ed25519 rSPYL+kdnVmyJ9/Ni96SNABxuvE4DDLwO9l9tqeicoA
+m 25,26,27,28 sha256=hGcbcAmK7x/tNRe7iBBqNlj6sZByhmhTSwWw/wYZo3s
+r torrelay04 AH/ZCOnPz1nmT7pCt1cdLPQBvF0 n5XUymNehFnO8hTftwd4NIozrjQ 2018-10-14 18:52:54 159.69.153.73 9000 0
+a [2a01:4f8:1c1c:5cec::1]:9000
+s Fast Running Stable Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=8030 Measured=7880
+p reject 1-65535
+id ed25519 OJfT1k1R4UB3eNZ7+Mx9MCh9a5RBXqCcqtDfqQw4UvA
+m 25,26,27 sha256=R60ONtNwXwpVOajWfWlH4yQ7a6rBYwgLJ87p5wH55fk
+m 28 sha256=8oP/xai6UYcjJY5gx9fgrLTYY66vgdAr4y2F+R7tqUQ
+r bigamy AH/cDMglli4ShKYaaMk4pmr75XA dMrr7wRTfKoVfCRHLuR0IZFSWsI 2018-10-14 13:28:57 185.24.218.171 9001 8080
+s Exit Fast Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=10000 Measured=9510
+p accept 20-21,23,43,53,79-81,88,110,143,194,220,389,443,464-465,531,543-544,554,563,587,636,706,749,853,873,902-904,981,989-995,1220,1293,1500,1533,1677,1723,1755,1863,2082-2087,2095-2096,2102-2104,2374-2382,3128,3389,3690,4321,4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000-8100,8232-8233,8332-8333,8443,8888,9418,9999-10000,11371,19294,19638,50002,64738
+id ed25519 fTOvB48qkzbEyLpBemkuyBv9UieAUeM8RNfCypusrs0
+m 25,26,27,28 sha256=GpLj/6GejSGtvDxUAUrZgarLCVjbcZVDduWCqvShOBk
+r zech1989 AI57cMO0p1ILW+q4Bnq83I5j8f0 boK7UYxCaZy4lpe8irfYPFsX6SY 2018-10-14 21:18:08 185.243.53.99 9001 9030
+s Fast Guard HSDir Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=9726 Measured=10700
+p reject 1-65535
+id ed25519 wPhoSjgH8xrZ/K2wKhYKd2IR6iQ2/pO4V9rIObVIZ30
+m 25,26,27,28 sha256=hx94DElvCTLaJLuCbKr5rShGxcpKpz8f5eZdYhXwfEQ
+r paris AJf70aisEQPfmPHoXdUsUPAdOtY 9x4Jiv45IDG4EDSSXfOd+Ss9qtc 2018-10-14 22:06:20 178.19.111.147 9001 8000
+s Exit Fast Guard HSDir Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=10000 Measured=34800
+p accept 20-21,23,43,53,79-81,88,110,143,194,220,389,443,464-465,531,543-544,554,563,587,636,706,749,853,873,902-904,981,989-995,1220,1293,1500,1533,1677,1723,1755,1863,2082-2087,2095-2096,2102-2104,2374-2382,3128,3389,3690,4321,4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000-8100,8232-8233,8332-8333,8443,8888,9418,9999-10000,11371,19294,19638,50002,64738
+id ed25519 s49ps0kX/BuncEzm6dG6ZmLs7DCanj/WcXlPVt0D7Tw
+m 25,26,27,28 sha256=L9vWM3tYYI2RLlnofu1m9nMTckLVSKMXftVTDUwidhw
+r jactr AJhR35M3VLAN3odvzkCIzhtJQME RLckbaDnNUH4qGCkOmDo20UwE28 2018-10-14 23:26:36 84.40.112.70 9001 9030
+s Running Stable V2Dir Valid
+v Tor 0.2.9.15
+pr Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1-2 Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2
+w Bandwidth=131 Measured=66
+p reject 1-65535
+id ed25519 +Jc2Ppp+/hmdtaE8T2uxkZIhOD5/kCEfz8gyDCMiDpg
+m 25,26,27,28 sha256=tD+2RMOICoOQRFeZH6D4Kx8mRN4JzhfrtcQs8q78hPA
+r IsThisAGoodIdea AJ2M8WzPcSMatc5VyCoDRG2j+Us Gce3qKSpkK8w2D+9k75V7vNdWzI 2018-10-14 16:51:45 220.233.27.93 9001 0
+s Running Valid
+v Tor 0.3.3.9
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=80 Measured=5
+p reject 1-65535
+id ed25519 peXjh3W6mWzg9iZALqNEDyP3ONQO7Vp9uSzqSszVHOA
+m 25,26,27,28 sha256=1jmLpMLTUMJr2IoHyDpGCrc/ZuBakhVUWivZ7NKwAz4
+directory-footer
+directory-signature 27102BC123E7AF1D4741AE047E160C91ADC76B21 6FF3CD454038B7FDD7862B2DD778E4F702C31419
+-----BEGIN SIGNATURE-----
+ICRgDdm/BcGDGUaofpzbAwYibBsX9o88sIZ8Wec0w+LD8RBQT0G6/9S+joAJECZv
+WkJk+IqLhcipWAsTdts3ckCgPfP8x8DNmfdk0RhXf4otaYPs3wn5LD6hXnoXh+OU
+QJ7DQbM+Hw5+K3F5gd8s2AW3GgL1g6REBC3kfZwijI8ovG8SIbr7+npVTGHRwm11
+F0IiEGRMnhedDRAdhhEVnVgyRWjfJEdFe8VzUzFAF7yjCNgVzVE++e+g2afrMXaJ
+GYiWsJcKsgwbEUcFI6nvyPnbBGm8bTBnhhZFf60Ao5b0gNXpdGQVwAAHTwncLMCZ
+/faPhIKZreAjmt5vtvPsXg==
+-----END SIGNATURE-----
diff --git a/src/test/resources/totalcw/2018-10-15-00-00-00-vote-ED03BB616EB2F60BEC80151114BB25CEF515B226-2669AD153408F88E416CE6206D1A75EC3324A2F4.part b/src/test/resources/totalcw/2018-10-15-00-00-00-vote-ED03BB616EB2F60BEC80151114BB25CEF515B226-2669AD153408F88E416CE6206D1A75EC3324A2F4.part
new file mode 100644
index 0000000..13c618f
--- /dev/null
+++ b/src/test/resources/totalcw/2018-10-15-00-00-00-vote-ED03BB616EB2F60BEC80151114BB25CEF515B226-2669AD153408F88E416CE6206D1A75EC3324A2F4.part
@@ -0,0 +1,241 @@
+@type network-status-vote-3 1.0
+network-status-version 3
+vote-status vote
+consensus-methods 25 26 27 28
+published 2018-10-14 23:50:00
+valid-after 2018-10-15 00:00:00
+fresh-until 2018-10-15 01:00:00
+valid-until 2018-10-15 03:00:00
+voting-delay 300 300
+client-versions 0.2.9.14,0.2.9.15,0.2.9.16,0.2.9.17,0.3.2.6-alpha,0.3.2.7-rc,0.3.2.8-rc,0.3.2.9,0.3.2.10,0.3.2.11,0.3.2.12,0.3.3.1-alpha,0.3.3.2-alpha,0.3.3.3-alpha,0.3.3.4-alpha,0.3.3.5-rc,0.3.3.6,0.3.3.7,0.3.3.8,0.3.3.9,0.3.3.10,0.3.4.1-alpha,0.3.4.2-alpha,0.3.4.3-alpha,0.3.4.4-rc,0.3.4.5-rc,0.3.4.6-rc,0.3.4.7-rc,0.3.4.8,0.3.5.1-alpha,0.3.5.2-alpha,0.3.5.3-alpha
+server-versions 0.2.9.14,0.2.9.15,0.2.9.16,0.2.9.17,0.3.2.10,0.3.2.11,0.3.2.12,0.3.3.2-alpha,0.3.3.3-alpha,0.3.3.4-alpha,0.3.3.5-rc,0.3.3.6,0.3.3.7,0.3.3.8,0.3.3.9,0.3.3.10,0.3.4.1-alpha,0.3.4.2-alpha,0.3.4.3-alpha,0.3.4.4-rc,0.3.4.5-rc,0.3.4.6-rc,0.3.4.7-rc,0.3.4.8,0.3.5.1-alpha,0.3.5.2-alpha,0.3.5.3-alpha
+recommended-relay-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=4 Microdesc=1-2 Relay=2
+recommended-client-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=4 Microdesc=1-2 Relay=2
+required-relay-protocols Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=3-4 Microdesc=1 Relay=1-2
+required-client-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=4 Microdesc=1-2 Relay=2
+known-flags Authority BadExit Exit Fast Guard HSDir Running Stable V2Dir Valid
+flag-thresholds stable-uptime=1260391 stable-mtbf=3650978 fast-speed=53000 guard-wfu=98.000% guard-tk=691200 guard-bw-inc-exits=11000000 guard-bw-exc-exits=9050000 enough-mtbf=1 ignoring-advertised-bws=1
+params CircuitPriorityHalflifeMsec=30000 DoSCircuitCreationEnabled=1 DoSConnectionEnabled=1 DoSConnectionMaxConcurrentCount=50 DoSRefuseSingleHopClientRendezvous=1 NumDirectoryGuards=3 NumEntryGuards=1 NumNTorsPerTAP=100 Support022HiddenServices=0 UseNTorHandshake=1 UseOptimisticData=1 bwauthpid=1 cbttestfreq=10 hs_service_max_rdv_failures=1 hsdir_spread_store=4 pb_disablepct=0 usecreatefast=0
+dir-source gabelmoo ED03BB616EB2F60BEC80151114BB25CEF515B226 131.188.40.189 131.188.40.189 80 443
+contact 4096R/261C5FBE77285F88FB0C343266C8C2D7C5AA446D Sebastian Hahn <tor(a)sebastianhahn.net> - 12NbRAjAG5U3LLWETSF7fSTcdaz32Mu5CN
+shared-rand-participate
+shared-rand-commit 1 sha3-256 ED03BB616EB2F60BEC80151114BB25CEF515B226 AAAAAFvD2ID7sH6uVUd8dxs2u8VPr+JTJ14S6SLVjh69b5m7hTQEJA==
+shared-rand-previous-value 9 Vd2znClwwth89jp91diG/Bs1AH+0ExSgRFmyVOMJwwE=
+shared-rand-current-value 9 oiXRUZGkT26O9aQmu/A52utoBF3gp27h0TvJ4gkDHkw=
+dir-key-certificate-version 3
+fingerprint ED03BB616EB2F60BEC80151114BB25CEF515B226
+dir-key-published 2018-08-19 12:38:04
+dir-key-expires 2019-08-19 12:38:04
+dir-identity-key
+-----BEGIN RSA PUBLIC KEY-----
+MIIBigKCAYEA1d6uTRiqdMp4BHBYIHKR6NB599Z1Bqw4TbOVkM2N1aSA4V/L/hKI
+nl6m/2LL/UAS+E3NCFX0dhw2+D7r7BTJyfGwz0H2MR6Py5/rCMAnPl20wCjXk2qY
+ACQa0rJvIqXobwGnDlvxn4ezsj0IEY/FEb61zHnnPHf6d3uyFR1QT06qEOQyYzML
+76f/Lud8MUt+8KzsdnadAPL8okNvcS/nqa2bWbbGhC8S8rtDpPg5BhX2ikXa88RM
+QdrrackdppB2ttHlq9+iH3c8Wyp7bvdH8uhv410W7RnIE4P+KIxt3L0gqkxCjjyh
+mn9ONcdgNOKe31q2cdW5LOPSIK+I5/VTjYjICza7Euyg03drpoBMGLuuJZY6FXEV
+auIBncWe+So8FMxqU/fwo5xm6x085U1MwXUmi4XDYpr/kau6ytPnzzw9J++4W9iC
+em5Jp0vaxrDnPdphqT0FWsBAwsZFL7nZRnmUlTgGsXUa0oSM9/MErDwzELh/NwG4
+DNyyzRG8iP61AgMBAAE=
+-----END RSA PUBLIC KEY-----
+dir-signing-key
+-----BEGIN RSA PUBLIC KEY-----
+MIIBCgKCAQEAxtiD0MuGDfRBqibc06jZXSPoF5s0ToVSrVfNO75dYvfM9zabW+1q
+EP18KUFxeShMqd2mmj685XbGnGX49I5gjzezjaIMcszpiPHQNmr9McnX97yR62YJ
+XKy0i6lvKXLUb5y8V5HmdqKQ8HyCqsRcYFTFrHNCWC+oIhRrqpeQ5kSIdWiO2HCt
+rzx9mAgGLRNFTy+EsxAFzuHaJVV5U7g/G4RMYXMP514aSpJVNgd2kdkXWsSIQ4HX
+a/vRNECuBQyJ1YpHySqqpdOogJxFot8oW/WkmFTGh/amJjMrTUNGgDMD5mpwMCRV
+ifA4hQzvJljkmfGMwgU8kYraMLDHvKzu4wIDAQAB
+-----END RSA PUBLIC KEY-----
+dir-key-crosscert
+-----BEGIN ID SIGNATURE-----
+ruE2woKIct6glnXsJOjnj0thwiv2KYTr/vVMcX5I4OBgzNj9jQPuXe84fUvFNsyQ
+DVf0p6xrjnSmDjdsEpmZUtN16So9m/Zw7Xmn7v7Ktq8TS4jguA1xfhvPxlk7KTz4
+Fxkg3si4qWs00PZxiLERBK3bqMSChPOrzO9AZTjCAngeMIc8+TkXX8EowfqJDO92
+uGCtZReWfFMPObr0Ki4UDOzlTsx4zriRvMNjNqKC43tj2l0jj8RpSDmsWG0QeS+A
+983kODQiVx5URr0O4eev1l0+pI+i8o2+05ZRci32cqvtlonKEtREbgUXzlMHU/ps
+V3kONRvhP+0ABHL/JCVTzg==
+-----END ID SIGNATURE-----
+dir-key-certification
+-----BEGIN SIGNATURE-----
+MYFZL03QkjZKPN8uckION+Yzb3icKjxnzUqtgmTeBaxrjLCsxAXhKFgzgDZkDzun
+REk+fSc9yG2YpxxUgE/l2ya36gr695q0EyJjnbQoNNgBX8YbeN0WaJER/jQtd5tY
+3hEPnSqz33jUGf/BmBqO+ZHOrSKY7CLhMrSxY+NgjcdGWwI0eDrnAklGfgG6xVFe
+YcXjj9aPB4+CtGJ0pgGM9SCZxETtCyBsJNYuYzowA/tz7VnymD8o3/CnW7bAlkGa
+6YsMibhAKxKl/lBcBDcRz8HPVdj4txME9d5YAmejIDIIRscEQ56LqDBrwvHJ6Gkl
+KQJXUwEiMAjM/wVLxd7zvgHOrR/hipCPe9vnVpYaSO446Z5d85p39BXR2x+vDfiT
+nOsiF+J+Sk13KTt+45gp26lMzY5E03BxKXY3izs7zEl6BZHM+95dC211C6xlojuP
+c8WkUcJuyb0xFS3nUy+dhLBzdW8nKlu/7Hn7uzugUUN4AWX/34+2qTqhNy0d2+Et
+-----END SIGNATURE-----
+r seele AAoQ1DAR6kkoo19hBAX5K0QztNw xlbC5aW8ovDVh2t6VcKF/phheSg 2018-10-14 21:41:22 67.174.243.193 9001 0
+s Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=60 Measured=4
+p reject 1-65535
+id ed25519 ZtzhbIWHJpGQG+5N7hbRTtenyzq2RNJrx0QegtoY+bY
+m 25,26,27,28 sha256=4l7u4Oy1sCG/08MtsHimtWtaC1ydC/GOtCqRD/5ewgs
+r PutoElQueLee293884 AAwffNL+oHO5EdyUoWAOwvEX3ws Ay3cnaaHjnolSRe3ZjKcGlY17Q8 2018-10-14 08:06:47 174.127.217.73 55554 0
+s Fast Guard Running Stable V2Dir Valid
+v Tor 0.3.3.9
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=4950 Measured=4380
+p reject 1-65535
+id ed25519 /58lPWzxxQfwoxed8I9l+D3+hTdOQ6RpgVgM9FCxil4
+m 25,26,27,28 sha256=0/b37HV9enGTzbwrxxg/0R5PfwRtRosXgtU5+vi/gBk
+r CalyxInstitute14 ABG9JIWtRdmE7EFZyI/AZuXjMA4 +8xNQyAVPkKgtLH0AISVZFNuAq0 2018-10-14 11:10:20 162.247.74.201 443 80
+s Exit Fast Guard HSDir Running Stable V2Dir Valid
+v Tor 0.3.3.7
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=10000 Measured=14400
+p accept 20-23,43,53,79-81,88,110,143,194,220,389,443,464,531,543-544,554,563,636,706,749,873,902-904,981,989-995,1194,1220,1293,1500,1533,1677,1723,1755,1863,2082-2083,2086-2087,2095-2096,2102-2104,3128,3389,3690,4321,4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000,8008,8074,8080,8087-8088,8332-8333,8443,8888,9418,9999-10000,11371,12350,19294,19638,23456,33033,64738
+id ed25519 /AcSvVFxaIBxaB5I/7mwdVbMdP6JjDsEPVLU6Hj22a0
+m 25,26,27,28 sha256=7nBT4mYr4H5Jj71Qq7Nan7R7KqWPTRG89Oq1/5m4c1M
+r Neldoreth ABUk3UA9cp8I9+XXeBPvEnVs+o0 nms8ZM18C/K5XzLmgO5fHchjhFc 2018-10-14 19:09:56 185.13.39.197 443 80
+s Fast Guard Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=5242 Measured=13600
+p reject 1-65535
+id ed25519 eZ49J/mRCMtMbD2GDZsjZm/gqqG9z5S6M5GykZmd2ro
+m 25,26,27,28 sha256=IxCWEdRM6Hqk1fiYx+DK9ufUPI49SE6Z4C8onbsBVXo
+r UbuntuCore246 ACenhGJF+i26Aj46wfqAaI8dc7s EmwRg8iMnKJc8GEusZxhsVf9PJI 2018-10-14 23:13:35 212.199.61.38 43511 0
+s Fast V2Dir Valid
+v Tor 0.3.3.10
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=121 Measured=71
+p reject 1-65535
+id ed25519 crWebog3RcKMV49zyu6pF5A/MxQM89x2cKNRK1wbTjM
+m 25,26,27,28 sha256=98nmfhxVBZqk7VUfyXQWpJmLAfWloZtMXu5H9FVt+p8
+r rotor25 ADQsDhVdRULlU5F4iy13nxRXjes m/3hkTP+ETmoYWZ3JrniJAeRHho 2018-10-14 17:19:08 188.24.22.193 9001 9030
+s Fast Running V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=3987 Measured=6730
+p reject 1-65535
+id ed25519 8ybCWdul+E3HdzEb1YR3Fbp6EIwnGGPsUhsSh7FUnfs
+m 25,26,27,28 sha256=ZeMCL1T4SogPYK0iyuzYZoyFx+mp9/Ng8vGmE8wI9rU
+r torbogen AEHgFQsKMHUGwoY+/J8rfjpSOzY DXa3G+iPbGQo4AK2iazVY6R4eKs 2018-10-14 20:19:33 178.142.72.49 9001 9030
+s Fast Running V2Dir Valid
+v Tor 0.3.3.9
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=1275 Measured=1030
+p reject 1-65535
+id ed25519 NzIv9KWavK4f+6426ctrr9UnsFl/Yt8ixEAP6OO/kVo
+m 25,26,27,28 sha256=lFXLHMP6Map0qD298F+eeybL4pqtXDRG7a7r4R7knlw
+r nicolass67atoll AES/YhfxWxj3ZvK1O85bPg4BV+k iK+QhopNtEipMSL+EeI6b/DitVc 2018-10-14 15:48:15 163.172.10.89 9001 0
+s Fast Running Stable V2Dir Valid
+v Tor 0.2.9.16
+pr Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1-2 Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2
+w Bandwidth=1048 Measured=1180
+p reject 1-65535
+id ed25519 ++ezzEs6r+0m0YDwFAjwpIIbNKtzMbKB13osAAoGQ/c
+m 25,26,27,28 sha256=kGlToMt+rD0tL3N+5Plqj8Nav1FVQMiSYpz0adtzs0I
+r zzzzzzzzzzzzzzzzzzz AEVz/pNLpV0H2ucjF3k69OQbdbY 1FUGYNQtQo1MgV4jRP2XJapWemI 2018-10-14 23:43:10 77.12.174.141 9002 9031
+s Fast Running V2Dir Valid
+v Tor 0.3.2.10
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=565 Measured=235
+p reject 1-65535
+id ed25519 QnM3RTCh53wbKiC6N6IsCQHs3pj9J547xUNtrk5FnYE
+m 25,26,27,28 sha256=+1jqHQZNEebT5DFJHZIPE35at9TzFCB5hzqDWAxrXGk
+r helga AFnZKULbO4TlLrTYfp97GVz00AU ngp++P8hEV+Pj/j9OvvDCyuDqKA 2018-10-14 17:09:42 88.99.216.194 9001 9030
+s Fast HSDir Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=409 Measured=328
+p reject 1-65535
+id ed25519 M1nVVuHebCChHayS1hsajAkzpe4c8bEFKP499V3yBG4
+m 25,26,27,28 sha256=//iWXvWzIkvqFqwTf367uZJ2UoNwcrCrLcOs7DY5xvE
+r Torpi AHJ/OiwdDxcxqRJnxBFQecFrILQ bLi/i6JuuPmoWm1aGd73dy2tYlA 2018-10-14 15:51:48 110.146.4.151 9001 9030
+s Running Stable V2Dir Valid
+v Tor 0.2.4.29
+pr Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=1-4 LinkAuth=1 Microdesc=1 Relay=1-2
+w Bandwidth=92 Measured=14
+p reject 1-65535
+id ed25519 none
+m 25,26,27,28 sha256=TiQfeDGlZq6VrCEKFDCA0uvS6hiv8CVMdkLyRxuRZpU
+r VeespRU2 AHTsqCvVi4uxkJycTyN/2XebI/w r3AsIyXKvSFzC+5eDuiUgPTmG9g 2018-10-14 09:25:26 185.22.172.237 443 80
+s Fast Guard HSDir Running Stable V2Dir Valid
+v Tor 0.3.3.7
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=10000 Measured=21300
+p reject 1-65535
+id ed25519 obR35oXyInFbtgitDsbasslq2SNFSTZeI1+2bJfK6HE
+m 25,26,27,28 sha256=+tNKyFkcOSA/CufXMtSfprtve7qkoDx4yjGG/XdH6Ec
+r Quintex13 AHe8unJE2z5qXtJ0boYXAGZoSIc GwoBS5ddT7ktxwqHjHYNc2u53xU 2018-10-14 07:38:44 199.249.223.62 443 80
+s Exit Fast HSDir Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=9426 Measured=4550
+p accept 20-23,43,53,79-81,88,110,143,194,220,389,443,464-465,531,543-544,554,563,587,636,706,749,873,902-904,981,989-995,1194,1220,1293,1500,1533,1677,1723,1755,1863,2082-2083,2086-2087,2095-2096,2102-2104,3128,3389,3690,4321,4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000,8008,8074,8080,8082,8087-8088,8232-8233,8332-8333,8443,8888,9418,9999-10000,11371,19294,19638,50002,64738
+id ed25519 cHUct3VAkb+T4KQQQg9fLuH5feoGyjxang+Jihs0E80
+m 25,26,27,28 sha256=/JQIKi/L0aSZ8uMKc0DsHBeH2OmsvAj9GbXq8i8QQcE
+r UbuntuCore246 AH4Yastcf3fGnzddzt9ZT/1r9pk 9kJql4jbylNMYXhvn4OasP55fck 2018-10-14 11:03:40 77.146.180.194 40523 0
+s V2Dir Valid
+v Tor 0.3.3.10
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=382
+p reject 1-65535
+id ed25519 rSPYL+kdnVmyJ9/Ni96SNABxuvE4DDLwO9l9tqeicoA
+m 25,26,27,28 sha256=hGcbcAmK7x/tNRe7iBBqNlj6sZByhmhTSwWw/wYZo3s
+r torrelay04 AH/ZCOnPz1nmT7pCt1cdLPQBvF0 n5XUymNehFnO8hTftwd4NIozrjQ 2018-10-14 18:52:54 159.69.153.73 9000 0
+a [2a01:4f8:1c1c:5cec::1]:9000
+s Fast Running Stable Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=8030 Measured=8460
+p reject 1-65535
+id ed25519 OJfT1k1R4UB3eNZ7+Mx9MCh9a5RBXqCcqtDfqQw4UvA
+m 25,26,27 sha256=R60ONtNwXwpVOajWfWlH4yQ7a6rBYwgLJ87p5wH55fk
+m 28 sha256=8oP/xai6UYcjJY5gx9fgrLTYY66vgdAr4y2F+R7tqUQ
+r bigamy AH/cDMglli4ShKYaaMk4pmr75XA dMrr7wRTfKoVfCRHLuR0IZFSWsI 2018-10-14 13:28:57 185.24.218.171 9001 8080
+s Exit Fast Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=10000 Measured=21600
+p accept 20-21,23,43,53,79-81,88,110,143,194,220,389,443,464-465,531,543-544,554,563,587,636,706,749,853,873,902-904,981,989-995,1220,1293,1500,1533,1677,1723,1755,1863,2082-2087,2095-2096,2102-2104,2374-2382,3128,3389,3690,4321,4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000-8100,8232-8233,8332-8333,8443,8888,9418,9999-10000,11371,19294,19638,50002,64738
+id ed25519 fTOvB48qkzbEyLpBemkuyBv9UieAUeM8RNfCypusrs0
+m 25,26,27,28 sha256=GpLj/6GejSGtvDxUAUrZgarLCVjbcZVDduWCqvShOBk
+r zech1989 AI57cMO0p1ILW+q4Bnq83I5j8f0 boK7UYxCaZy4lpe8irfYPFsX6SY 2018-10-14 21:18:08 185.243.53.99 9001 9030
+s Fast Guard HSDir Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=9726 Measured=3920
+p reject 1-65535
+id ed25519 wPhoSjgH8xrZ/K2wKhYKd2IR6iQ2/pO4V9rIObVIZ30
+m 25,26,27,28 sha256=hx94DElvCTLaJLuCbKr5rShGxcpKpz8f5eZdYhXwfEQ
+r paris AJf70aisEQPfmPHoXdUsUPAdOtY 9x4Jiv45IDG4EDSSXfOd+Ss9qtc 2018-10-14 22:06:20 178.19.111.147 9001 8000
+s Exit Fast Guard HSDir Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=10000 Measured=31600
+p accept 20-21,23,43,53,79-81,88,110,143,194,220,389,443,464-465,531,543-544,554,563,587,636,706,749,853,873,902-904,981,989-995,1220,1293,1500,1533,1677,1723,1755,1863,2082-2087,2095-2096,2102-2104,2374-2382,3128,3389,3690,4321,4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000-8100,8232-8233,8332-8333,8443,8888,9418,9999-10000,11371,19294,19638,50002,64738
+id ed25519 s49ps0kX/BuncEzm6dG6ZmLs7DCanj/WcXlPVt0D7Tw
+m 25,26,27,28 sha256=L9vWM3tYYI2RLlnofu1m9nMTckLVSKMXftVTDUwidhw
+r jactr AJhR35M3VLAN3odvzkCIzhtJQME RLckbaDnNUH4qGCkOmDo20UwE28 2018-10-14 23:26:36 84.40.112.70 9001 9030
+s Running Stable V2Dir Valid
+v Tor 0.2.9.15
+pr Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1-2 Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2
+w Bandwidth=131 Measured=37
+p reject 1-65535
+id ed25519 +Jc2Ppp+/hmdtaE8T2uxkZIhOD5/kCEfz8gyDCMiDpg
+m 25,26,27,28 sha256=tD+2RMOICoOQRFeZH6D4Kx8mRN4JzhfrtcQs8q78hPA
+r IsThisAGoodIdea AJ2M8WzPcSMatc5VyCoDRG2j+Us Gce3qKSpkK8w2D+9k75V7vNdWzI 2018-10-14 16:51:45 220.233.27.93 9001 0
+s Running Valid
+v Tor 0.3.3.9
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=80 Measured=2
+p reject 1-65535
+id ed25519 peXjh3W6mWzg9iZALqNEDyP3ONQO7Vp9uSzqSszVHOA
+m 25,26,27,28 sha256=1jmLpMLTUMJr2IoHyDpGCrc/ZuBakhVUWivZ7NKwAz4
+directory-footer
+directory-signature ED03BB616EB2F60BEC80151114BB25CEF515B226 E1249D5F87EAD43CD4A48DF9CFCFE810BEEE5287
+-----BEGIN SIGNATURE-----
+sj0ppS2fGOktEGRBXEQJ7KjG+rjBUWNlTUGa9XDbUEbrSDGsKC7ik2r/k0nTCmYI
+FFDmaOhIgpEoz8548RkA0ZdKt8SQQHGHQ82zeDQ8IxLP2bsZj+Jmn/NHN3KwUnpc
+tc+/olUskXOVpKZaoZDXXTzOSypOhV6MHJFQ6AjkGj6BO6VnTVfmdDNf/McEPQvw
+4y6KF3NDjgXoea7sDy6IswuZK+Neia0GPTQVOIit1F06ka2cvOk2gOVEIXqHyGQi
+erJUZfMCbKUIX3IFlhuOgnx9x0Wp/U+Jx8JO+i7NxIyUhR56EY1Go6+mxxRR2wTB
+p1+T39J1+80MEXuRaguLCg==
+-----END SIGNATURE-----
diff --git a/src/test/resources/totalcw/2018-10-15-00-00-00-vote-EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97-38C6A19F78948B689345EE41D7119D76246C4D3E.part b/src/test/resources/totalcw/2018-10-15-00-00-00-vote-EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97-38C6A19F78948B689345EE41D7119D76246C4D3E.part
new file mode 100644
index 0000000..c7ffb53
--- /dev/null
+++ b/src/test/resources/totalcw/2018-10-15-00-00-00-vote-EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97-38C6A19F78948B689345EE41D7119D76246C4D3E.part
@@ -0,0 +1,239 @@
+@type network-status-vote-3 1.0
+network-status-version 3
+vote-status vote
+consensus-methods 25 26 27 28
+published 2018-10-14 23:50:00
+valid-after 2018-10-15 00:00:00
+fresh-until 2018-10-15 01:00:00
+valid-until 2018-10-15 03:00:00
+voting-delay 300 300
+recommended-relay-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=4 Microdesc=1-2 Relay=2
+recommended-client-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=4 Microdesc=1-2 Relay=2
+required-relay-protocols Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=3-4 Microdesc=1 Relay=1-2
+required-client-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=4 Microdesc=1-2 Relay=2
+known-flags Authority BadExit Exit Fast Guard HSDir Running Stable V2Dir Valid
+flag-thresholds stable-uptime=1224875 stable-mtbf=2831347 fast-speed=96000 guard-wfu=98.000% guard-tk=691200 guard-bw-inc-exits=9950000 guard-bw-exc-exits=8290000 enough-mtbf=1 ignoring-advertised-bws=1
+params CircuitPriorityHalflifeMsec=30000 DoSCircuitCreationEnabled=1 DoSConnectionEnabled=1 DoSConnectionMaxConcurrentCount=50 DoSRefuseSingleHopClientRendezvous=1 NumDirectoryGuards=3 NumEntryGuards=1 NumNTorsPerTAP=100 Support022HiddenServices=0 UseNTorHandshake=1 UseOptimisticData=1 bwauthpid=1 cbttestfreq=10 hs_service_max_rdv_failures=1 hsdir_spread_store=4 pb_disablepct=0 usecreatefast=0
+dir-source Faravahar EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97 154.35.175.225 154.35.175.225 80 443
+contact 0x0B47D56D Sina Rabbani (inf0) <sina redteam net>
+shared-rand-participate
+shared-rand-commit 1 sha3-256 EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97 AAAAAFvD2IDD8w9Bf35KfCrPuE49LIm5bKHISapdb3PoYLGv5ZOo6w==
+shared-rand-previous-value 9 Vd2znClwwth89jp91diG/Bs1AH+0ExSgRFmyVOMJwwE=
+shared-rand-current-value 9 oiXRUZGkT26O9aQmu/A52utoBF3gp27h0TvJ4gkDHkw=
+dir-key-certificate-version 3
+fingerprint EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97
+dir-key-published 2018-06-30 16:50:42
+dir-key-expires 2019-06-30 16:50:42
+dir-identity-key
+-----BEGIN RSA PUBLIC KEY-----
+MIIBigKCAYEAwBmqdD+G0q3smN5OBFHCcK5pQH5G1GIpFJ1JxCVEp92tTK4ZHnot
+9RzMfag6zQFqwLaJ+yEb1DOjTdTMfcUTsj5f3GUqPB+U7shSMAvvAAM+Bx/4m1AU
+u6sk4XmPB1bCBfcRl4zhnY6XFIbj0ktuBDblcxHz3lDgHFpBoci9sF59mM14MZ09
+EdwgeckcU5oeq6ApuSlUVaOT8xsKV/yeK4SKaFfDclwPAJuitQ5CpqctP7ExmlrY
+sboTDtz7/Xa6OccaGDEUf7TRlipvUX6rvlmvHm3qjdixVfExpa8E5QG79GZTL82p
+1zBd3iqc6QEnRDTiW9cMUeQt4EvrwOUVVYPWo3hp1C/iiNzWraDays2xuhaSB0gj
+fPatu2CFW5XB2vd9IvIiWeklSFqnF8DL38jDL7DbFiETJreGsDMR03yHWVd0MbPz
+OrvAxG4tJn+JtnwhzlbRjnfk53jOTbiM0vMV8h/ztapCiJeT/6i7nVQ1xL2boeYw
+5RDUlwZaQiaXAgMBAAE=
+-----END RSA PUBLIC KEY-----
+dir-signing-key
+-----BEGIN RSA PUBLIC KEY-----
+MIIBCgKCAQEA5KK/zS8zPPinA7QGbcPEPH90hoagF9EmvvgYjQ8m2L2g15/3WrdQ
+J8Ue0n0JaOb2crZEJ+d1IwbvX9mFpFx4xNsGA3CPg1/RAHwvmB7HctGOO39Y+I4h
+KLyV4AWVPXb7uvWr8Stec9sW6QLwkz2bhW61DvBlCapBzQPPqm2R+dNCUXE6ATQv
+se8jzggu1YHKk5GSLrXUvuWKbZFBb5WKaZjRXAXt3I9jSS61SG6rxJmM7uujfrkF
+UOmb5T83oWCU1zgjyz5bs0j39A7K+FxQPlMUYUe8eyo0dNu6bwSwnm0C6JGmEZXn
+ee+KeQd6Kai8++uOiPWxfqIpSD8XkF0PkwIDAQAB
+-----END RSA PUBLIC KEY-----
+dir-key-crosscert
+-----BEGIN ID SIGNATURE-----
+gVMfY1HWevSFyr5COCTcd3Y7MdSF1WnaSNUQ1YjGrM1TC4leTRAuJ1ds1uQ/jt4w
+0vbM+1X0PRuOeb7o3+Cdn2DAgjeZE5R2BCk/qowXwxHpGwCjsjkCAhHWoiXtNprj
+hUveHhMgESRK5e7l/bXC/EIFnwvmw1caQZdfwMsxzJMU8is3XJGZIEtHgAvk5AzV
+SUL+CamS/S22FdHKzvsxtLM54lWu2IQZh3QYKQnST3kdDg2fVO8eDSn5iROS58oU
+FRB8duf4knv2PGPaCB9SVfM4XouXePZZkC73VEvqnjQSxjYK3yP/msL/bbqwIgu9
+6DE1Rfqw3bfT3/yObotAYg==
+-----END ID SIGNATURE-----
+dir-key-certification
+-----BEGIN SIGNATURE-----
+ZfZ6bRHneOwthgn3/yKeLTixlaj/xVBEtFGrB5sISPAOS3XddBPMR1w2eGMZ5Dlm
+DT6Gd+C4jTOt3KjMo0NiJVpWJBNqb9sBUR0Qkc23urjgvWvy8e9AmaIKFuTrtNCX
+4qHFs3ZDElNwyIQEhLsLTtbKd1/EO0rDUlhKOT1U4rJAc+Vc51FqlFPHON8d34GK
+Qsmj+njIsMV06K1XO9TF4ERtbglzFAw+QjrzEsUSa3EIzmeYAxekiWI/KfG38WuL
+zW+11z0aJbGRtbfQbTbOuq02R3VlpkyxKDwOn0lOCXdUUpN2BK5+noAxyZeESjuN
++1rzNl5QdHgOddQA8rwUeeLYpCublWiDpm231K4q/CrT0+PJTjApfyGY5OK9HSUB
+pF/wudq+aeGT94w6A27YAQufzRYZssJZx/XOD+ptY22ddJMF8d5vvL4ZqAkd6HI6
+V55pW4NgCu6pAliAarFo3y0SUXQaiDSOzJiB9Sm6NyPpVvmVywfUvD7380u6YHf/
+-----END SIGNATURE-----
+r seele AAoQ1DAR6kkoo19hBAX5K0QztNw xlbC5aW8ovDVh2t6VcKF/phheSg 2018-10-14 21:41:22 67.174.243.193 9001 0
+s Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=60 Measured=15
+p reject 1-65535
+id ed25519 ZtzhbIWHJpGQG+5N7hbRTtenyzq2RNJrx0QegtoY+bY
+m 25,26,27,28 sha256=4l7u4Oy1sCG/08MtsHimtWtaC1ydC/GOtCqRD/5ewgs
+r PutoElQueLee293884 AAwffNL+oHO5EdyUoWAOwvEX3ws Ay3cnaaHjnolSRe3ZjKcGlY17Q8 2018-10-14 08:06:47 174.127.217.73 55554 0
+s Fast Guard Running Stable V2Dir Valid
+v Tor 0.3.3.9
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=4950 Measured=5550
+p reject 1-65535
+id ed25519 /58lPWzxxQfwoxed8I9l+D3+hTdOQ6RpgVgM9FCxil4
+m 25,26,27,28 sha256=0/b37HV9enGTzbwrxxg/0R5PfwRtRosXgtU5+vi/gBk
+r CalyxInstitute14 ABG9JIWtRdmE7EFZyI/AZuXjMA4 +8xNQyAVPkKgtLH0AISVZFNuAq0 2018-10-14 11:10:20 162.247.74.201 443 80
+s Exit Fast Guard HSDir Running Stable V2Dir Valid
+v Tor 0.3.3.7
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=10000 Measured=12900
+p accept 20-23,43,53,79-81,88,110,143,194,220,389,443,464,531,543-544,554,563,636,706,749,873,902-904,981,989-995,1194,1220,1293,1500,1533,1677,1723,1755,1863,2082-2083,2086-2087,2095-2096,2102-2104,3128,3389,3690,4321,4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000,8008,8074,8080,8087-8088,8332-8333,8443,8888,9418,9999-10000,11371,12350,19294,19638,23456,33033,64738
+id ed25519 /AcSvVFxaIBxaB5I/7mwdVbMdP6JjDsEPVLU6Hj22a0
+m 25,26,27,28 sha256=7nBT4mYr4H5Jj71Qq7Nan7R7KqWPTRG89Oq1/5m4c1M
+r Neldoreth ABUk3UA9cp8I9+XXeBPvEnVs+o0 nms8ZM18C/K5XzLmgO5fHchjhFc 2018-10-14 19:09:56 185.13.39.197 443 80
+s Fast Guard Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=5242 Measured=8480
+p reject 1-65535
+id ed25519 eZ49J/mRCMtMbD2GDZsjZm/gqqG9z5S6M5GykZmd2ro
+m 25,26,27,28 sha256=IxCWEdRM6Hqk1fiYx+DK9ufUPI49SE6Z4C8onbsBVXo
+r UbuntuCore246 ACenhGJF+i26Aj46wfqAaI8dc7s EmwRg8iMnKJc8GEusZxhsVf9PJI 2018-10-14 23:13:35 212.199.61.38 43511 0
+s Fast V2Dir Valid
+v Tor 0.3.3.10
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=121 Measured=120
+p reject 1-65535
+id ed25519 crWebog3RcKMV49zyu6pF5A/MxQM89x2cKNRK1wbTjM
+m 25,26,27,28 sha256=98nmfhxVBZqk7VUfyXQWpJmLAfWloZtMXu5H9FVt+p8
+r rotor25 ADQsDhVdRULlU5F4iy13nxRXjes m/3hkTP+ETmoYWZ3JrniJAeRHho 2018-10-14 17:19:08 188.24.22.193 9001 9030
+s Fast Running V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=3987 Measured=5580
+p reject 1-65535
+id ed25519 8ybCWdul+E3HdzEb1YR3Fbp6EIwnGGPsUhsSh7FUnfs
+m 25,26,27,28 sha256=ZeMCL1T4SogPYK0iyuzYZoyFx+mp9/Ng8vGmE8wI9rU
+r torbogen AEHgFQsKMHUGwoY+/J8rfjpSOzY DXa3G+iPbGQo4AK2iazVY6R4eKs 2018-10-14 20:19:33 178.142.72.49 9001 9030
+s Fast Running V2Dir Valid
+v Tor 0.3.3.9
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=1275 Measured=1430
+p reject 1-65535
+id ed25519 NzIv9KWavK4f+6426ctrr9UnsFl/Yt8ixEAP6OO/kVo
+m 25,26,27,28 sha256=lFXLHMP6Map0qD298F+eeybL4pqtXDRG7a7r4R7knlw
+r nicolass67atoll AES/YhfxWxj3ZvK1O85bPg4BV+k iK+QhopNtEipMSL+EeI6b/DitVc 2018-10-14 15:48:15 163.172.10.89 9001 0
+s Fast Running Stable V2Dir Valid
+v Tor 0.2.9.16
+pr Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1-2 Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2
+w Bandwidth=1048 Measured=773
+p reject 1-65535
+id ed25519 ++ezzEs6r+0m0YDwFAjwpIIbNKtzMbKB13osAAoGQ/c
+m 25,26,27,28 sha256=kGlToMt+rD0tL3N+5Plqj8Nav1FVQMiSYpz0adtzs0I
+r zzzzzzzzzzzzzzzzzzz AEVz/pNLpV0H2ucjF3k69OQbdbY 1FUGYNQtQo1MgV4jRP2XJapWemI 2018-10-14 23:43:10 77.12.174.141 9002 9031
+s Fast Running V2Dir Valid
+v Tor 0.3.2.10
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=565 Measured=189
+p reject 1-65535
+id ed25519 QnM3RTCh53wbKiC6N6IsCQHs3pj9J547xUNtrk5FnYE
+m 25,26,27,28 sha256=+1jqHQZNEebT5DFJHZIPE35at9TzFCB5hzqDWAxrXGk
+r helga AFnZKULbO4TlLrTYfp97GVz00AU ngp++P8hEV+Pj/j9OvvDCyuDqKA 2018-10-14 17:09:42 88.99.216.194 9001 9030
+s Fast HSDir Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=409 Measured=254
+p reject 1-65535
+id ed25519 M1nVVuHebCChHayS1hsajAkzpe4c8bEFKP499V3yBG4
+m 25,26,27,28 sha256=//iWXvWzIkvqFqwTf367uZJ2UoNwcrCrLcOs7DY5xvE
+r Torpi AHJ/OiwdDxcxqRJnxBFQecFrILQ bLi/i6JuuPmoWm1aGd73dy2tYlA 2018-10-14 15:51:48 110.146.4.151 9001 9030
+s Running Stable V2Dir Valid
+v Tor 0.2.4.29
+pr Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=1-4 LinkAuth=1 Microdesc=1 Relay=1-2
+w Bandwidth=92 Measured=31
+p reject 1-65535
+id ed25519 none
+m 25,26,27,28 sha256=TiQfeDGlZq6VrCEKFDCA0uvS6hiv8CVMdkLyRxuRZpU
+r VeespRU2 AHTsqCvVi4uxkJycTyN/2XebI/w r3AsIyXKvSFzC+5eDuiUgPTmG9g 2018-10-14 09:25:26 185.22.172.237 443 80
+s Fast Guard HSDir Running Stable V2Dir Valid
+v Tor 0.3.3.7
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=10000 Measured=26700
+p reject 1-65535
+id ed25519 obR35oXyInFbtgitDsbasslq2SNFSTZeI1+2bJfK6HE
+m 25,26,27,28 sha256=+tNKyFkcOSA/CufXMtSfprtve7qkoDx4yjGG/XdH6Ec
+r Quintex13 AHe8unJE2z5qXtJ0boYXAGZoSIc GwoBS5ddT7ktxwqHjHYNc2u53xU 2018-10-14 07:38:44 199.249.223.62 443 80
+s Exit Fast HSDir Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=9426 Measured=5000
+p accept 20-23,43,53,79-81,88,110,143,194,220,389,443,464-465,531,543-544,554,563,587,636,706,749,873,902-904,981,989-995,1194,1220,1293,1500,1533,1677,1723,1755,1863,2082-2083,2086-2087,2095-2096,2102-2104,3128,3389,3690,4321,4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000,8008,8074,8080,8082,8087-8088,8232-8233,8332-8333,8443,8888,9418,9999-10000,11371,19294,19638,50002,64738
+id ed25519 cHUct3VAkb+T4KQQQg9fLuH5feoGyjxang+Jihs0E80
+m 25,26,27,28 sha256=/JQIKi/L0aSZ8uMKc0DsHBeH2OmsvAj9GbXq8i8QQcE
+r UbuntuCore246 AH4Yastcf3fGnzddzt9ZT/1r9pk 9kJql4jbylNMYXhvn4OasP55fck 2018-10-14 11:03:40 77.146.180.194 40523 0
+s V2Dir Valid
+v Tor 0.3.3.10
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=382 Measured=19
+p reject 1-65535
+id ed25519 rSPYL+kdnVmyJ9/Ni96SNABxuvE4DDLwO9l9tqeicoA
+m 25,26,27,28 sha256=hGcbcAmK7x/tNRe7iBBqNlj6sZByhmhTSwWw/wYZo3s
+r torrelay04 AH/ZCOnPz1nmT7pCt1cdLPQBvF0 n5XUymNehFnO8hTftwd4NIozrjQ 2018-10-14 18:52:54 159.69.153.73 9000 0
+a [2a01:4f8:1c1c:5cec::1]:9000
+s Fast Running Stable Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=8030 Measured=9010
+p reject 1-65535
+id ed25519 OJfT1k1R4UB3eNZ7+Mx9MCh9a5RBXqCcqtDfqQw4UvA
+m 25,26,27 sha256=R60ONtNwXwpVOajWfWlH4yQ7a6rBYwgLJ87p5wH55fk
+m 28 sha256=8oP/xai6UYcjJY5gx9fgrLTYY66vgdAr4y2F+R7tqUQ
+r bigamy AH/cDMglli4ShKYaaMk4pmr75XA dMrr7wRTfKoVfCRHLuR0IZFSWsI 2018-10-14 13:28:57 185.24.218.171 9001 8080
+s Exit Fast Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=10000 Measured=30400
+p accept 20-21,23,43,53,79-81,88,110,143,194,220,389,443,464-465,531,543-544,554,563,587,636,706,749,853,873,902-904,981,989-995,1220,1293,1500,1533,1677,1723,1755,1863,2082-2087,2095-2096,2102-2104,2374-2382,3128,3389,3690,4321,4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000-8100,8232-8233,8332-8333,8443,8888,9418,9999-10000,11371,19294,19638,50002,64738
+id ed25519 fTOvB48qkzbEyLpBemkuyBv9UieAUeM8RNfCypusrs0
+m 25,26,27,28 sha256=GpLj/6GejSGtvDxUAUrZgarLCVjbcZVDduWCqvShOBk
+r zech1989 AI57cMO0p1ILW+q4Bnq83I5j8f0 boK7UYxCaZy4lpe8irfYPFsX6SY 2018-10-14 21:18:08 185.243.53.99 9001 9030
+s Fast Guard HSDir Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=9726 Measured=11300
+p reject 1-65535
+id ed25519 wPhoSjgH8xrZ/K2wKhYKd2IR6iQ2/pO4V9rIObVIZ30
+m 25,26,27,28 sha256=hx94DElvCTLaJLuCbKr5rShGxcpKpz8f5eZdYhXwfEQ
+r paris AJf70aisEQPfmPHoXdUsUPAdOtY 9x4Jiv45IDG4EDSSXfOd+Ss9qtc 2018-10-14 22:06:20 178.19.111.147 9001 8000
+s Exit Fast Guard HSDir Running Stable V2Dir Valid
+v Tor 0.3.4.8
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=10000 Measured=40700
+p accept 20-21,23,43,53,79-81,88,110,143,194,220,389,443,464-465,531,543-544,554,563,587,636,706,749,853,873,902-904,981,989-995,1220,1293,1500,1533,1677,1723,1755,1863,2082-2087,2095-2096,2102-2104,2374-2382,3128,3389,3690,4321,4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000-8100,8232-8233,8332-8333,8443,8888,9418,9999-10000,11371,19294,19638,50002,64738
+id ed25519 s49ps0kX/BuncEzm6dG6ZmLs7DCanj/WcXlPVt0D7Tw
+m 25,26,27,28 sha256=L9vWM3tYYI2RLlnofu1m9nMTckLVSKMXftVTDUwidhw
+r jactr AJhR35M3VLAN3odvzkCIzhtJQME RLckbaDnNUH4qGCkOmDo20UwE28 2018-10-14 23:26:36 84.40.112.70 9001 9030
+s Running Stable V2Dir Valid
+v Tor 0.2.9.15
+pr Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1-2 Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2
+w Bandwidth=131 Measured=77
+p reject 1-65535
+id ed25519 +Jc2Ppp+/hmdtaE8T2uxkZIhOD5/kCEfz8gyDCMiDpg
+m 25,26,27,28 sha256=tD+2RMOICoOQRFeZH6D4Kx8mRN4JzhfrtcQs8q78hPA
+r IsThisAGoodIdea AJ2M8WzPcSMatc5VyCoDRG2j+Us Gce3qKSpkK8w2D+9k75V7vNdWzI 2018-10-14 16:51:45 220.233.27.93 9001 0
+s Running Valid
+v Tor 0.3.3.9
+pr Cons=1-2 Desc=1-2 DirCache=1-2 HSDir=1-2 HSIntro=3-4 HSRend=1-2 Link=1-5 LinkAuth=1,3 Microdesc=1-2 Relay=1-2
+w Bandwidth=80 Measured=6
+p reject 1-65535
+id ed25519 peXjh3W6mWzg9iZALqNEDyP3ONQO7Vp9uSzqSszVHOA
+m 25,26,27,28 sha256=1jmLpMLTUMJr2IoHyDpGCrc/ZuBakhVUWivZ7NKwAz4
+directory-footer
+directory-signature EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97 517095062288D1B5C7BD6517A677C586D996818B
+-----BEGIN SIGNATURE-----
+RdWrXeRxh4lZB1oTsOTy+AcYFruKg6Xzfl/D/srqmSfihcLoBIwWXePe2aLDycTj
+fcLPRt3HUnHzII61reqsVKzDXGAybMdYpPE9kygA60INsyAYFAdigMEDwY0C2qIZ
+MMJrSyORovHh4YOQHoesZPDgCUuYkuAjheKwwxdJ4O6mcYJyusm9OWVy8Iej4lKW
+ySFXC6sQt3L81MVAE24eBA1eTmUUf3fWpK/UbD0GjHYD8J+qgxm8hDwrYwVUQKmU
+u3RuRcuPYfC0tPgKLNB8sHP2o292q026M8RbcE0GnvPBQS6r2uIDfBfQLETwg0do
+h7HVCq11GZIdYe4eCZu8rQ==
+-----END SIGNATURE-----
1
0

09 Nov '19
commit 2cf14cb0f2bdc1383503b1ec5d90a518333d9e54
Author: Iain R. Learmonth <irl(a)fsfe.org>
Date: Mon Oct 15 17:52:00 2018 +0100
Update ExoneraTor URL on home page
Fixes: #28049
---
src/main/resources/web/jsps/index.jsp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/resources/web/jsps/index.jsp b/src/main/resources/web/jsps/index.jsp
index 5968727..3fa49b8 100644
--- a/src/main/resources/web/jsps/index.jsp
+++ b/src/main/resources/web/jsps/index.jsp
@@ -73,7 +73,7 @@
</div>
<div class="col-sm-4">
- <a href="https://exonerator.torproject.org/"><i class="fa fa-history fa-fw fa-4x" aria-hidden="true"></i> <h3>Network Archive</h3> <p>Look up if a particular IP address was used as a Tor relay on a particular date.</p></a>
+ <a href="/exonerator.html"><i class="fa fa-history fa-fw fa-4x" aria-hidden="true"></i> <h3>Network Archive</h3> <p>Look up if a particular IP address was used as a Tor relay on a particular date.</p></a>
</div>
</div>
1
0

[metrics-web/release] Compute percentiles in clients module using linear interpolation.
by karsten@torproject.org 09 Nov '19
by karsten@torproject.org 09 Nov '19
09 Nov '19
commit 95d091a87c3ebff8a35a57967b4e144898e2e1a5
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Sat May 19 21:02:28 2018 +0200
Compute percentiles in clients module using linear interpolation.
So far we computed percentiles in the censorship detector of the
clients module using our own formula that picked existing values as
median or quartiles.
Now we're using numpy.percentile which, by default, uses linear
interpolation when the desired quantile lies between two data points.
We're also removing unused imports of pylab and matplotlib in this
commit, which is not directly related.
Implements part of #26035.
---
src/main/python/clients/detector.py | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/src/main/python/clients/detector.py b/src/main/python/clients/detector.py
index 9944710..b0a98af 100644
--- a/src/main/python/clients/detector.py
+++ b/src/main/python/clients/detector.py
@@ -37,13 +37,9 @@
## This script reads a .csv file of the number of Tor users and finds
## anomalies that might be indicative of censorship.
-# Dep: matplotlib
-from pylab import *
-import matplotlib
-
# Dep: numpy
import numpy
-from numpy import mean, std
+from numpy import mean, std, percentile
# Dep: scipy
import scipy.stats
@@ -190,9 +186,9 @@ def make_tendencies_minmax(l, INTERVAL = 1):
maxx += [None]
else:
vals.sort()
- median = vals[len(vals)/2]
- q1 = vals[len(vals)/4]
- q2 = vals[(3*len(vals))/4]
+ median = percentile(vals, 50)
+ q1 = percentile(vals, 25)
+ q2 = percentile(vals, 75)
qd = q2 - q1
vals = [v for v in vals if median - qd*4 < v and v < median + qd*4]
if len(vals) < 8:
1
0

[metrics-web/release] Handle days without any successful measurements.
by karsten@torproject.org 09 Nov '19
by karsten@torproject.org 09 Nov '19
09 Nov '19
commit 0408b73e647a0207f6b6db486d46f1c9993323b6
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Mon Oct 22 09:42:41 2018 +0200
Handle days without any successful measurements.
In the past, there have been days without a single OnionPerf
successful measurement to onion services. Despite the fact that we
seriously need better monitoring, we also need to treat these results
correctly. So far, we'd have computed median and quartile values of
0.0 seconds for these cases, which is just wrong. We need to treat
these days as missing values when it comes to medians or quartiles.
That's what we're fixing here.
Fixes #28136.
---
src/main/sql/onionperf/init-onionperf.sql | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/src/main/sql/onionperf/init-onionperf.sql b/src/main/sql/onionperf/init-onionperf.sql
index fad6bef..49eff4a 100644
--- a/src/main/sql/onionperf/init-onionperf.sql
+++ b/src/main/sql/onionperf/init-onionperf.sql
@@ -58,9 +58,9 @@ SELECT date,
filesize,
source,
server,
- q[1] AS q1,
- q[2] AS md,
- q[3] AS q3,
+ CASE WHEN q IS NULL THEN NULL ELSE q[1] END AS q1,
+ CASE WHEN q IS NULL THEN NULL ELSE q[2] END AS md,
+ CASE WHEN q IS NULL THEN NULL ELSE q[3] END AS q3,
timeouts,
failures,
requests
@@ -70,7 +70,9 @@ SELECT DATE(start) AS date,
source,
CASE WHEN endpointremote LIKE '%.onion%' THEN 'onion'
ELSE 'public' END AS server,
- PERCENTILE_CONT(ARRAY[0.25,0.5,0.75]) WITHIN GROUP(ORDER BY datacomplete) AS q,
+ CASE WHEN COUNT(*) > 0 THEN
+ PERCENTILE_CONT(ARRAY[0.25,0.5,0.75]) WITHIN GROUP(ORDER BY datacomplete)
+ ELSE NULL END AS q,
COUNT(CASE WHEN didtimeout OR datacomplete < 1 THEN 1 ELSE NULL END)
AS timeouts,
COUNT(CASE WHEN NOT didtimeout AND datacomplete >= 1
@@ -85,7 +87,9 @@ SELECT DATE(start) AS date,
'' AS source,
CASE WHEN endpointremote LIKE '%.onion%' THEN 'onion'
ELSE 'public' END AS server,
- PERCENTILE_CONT(ARRAY[0.25,0.5,0.75]) WITHIN GROUP(ORDER BY datacomplete) AS q,
+ CASE WHEN COUNT(*) > 0 THEN
+ PERCENTILE_CONT(ARRAY[0.25,0.5,0.75]) WITHIN GROUP(ORDER BY datacomplete)
+ ELSE NULL END AS q,
COUNT(CASE WHEN didtimeout OR datacomplete < 1 THEN 1 ELSE NULL END)
AS timeouts,
COUNT(CASE WHEN NOT didtimeout AND datacomplete >= 1
1
0

[metrics-web/release] Move "We're hiring" link from navbar to start page.
by karsten@torproject.org 09 Nov '19
by karsten@torproject.org 09 Nov '19
09 Nov '19
commit debe0cfb3ee8fe6fdb8c234479adc729fe42dcb3
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Wed Nov 7 23:45:00 2018 +0100
Move "We're hiring" link from navbar to start page.
Suggested by antonela on #28349.
---
src/main/resources/web/jsps/index.jsp | 5 +++++
src/main/resources/web/jsps/top.jsp | 1 -
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/main/resources/web/jsps/index.jsp b/src/main/resources/web/jsps/index.jsp
index 3fa49b8..fbccd7d 100644
--- a/src/main/resources/web/jsps/index.jsp
+++ b/src/main/resources/web/jsps/index.jsp
@@ -6,6 +6,11 @@
</jsp:include>
<div class="container">
+
+ <div class="col-12 alert alert-success" role="alert">
+ <strong>We're hiring</strong> – The Tor Project is seeking an experienced Data Architect to take our metrics work to the next level. <a href="https://www.torproject.org/about/jobs-metrics-data-architect.html.en" target="_blank">Read the job posting.</a>
+ </div>
+
<div class="jumbotron">
<h1>Welcome to Tor Metrics!</h1>
<div class="row">
diff --git a/src/main/resources/web/jsps/top.jsp b/src/main/resources/web/jsps/top.jsp
index 96b3480..440b2ee 100644
--- a/src/main/resources/web/jsps/top.jsp
+++ b/src/main/resources/web/jsps/top.jsp
@@ -89,7 +89,6 @@ document.write('<div class="topButton" style="display:none;"><a href="#top"><i c
<!-- secondary navigation items -->
<li class="visible-xs section-header">More</li>
- <li><a href="https://www.torproject.org/about/jobs-metrics-data-architect.html.en"><i class="fa fa-bullhorn fa-fw hidden-sm" aria-hidden="true"></i> We Are Hiring!</a></li>
<li <c:if test="${'News'.equals(param.navActive)}"> class="active"</c:if>><a href="/news.html"><i class="fa fa-newspaper-o fa-fw hidden-sm" aria-hidden="true"></i> News</a></li>
<li <c:if test="${'Sources'.equals(param.navActive)}"> class="active"</c:if>><a href="/sources.html"><i class="fa fa-archive fa-fw hidden-sm" aria-hidden="true"></i> Sources</a></li>
<li <c:if test="${'Services'.equals(param.navActive)}"> class="active"</c:if>><a href="/services.html"><i class="fa fa-cogs fa-fw hidden-sm" aria-hidden="true"></i> Services</a></li>
1
0
commit b1758e3d6fa4bb6ea5ec5a5f2da81b289ed7ef42
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Sun Oct 21 11:39:52 2018 +0200
Add 0.3.5 to known versions.
Fixes #28126.
---
src/main/R/rserver/graphs.R | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/main/R/rserver/graphs.R b/src/main/R/rserver/graphs.R
index 12915fa..60d905f 100644
--- a/src/main/R/rserver/graphs.R
+++ b/src/main/R/rserver/graphs.R
@@ -400,7 +400,8 @@ plot_versions <- function(start_p, end_p, path_p) {
s <- prepare_versions(start_p, end_p)
known_versions <- c("Other", "0.1.0", "0.1.1", "0.1.2", "0.2.0",
"0.2.1", "0.2.2", "0.2.3", "0.2.4", "0.2.5", "0.2.6", "0.2.7",
- "0.2.8", "0.2.9", "0.3.0", "0.3.1", "0.3.2", "0.3.3", "0.3.4")
+ "0.2.8", "0.2.9", "0.3.0", "0.3.1", "0.3.2", "0.3.3", "0.3.4",
+ "0.3.5")
getPalette <- colorRampPalette(brewer.pal(12, "Paired"))
colours <- data.frame(breaks = known_versions,
values = rep(brewer.pal(min(12, length(known_versions)), "Paired"),
1
0

[metrics-web/release] Compute percentiles in advbwdist module using linear interpolation.
by karsten@torproject.org 09 Nov '19
by karsten@torproject.org 09 Nov '19
09 Nov '19
commit dda2b51eaa6efdd871d5190f4b86fad008beee42
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Sat May 19 21:08:42 2018 +0200
Compute percentiles in advbwdist module using linear interpolation.
So far we computed percentiles in the advbwdist module using our own
formula that picked existing values for the various percentiles we
provided.
Now we're using Apache Commons Math with method R_7 which uses linear
interpolation when a percentile lies between two data points.
Implements another part of #26035.
---
build.xml | 1 +
.../torproject/metrics/stats/advbwdist/Main.java | 49 ++++++++++++---
.../metrics/stats/advbwdist/MainTest.java | 72 ++++++++++++++++++++++
3 files changed, 112 insertions(+), 10 deletions(-)
diff --git a/build.xml b/build.xml
index 3614d78..b498ea8 100644
--- a/build.xml
+++ b/build.xml
@@ -60,6 +60,7 @@
<patternset refid="common" />
<include name="metrics-lib-${metricslibversion}.jar"/>
<include name="commons-compress-1.13.jar"/>
+ <include name="commons-math3-3.6.1.jar"/>
<include name="postgresql-9.4.1212.jar"/>
<include name="servlet-api-3.1.jar"/>
<include name="xz-1.6.jar"/>
diff --git a/src/main/java/org/torproject/metrics/stats/advbwdist/Main.java b/src/main/java/org/torproject/metrics/stats/advbwdist/Main.java
index a16e7b0..7216581 100644
--- a/src/main/java/org/torproject/metrics/stats/advbwdist/Main.java
+++ b/src/main/java/org/torproject/metrics/stats/advbwdist/Main.java
@@ -10,6 +10,8 @@ import org.torproject.descriptor.NetworkStatusEntry;
import org.torproject.descriptor.RelayNetworkStatusConsensus;
import org.torproject.descriptor.ServerDescriptor;
+import org.apache.commons.math3.stat.descriptive.rank.Percentile;
+
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
@@ -20,7 +22,9 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.SortedMap;
import java.util.TimeZone;
+import java.util.TreeMap;
public class Main {
@@ -115,29 +119,54 @@ public class Main {
}
/* 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) {
+ for (Map.Entry<Integer, Long> e : computePercentiles(
+ advertisedBandwidthsAllRelays, percentiles).entrySet()) {
bw.write(String.format("%s,,,%d,%d%n", validAfter,
- percentile, advertisedBandwidthsAllRelays.get(
- ((advertisedBandwidthsAllRelays.size() - 1)
- * percentile) / 100)));
+ e.getKey(), e.getValue()));
}
}
if (!advertisedBandwidthsExitsOnly.isEmpty()) {
- for (int percentile : percentiles) {
+ for (Map.Entry<Integer, Long> e : computePercentiles(
+ advertisedBandwidthsExitsOnly, percentiles).entrySet()) {
bw.write(String.format("%s,TRUE,,%d,%d%n", validAfter,
- percentile, advertisedBandwidthsExitsOnly.get(
- ((advertisedBandwidthsExitsOnly.size() - 1)
- * percentile) / 100)));
+ e.getKey(), e.getValue()));
}
}
}
descriptorReader.saveHistoryFile(historyFile);
bw.close();
}
+
+ /** Compute percentiles (between 0 and 100) for the given list of values, and
+ * return a map with percentiles as keys and computed values as values. If the
+ * list of values is empty, the returned map contains all zeros. */
+ static SortedMap<Integer, Long> computePercentiles(
+ List<Long> valueList, int ... percentiles) {
+ SortedMap<Integer, Long> computedPercentiles = new TreeMap<>();
+ double[] valueArray = new double[valueList.size()];
+ long minValue = Long.MAX_VALUE;
+ for (int i = 0; i < valueList.size(); i++) {
+ valueArray[i] = valueList.get(i).doubleValue();
+ minValue = Math.min(minValue, valueList.get(i));
+ }
+ if (valueList.isEmpty()) {
+ minValue = 0L;
+ }
+ Percentile percentile = new Percentile()
+ .withEstimationType(Percentile.EstimationType.R_7);
+ percentile.setData(valueArray);
+ for (int p : percentiles) {
+ if (0 == p) {
+ computedPercentiles.put(p, minValue);
+ } else {
+ computedPercentiles.put(p,
+ (long) Math.floor(percentile.evaluate((double) p)));
+ }
+ }
+ return computedPercentiles;
+ }
}
diff --git a/src/test/java/org/torproject/metrics/stats/advbwdist/MainTest.java b/src/test/java/org/torproject/metrics/stats/advbwdist/MainTest.java
new file mode 100644
index 0000000..92752ef
--- /dev/null
+++ b/src/test/java/org/torproject/metrics/stats/advbwdist/MainTest.java
@@ -0,0 +1,72 @@
+/* Copyright 2018 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.stats.advbwdist;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.SortedMap;
+
+public class MainTest {
+
+ @Test
+ public void testComputePercentilesZeroValues() {
+ List<Long> valueList = new ArrayList<>();
+ SortedMap<Integer, Long> computedPercentiles = Main.computePercentiles(
+ valueList, 0, 25, 50, 75, 100);
+ assertEquals(0L, (long) computedPercentiles.get(0));
+ assertEquals(0L, (long) computedPercentiles.get(25));
+ assertEquals(0L, (long) computedPercentiles.get(50));
+ assertEquals(0L, (long) computedPercentiles.get(75));
+ assertEquals(0L, (long) computedPercentiles.get(100));
+ }
+
+ @Test
+ public void testComputePercentilesTenValues() {
+ List<Long> valueList = new ArrayList<>();
+ valueList.add(3L);
+ valueList.add(6L);
+ valueList.add(7L);
+ valueList.add(8L);
+ valueList.add(8L);
+ valueList.add(10L);
+ valueList.add(13L);
+ valueList.add(15L);
+ valueList.add(16L);
+ valueList.add(20L);
+ SortedMap<Integer, Long> computedPercentiles = Main.computePercentiles(
+ valueList, 0, 25, 50, 75, 100);
+ assertEquals(3L, (long) computedPercentiles.get(0));
+ assertEquals(7L, (long) computedPercentiles.get(25));
+ assertEquals(9L, (long) computedPercentiles.get(50));
+ assertEquals(14L, (long) computedPercentiles.get(75));
+ assertEquals(20L, (long) computedPercentiles.get(100));
+ }
+
+ @Test
+ public void testComputePercentilesElevenValues() {
+ List<Long> valueList = new ArrayList<>();
+ valueList.add(3L);
+ valueList.add(6L);
+ valueList.add(7L);
+ valueList.add(8L);
+ valueList.add(8L);
+ valueList.add(9L);
+ valueList.add(10L);
+ valueList.add(13L);
+ valueList.add(15L);
+ valueList.add(16L);
+ valueList.add(20L);
+ SortedMap<Integer, Long> computedPercentiles = Main.computePercentiles(
+ valueList, 0, 25, 50, 75, 100);
+ assertEquals(3L, (long) computedPercentiles.get(0));
+ assertEquals(7L, (long) computedPercentiles.get(25));
+ assertEquals(9L, (long) computedPercentiles.get(50));
+ assertEquals(14L, (long) computedPercentiles.get(75));
+ assertEquals(20L, (long) computedPercentiles.get(100));
+ }
+}
1
0