tor-commits
Threads by month
- ----- 2025 -----
- October
- September
- August
- July
- June
- 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
July 2011
- 16 participants
- 868 discussions
commit f47735e2714a76af3d5c49f1687bdcf9dde0a802
Merge: 4d17699 88926c8
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Mon Jul 4 09:45:13 2011 +0200
Merge branch 'task-2911'
task-2911/.gitignore | 9 +
task-2911/README | 143 ++++++
task-2911/SimulateStableGuard.java | 842 ++++++++++++++++++++++++++++++++++++
task-2911/report.bib | 31 ++
task-2911/report.tex | 534 +++++++++++++++++++++++
task-2911/stability.R | 301 +++++++++++++
6 files changed, 1860 insertions(+), 0 deletions(-)
1
0
[metrics-tasks/master] Add simulation code and LaTeX sources of #2911 draft.
by karsten@torproject.org 04 Jul '11
by karsten@torproject.org 04 Jul '11
04 Jul '11
commit cff86510fd86e73a2f66f21ac8b47068d187d972
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Mon May 30 11:16:10 2011 +0200
Add simulation code and LaTeX sources of #2911 draft.
---
task-2911/.gitignore | 9 +
task-2911/README | 105 ++++++
.../mtbf-sim/SimulateMeanTimeBetweenFailure.java | 351 ++++++++++++++++++++
task-2911/mtbf-sim/mtbf-sim.R | 73 ++++
task-2911/report.tex | 295 ++++++++++++++++
.../wfu-sim/SimulateWeightedFractionalUptime.java | 314 +++++++++++++++++
task-2911/wfu-sim/wfu-sim.R | 57 ++++
7 files changed, 1204 insertions(+), 0 deletions(-)
diff --git a/task-2911/.gitignore b/task-2911/.gitignore
new file mode 100644
index 0000000..d2480c1
--- /dev/null
+++ b/task-2911/.gitignore
@@ -0,0 +1,9 @@
+*.class
+mtbf-sim/tunf/
+wfu-sim/fwfu/
+wfu-sim/consensuses/
+*.csv
+*.aux
+*.log
+*.pdf
+
diff --git a/task-2911/README b/task-2911/README
new file mode 100644
index 0000000..bcefa2d
--- /dev/null
+++ b/task-2911/README
@@ -0,0 +1,105 @@
+Tech report: An Analysis of Tor Relay Stability
+===============================================
+
+Simulation of MTBF requirements
+-------------------------------
+
+Change to the MTBF simulation directory:
+
+ $ cd mtbf-sim/
+
+Export status entries and server descriptor parts from the metrics
+database, once in reverse and once in forward order. Note that each file
+will be 2.2G large for roughly 2.5 years of data. Plan for a buffer of at
+least 4 months before and after the interval to investigate:
+
+ tordir=> \o running-relays-reverse.csv
+ tordir=> SELECT statusentry.validafter,
+ statusentry.fingerprint,
+ CASE WHEN descriptor.uptime IS NULL THEN FALSE ELSE
+ statusentry.validafter - descriptor.published +
+ descriptor.uptime * '1 second'::INTERVAL <
+ '01:00:00'::INTERVAL END AS restarted
+ FROM statusentry
+ LEFT JOIN descriptor
+ ON statusentry.descriptor = descriptor.descriptor
+ WHERE statusentry.isrunning
+ AND statusentry.validafter >= '2009-01-01 00:00:00'
+ ORDER BY statusentry.validafter DESC, statusentry.fingerprint;
+ tordir=> \o
+ tordir=> \o running-relays-forward.csv
+ tordir=> SELECT statusentry.validafter,
+ statusentry.fingerprint,
+ CASE WHEN descriptor.uptime IS NULL THEN FALSE ELSE
+ statusentry.validafter - descriptor.published +
+ descriptor.uptime * '1 second'::INTERVAL <
+ '01:00:00'::INTERVAL END AS restarted
+ FROM statusentry
+ LEFT JOIN descriptor
+ ON statusentry.descriptor = descriptor.descriptor
+ WHERE statusentry.isrunning
+ AND statusentry.validafter >= '2009-01-01 00:00:00'
+ ORDER BY statusentry.validafter, statusentry.fingerprint;
+ tordir=> \o
+
+Run the simulation consisting of a reverse and a forward run. The results
+of the reverse run will be stored to the tunf/ directory and will be
+re-used in subsequent simulations. Delete the tunf/ directory to repeat
+the reverse run, too.
+
+ $ javac SimulateMeanTimeBetweenFailure.java
+ $ java SimulateMeanTimeBetweenFailure
+
+Plot the results:
+
+ $ R --slave -f mtbf-sim.R
+
+Once you're satisfied with the result, copy the graph to the parent
+directory to include it in the report:
+
+ $ cp mtbf-sim.pdf ../
+
+
+Simulation of WFU requirements
+------------------------------
+
+Change to the WFU simulation directory:
+
+ $ cd wfu-sim/
+
+Create a consensuses/ directory and put the consensus files of the
+interval to investigate plus 4+ months before and 4+ months after in it:
+
+ $ mkdir consensuses/
+ $ ln -s $extracted/consensuses-20* .
+
+Run the simulation that first parses consensuses from last to first and
+then from first to last. The results from the reverse direction will be
+stored in the fwfu/ directory and re-used in subsequent simulations.
+Delete the fwfu/ directory to re-run both simulation parts.
+
+ $ javac SimulateWeightedFractionalUptime.java
+ $ java SimulateWeightedFractionalUptime
+
+Plot the results:
+
+ $ R --slave -f wfu-sim.R
+
+Copy the graph to the parent directory to include it in the report:
+
+ $ cp wfu-sim.pdf ../
+
+
+Compiling the report
+--------------------
+
+Copy the generated graphs to the base directory, unless you have done so
+before:
+
+ $ cp mtbf-sim/mtbf-sim.pdf .
+ $ cp wfu-sim/wfu-sim.pdf .
+
+Compile the report:
+
+ $ pdflatex report.tex
+
diff --git a/task-2911/mtbf-sim/SimulateMeanTimeBetweenFailure.java b/task-2911/mtbf-sim/SimulateMeanTimeBetweenFailure.java
new file mode 100644
index 0000000..cd73f82
--- /dev/null
+++ b/task-2911/mtbf-sim/SimulateMeanTimeBetweenFailure.java
@@ -0,0 +1,351 @@
+/**
+ * Simulate variation of mean time between failure on Stable relays. The
+ * simulation is based on the previously generated SQL results containing
+ * network status entries and parts of server descriptors. In a first
+ * step, parse the SQL results that are in descending order to calculate
+ * time until next failure for all relays and write them to disk as one
+ * file per network status in tunf/$filename. (Skip this step if there is
+ * already a tunf/ directory.) In a second step, parse the network
+ * statuses again, but this time from first to last, calculate mean times
+ * between failure for all relays, form relay subsets based on minimal
+ * MTBF, look up what the time until next failure would be for a subset,
+ * and write results to mtbf-sim.csv to disk. */
+import java.io.*;
+import java.text.*;
+import java.util.*;
+public class SimulateMeanTimeBetweenFailure {
+ public static void main(String[] args) throws Exception {
+
+ /* Measure how long this execution takes. */
+ long started = System.currentTimeMillis();
+
+ /* Decide whether we need to do the reverse run, or if we can use
+ * previous results. */
+ if (!new File("tunf").exists()) {
+
+ /* For each relay as identified by its hex encoded fingerprint,
+ * track time until next failure in seconds in a long. */
+ SortedMap<String, Long> knownRelays = new TreeMap<String, Long>();
+
+ /* Parse previously exported network status entries in reverse
+ * order. */
+ SimpleDateFormat formatter = new SimpleDateFormat(
+ "yyyy-MM-dd-HH-mm-ss");
+ formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+ SimpleDateFormat isoFormatter = new SimpleDateFormat(
+ "yyyy-MM-dd HH:mm:ss");
+ isoFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+ Map<String, String> runningRelays = new HashMap<String, String>();
+ BufferedReader br = new BufferedReader(new FileReader(
+ "running-relays-reverse.csv"));
+ String line, lastValidAfter = null, lastButOneValidAfter = null;
+ while ((line = br.readLine()) != null) {
+ if (!line.startsWith("20")) {
+ continue;
+ }
+ String[] parts = line.split(",");
+ String validAfter = parts[0];
+ if (lastValidAfter != null &&
+ !lastValidAfter.equals(validAfter)) {
+
+ /* We just parsed all lines of a consensus. Let's write times
+ * until next failure to disk for all running relays and update
+ * our internal history. */
+ if (lastButOneValidAfter == null) {
+ lastButOneValidAfter = lastValidAfter;
+ }
+ long lastValidAfterMillis = isoFormatter.parse(lastValidAfter).
+ getTime();
+ File tunfFile = new File("tunf",
+ formatter.format(lastValidAfterMillis));
+ tunfFile.getParentFile().mkdirs();
+ BufferedWriter bw = new BufferedWriter(new FileWriter(
+ tunfFile));
+ long secondsSinceLastValidAfter =
+ (isoFormatter.parse(lastButOneValidAfter).getTime()
+ - lastValidAfterMillis) / 1000L;
+
+ /* Iterate over our history first and see if these relays have
+ * been running in the considered consensus. Remember changes
+ * to our history and modify it below to avoid concurrent
+ * modification errors. */
+ Set<String> removeFromHistory = new HashSet<String>();
+ Map<String, Long> addToHistory = new HashMap<String, Long>();
+ for (Map.Entry<String, Long> e : knownRelays.entrySet()) {
+ String fingerprint = e.getKey();
+ if (runningRelays.containsKey(fingerprint)) {
+
+ /* This relay has been running, so write it to the output
+ * file and update our history. */
+ long hoursUntilFailure = e.getValue();
+ bw.write(fingerprint + "," + (secondsSinceLastValidAfter
+ + hoursUntilFailure) + "\n");
+ boolean restarted = runningRelays.get(fingerprint).
+ split(",")[2].equals("t");
+ if (restarted) {
+ removeFromHistory.add(fingerprint);
+ } else {
+ addToHistory.put(fingerprint, secondsSinceLastValidAfter
+ + hoursUntilFailure);
+ }
+ runningRelays.remove(fingerprint);
+ } else {
+
+ /* This relay has not been running, so remove it from our
+ * history. */
+ removeFromHistory.add(fingerprint);
+ }
+ }
+
+ /* Update our history for real now. We couldn't do this above,
+ * or we'd have modified the set we've been iterating over. */
+ for (String f : removeFromHistory) {
+ knownRelays.remove(f);
+ }
+ for (Map.Entry<String, Long> e : addToHistory.entrySet()) {
+ knownRelays.put(e.getKey(), e.getValue());
+ }
+
+ /* Iterate over the relays that we found in the consensus, but
+ * that we didn't have in our history. */
+ for (Map.Entry<String, String> e : runningRelays.entrySet()) {
+ String fingerprint = e.getKey();
+ bw.write(fingerprint + ",0\n");
+ boolean restarted = e.getValue().split(",")[2].equals("t");
+ if (!restarted) {
+ knownRelays.put(fingerprint, 0L);
+ }
+ }
+ bw.close();
+
+ /* Prepare for next consensus. */
+ runningRelays = new HashMap<String, String>();
+ lastButOneValidAfter = lastValidAfter;
+ }
+
+ /* Add the running relay lines to a map that we parse once we have
+ * all lines of a consensus. */
+ String fingerprint = parts[1];
+ runningRelays.put(fingerprint, line);
+ lastValidAfter = validAfter;
+ }
+ }
+
+ /* Run the simulation for the following WMTBF percentiles: */
+ List<Long> requiredWMTBFs = new ArrayList<Long>();
+ for (long l : new long[] { 20, 30, 40, 50, 60, 70, 80 }) {
+ requiredWMTBFs.add(l);
+ }
+ Collections.sort(requiredWMTBFs);
+ BufferedWriter bw = new BufferedWriter(new FileWriter(
+ "mtbf-sim.csv"));
+ bw.write("time");
+ for (long requiredWMTBF : requiredWMTBFs) {
+ bw.write(",mtunf" + requiredWMTBF + ",perc75tunf" + requiredWMTBF
+ + ",perc80tunf" + requiredWMTBF + ",perc85tunf" + requiredWMTBF
+ + ",perc90tunf" + requiredWMTBF + ",perc95tunf" + requiredWMTBF
+ + ",wmtbf" + requiredWMTBF);
+ }
+ bw.write("\n");
+
+ /* For each relay as identified by its base-64 encoded fingerprint,
+ * track weighted run length, total run weights, and current run
+ * length in a double[3]. */
+ SortedMap<String, double[]> knownRelays =
+ new TreeMap<String, double[]>();
+
+ /* Parse previously exported network status entries again, but this
+ * time in forward order. */
+ SimpleDateFormat formatter = new SimpleDateFormat(
+ "yyyy-MM-dd-HH-mm-ss");
+ formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+ SimpleDateFormat isoFormatter = new SimpleDateFormat(
+ "yyyy-MM-dd HH:mm:ss");
+ isoFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+ Map<String, String> runningRelays = new HashMap<String, String>(),
+ lastRunningRelays = new HashMap<String, String>();
+ BufferedReader br = new BufferedReader(new FileReader(
+ "running-relays-forward.csv"));
+ String line, lastValidAfter = null, firstValidAfter = null;
+ long nextWeightingInterval = -1L;
+ while ((line = br.readLine()) != null) {
+ if (!line.startsWith("20")) {
+ continue;
+ }
+ String[] parts = line.split(",");
+ String validAfter = parts[0];
+ if (firstValidAfter == null) {
+ firstValidAfter = validAfter;
+ }
+ if (lastValidAfter != null &&
+ !lastValidAfter.equals(validAfter)) {
+
+ /* We just parsed all lines of a consensus. First, see if 12
+ * hours have passed since we last discounted weighted run lengths
+ * and total run weights. If so, discount both variables for all
+ * known relays by factor 0.95 (or 19/20 since these are long
+ * integers) and remove those relays with a total run weight below
+ * 1/10000. */
+ long lastValidAfterMillis = isoFormatter.parse(lastValidAfter).
+ getTime();
+ long validAfterMillis = isoFormatter.parse(validAfter).getTime();
+ long weightingInterval = validAfterMillis
+ / (12L * 60L * 60L * 1000L);
+ if (nextWeightingInterval < 0L) {
+ nextWeightingInterval = weightingInterval;
+ }
+ while (weightingInterval > nextWeightingInterval) {
+ Set<String> relaysToRemove = new HashSet<String>();
+ for (Map.Entry<String, double[]> e : knownRelays.entrySet()) {
+ double[] w = e.getValue();
+ w[0] *= 0.95;
+ w[1] *= 0.95;
+ }
+ for (String fingerprint : relaysToRemove) {
+ knownRelays.remove(fingerprint);
+ }
+ nextWeightingInterval += 1L;
+ }
+
+ /* Update history for running relays. Start by iterating over all
+ * relays in the history, see if they're running now and whether
+ * they have been restarted. Distinguish four cases for relays in
+ * the history: 1) still running, 2) still running but restarted,
+ * 3) started in this consensus, 4) stopped in this consensus. */
+ double secondsSinceLastValidAfter =
+ (double) ((validAfterMillis - lastValidAfterMillis) / 1000L);
+ Set<String> updatedRelays = new HashSet<String>();
+ for (Map.Entry<String, double[]> e : knownRelays.entrySet()) {
+ String fingerprint = e.getKey();
+ double[] w = e.getValue();
+ if (runningRelays.containsKey(fingerprint)) {
+ if (w[2] > 0.1) {
+ if (!runningRelays.get(fingerprint).split(",")[2].
+ equals("t")) {
+
+ /* Case 1) still running: */
+ w[2] += secondsSinceLastValidAfter;
+ } else {
+
+ /* Case 2) still running but restarted: */
+ w[0] += w[2];
+ w[1] += 1.0;
+ w[2] = secondsSinceLastValidAfter;
+ }
+ } else {
+
+ /* Case 3) started in this consensus: */
+ w[2] = secondsSinceLastValidAfter;
+ }
+
+ /* Mark relay as already processed, or we'd add it to the
+ * history as a new relay below. */
+ updatedRelays.add(fingerprint);
+ } else if (w[2] > 0.1) {
+
+ /* Case 4) stopped in this consensus: */
+ w[0] += w[2];
+ w[1] += 1.0;
+ w[2] = 0.0;
+ }
+ }
+
+ /* Iterate over the set of currently running relays and add those
+ * that we haven't processed above to our history. */
+ for (String fingerprint : runningRelays.keySet()) {
+ if (!updatedRelays.contains(fingerprint)) {
+ updatedRelays.add(fingerprint);
+ knownRelays.put(fingerprint, new double[] { 0.0, 0.0,
+ secondsSinceLastValidAfter });
+ }
+ }
+
+ /* Calculate WMTBFs for all running relays and put them in a list
+ * that we can sort by WMTBF in descending order. */
+ List<String> wmtbfs = new ArrayList<String>();
+ for (String fingerprint : runningRelays.keySet()) {
+ double[] w = knownRelays.get(fingerprint);
+ double totalRunLength = w[0] + w[2];
+ double totalWeights = w[1] + (w[2] > 0.1 ? 1.0 : 0.0);
+ long wmtbf = totalWeights < 0.0001 ? 0
+ : (long) (totalRunLength / totalWeights);
+ wmtbfs.add(String.format("%012d %s", wmtbf, fingerprint));
+ }
+ Collections.sort(wmtbfs, Collections.reverseOrder());
+
+ /* Read previously calculated TUNFs from disk. */
+ Map<String, Long> tunfs = new HashMap<String, Long>();
+ File tunfFile = new File("tunf",
+ formatter.format(lastValidAfterMillis));
+ if (!tunfFile.exists()) {
+ if (!lastValidAfter.equals(firstValidAfter)) {
+ System.out.println("Could not find file " + tunfFile
+ + ". Skipping simulation!");
+ }
+ } else {
+ BufferedReader tunfBr = new BufferedReader(new FileReader(
+ tunfFile));
+ String tunfLine;
+ while ((tunfLine = tunfBr.readLine()) != null) {
+ String[] tunfParts = tunfLine.split(",");
+ tunfs.put(tunfParts[0], Long.parseLong(tunfParts[1]));
+ }
+ tunfBr.close();
+
+ /* Run the simulation for the relays in the current consensus
+ * for various required WFUs. */
+ bw.write(isoFormatter.format(lastValidAfterMillis));
+ long totalRelays = (long) wmtbfs.size(), selectedRelays = 0L,
+ totalTunf = 0L, minimalWmtbf = 0L;
+ int simulationIndex = 0;
+ List<Long> tunfList = new ArrayList<Long>();
+ for (String relay : wmtbfs) {
+ while (simulationIndex < requiredWMTBFs.size() &&
+ selectedRelays * 100L > totalRelays
+ * requiredWMTBFs.get(simulationIndex)) {
+ if (selectedRelays == 0L) {
+ bw.write(",NA,NA,NA,NA,NA,NA");
+ } else {
+ Collections.sort(tunfList, Collections.reverseOrder());
+ long perc75 = tunfList.get((75 * tunfList.size()) / 100);
+ long perc80 = tunfList.get((80 * tunfList.size()) / 100);
+ long perc85 = tunfList.get((85 * tunfList.size()) / 100);
+ long perc90 = tunfList.get((90 * tunfList.size()) / 100);
+ long perc95 = tunfList.get((95 * tunfList.size()) / 100);
+ bw.write("," + (totalTunf / selectedRelays) + "," + perc75
+ + "," + perc80 + "," + perc85 + "," + perc90 + ","
+ + perc95);
+ }
+ bw.write("," + minimalWmtbf);
+ simulationIndex++;
+ }
+ String[] wmtbfParts = relay.split(" ");
+ minimalWmtbf = Long.parseLong(wmtbfParts[0]);
+ String fingerprint = wmtbfParts[1];
+ long tunf = tunfs.get(fingerprint);
+ totalTunf += tunf;
+ tunfList.add(tunf);
+ selectedRelays += 1L;
+ }
+ bw.write("\n");
+ }
+
+ /* We're done with this consensus. Prepare for the next. */
+ lastRunningRelays = runningRelays;
+ runningRelays = new HashMap<String, String>();
+ }
+
+ /* Add the running relay lines to a map that we parse once we have
+ * all lines of a consensus. */
+ String fingerprint = parts[1];
+ runningRelays.put(fingerprint, line);
+ lastValidAfter = validAfter;
+ }
+ bw.close();
+
+ /* Print how long this execution took and exit. */
+ System.out.println("Execution took " + ((System.currentTimeMillis()
+ - started) / (60L * 1000L)) + " minutes.");
+ }
+}
+
diff --git a/task-2911/mtbf-sim/mtbf-sim.R b/task-2911/mtbf-sim/mtbf-sim.R
new file mode 100644
index 0000000..a630406
--- /dev/null
+++ b/task-2911/mtbf-sim/mtbf-sim.R
@@ -0,0 +1,73 @@
+library(ggplot2)
+
+data <- read.csv("mtbf-sim.csv", stringsAsFactors = FALSE)
+d <- data[data$time >= '2010' & data$time < '2011', ]
+d <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)), mean)
+d <- rbind(
+ data.frame(x = d$wmtbf30, y = d$perc90tunf30, sim = "30 %"),
+ data.frame(x = d$wmtbf40, y = d$perc90tunf40, sim = "40 %"),
+ data.frame(x = d$wmtbf50, y = d$perc90tunf50, sim = "50 % (default)"),
+ data.frame(x = d$wmtbf60, y = d$perc90tunf60, sim = "60 %"),
+ data.frame(x = d$wmtbf70, y = d$perc90tunf70, sim = "70 %"))
+ggplot(d, aes(x = x / (24 * 60 * 60), y = y / (60 * 60))) +
+facet_wrap(~ sim) +
+geom_path() +
+scale_x_continuous("\nRequired WMTBF in days",
+ breaks = seq(0, max(d$x, na.rm = TRUE) / (24 * 60 * 60), 7),
+ minor = seq(0, max(d$x, na.rm = TRUE) / (24 * 60 * 60), 1)) +
+scale_y_continuous(paste("Time in hours until 10 % of relays\nor ",
+ "27.1 % of streams have failed\n", sep = ""),
+ breaks = seq(0, max(d$y, na.rm = TRUE) / (60 * 60), 24))
+ggsave(filename = "mtbf-sim.pdf", width = 8, height = 5, dpi = 100)
+
+## Commented out, because this graph is meaningless in b/w. The graph
+## above contains the same data, but can be printed in b/w.
+#data <- read.csv("mtbf-sim.csv", stringsAsFactors = FALSE)
+#d <- data[data$time >= '2010' & data$time < '2011', ]
+#d <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)), mean)
+#d <- rbind(
+# data.frame(x = d$wmtbf70, y = d$perc90tunf70, sim = "70 %"),
+# data.frame(x = d$wmtbf60, y = d$perc90tunf60, sim = "60 %"),
+# data.frame(x = d$wmtbf50, y = d$perc90tunf50, sim = "50 % (default)"),
+# data.frame(x = d$wmtbf40, y = d$perc90tunf40, sim = "40 %"),
+# data.frame(x = d$wmtbf30, y = d$perc90tunf30, sim = "30 %"))
+#ggplot(d, aes(x = x / (24 * 60 * 60), y = y / (60 * 60),
+# colour = sim)) +
+#geom_path() +
+#scale_x_continuous("\nRequired WMTBF in days",
+# breaks = seq(0, max(d$x, na.rm = TRUE) / (24 * 60 * 60), 7),
+# minor = seq(0, max(d$x, na.rm = TRUE) / (24 * 60 * 60), 1)) +
+#scale_y_continuous(paste("Time until \n10 % of relays or \n",
+# "27.1 % of streams \nhave failed \nin hours ", sep = ""),
+# breaks = seq(0, max(d$y, na.rm = TRUE) / (60 * 60), 24)) +
+#scale_colour_hue("Fraction of relays\nby highest WMTBF",
+# breaks = c("30 %", "40 %", "50 % (default)", "60 %", "70 %")) +
+#opts(axis.title.x = theme_text(size = 12 * 0.8, face = "bold",
+# hjust = 0.5),
+# axis.title.y = theme_text(size = 12 * 0.8, face = "bold", vjust = 0.5,
+# hjust = 1))
+#ggsave(filename = "mtbf-sim.pdf", width = 8, height = 5, dpi = 100)
+
+## Commented out, because focusing on the development over time is the
+## wrong thing here.
+#simulations <- paste("mtunf", c(20, 30, 40, 50, 60, 70, 80),
+# sep = "")
+#d <- data[data$time >= '2010' & data$time < '2011',
+# c("time", simulations)]
+#d <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)), mean)
+#d <- melt(d, id.vars = 1)
+#ggplot(d, aes(x = date, y = value / (24 * 60 * 60), colour = variable)) +
+#geom_line() +
+#scale_x_date("", major = "3 months", minor = "1 month",
+# format = "%b %Y") +
+#scale_y_continuous(paste("Mean time \nuntil next \nfailure \n",
+# "in days \n", sep = ""),
+# limits = c(0, max(d$value, na.rm = TRUE) / (24 * 60 * 60))) +
+#scale_colour_hue(paste("Percentile\nhighest\nweighted mean\n",
+# "time between\nfailures", sep = ""), breaks = simulations,
+# labels = paste(substr(simulations, 6, 9),
+# ifelse(simulations == "mtunf50", "(default)", ""))) +
+#opts(axis.title.y = theme_text(size = 12 * 0.8, face = "bold",
+# vjust = 0.5, hjust = 1))
+#ggsave(filename = "mtbf-sim1.pdf", width = 8, height = 5, dpi = 100)
+
diff --git a/task-2911/report.tex b/task-2911/report.tex
new file mode 100644
index 0000000..4dc6ab9
--- /dev/null
+++ b/task-2911/report.tex
@@ -0,0 +1,295 @@
+\documentclass{article}
+\usepackage{url}
+\usepackage[pdftex]{graphicx}
+\usepackage{graphics}
+\usepackage{color}
+\begin{document}
+\title{An Analysis of Tor Relay Stability\\(DRAFT)}
+\author{Karsten Loesing\\{\tt karsten(a)torproject.org}}
+
+\maketitle
+
+\section{Introduction}
+
+The Tor network consists of 2,200 relays and 600 bridges run by
+volunteers, some of which are on dedicated servers and some on laptops or
+mobile devices.
+% TODO Look up more recent relay and bridge numbers. -KL
+Obviously, we can expect the relays run on dedicated servers to be more
+``stable'' than those on mobile phones.
+But it is difficult to draw a line between stable and unstable relays.
+In most cases it depends on the context which relays count as stable:
+
+\begin{itemize}
+\item A stable relay that is supposed to be part of a circuit for a
+\emph{long-running stream} should not go offline during the next day.
+\item A stable relay that clients pick as \emph{entry guard} doesn't have
+to be running continuously, but should be online most of the time in the
+upcoming weeks.
+\item A stable relay that acts as \emph{hidden-service directory} should
+be part of a relay subset that mostly overlaps with the subsets 1, 2, or
+even 3 hours in the future.
+That means that the relays in this set should be stable, but also that not
+too many new relays should join the set of stable relays at once.
+\item A stable relay that clients use in a \emph{fallback consensus} that
+is already a few days or even weeks old should still be available on the
+same IP address and port.\footnote{See also proposal 146.}
+Such a relay doesn't necessarily have to run without interruption, though.
+% TODO Correctly cite proposal 146 here. -KL
+\item A stable \emph{bridge relay} should be running on the same IP
+address a few days after a client learns about the bridge, but again,
+doesn't have to run continuously.
+\end{itemize}
+
+All these stability notions have in common that some relays or bridges are
+better suited for the described contexts than others.
+In this analysis we will look at various relay stability metrics to find
+the best suited set of relays for each context.
+The idea of this report is to use the results to optimize how the
+directory authorities assign relay flags that clients use to make path
+select decisions.
+
+For every context, we try to simulate what requirements based on past
+observations would have resulted in what relay stabilities in the near
+future.
+Generally, we'd expect that stricter requirements lead to higher
+stability.
+But every prediction contains a certain amount of randomness, so that we
+cannot tighten the requirements arbitrarily.
+Further, we want to ensure that the subset of relays identified as stable
+does not become too small.
+The reason is that there should be some diversity, so that not a few
+operators can aim at running most relays used in a given context.
+In some cases, the stable relays also need to provide sufficient bandwidth
+to the network in order not to become a performance bottleneck.
+We are going into more details about the requirements when looking into
+the separate analyses in the sections below.
+
+The analysis data and tools are available on the Tor metrics website at
+\url{https://metrics.torproject.org/}.\footnote{Or rather, will be made
+available.}
+
+\section{Choosing relays for long-lived streams}
+\label{sec:mtbf-sim}
+
+Whenever clients request Tor to open a long-lived stream, Tor should try
+to pick only those relays for the circuit that are not likely to disappear
+shortly after.
+If only a single relay in the circuit fails, the stream collapses and a
+new circuit needs to be built.
+Depending on how well the application handles connection failures this may
+impact usability significantly.
+
+In order to declare some relays as more useful for long-lived streams, the
+directory authorities track uptime sessions of all relays over time.
+Based on this history, they calculate the \emph{weighted mean time between
+failure (WMTBF)} for each relay.
+The MTBF part simply measures the average uptime between a relay showing
+up in the Tor network and either leaving or failing.
+In the weighted form of this metric, which is used here, older sessions
+are weighted to count less.
+The directory authorities assign the \texttt{Stable} flag to the 50~\% of
+relays with the highest WMTBF.
+
+In this simulation we want to find out how useful the WMTBF metric is for
+predicting future stability and how stability would be affected when
+declaring more or less than 50~\% of the relays as stable.
+The metric we chose for evaluating how stable a relay is is the \emph{time
+until next failure}.
+When running a simulation we determine the time until 10~\% of the
+``stable'' relays have failed.
+Under the (grossly simplified) assumption that relays are chosen
+uniformly, $1 - 0.9^3 = 27.1~\%$ of streams using relays from this set
+would have failed up to this point.
+
+\begin{figure}[t]
+\includegraphics[width=\textwidth]{mtbf-sim.pdf}
+\caption{Impact of assigning the \texttt{Stable} flag to a given fraction
+of relays on the actual required WMTBF ($x$ axis) and on the time
+until 10~\% of relays or 27.1~\% of streams have failed ($y$ axis)}
+\label{fig:mtbf-sim}
+\end{figure}
+
+Figure~\ref{fig:mtbf-sim} shows the analysis results for assigning the
+\texttt{Stable} flag to fractions of relays between 30~\% and 70~\% in a
+path plot.
+This path plot shows the effect of choosing a different fraction of
+relays on the actual required WMTBF value on the $x$ axis and on the
+resulting time until 10~\% of relays have failed on the $y$ axis.
+Two data points adjacent in time are connected by a line, forming a path.
+
+The results indicate a somewhat linear relation between required WMTBF and
+time until failure, which is as expected.
+The time until 10~\% of relays have failed in the default case of having
+50~\% stable relays is somewhere between 12 and 48 hours.
+If the directory authorities assigned the \texttt{Stable} flag to 60~\% or
+even 70~\% of all relays, this time would go down to on average 24 or 12
+hours.
+Reducing the set to only 40~\% or 30\% of relays would increase the time
+until failure to 36 or even 48 hours on average.
+
+\subsubsection*{Next steps}
+
+{\it
+\begin{itemize}
+\item What's the desired stability goal here?
+\item What other requirements (bandwidth) should go into the simulation?
+\end{itemize}
+}
+
+\section{Picking stable entry guards}
+
+Clients pick a set of entry guards as fixed entry points into the Tor
+network.
+Optimally, clients should be able to stick with their choice for a few
+weeks.
+While it is not required for all their entry guards to be running all the
+time, at least a subset of them should be running, or the client needs to
+pick a new set.
+
+Tor's metric for deciding which relays are stable enough to be entry
+guards is \emph{weighted fractional uptime (WFU)}.
+WFU measures the fraction of uptime of a relay in the past with older
+observations weighted to count less.
+The assumption is that a relay that was available most of the time in the
+past will also be available most of the time in the future.
+
+In a first analysis we simulate the effect of varying the requirements for
+becoming an entry guard on the average relay stability in the future.
+We measure future stability by using the same WFU metric, but for uptime
+in the future.
+We similarly weight observations farther in the future less than
+observations in the near future.
+We then simulate different pre-defined required WFUs between $90~\%$ and
+$99.9~\%$ and calculate what the mean future WFUs would be.
+
+\begin{figure}[t]
+\includegraphics[width=\textwidth]{wfu-sim.pdf}
+\caption{Impact of different required WFU on the mean empirical future WFU
+and fraction of potential entry guards}
+\label{fig:wfu-sim}
+\end{figure}
+
+Figure~\ref{fig:wfu-sim} shows the analysis results in a path plot similar
+to the one in Section~\ref{sec:mtbf-sim}.
+This path plot shows the effect of varying the WFU requirement, displayed
+as different line colors, on the fraction of relays meeting this
+requirement on the $x$ axis and on the WFU in the future on the $y$ axis.
+Two data points adjacent in time are connected by a line, forming a path.
+
+In this graph we can see that the majority of data points for the default
+required WFU of 98~\% falls in a future WFU range of 94~\% to 96\% with
+the smallest WFU being no less than 89~\%.
+In most cases, the fraction of relays meeting the default WFU requirement
+is between 40~\% and 50~\%.
+
+If the WFU requirement is relaxed to 95~\% or even 90~\%, the WFU in the
+future decreases slightly towards around 94~\% to 95~\% for most cases.
+At first sight it may seem surprising that a past WFU of 90~\% leads to
+a future WFU of 94~\%, but it makes sense, because the past WFU is a
+required minimum whereas the future WFU is a mean value of all relays
+meeting the requirement.
+Another effect of relaxing the required WFU is that the fraction of relays
+meeting the requirement increases from 50~\% to almost 66~\%.
+
+Interestingly, when tightening the requirement to a WFU value of 99~\% or
+even 99.9~\%, the future WFU does not increase significantly, if at all.
+To the contrary, the future WFU of relays meeting the 99.9~\% requirement
+drops to a range of 91~\% to 94~\% for quite a while.
+A likely explanation for this effect is that the fraction of relays
+meeting these high requirements is only 15~\%.
+While these 15~\% of relays may have had a very high uptime in the past,
+failure of only a few of these relays ruin the WFU metric in the future.
+
+A cautious conclusion of this analysis could be that, if the goal is to
+increase the number of \texttt{Guard} relays, reducing the required WFU to
+95~\% or even 90~\% wouldn't impact relay stability by too much.
+Conversely, increasing the required WFU beyond the current value of 98~\%
+doesn't make much sense and might even negatively affect relay stability.
+
+\subsubsection*{Next steps}
+
+{\it
+\begin{itemize}
+\item Tor penalizes relays that change their IP address or port by ending
+the running uptime session and starting a new uptime session. This
+reduces both WFU and MTBF. The simulation doesn't take this into account
+yet. Should it?
+\item Add the bandwidth requirements to the simulation. The current
+simulation doesn't make any assumptions about relay bandwidth when
+assigning \texttt{Guard} flags. Which bandwidth value would we use here?
+\item Add another graph similar to Figure~\ref{fig:wfu-sim}, but replace
+the ``Fraction of relays meeting WFU requirement'' on the \emph{x} axis
+with the ``Fraction of \emph{bandwidth} of relays meeting WFU
+requirement.''
+After all, we're interested in having enough bandwidth capacity for the
+entry guard position, not (only) in having enough distinct relays.
+Which bandwidth value would we use here?
+\item Roger suggests to come up with a better metric than ``WFU since we
+first saw a relay.''
+He says ``it seems wrong to make something that we saw earlier have a
+worse WFU than something we saw later, even if they've had identical
+uptimes in that second period.''
+What would be good candidate metrics?
+\item Ponder finding another metric than WFU for future observations. In
+particular, with the current WFU parameters of $0.95$ and $12$ hours, the
+WFU reaches up to 4 months into the future. It seems useful to weight
+uptime in the near future higher than uptime in the farther future, but
+maybe we should use parameters to limit the interval to $1$ or $2$ months.
+\end{itemize}
+}
+
+\section{Forming stable hidden-service directory sets}
+
+{\it
+In this section we should evaluate the current requirements for getting
+the \texttt{HSDir} flag.
+Also, what happened to the number of relays with the \texttt{HSDir} flag
+in August 2010?
+}
+
+\section{Selecting stable relays for a fallback consensus}
+
+{\it
+Is the concept of a fallback consensus still worth considering?
+If so, we should analyze how to identify those relays that are most likely
+to be around and reachable under the same IP address.
+The result of this analysis could lead to adding a new \texttt{Longterm}
+(or \texttt{Fallback}?) flag as suggested in proposal 146.
+% TODO Correctly cite proposal 146 here. -KL
+Maybe the analysis of bridges on stable IP addresses should come first,
+though.
+}
+
+\section{Distributing bridges with stable IP addresses}
+
+{\it
+A possible outcome of this analysis could be to add a new flag
+\texttt{StableAddress} (similar to the \texttt{Longterm} flag from the
+previous section) to bridge network statuses and to change BridgeDB to
+include at least one bridge with this flag in its results.
+One of the challenges of this analysis will be to connect sanitized bridge
+descriptors from two months with each other.
+The sanitized IP addresses of two bridges in two months do not match,
+because we're using a new secret key as input to the hash function every
+month.
+We might be able to correlate the descriptors of running bridges via their
+descriptor publication times or bridge statistics.
+But if that fails, we'll have to run the analysis with only 1 month of
+data at a time.
+}
+
+\section{Discussion and future work}
+
+The approach taken in this analysis was to select relays that are most
+stable in a given context based on their history.
+A different angle to obtain higher relay stability might be to identify
+what properties of a relay have a positive or negative impact on its
+stability.
+For example, relays running a given operating system or given Tor software
+version might have a higher stability than others.
+Possible consequences could be to facilitate setting up relays on a given
+operating system or to improve the upgrade process of the Tor software.
+
+\end{document}
+
diff --git a/task-2911/wfu-sim/SimulateWeightedFractionalUptime.java b/task-2911/wfu-sim/SimulateWeightedFractionalUptime.java
new file mode 100644
index 0000000..6a2d7a9
--- /dev/null
+++ b/task-2911/wfu-sim/SimulateWeightedFractionalUptime.java
@@ -0,0 +1,314 @@
+/**
+ * Simulate variation of weighted fractional uptime on Guard relays. In
+ * a first step, parse network status consensus in consensuses/ from last
+ * to first, calculate future weighted fractional uptimes for all relays,
+ * and write them to disk as one file per network status in
+ * fwfu/$filename. (Skip this step if there is already a fwfu/
+ * directory.) In a second step, parse the network statuse consensus
+ * again, but this time from first to last, calculate past weighted
+ * fractional uptimes for all relays, form relay subsets based on minimal
+ * WFU, look up what the mean future WFU would be for a subset, and write
+ * results to wfu-sim.csv to disk. */
+import java.io.*;
+import java.text.*;
+import java.util.*;
+public class SimulateWeightedFractionalUptime {
+ public static void main(String[] args) throws Exception {
+
+ /* Measure how long this execution takes. */
+ long started = System.currentTimeMillis();
+
+ /* Decide whether we need to do the reverse run, or if we can use
+ * previous results. */
+ if (!new File("fwfu").exists()) {
+
+ /* Scan existing consensus files and sort them in reverse order. */
+ SortedSet<File> allConsensuses =
+ new TreeSet<File>(Collections.reverseOrder());
+ Stack<File> files = new Stack<File>();
+ files.add(new File("consensuses"));
+ while (!files.isEmpty()) {
+ File file = files.pop();
+ if (file.isDirectory()) {
+ files.addAll(Arrays.asList(file.listFiles()));
+ } else {
+ if (file.getName().endsWith("-consensus")) {
+ allConsensuses.add(file);
+ }
+ }
+ }
+
+ /* For each relay as identified by its base-64 encoded fingerprint,
+ * track weighted uptime and total weighted time in a long[2]. */
+ SortedMap<String, long[]> knownRelays =
+ new TreeMap<String, long[]>();
+
+ /* Parse all consensuses in reverse order. */
+ SimpleDateFormat formatter = new SimpleDateFormat(
+ "yyyy-MM-dd-HH-mm-ss");
+ formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+ long nextWeightingInterval = formatter.parse(allConsensuses.first().
+ getName().substring(0, "yyyy-MM-dd-HH-mm-ss".length())).
+ getTime() / (12L * 60L * 60L * 1000L);
+ for (File consensus : allConsensuses) {
+
+ /* Every 12 hours, weight both uptime and total time of all known
+ * relays with 0.95 (or 19/20 since these are long integers) and
+ * remove all with a weighted fractional uptime below 1/10000. */
+ long validAfter = formatter.parse(consensus.getName().substring(0,
+ "yyyy-MM-dd-HH-mm-ss".length())).getTime();
+ long weightingInterval = validAfter / (12L * 60L * 60L * 1000L);
+ while (weightingInterval < nextWeightingInterval) {
+ Set<String> relaysToRemove = new HashSet<String>();
+ for (Map.Entry<String, long[]> e : knownRelays.entrySet()) {
+ long[] w = e.getValue();
+ w[0] *= 19L;
+ w[0] /= 20L;
+ w[1] *= 19L;
+ w[1] /= 20L;
+ if (((10000L * w[0]) / w[1]) < 1L) {
+ relaysToRemove.add(e.getKey());
+ }
+ }
+ for (String fingerprint : relaysToRemove) {
+ knownRelays.remove(fingerprint);
+ }
+ nextWeightingInterval -= 1L;
+ }
+
+ /* Parse all fingerprints of Running relays from the consensus. */
+ Set<String> fingerprints = new HashSet<String>();
+ BufferedReader br = new BufferedReader(new FileReader(consensus));
+ String line, rLine = null;
+ boolean reachedEnd = false;
+ while ((line = br.readLine()) != null) {
+ if (line.startsWith("r ")) {
+ rLine = line;
+ } else if (line.startsWith("s ") && line.contains(" Running")) {
+ String[] parts = rLine.split(" ");
+ if (parts.length < 3) {
+ System.out.println("Illegal line '" + rLine + "' in "
+ + consensus + ". Skipping consensus.");
+ continue;
+ } else {
+ String fingerprint = parts[2];
+ if (fingerprint.length() !=
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAA".length()) {
+ System.out.println("Illegal line '" + rLine + "' in "
+ + consensus + ". Skipping consensus.");
+ continue;
+ }
+ fingerprints.add(fingerprint);
+ }
+ } else if (line.startsWith("directory-signature ")) {
+ reachedEnd = true;
+ break;
+ }
+ }
+ br.close();
+ if (!reachedEnd) {
+ System.out.println("Did not reach the consensus end of "
+ + consensus + ". Skipping consensus.");
+ continue;
+ }
+
+ /* Increment weighted uptime for all running relays by 3600
+ * seconds. */
+ for (String fingerprint : fingerprints) {
+ if (!knownRelays.containsKey(fingerprint)) {
+ knownRelays.put(fingerprint, new long[] { 3600L, 0L });
+ } else {
+ knownRelays.get(fingerprint)[0] += 3600L;
+ }
+ }
+
+ /* Increment total weighted time for all relays by 3600 seconds. */
+ for (long[] w : knownRelays.values()) {
+ w[1] += 3600L;
+ }
+
+ /* Write future WFUs for all known relays to disk. */
+ File fwfuFile = new File("fwfu", consensus.getName());
+ fwfuFile.getParentFile().mkdirs();
+ BufferedWriter bw = new BufferedWriter(new FileWriter(fwfuFile));
+ for (Map.Entry<String, long[]> e : knownRelays.entrySet()) {
+ bw.write(e.getKey() + " "
+ + ((10000L * e.getValue()[0]) / e.getValue()[1]) + "\n");
+ }
+ bw.close();
+ }
+ }
+
+ /* Run the simulation for the following WFU/10000 values: */
+ long[] requiredWFUs = new long[] { 9000, 9100, 9200, 9300, 9400, 9500,
+ 9600, 9700, 9750, 9800, 9850, 9900, 9950, 9975, 9990, 9999 };
+ BufferedWriter bw = new BufferedWriter(new FileWriter("wfu-sim.csv"));
+ bw.write("time");
+ for (long requiredWFU : requiredWFUs) {
+ bw.write(",wfu" + requiredWFU + ",perc85wfu" + requiredWFU
+ + ",perc90wfu" + requiredWFU + ",perc95wfu" + requiredWFU
+ + ",guards" + requiredWFU);
+ }
+ bw.write("\n");
+
+ /* Scan existing consensus files and sort them in forward order. */
+ SortedSet<File> allConsensuses = new TreeSet<File>();
+ Stack<File> files = new Stack<File>();
+ files.add(new File("consensuses"));
+ while (!files.isEmpty()) {
+ File file = files.pop();
+ if (file.isDirectory()) {
+ files.addAll(Arrays.asList(file.listFiles()));
+ } else {
+ if (file.getName().endsWith("-consensus")) {
+ allConsensuses.add(file);
+ }
+ }
+ }
+
+ /* For each relay as identified by its base-64 encoded fingerprint,
+ * track weighted uptime and total weighted time in a long[2]. */
+ SortedMap<String, long[]> knownRelays = new TreeMap<String, long[]>();
+
+ /* Parse all consensuses in forward order. */
+ SimpleDateFormat formatter = new SimpleDateFormat(
+ "yyyy-MM-dd-HH-mm-ss");
+ formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+ SimpleDateFormat isoFormatter = new SimpleDateFormat(
+ "yyyy-MM-dd HH:mm:ss");
+ isoFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+ long nextWeightingInterval = formatter.parse(allConsensuses.first().
+ getName().substring(0, "yyyy-MM-dd-HH-mm-ss".length())).getTime()
+ / (12L * 60L * 60L * 1000L);
+ for (File consensus : allConsensuses) {
+
+ /* Every 12 hours, weight both uptime and total time of all known
+ * relays with 0.95 (or 19/20 since these are long integers) and
+ * remove all with a weighted fractional uptime below 1/10000. */
+ long validAfter = formatter.parse(consensus.getName().substring(0,
+ "yyyy-MM-dd-HH-mm-ss".length())).getTime();
+ long weightingInterval = validAfter / (12L * 60L * 60L * 1000L);
+ while (weightingInterval > nextWeightingInterval) {
+ Set<String> relaysToRemove = new HashSet<String>();
+ for (Map.Entry<String, long[]> e : knownRelays.entrySet()) {
+ long[] w = e.getValue();
+ w[0] *= 19L;
+ w[0] /= 20L;
+ w[1] *= 19L;
+ w[1] /= 20L;
+ if (((10000L * w[0]) / w[1]) < 1L) {
+ relaysToRemove.add(e.getKey());
+ }
+ }
+ for (String fingerprint : relaysToRemove) {
+ knownRelays.remove(fingerprint);
+ }
+ nextWeightingInterval += 1L;
+ }
+
+ /* Parse all fingerprints of Running relays from the consensus. */
+ Set<String> fingerprints = new HashSet<String>();
+ BufferedReader br = new BufferedReader(new FileReader(consensus));
+ String line, rLine = null;
+ boolean reachedEnd = false;
+ while ((line = br.readLine()) != null) {
+ if (line.startsWith("r ")) {
+ rLine = line;
+ } else if (line.startsWith("s ") && line.contains(" Running")) {
+ String[] parts = rLine.split(" ");
+ if (parts.length < 3) {
+ System.out.println("Illegal line '" + rLine + "' in "
+ + consensus + ". Skipping consensus.");
+ continue;
+ } else {
+ String fingerprint = parts[2];
+ if (fingerprint.length() !=
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAA".length()) {
+ System.out.println("Illegal line '" + rLine + "' in "
+ + consensus + ". Skipping consensus.");
+ continue;
+ }
+ fingerprints.add(fingerprint);
+ }
+ } else if (line.startsWith("directory-signature ")) {
+ reachedEnd = true;
+ break;
+ }
+ }
+ br.close();
+ if (!reachedEnd) {
+ System.out.println("Did not reach the consensus end of "
+ + consensus + ". Skipping consensus.");
+ continue;
+ }
+
+ /* Increment weighted uptime for all running relays by 3600
+ * seconds. */
+ for (String fingerprint : fingerprints) {
+ if (!knownRelays.containsKey(fingerprint)) {
+ knownRelays.put(fingerprint, new long[] { 3600L, 0L });
+ } else {
+ knownRelays.get(fingerprint)[0] += 3600L;
+ }
+ }
+
+ /* Increment total weighted time for all relays by 3600 seconds. */
+ for (long[] w : knownRelays.values()) {
+ w[1] += 3600L;
+ }
+
+ /* Read previously calculated future WFUs from disk. */
+ Map<String, Long> fwfus = new HashMap<String, Long>();
+ File fwfuFile = new File("fwfu", consensus.getName());
+ if (!fwfuFile.exists()) {
+ System.out.println("Could not find file " + fwfuFile
+ + ". Exiting!");
+ System.exit(1);
+ }
+ br = new BufferedReader(new FileReader(fwfuFile));
+ while ((line = br.readLine()) != null) {
+ String[] parts = line.split(" ");
+ fwfus.put(parts[0], Long.parseLong(parts[1]));
+ }
+
+ /* Run the simulation for the relays in the current consensus for
+ * various required WFUs. */
+ bw.write(isoFormatter.format(validAfter));
+ for (long requiredWFU : requiredWFUs) {
+ long selectedRelays = 0L,
+ totalRelays = (long) fingerprints.size(), totalFwfu = 0L;
+ List<Long> fwfuList = new ArrayList<Long>();
+ for (String fingerprint : fingerprints) {
+ long[] pwfu = knownRelays.get(fingerprint);
+ long wfu = (10000L * pwfu[0]) / pwfu[1];
+ if (wfu >= requiredWFU) {
+ selectedRelays += 1L;
+ if (fwfus.containsKey(fingerprint)) {
+ long fwfu = fwfus.get(fingerprint);
+ totalFwfu += fwfu;
+ fwfuList.add(fwfu);
+ }
+ }
+ }
+ if (selectedRelays == 0L) {
+ bw.write(",NA,NA,NA,NA");
+ } else {
+ Collections.sort(fwfuList, Collections.reverseOrder());
+ long perc85 = fwfuList.get((85 * fwfuList.size()) / 100);
+ long perc90 = fwfuList.get((90 * fwfuList.size()) / 100);
+ long perc95 = fwfuList.get((95 * fwfuList.size()) / 100);
+ bw.write("," + (totalFwfu / selectedRelays) + "," + perc85
+ + "," + perc90 + "," + perc95);
+ }
+ bw.write("," + (10000L * selectedRelays / totalRelays));
+ }
+ bw.write("\n");
+ }
+ bw.close();
+
+ /* Print how long this execution took and exit. */
+ System.out.println("Execution took " + ((System.currentTimeMillis()
+ - started) / (60L * 1000L)) + " minutes.");
+ }
+}
+
diff --git a/task-2911/wfu-sim/wfu-sim.R b/task-2911/wfu-sim/wfu-sim.R
new file mode 100644
index 0000000..149ce6d
--- /dev/null
+++ b/task-2911/wfu-sim/wfu-sim.R
@@ -0,0 +1,57 @@
+library(ggplot2)
+data <- read.csv("wfu-sim.csv", stringsAsFactors = FALSE)
+
+d <- data[data$time >= '2010' & data$time < '2011', ]
+d <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)), mean)
+d <- rbind(
+ data.frame(x = d$guards9000, y = d$wfu9000, sim = "90 %"),
+ data.frame(x = d$guards9500, y = d$wfu9500, sim = "95 %"),
+ data.frame(x = d$guards9800, y = d$wfu9800, sim = "98 % (default)"),
+ data.frame(x = d$guards9900, y = d$wfu9900, sim = "99 %"),
+ data.frame(x = d$guards9990, y = d$wfu9990, sim = "99.9 %"))
+ggplot(d, aes(x = x / 10000.0, y = y / 10000.0)) +
+geom_path() +
+facet_wrap(~ sim) +
+scale_x_continuous("\nFraction of relays meeting WFU requirement",
+ formatter = "percent") +
+scale_y_continuous("Mean WFU in the future\n", formatter = "percent")
+ggsave(filename = "wfu-sim.pdf", width = 8, height = 5, dpi = 100)
+
+## Commented out, because graph is meaningless in b/w.
+#d <- data[data$time >= '2010' & data$time < '2011', ]
+#d <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)), mean)
+#d <- rbind(
+# data.frame(x = d$guards9000, y = d$wfu9000, sim = "90 %"),
+# data.frame(x = d$guards9500, y = d$wfu9500, sim = "95 %"),
+# data.frame(x = d$guards9800, y = d$wfu9800, sim = "98 % (default)"),
+# data.frame(x = d$guards9900, y = d$wfu9900, sim = "99 %"),
+# data.frame(x = d$guards9990, y = d$wfu9990, sim = "99.9 %"))
+#ggplot(d, aes(x = x / 10000.0, y = y / 10000.0, colour = sim)) +
+#geom_path() +
+#scale_x_continuous("\nFraction of relays meeting WFU requirement",
+# formatter = "percent") +#, trans = "reverse") +
+#scale_y_continuous("Mean WFU \nin the future ",
+# formatter = "percent") +
+#scale_colour_hue("Required WFU") +
+#opts(axis.title.x = theme_text(size = 12 * 0.8, face = "bold",
+# hjust = 0.5),
+# axis.title.y = theme_text(size = 12 * 0.8, face = "bold", vjust = 0.5,
+# hjust = 1))
+#ggsave(filename = "wfu-sim.pdf", width = 8, height = 5, dpi = 100)
+
+## Commented out, because the time plot is not as useful as expected.
+#simulations <- paste("wfu", rev(c(9000, 9200, 9400, 9600, 9800)),
+# sep = "")
+#d <- data[data$time >= '2010' & data$time < '2011',
+# c("time", simulations)]
+#d <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)), mean)
+#d <- melt(d, id.vars = 1)
+#ggplot(d, aes(x = date, y = value / 10000.0, colour = variable)) +
+#geom_line() +
+#scale_x_date("", major = "3 months", minor = "1 month",
+# format = "%b %Y") +
+#scale_y_continuous("Empirical future WFU\n", formatter = "percent") +
+#scale_colour_hue("Required past WFU\n", breaks = simulations,
+# labels = paste(as.numeric(substr(simulations, 4, 9)) / 100.0, "%"))
+#ggsave(filename = "wfu-sim-time.pdf", width = 8, height = 5, dpi = 100)
+
1
0
r24857: {website} remove BOM character from translated wmi files (in website/trunk: donate/de getinvolved/de include/de press/de projects/de torbutton/de)
by Runa Sandvik 03 Jul '11
by Runa Sandvik 03 Jul '11
03 Jul '11
Author: runa
Date: 2011-07-03 09:34:34 +0000 (Sun, 03 Jul 2011)
New Revision: 24857
Modified:
website/trunk/donate/de/sidenav.wmi
website/trunk/getinvolved/de/sidenav.wmi
website/trunk/include/de/navigation.wmi
website/trunk/press/de/info.wmi
website/trunk/press/de/sidenav.wmi
website/trunk/projects/de/sidenav.wmi
website/trunk/torbutton/de/sidenav.wmi
Log:
remove BOM character from translated wmi files
Modified: website/trunk/donate/de/sidenav.wmi
===================================================================
--- website/trunk/donate/de/sidenav.wmi 2011-07-03 09:32:15 UTC (rev 24856)
+++ website/trunk/donate/de/sidenav.wmi 2011-07-03 09:34:34 UTC (rev 24857)
@@ -1,4 +1,4 @@
-#!/usr/bin/wml
+#!/usr/bin/wml
## translation metadata
# Revision: $Revision$
Modified: website/trunk/getinvolved/de/sidenav.wmi
===================================================================
--- website/trunk/getinvolved/de/sidenav.wmi 2011-07-03 09:32:15 UTC (rev 24856)
+++ website/trunk/getinvolved/de/sidenav.wmi 2011-07-03 09:34:34 UTC (rev 24857)
@@ -1,4 +1,4 @@
-#!/usr/bin/wml
+#!/usr/bin/wml
## translation metadata
# Revision: $Revision$
Modified: website/trunk/include/de/navigation.wmi
===================================================================
--- website/trunk/include/de/navigation.wmi 2011-07-03 09:32:15 UTC (rev 24856)
+++ website/trunk/include/de/navigation.wmi 2011-07-03 09:34:34 UTC (rev 24857)
@@ -1,4 +1,4 @@
-#!/usr/bin/wml
+#!/usr/bin/wml
## translation metadata
# Revision: $Revision$
Modified: website/trunk/press/de/info.wmi
===================================================================
--- website/trunk/press/de/info.wmi 2011-07-03 09:32:15 UTC (rev 24856)
+++ website/trunk/press/de/info.wmi 2011-07-03 09:34:34 UTC (rev 24857)
@@ -1,4 +1,4 @@
-## translation metadata
+## translation metadata
# Revision: $Revision$
# Translation-Priority: 2-medium
#!/usr/bin/env wml
Modified: website/trunk/press/de/sidenav.wmi
===================================================================
--- website/trunk/press/de/sidenav.wmi 2011-07-03 09:32:15 UTC (rev 24856)
+++ website/trunk/press/de/sidenav.wmi 2011-07-03 09:34:34 UTC (rev 24857)
@@ -1,4 +1,4 @@
-#!/usr/bin/wml
+#!/usr/bin/wml
## translation metadata
# Revision: $Revision$
Modified: website/trunk/projects/de/sidenav.wmi
===================================================================
--- website/trunk/projects/de/sidenav.wmi 2011-07-03 09:32:15 UTC (rev 24856)
+++ website/trunk/projects/de/sidenav.wmi 2011-07-03 09:34:34 UTC (rev 24857)
@@ -1,4 +1,4 @@
-#!/usr/bin/wml
+#!/usr/bin/wml
## translation metadata
# Revision: $Revision$
Modified: website/trunk/torbutton/de/sidenav.wmi
===================================================================
--- website/trunk/torbutton/de/sidenav.wmi 2011-07-03 09:32:15 UTC (rev 24856)
+++ website/trunk/torbutton/de/sidenav.wmi 2011-07-03 09:34:34 UTC (rev 24857)
@@ -1,4 +1,4 @@
-#!/usr/bin/wml
+#!/usr/bin/wml
## translation metadata
# Revision: $Revision$
1
0
r24856: {website} translated de/foot.wmi from sacro (website/trunk/include/de)
by Runa Sandvik 03 Jul '11
by Runa Sandvik 03 Jul '11
03 Jul '11
Author: runa
Date: 2011-07-03 09:32:15 +0000 (Sun, 03 Jul 2011)
New Revision: 24856
Modified:
website/trunk/include/de/foot.wmi
Log:
translated de/foot.wmi from sacro
Modified: website/trunk/include/de/foot.wmi
===================================================================
--- website/trunk/include/de/foot.wmi 2011-07-02 01:09:57 UTC (rev 24855)
+++ website/trunk/include/de/foot.wmi 2011-07-03 09:32:15 UTC (rev 24856)
@@ -39,54 +39,54 @@
</div>
END NEWSLETTER -->
<div class="col first">
- <h4>About Tor</h4>
+ <h4>Über Tor</h4>
<ul>
- <li><a href="<page about/overview>">What Tor Does</a></li>
- <li><a href="<page about/torusers>">Users of Tor</a></li>
- <li><a href="<page about/corepeople>">Core Tor People</a></li>
- <li><a href="<page about/sponsors>">Sponsors</a></li>
- <li><a href="<page about/contact>">Contact Us</a></li>
+ <li><a href="<page about/overview>">Was Tor macht</a></li>
+ <li><a href="<page about/torusers>">Benutzer von Tor</a></li>
+ <li><a href="<page about/corepeople>">Tor Hauptpersonen</a></li>
+ <li><a href="<page about/sponsors>">Sponsoren</a></li>
+ <li><a href="<page about/contact>">Kontakt</a></li>
</ul>
</div>
<!-- END COL -->
<div class="col">
- <h4>Get Involved</h4>
+ <h4>Engagiere Dich</h4>
<ul>
- <li><a href="<page donate/donate>">Donate</a></li>
- <li><a href="<page docs/documentation>#MailingLists">Mailing List</a></li>
- <li><a href="<page getinvolved/mirrors>">Mirrors</a></li>
- <li><a href="<page docs/hidden-services>">Hidden Services</a></li>
- <li><a href="<page getinvolved/translation>">Translations</a></li>
- <li><a href="<page getinvolved/open-positions>">Careers</a></li>
+ <li><a href="<page donate/donate>">Spenden</a></li>
+ <li><a href="<page docs/documentation>#MailingLists">Mailing Liste</a></li>
+ <li><a href="<page getinvolved/mirrors>">Spiegelserver</a></li>
+ <li><a href="<page docs/hidden-services>">Versteckte Dienste</a></li>
+ <li><a href="<page getinvolved/translation>">Übersetzungen</a></li>
+ <li><a href="<page getinvolved/open-positions>">Karrieren</a></li>
</ul>
</div>
<!-- END COL -->
<div class="col">
- <h4>Documentation</h4>
+ <h4>Dokumentation</h4>
<ul>
- <li><a href="<page docs/tor-manual>">Manuals</a></li>
- <li><a href="<page docs/documentation>">Installation Guides</a></li>
+ <li><a href="<page docs/tor-manual>">Handbücher</a></li>
+ <li><a href="<page docs/documentation>">Installationsanweisungen</a></li>
<li><a href="<wiki>">Tor Wiki</a></li>
- <li><a href="<page docs/faq>">General Tor FAQ</a></li>
+ <li><a href="<page docs/faq>">Allgemeines Tor FAQ</a></li>
</ul>
</div>
<!-- END COL -->
<!-- List available languages -->
<div class="col wider">
- <h4>Languages</h4>
+ <h4>Sprachen</h4>
<: if (has_translations()) { :>
<p>
- This page is also available in the following languages:
+ Diese Seite ist auch in den folgenden Sprachen verfügbar:
<: print list_translations() :>.<br />
- How to set <a href="http://www.debian.org/intro/cn#howtoset">the default document language</a>.
+ Erfahre, wie man die <a href="http://www.debian.org/intro/cn#howtoset">Standard Sprache einstellt</a>.
</p>
<: }; :>
</div>
<!-- LANGUAGE SWITCH CGI
<div class="col wider">
- <h4>Languages</h4>
+ <h4>Sprachen</h4>
# this is a cgi trampoline to bounce us to the right page
# alternately, if the client supports javascript we can redirect that way
# noscript does not block onclick but clients may have disabled javascript completely
@@ -119,7 +119,7 @@
</select>
<input class="go" type="submit" name="submit" value="Go">
</form>
- <p>Questions on this? Visit <a href="http://www.debian.org/intro/cn#howtoset">how to set the default document language</a>.</p>
+ <p>Erfahre, wie man <a href="http://www.debian.org/intro/cn#howtoset">die Standard Sprache einstellt</a>.</p>
</div>
-->
</div>
@@ -129,3 +129,4 @@
<!-- END WRAP -->
</body>
</html>
+
1
0
[vidalia/alpha] Improve search in the routers list by adding a search field
by chiiph@torproject.org 03 Jul '11
by chiiph@torproject.org 03 Jul '11
03 Jul '11
commit d2ba7d6b2d3a429f66571039dd2d56154aba6ffb
Author: Thibault Vataire <vatairethibault(a)no-log.org>
Date: Thu Jun 16 20:30:41 2011 +0200
Improve search in the routers list by adding a search field
---
src/vidalia/network/NetViewer.cpp | 12 ++++
src/vidalia/network/NetViewer.h | 3 +
src/vidalia/network/NetViewer.ui | 91 +++++++++++++++++++++---------
src/vidalia/network/RouterListWidget.cpp | 34 +++++++++++
src/vidalia/network/RouterListWidget.h | 5 ++
5 files changed, 118 insertions(+), 27 deletions(-)
diff --git a/src/vidalia/network/NetViewer.cpp b/src/vidalia/network/NetViewer.cpp
index 405fd35..0e0a161 100644
--- a/src/vidalia/network/NetViewer.cpp
+++ b/src/vidalia/network/NetViewer.cpp
@@ -128,6 +128,10 @@ NetViewer::NetViewer(QWidget *parent)
_torControl, SLOT(closeCircuit(CircuitId)));
connect(ui.treeCircuitList, SIGNAL(closeStream(StreamId)),
_torControl, SLOT(closeStream(StreamId)));
+ connect(ui.lineRouterSearch, SIGNAL(returnPressed()),
+ this, SLOT(onRouterSearch()));
+ connect(ui.lineRouterSearch, SIGNAL(textChanged(QString)),
+ ui.treeRouterList, SLOT(onRouterSearch(QString)));
setupGeoIpResolver();
@@ -246,6 +250,14 @@ NetViewer::clear()
ui.textRouterInfo->clear();
}
+/** Called when the search of a router is triggered by the signal
+ * returnPressed from the search field. */
+void
+NetViewer::onRouterSearch()
+{
+ ui.treeRouterList->searchNextRouter(ui.lineRouterSearch->text());
+}
+
/** Loads a list of all current address mappings. */
void
NetViewer::loadAddressMap()
diff --git a/src/vidalia/network/NetViewer.h b/src/vidalia/network/NetViewer.h
index 3c0a675..b1c40be 100644
--- a/src/vidalia/network/NetViewer.h
+++ b/src/vidalia/network/NetViewer.h
@@ -101,6 +101,9 @@ private slots:
/** Called when the user clicks "Full Screen" or presses Escape on the map.
* Toggles the map between normal and a full screen viewing modes. */
void toggleFullScreen();
+ /** Called when the search of a router is triggered by the signal
+ * returnPressed from the search field. */
+ void onRouterSearch();
private:
/** */
diff --git a/src/vidalia/network/NetViewer.ui b/src/vidalia/network/NetViewer.ui
index 863907b..11fd0a2 100644
--- a/src/vidalia/network/NetViewer.ui
+++ b/src/vidalia/network/NetViewer.ui
@@ -28,9 +28,9 @@
<property name="childrenCollapsible">
<bool>false</bool>
</property>
- <widget class="RouterListWidget" name="treeRouterList">
+ <widget class="QSplitter" name="splitter4">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@@ -42,38 +42,75 @@
</size>
</property>
<property name="contextMenuPolicy">
- <enum>Qt::DefaultContextMenu</enum>
- </property>
- <property name="statusTip">
- <string/>
- </property>
- <property name="selectionMode">
- <enum>QAbstractItemView::ExtendedSelection</enum>
- </property>
- <property name="indentation">
- <number>0</number>
+ <enum>Qt::NoContextMenu</enum>
</property>
- <property name="sortingEnabled">
- <bool>true</bool>
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
</property>
- <property name="columnCount">
- <number>3</number>
+ <property name="childrenCollapsible">
+ <bool>false</bool>
</property>
- <column>
- <property name="text">
- <string/>
+ <widget class="QLineEdit" name="lineRouterSearch">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>30</height>
+ </size>
</property>
- </column>
- <column>
- <property name="text">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>30</height>
+ </size>
+ </property>
+ </widget>
+ <widget class="RouterListWidget" name="treeRouterList">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>175</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="contextMenuPolicy">
+ <enum>Qt::DefaultContextMenu</enum>
+ </property>
+ <property name="statusTip">
<string/>
</property>
- </column>
- <column>
- <property name="text">
- <string>Relay</string>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="indentation">
+ <number>0</number>
</property>
- </column>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ <property name="columnCount">
+ <number>3</number>
+ </property>
+ <column>
+ <property name="text">
+ <string/>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string/>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Relay</string>
+ </property>
+ </column>
+ </widget>
</widget>
<widget class="QSplitter" name="splitter3">
<property name="sizePolicy">
diff --git a/src/vidalia/network/RouterListWidget.cpp b/src/vidalia/network/RouterListWidget.cpp
index e35bc22..851a5ea 100644
--- a/src/vidalia/network/RouterListWidget.cpp
+++ b/src/vidalia/network/RouterListWidget.cpp
@@ -152,6 +152,40 @@ RouterListWidget::clearRouters()
setStatusTip(tr("%1 relays online").arg(0));
}
+/** Called when the search of a router is triggered by the signal
+ * textChanged from the search field. */
+void
+RouterListWidget::onRouterSearch(const QString routerNickname)
+{
+ if (!currentIndex().data().toString().toLower().startsWith(routerNickname.toLower()))
+ {
+ searchNextRouter(routerNickname);
+ } else {
+ /* If item at currentIndex() isn't visible, make it visible. */
+ scrollToItem(itemFromIndex(currentIndex()));
+ }
+}
+
+/** Selects the following router whose name starts by routerNickname. */
+void
+RouterListWidget::searchNextRouter(const QString routerNickname)
+{
+ /* currentIndex().row() = -1 if no item is selected. */
+ int startIndex = currentIndex().row() + 1;
+ /* Search for a router whose name start with routerNickname. Case-insensitive search. */
+ QModelIndexList qmIndList = model()->match(model()->index(startIndex, NameColumn),
+ Qt::DisplayRole,
+ QVariant(routerNickname),
+ 1,
+ (Qt::MatchStartsWith | Qt::MatchWrap));
+ if (qmIndList.count() > 0) {
+ setCurrentIndex(qmIndList.at(0));
+ /* If item at currentIndex() was already selected but not visible,
+ * make it visible. */
+ scrollToItem(itemFromIndex(currentIndex()));
+ }
+}
+
/** Called when the user selects a router from the list. This will search the
* list for a router whose names starts with the key pressed. */
void
diff --git a/src/vidalia/network/RouterListWidget.h b/src/vidalia/network/RouterListWidget.h
index 04d7263..95b471e 100644
--- a/src/vidalia/network/RouterListWidget.h
+++ b/src/vidalia/network/RouterListWidget.h
@@ -54,6 +54,8 @@ public:
void deselectAll();
/** Called when the user changes the UI translation. */
void retranslateUi();
+ /** Selects the following router whose name starts by routerNickname. */
+ void searchNextRouter(const QString routerNickname);
signals:
/** Emitted when the user selects a router from the list. */
@@ -64,6 +66,9 @@ signals:
public slots:
/** Clears the list of router items. */
void clearRouters();
+ /** Called when the search of a router is triggered by the signal
+ * textChanged from the search field. */
+ void onRouterSearch(const QString routerNickname);
private slots:
/** Called when the user clicks on an item in the list. */
1
0
commit 92ddcc353e5fb98cd88c446537ca940bcc46efef
Author: Tomas Touceda <chiiph(a)gentoo.org>
Date: Tue May 24 15:04:45 2011 -0300
Add a nice icon
---
src/vidalia/VAttachButton.cpp | 6 +++++-
src/vidalia/res/16x16/detach-arrow.png | Bin 0 -> 602 bytes
src/vidalia/res/vidalia.qrc | 1 +
3 files changed, 6 insertions(+), 1 deletions(-)
diff --git a/src/vidalia/VAttachButton.cpp b/src/vidalia/VAttachButton.cpp
index fa51b0d..6d9991b 100644
--- a/src/vidalia/VAttachButton.cpp
+++ b/src/vidalia/VAttachButton.cpp
@@ -1,11 +1,15 @@
#include "VAttachButton.h"
+#define IMG_DETACH ":/images/16x16/detach-arrow.png"
+
VAttachButton::VAttachButton(QWidget *parent) :
QPushButton(parent)
{
_tab = 0;
_attached = true;
- setText(QString("X"));
+ setIcon(QIcon(IMG_DETACH));
+ setFlat(true);
+ resize(16,16);
}
VAttachButton::~VAttachButton()
diff --git a/src/vidalia/res/16x16/detach-arrow.png b/src/vidalia/res/16x16/detach-arrow.png
new file mode 100644
index 0000000..8beaf74
Binary files /dev/null and b/src/vidalia/res/16x16/detach-arrow.png differ
diff --git a/src/vidalia/res/vidalia.qrc b/src/vidalia/res/vidalia.qrc
index cfb1004..aa5cfe1 100644
--- a/src/vidalia/res/vidalia.qrc
+++ b/src/vidalia/res/vidalia.qrc
@@ -27,6 +27,7 @@
<file>16x16/utilities-log-viewer.png</file>
<file>16x16/utilities-system-monitor.png</file>
<file>16x16/view-media-artist.png</file>
+ <file>16x16/detach-arrow.png</file>
</qresource>
<qresource prefix="/images">
<file>22x22/edit_undo.png</file>
1
0
commit 2e4fbffbe0e0593712e964f5710a6c970195514b
Author: Tomas Touceda <chiiph(a)gentoo.org>
Date: Mon May 23 14:07:49 2011 -0300
Fix typo: It's detach, not dettach
---
src/vidalia/MainWindow.cpp | 8 ++++----
src/vidalia/MainWindow.h | 4 ++--
src/vidalia/VAttachButton.cpp | 2 +-
src/vidalia/VAttachButton.h | 2 +-
4 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/vidalia/MainWindow.cpp b/src/vidalia/MainWindow.cpp
index 22949b0..6e16ee4 100644
--- a/src/vidalia/MainWindow.cpp
+++ b/src/vidalia/MainWindow.cpp
@@ -1506,7 +1506,7 @@ MainWindow::attachTab()
}
void
-MainWindow::dettachTab()
+MainWindow::detachTab()
{
VAttachButton *but = qobject_cast<VAttachButton *>(sender());
VidaliaTab *tab = but->getTab();
@@ -1518,7 +1518,7 @@ MainWindow::dettachTab()
QString key = _tabMap.at(index);
_tabMap.removeAll(key);
- _dettachedTabMap << key;
+ _detachedTabMap << key;
}
void
@@ -1573,8 +1573,8 @@ MainWindow::addTab(VidaliaTab *tab)
connect(atb, SIGNAL(attachTab()),
this, SLOT(attachTab()));
- connect(atb, SIGNAL(dettachTab()),
- this, SLOT(dettachTab()));
+ connect(atb, SIGNAL(detachTab()),
+ this, SLOT(detachTab()));
/** The new tab is added to the last position */
_tabMap << tab->getTitle();
connect(tab, SIGNAL(helpRequested(QString)),
diff --git a/src/vidalia/MainWindow.h b/src/vidalia/MainWindow.h
index 2bfd02f..722a217 100644
--- a/src/vidalia/MainWindow.h
+++ b/src/vidalia/MainWindow.h
@@ -152,7 +152,7 @@ private slots:
void delTab(int index = -1);
void attachTab();
- void dettachTab();
+ void detachTab();
#if defined(USE_AUTOUPDATE)
/** Called when the user clicks the 'Check Now' button in the General
@@ -290,7 +290,7 @@ private:
MessageLog *_messageLog; /**< Message log that displays a more detailed log from Tor */
NetViewer _netViewer; /**< Network map that draws circuits */
QStringList _tabMap; /**< Map to handle opened tabs */
- QStringList _dettachedTabMap;
+ QStringList _detachedTabMap;
BandwidthGraph *_graph; /**< Graph that draws bandwidth usage */
PluginEngine *_engine;
diff --git a/src/vidalia/VAttachButton.cpp b/src/vidalia/VAttachButton.cpp
index 80e512b..42a4b3e 100644
--- a/src/vidalia/VAttachButton.cpp
+++ b/src/vidalia/VAttachButton.cpp
@@ -30,7 +30,7 @@ void
VAttachButton::toggleAttach()
{
if(_attached) {
- emit dettachTab();
+ emit detachTab();
} else {
emit attachTab();
}
diff --git a/src/vidalia/VAttachButton.h b/src/vidalia/VAttachButton.h
index e1ad3de..bdced09 100644
--- a/src/vidalia/VAttachButton.h
+++ b/src/vidalia/VAttachButton.h
@@ -17,7 +17,7 @@ class VAttachButton : public QPushButton {
signals:
void attachTab();
- void dettachTab();
+ void detachTab();
public slots:
void toggleAttach();
1
0
commit 6dcf75a834e65715a1d3cd90664be377f3312c60
Author: Tomas Touceda <chiiph(a)gentoo.org>
Date: Mon May 23 14:05:14 2011 -0300
First phase of detach tabs
Commentted is the code that handles drag and drop for re-attaching the tabs
again. It will probably work on windows, but I need to figure out a way to
handle this in a generic way first, and then go for the ifdefs.
---
src/vidalia/CMakeLists.txt | 2 +
src/vidalia/MainWindow.cpp | 57 ++++++++++++++++++++++++++++++++++++++++-
src/vidalia/MainWindow.h | 8 ++++++
src/vidalia/VAttachButton.cpp | 39 ++++++++++++++++++++++++++++
src/vidalia/VAttachButton.h | 30 +++++++++++++++++++++
src/vidalia/VTabWidget.cpp | 9 +++++-
src/vidalia/VTabWidget.h | 3 ++
src/vidalia/VidaliaTab.cpp | 18 +++++++++++++
src/vidalia/VidaliaTab.h | 6 ++++
9 files changed, 169 insertions(+), 3 deletions(-)
diff --git a/src/vidalia/CMakeLists.txt b/src/vidalia/CMakeLists.txt
index f340b65..9fd58ca 100644
--- a/src/vidalia/CMakeLists.txt
+++ b/src/vidalia/CMakeLists.txt
@@ -176,6 +176,7 @@ set(vidalia_SRCS ${vidalia_SRCS}
Vidalia.cpp
LanguageSupport.cpp
VTabWidget.cpp
+ VAttachButton.cpp
MainWindow.cpp
VidaliaWindow.cpp
VMessageBox.cpp
@@ -187,6 +188,7 @@ set(vidalia_SRCS ${vidalia_SRCS}
qt4_wrap_cpp(vidalia_SRCS
Vidalia.h
VTabWidget.h
+ VAttachButton.h
MainWindow.h
VidaliaWindow.h
VMessageBox.h
diff --git a/src/vidalia/MainWindow.cpp b/src/vidalia/MainWindow.cpp
index 56b24c7..22949b0 100644
--- a/src/vidalia/MainWindow.cpp
+++ b/src/vidalia/MainWindow.cpp
@@ -25,6 +25,7 @@
#include "ServerSettings.h"
#include "AboutDialog.h"
#include "HelpBrowser.h"
+#include "VAttachButton.h"
#ifdef USE_AUTOUPDATE
#include "UpdatesAvailableDialog.h"
#endif
@@ -112,6 +113,8 @@ MainWindow::MainWindow()
_status = Unset;
_isVidaliaRunningTor = false;
updateTorStatus(Stopped);
+
+ setAcceptDrops(true);
}
/** Destructor */
@@ -1497,6 +1500,44 @@ MainWindow::handleCloseTab(int index)
}
void
+MainWindow::attachTab()
+{
+ qWarning() << "ATTACHHHHHHHHHH";
+}
+
+void
+MainWindow::dettachTab()
+{
+ VAttachButton *but = qobject_cast<VAttachButton *>(sender());
+ VidaliaTab *tab = but->getTab();
+ int index = ui.tabWidget->indexOf(tab);
+
+ ui.tabWidget->removeTab(index);
+ tab->setParent(0);
+ tab->show();
+
+ QString key = _tabMap.at(index);
+ _tabMap.removeAll(key);
+ _dettachedTabMap << key;
+}
+
+void
+MainWindow::handleAttachedClose()
+{
+ VidaliaTab *tab = qobject_cast<VidaliaTab *>(sender());
+ int index = ui.tabWidget->indexOf(tab);
+ qWarning() << index;
+ if(index < 0) {
+ qWarning() << "DETACHEEEEDDDDDDDDDDDDD";
+ tab->setParent(ui.tabWidget);
+ addTab(tab);
+ delTab(ui.tabWidget->currentIndex());
+ } else {
+ qWarning() << "ATTACHEEEEEDDDD";
+ }
+}
+
+void
MainWindow::addTab(VidaliaTab *tab)
{
/** If the tab's already open, display it and delete the
@@ -1518,8 +1559,22 @@ MainWindow::addTab(VidaliaTab *tab)
return;
}
+ VAttachButton *atb = new VAttachButton();
+
ui.tabWidget->addTab(tab, tab->getTitle());
- ui.tabWidget->setCurrentIndex(ui.tabWidget->count() - 1);
+ int pos = ui.tabWidget->count() - 1;
+ ui.tabWidget->setCurrentIndex(pos);
+
+ atb->setTab(tab);
+ ui.tabWidget->setTabButton(pos, QTabBar::LeftSide, atb);
+
+ connect(tab, SIGNAL(closeTab()),
+ this, SLOT(handleAttachedClose()));
+
+ connect(atb, SIGNAL(attachTab()),
+ this, SLOT(attachTab()));
+ connect(atb, SIGNAL(dettachTab()),
+ this, SLOT(dettachTab()));
/** The new tab is added to the last position */
_tabMap << tab->getTitle();
connect(tab, SIGNAL(helpRequested(QString)),
diff --git a/src/vidalia/MainWindow.h b/src/vidalia/MainWindow.h
index 3b7d544..2bfd02f 100644
--- a/src/vidalia/MainWindow.h
+++ b/src/vidalia/MainWindow.h
@@ -60,6 +60,10 @@ protected:
/** Called when the user changes the UI translation. */
virtual void retranslateUi();
+// void dropEvent(QDropEvent *de);
+// void dragMoveEvent(QDragMoveEvent *de);
+// void dragEnterEvent(QDragEnterEvent *event);
+
private slots:
/** Respond to a double-click on the tray icon by opening the Control Panel
* window. */
@@ -147,6 +151,9 @@ private slots:
/** Deletes the tab at index if it exists and it isn't the Status tab */
void delTab(int index = -1);
+ void attachTab();
+ void dettachTab();
+
#if defined(USE_AUTOUPDATE)
/** Called when the user clicks the 'Check Now' button in the General
* settings page. */
@@ -283,6 +290,7 @@ private:
MessageLog *_messageLog; /**< Message log that displays a more detailed log from Tor */
NetViewer _netViewer; /**< Network map that draws circuits */
QStringList _tabMap; /**< Map to handle opened tabs */
+ QStringList _dettachedTabMap;
BandwidthGraph *_graph; /**< Graph that draws bandwidth usage */
PluginEngine *_engine;
diff --git a/src/vidalia/VAttachButton.cpp b/src/vidalia/VAttachButton.cpp
new file mode 100644
index 0000000..80e512b
--- /dev/null
+++ b/src/vidalia/VAttachButton.cpp
@@ -0,0 +1,39 @@
+#include "VAttachButton.h"
+
+VAttachButton::VAttachButton(QWidget *parent) :
+ QPushButton(parent)
+{
+ _tab = 0;
+ _attached = true;
+ setText(QString("X"));
+}
+
+VAttachButton::~VAttachButton()
+{
+ disconnect(0,0,0,0);
+}
+
+void
+VAttachButton::setTab(VidaliaTab *tab)
+{
+ _tab = tab;
+ connect(this, SIGNAL(clicked()), this, SLOT(toggleAttach()));
+}
+
+VidaliaTab *
+VAttachButton::getTab()
+{
+ return _tab;
+}
+
+void
+VAttachButton::toggleAttach()
+{
+ if(_attached) {
+ emit dettachTab();
+ } else {
+ emit attachTab();
+ }
+ _attached = !_attached;
+}
+
diff --git a/src/vidalia/VAttachButton.h b/src/vidalia/VAttachButton.h
new file mode 100644
index 0000000..e1ad3de
--- /dev/null
+++ b/src/vidalia/VAttachButton.h
@@ -0,0 +1,30 @@
+#ifndef VATTACHBUTTON_H
+#define VATTACHBUTTON_H
+
+#include <QtGui>
+
+#include "VidaliaTab.h"
+
+class VAttachButton : public QPushButton {
+ Q_OBJECT
+
+ public:
+ VAttachButton(QWidget *parent = 0);
+ ~VAttachButton();
+
+ void setTab(VidaliaTab *tab);
+ VidaliaTab *getTab();
+
+ signals:
+ void attachTab();
+ void dettachTab();
+
+ public slots:
+ void toggleAttach();
+
+ private:
+ VidaliaTab *_tab;
+ bool _attached;
+};
+
+#endif
diff --git a/src/vidalia/VTabWidget.cpp b/src/vidalia/VTabWidget.cpp
index 421f069..a1bef76 100644
--- a/src/vidalia/VTabWidget.cpp
+++ b/src/vidalia/VTabWidget.cpp
@@ -1,5 +1,3 @@
-#include <QTabBar>
-
#include "VTabWidget.h"
#include "VidaliaTab.h"
@@ -50,3 +48,10 @@ VTabWidget::retranslateUi()
setTabText(i, qobject_cast<VidaliaTab *>(widget(i))->getTitle());
}
}
+
+void
+VTabWidget::setTabButton(int pos, QTabBar::ButtonPosition butpos, QWidget *w)
+{
+ tabBar()->setTabButton(pos, butpos, w);
+}
+
diff --git a/src/vidalia/VTabWidget.h b/src/vidalia/VTabWidget.h
index c3eb8ce..45028e8 100644
--- a/src/vidalia/VTabWidget.h
+++ b/src/vidalia/VTabWidget.h
@@ -17,6 +17,7 @@
#define _VTABWIDGET_H
#include <QTabWidget>
+#include <QTabBar>
class VTabWidget : public QTabWidget
{
@@ -31,6 +32,8 @@ public:
/** Makes the tab at position unclosable */
void pinTab(int position);
+ void setTabButton(int pos, QTabBar::ButtonPosition butpos, QWidget *w);
+
protected:
void changeEvent(QEvent *e);
void retranslateUi();
diff --git a/src/vidalia/VidaliaTab.cpp b/src/vidalia/VidaliaTab.cpp
index ec5f1bf..a35838e 100644
--- a/src/vidalia/VidaliaTab.cpp
+++ b/src/vidalia/VidaliaTab.cpp
@@ -64,3 +64,21 @@ VidaliaTab::setOnTop(bool top)
{
_onTop = top;
}
+
+void
+VidaliaTab::closeEvent(QCloseEvent *event)
+{
+ event->ignore();
+ emit closeTab();
+}
+
+//void
+//VidaliaTab::mouseMoveEvent(QMouseEvent *event)
+//{
+// QDrag *dr = new QDrag(this);
+// QMimeData *data = new QMimeData();
+// qWarning() << "THIS" << this;
+// data->setData(tr("vidalia/pointer"), QByteArray().setNum((int)this));
+// dr->setMimeData(data);
+// dr->start();
+//}
diff --git a/src/vidalia/VidaliaTab.h b/src/vidalia/VidaliaTab.h
index ac6edb1..df26c8a 100644
--- a/src/vidalia/VidaliaTab.h
+++ b/src/vidalia/VidaliaTab.h
@@ -51,6 +51,8 @@ signals:
* <b>topic</b>. */
void helpRequested(const QString &topic);
+ void closeTab();
+
protected:
/** Reimplement the windows' changeEvent() method to check if the event
* is a QEvent::LanguageChange event. If so, call retranslateUi(), which
@@ -59,6 +61,10 @@ protected:
/** Called when the user wants to change the currently visible language. */
virtual void retranslateUi();
+ virtual void closeEvent(QCloseEvent *event);
+
+// virtual void mouseMoveEvent(QMouseEvent *event);
+
bool _onTop; /**< True if the current tab is the one being displayed */
private:
1
0
commit 3db72ba0776c8ec42a4601f9b3e6f4e7302a3525
Author: Tomas Touceda <chiiph(a)gentoo.org>
Date: Fri May 27 20:53:58 2011 -0300
Add comments and clean up a bit
---
src/vidalia/MainWindow.cpp | 3 ---
src/vidalia/MainWindow.h | 17 +++++++++--------
src/vidalia/VAttachButton.cpp | 15 +++++++++++++++
src/vidalia/VAttachButton.h | 20 ++++++++++++++++++++
4 files changed, 44 insertions(+), 11 deletions(-)
diff --git a/src/vidalia/MainWindow.cpp b/src/vidalia/MainWindow.cpp
index 3e9ce40..4c4359e 100644
--- a/src/vidalia/MainWindow.cpp
+++ b/src/vidalia/MainWindow.cpp
@@ -115,8 +115,6 @@ MainWindow::MainWindow()
_status = Unset;
_isVidaliaRunningTor = false;
updateTorStatus(Stopped);
-
- setAcceptDrops(true);
}
/** Destructor */
@@ -197,7 +195,6 @@ MainWindow::createMenuBar()
pluginsMenu->addAction(_actionDebugDialog);
menu->addMenu(&_reattachMenu);
- _dummy->setText(tr("No detached tabs"));
_reattachMenu.addAction(_dummy);
_dummy->setEnabled(false);
diff --git a/src/vidalia/MainWindow.h b/src/vidalia/MainWindow.h
index b7874fd..d30c059 100644
--- a/src/vidalia/MainWindow.h
+++ b/src/vidalia/MainWindow.h
@@ -60,10 +60,6 @@ protected:
/** Called when the user changes the UI translation. */
virtual void retranslateUi();
-// void dropEvent(QDropEvent *de);
-// void dragMoveEvent(QDragMoveEvent *de);
-// void dragEnterEvent(QDragEnterEvent *event);
-
private slots:
/** Respond to a double-click on the tray icon by opening the Control Panel
* window. */
@@ -151,9 +147,14 @@ private slots:
/** Deletes the tab at index if it exists and it isn't the Status tab */
void delTab(int index = -1);
+ /** Attaches a tab to the tabwidget */
void attachTab();
+ /** Detaches a tab from the tabwidget */
void detachTab();
+ /** Called when trying to close a tab that has been detached */
+ void handleAttachedClose();
+
#if defined(USE_AUTOUPDATE)
/** Called when the user clicks the 'Check Now' button in the General
* settings page. */
@@ -284,19 +285,19 @@ private:
QAction *_actionExit;
QAction *_actionDebugDialog;
- QMenu _reattachMenu;
- QAction *_dummy;
+ QMenu _reattachMenu; /**< Menu used to handle tab re-attaching */
+ QAction *_dummy; /**< Dummy action to display when there are no more tabs */
Ui::MainWindow ui; /**< Qt Designer generated object. */
StatusTab _statusTab; /**< Status tab that displays the load progress and a short log */
MessageLog *_messageLog; /**< Message log that displays a more detailed log from Tor */
NetViewer _netViewer; /**< Network map that draws circuits */
- QStringList _tabMap; /**< Map to handle opened tabs */
- QStringList _detachedTabMap;
BandwidthGraph *_graph; /**< Graph that draws bandwidth usage */
PluginEngine *_engine;
+ QStringList _tabMap; /**< Map to handle opened tabs */
+ QStringList _detachedTabMap; /**< Map to handle detached tabs */
};
#endif
diff --git a/src/vidalia/VAttachButton.cpp b/src/vidalia/VAttachButton.cpp
index 6d9991b..b5a5bab 100644
--- a/src/vidalia/VAttachButton.cpp
+++ b/src/vidalia/VAttachButton.cpp
@@ -1,3 +1,18 @@
+/*
+** This file is part of Vidalia, and is subject to the license terms in the
+** LICENSE file, found in the top level directory of this distribution. If you
+** did not receive the LICENSE file with this file, you may obtain it from the
+** Vidalia source package distributed by the Vidalia Project at
+** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
+** including this file, may be copied, modified, propagated, or distributed
+** except according to the terms described in the LICENSE file.
+*/
+
+/*
+** \file VAttachButton.cpp
+** \brief Button that handles detaching of tabs
+*/
+
#include "VAttachButton.h"
#define IMG_DETACH ":/images/16x16/detach-arrow.png"
diff --git a/src/vidalia/VAttachButton.h b/src/vidalia/VAttachButton.h
index bdced09..038eef9 100644
--- a/src/vidalia/VAttachButton.h
+++ b/src/vidalia/VAttachButton.h
@@ -1,3 +1,18 @@
+/*
+** This file is part of Vidalia, and is subject to the license terms in the
+** LICENSE file, found in the top level directory of this distribution. If you
+** did not receive the LICENSE file with this file, you may obtain it from the
+** Vidalia source package distributed by the Vidalia Project at
+** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
+** including this file, may be copied, modified, propagated, or distributed
+** except according to the terms described in the LICENSE file.
+*/
+
+/*
+** \file VAttachButton.cpp
+** \brief Button that handles detaching of tabs
+*/
+
#ifndef VATTACHBUTTON_H
#define VATTACHBUTTON_H
@@ -12,14 +27,19 @@ class VAttachButton : public QPushButton {
VAttachButton(QWidget *parent = 0);
~VAttachButton();
+ /** Sets the parent tab for this button */
void setTab(VidaliaTab *tab);
+ /** Returns the parent tab for this button */
VidaliaTab *getTab();
signals:
+ /** Emitted when the button is pressed and the tab is detached */
void attachTab();
+ /** Emitted when the button is pressed and the tab is attached */
void detachTab();
public slots:
+ /** Handles the onClicked signal */
void toggleAttach();
private:
1
0
02 Jul '11
commit a1d95d1c1734b208ab69452de158eb0234b623db
Author: Tomas Touceda <chiiph(a)gentoo.org>
Date: Thu Jun 2 14:18:47 2011 -0300
Remove the commented drag and drop code
---
src/vidalia/VidaliaTab.cpp | 9 ---------
src/vidalia/VidaliaTab.h | 2 --
2 files changed, 0 insertions(+), 11 deletions(-)
diff --git a/src/vidalia/VidaliaTab.cpp b/src/vidalia/VidaliaTab.cpp
index 62377e7..ee2da03 100644
--- a/src/vidalia/VidaliaTab.cpp
+++ b/src/vidalia/VidaliaTab.cpp
@@ -72,12 +72,3 @@ VidaliaTab::closeEvent(QCloseEvent *event)
emit closeTab();
}
-//void
-//VidaliaTab::mouseMoveEvent(QMouseEvent *event)
-//{
-// QDrag *dr = new QDrag(this);
-// QMimeData *data = new QMimeData();
-// data->setData(tr("vidalia/pointer"), QByteArray().setNum((int)this));
-// dr->setMimeData(data);
-// dr->start();
-//}
diff --git a/src/vidalia/VidaliaTab.h b/src/vidalia/VidaliaTab.h
index df26c8a..8a053dc 100644
--- a/src/vidalia/VidaliaTab.h
+++ b/src/vidalia/VidaliaTab.h
@@ -63,8 +63,6 @@ protected:
virtual void closeEvent(QCloseEvent *event);
-// virtual void mouseMoveEvent(QMouseEvent *event);
-
bool _onTop; /**< True if the current tab is the one being displayed */
private:
1
0